Tuesday, April 29, 2014
Internet Explorer & Adobe Flash 0-Day Coverage
Microsoft Internet Explorer 0day CVE-2014-1776.
SIDs 30794 & 30803
https://technet.microsoft.com/en-US/library/security/2963983
Adobe Flash 0day CVE-2014-0515
SIDs 30876 & 30877
http://helpx.adobe.com/security/products/flash-player/apsb14-13.html
Coverage for both of these vulnerabilities were released yesterday, April 28, 2014. The latest rule pack will provide the updates for both of these vulnerabilities.
http://blog.snort.org/2014/04/sourcefire-vrt-certified-snort-rules_7339.html
http://blog.snort.org/2014/04/sourcefire-vrt-certified-snort-rules_28.html
Tuesday, April 22, 2014
Snake Campaign: A few words about the Uroburos Rootkit
Over the past few days, analyzing the new Uroburos (aka Turla) rootkit has been exciting. That's because the sample dropper (MD5: a86ac0ad1f8928e8d4e1b728448f54f9) includes a lot of clever features. We don’t want to rehash research already publicly available, but we will expand on some features that have not been covered in previous publications (like the driver loading strategy and the main dropper architecture).
The dropper is compressed with a simple packer that uses integer math, such a bit shifting, unsigned multiplication, and so on, to perform data decryption. At the end of the decryption routine, we end up with a jmp ebxopcode. The jump leads to a copy stub routine that replaces the original bytes of the executable:
The unpacked code first disables all possible error reporting windows from popping up by using the SetErrorMode Windows API function. The binary then checks the version of the operating system, even if the process is running in WOW64 mode. Arguments passed to the binary at execution time are checked as well: if any of the arguments is the string up, an auto-destruction routine is executed and all Uroburos files found on disk from possible previous runs are deleted. The dropper even checks for another instance of Uroburos running in memory on the target system by trying to open the following 3 mutexes:
- "{E9B1E207-B513-4cfc-86BE-6D6004E5CB9C}" - Local setup mutex
- “{B93DFED5-9A3B-459b-A617-59FD9FAD693E}” - Global Uroburos setup mutex
- "shell.{F21EDC09-85D3-4eb9-915F-1AFA2FF28153}" - Global still unknown mutex
If any of these mutexes is found, the executable terminates the setup process.
Otherwise, it prepares all data structures needed for all its inter-module communication.
BypassDSEAndLoadVirusDrvis the name of the key routine of the Uroburos dropper. Its final goal is to load the Uroburos rootkit driver, and this is accomplished in different ways depending on the target's operating system. We will provide an in-depth analysis of how this is done later on. After the rootkit driver is loaded, a function in an user-mode module of the dropper called format_ntfs_Win32, and identified within the binary as resource 4000 is used to format its virtual volume, which is accessible via the device \\.\Par1. As mentioned, the entire code responsible for formatting the virtual volume is written in user-mode. The malware authors interestingly decided not to use built-in low-level Windows formatting functions. The virtual volume is backed by a file called fixdata.dat found in the main directory of Uroburos. This directory is called $NtUninstallQXXXXXX$ (where the letters “XXXXXX” are 6 random numbers), is located under Windows root path, and is hidden by the kernel mode driver. The encrypted configuration file, found in the dropper as resource 103 is extracted in a file called system in the virtual volume. Finally the dropper is copied to a file called fdisk_mon.exe located in the main path for Uroburs, and its corresponding system service named ultra3 is installed. This ensures the piece of malware survives a system reboot.
Main Path: %systemroot%\$NtUninstallQxxxxxx$ fdisk.sys - Main Rootkit driver fdisk_mon.exe - Packed dropper executed as service fixdata.dat - Virtual File systems file
Between this and upcoming blog posts, we will go over 3 major features found in Uroburos, which are the:
- Kernel mode driver setup strategies
- Patchguard disarming code
- Virtual File System
Uroburos Dropper Architecture - Modules communication
We believe that to facilitate an in-depth understanding of the specific features of Uroburos, we should go over the dropper's architecture. All Uroburos modules are DLLs embedded in the resource directory of the main dropper. As needed, the dropper gets a pointer to the target module located in resource directory (using the Windows API functions FindResource and LockResource), and starts processing it: the VirusLoadDll routine takes the module resource buffer pointer as input, allocates a chunk of memory big as target PE virtual size, and then proceeds with the needed IAT resolution, relocations and fix-ups. At the end, the Uroburos main dropper has correctly loaded the DLL module in its address space. We can pinpoint that each of its resource modules is composed as follows:
- DllEntryPoint implements the unpacking routine and a simple function that saves the DLL base address to a global variable
- ee, anexported function that performs the actual module job
The routine ee is called with 3 parameters: a synchronization routine pointer, that resides in the main dropper; 2 custom parameters that usually point to the Uroburos driver buffer and its size. These last 2 parameters are needed for the exploit execution.
As the name implies, the “synchronization” routine initializes all synch data structures and an array of global functions pointer that could be called from the external module. In this way, the external library can always call the main dropper's internal routines. As we proceeded with the analysis, we saw that some modules were only a wrapper to some of the main dropper’s internal functions.
In summary, we have identified the following Snake (another name of Uroburos rootkit) modules:
- A 32-bit and 64-bit driver (resource number 101 and 161)
- A configuration file extracted and saved in the virtual volume system(resource number 103)
- ms09_025_Win32(resource number 1000), which exploits vulnerability CVE-2009-1123 in order to execute kernel-mode code (and automatically escalate privileges)
- ms10_015_Win32(resource number 2000), which exploit vulnerability CVE-2010-0232 in order to escalate privileges and gain access to the SYSTEM account
- vboxdrv_Win32 which is perhaps the most interesting one: it disables Windows 7 x64 Driver Signing Enforcement (DSE) by exploiting a bug in the VirtualBox signed driver. This module requires an in-depth analysis that we will cover afterwards
- format_ntfs_Win32, whichimplements virtual volume initialization and NTFS formatting code.
Loading the driver
All the work needed to properly load the unsigned driver is managed by the BypassDSEAndLoadVirusDrv function. This function tries to extract and run the ms09_025_Win32module with the aim of triggering exploiting CVE-2009-1123 and divert the kernel code execution to a custom routine that automatically escalates privileges and directly loads the already mapped driver. If the target OS isn't vulnerable to CVE-2009-1123, or if the exploit has failed, it tries to extract and run the modulems10_015_Win32, but this time with the goal of only obtaining SYSTEM privileges. By checking the following registry key (requesting KEY_SET_VALUE access right) it can determine if the attempt to escalate privileges was successful:
HKLM\Software\Microsoft\Windows Nt\CurrentVersion\Windows
This key can only be written to by a user or process with 'administrator' privileges. If the main dropper is still unable to open this key, the procedure exits and the entire setup process is aborted.
The function CreateTurlaPathAndExtractDrvis called to create the directory $NtUninstallQXXXXXX$in root directory of the operating system, and extract the kernel driver into a file called fdisk.sys. If the OS is 32-bit, the dropper creates and populates the registry key HKCR\Ultra3and executes the unsigned driver sengoku_x64 (the main “Uroburos” driver) by calling the ZwLoadDriver native API function. This method bypasses the standard Windows Service control manager.
Alternatively, if the OS is 64-bit, which means that it implements DSE and Patchguard, the vboxdrv_Win32module is extracted and executed. This DLL might be the most interesting from an analysis point of view.
The VBoxdrv module
The DLL starts by calling 2 functions located in the main dropper, with the goal of extracting and starting the signed VirtualBox driver named “sbhub.sys. Once the VirtualBox driver has been successfully started, pxinsi64.ex” (the executable that implements the user-mode part of the “VirtualBox exploit") is extracted from the module and executed using the CreateProcess API function. The VBoxDrv module now waits for the spawned process to complete execution.
This spawned 64-bit process first tries to open the VirtualBox device \\.\VBoxDrvand, if successful, calls the function GetDseSymbolPtrto get the address of the kernel DSE variable g_ciEnabled. If pxinsi64.exe can't open the VirtualBox device, it immediately terminates. In fact, if the VirtualBox driver has not started correctly, Uroburos is not able to load an unsigned driver in x64 environments.The function GetDseSymbolPtr warrants a closer look. I provide here the pseudo code:
NTSTATUS GetDseSymbolPtr (LPVOID * pCiEnableVa) { DWORD dwJmpCiIatRva = 0; // “JMP cs:_imp_CiInitialize” RVA // … Get needed buffer size … CALL ZwQuerySystemInformation(SystemModuleInformation, lpSysModInfo, 0, &buffSize); for (i = 0; i < lpSysModInfo.NumModules; i++) { OPEN kernel sys file directly from Disk and map // OpenReadAndRelocModule virus routine Analyse on-disk module Import Table, find “CiInitialize” imported name if (IAT_Symbol not found) continue; // goto next module for (offset = 0; offset < curModule.size; offset++) { curByte = curModuleBuff[offset]; // resolve “CiInitializeStub” routine address searching for “JMP _imp_CiInitialize” opcode if ((curByte == JMP FAR opcode) && (JMP FAR offset == “CiInitialize” IAT entry)) Save this RVA in dwJmpCiIatRva if (((curByte == CALL FAR opcode) && (CALL FAR offset == dwJmpCiIatRva)) // Go backward and search “MOV CS:g_ciEnabled, 1” while (offset > 0) { curByte = curModuleBuff[offset]; if (curByte == “MOV CS:REL32, imm8” opcode && sourceOperand == 1) Resolve destination REL32 operand and return it. This is the “g_ciEnabled” address } } } }
Strictly speaking, the algorithm resolves the CiInitializeStub stub function address, then tries to reach the CALL CiInitializeStub instruction located in the SepinitializeCodeIntegrity Nt kernel internal routine. This routine is the one responsible for initializing the Driver Signing Enforcement when the system boots up. When the Uroburos code locates this CALL, it proceeds to search backward for the mov cs:REL32, 1 opcode, and, if it finds it, resolves REL32 destination operand address. This symbol is the g_ciEnabled DSE Kernel variable.
At this point, pxinsi64.execan exploit the VirtualBox driver, by calling the Windows API function DeviceIoControl with the SUP_IOCTL_FAST_DO_NOP control code, as explained here. However, before triggering the exploit, pxinsi64.exe prepares the VirtualBox device, sending the following input/output controls, also known as IOCTLs: SUP_IOCTL_COOKIE, SUP_IOCTL_LDR_OPEN, SUP_IOCTL_LDR_LOAD. This is important, because the supdrvIOCtlFastinternal VirtualBox driver function, should return 0, and not an error code. The Write What Where conditions should indeed update the value of the g_ciEnabled variable with the value 0.
If all goes well, the Windows Driver Signature Enforcement protection is disabled and pxinsi64.exeexits with the error code 0. Otherwise, it terminates with a different error code.
The VboxDrv module wakes up and deletes the 2 extracted files (now no longer needed): the exploit executable pxinsi64.exe, and the bugged VirtualBox driver usbhub.sys. It finally exits. The main Uroburos dropper can now load and start its infection driver in the same manner as it does for 32-bit systems.
Conclusion
In this brief analysis, we provided an overview of the architecture of the Uroburos rootkit. Uroburos made use of a lot of clever tricks. We also provided an in-depth description of how Uroburos bypasses Driver Signature Enforcement (DSE).
In upcoming blog posts, we'll cover Uroburos':
- code to bypass Patchguard
- Virtual file system
Uroburos seems to have been put together with a lot of care. Interestingly, the packer used with the dropper doesn't seem to be as sophisticated as the rest of the techniques that are employed...
One last question remains: does the DSE bypass technique work on Windows 8 and/or Windows 8.1? The answer is no. As a matter of fact, if the host OS is a 64-bit version of Windows 8 or Windows 8.1, the VBoxDrv module fails to run and the entire setup process is aborted. DSE and Pathguard are implemented in a different way in Windows 8 and Windows 8.1. In upcoming blog posts we will look into the how in DSE and Patchguard are implemented differently between Windows 7 and Windows 8, and whether exploit mitigation techniques available on Windows 7 can be bypassed in Windows 8.
Stay tuned!
Past papers/bogs on Uroburos/Turla:
- BAE System reports: http://info.baesystemsdetica.com/rs/baesystems/images/snake_whitepaper.pdf
- Artemon Security report: http://artemonsecurity.com/uroburos.pdf
- GData Software blog post
Friday, April 18, 2014
Heartbleed for OpenVPN
The OpenVPN protocol encapsulates the SSL/TLS session used for authentication, key exchange, and data tunneling in order to provide the reliable transport layer the SSL/TLS session needs, (since OpenVPN is often run over UDP). One improvement, and challenge to exploitation, that OpenVPN provides over vanilla TLS is that it supports optional HMAC signing of OpenVPN messages using a passphrase or key. This is a challenge to the attacker because not only do you need to properly encapsulate your malicious heartbeat message, you also (in cases where the server requires message signing) have to sign it with a valid HMAC. It is important to note that HMAC signing does not prevent the OpenVPN server from being vulnerable, as it is still possible to leak memory using HMAC signing if you have the passphrase or key. Unfortunately many OpenVPN servers have this feature disabled and are vulnerable to memory disclosure without authentication. If you are running an OpenVPN server, it is strongly recommended that you upgrade to the latest version of OpenSSL and enable HMAC signing of OpenVPN messages.
The VRT has developed working Heartbleed exploits for OpenVPN running over TCP and UDP. Detection for this vulnerability includes coverage for servers running over TCP and UDP with HMAC signing and without HMAC signing in SIDs 30711 through 30742.
Thursday, April 10, 2014
Performing the Heartbleed Attack After the TLS Handshake
Our detection from the beginning has always ignored the heartbeat message data itself to avoid false positives arising from using ciphertext as if it was readable on the wire. Instead, we only use the unencrypted values within the TLS header.
Monday night, before Heartbleed really hit the news and public exploit code became available, the VRT created a proof-of-concept to demonstrate the Heartbleed bug by analyzing the openssl-1.0.1f code and modifying it to send malicious heartbeats and dump out the response to view the exposed data. By using this approach, the heartbeat request is sent after the TLS handshake, resulting in encrypted payloads. It turns out that by using our own exploit as the basis for detection, we were able to avoid the mistakes made by some others that will result in false positives against legitimate traffic since we never made the assumption that we could read the heartbeat message size.
t1_lib.c.diff is a patch to the openssl-1.0.1f source tree that implements the Heartbleed attack, after the TLS handshake has occurred. Steps to create the PoC are as follows --
$ wget https://labs.snort.org/files/t1_lib.c.diff
$ wget http://www.openssl.org/source/openssl-1.0.1f.tar.gz
$ tar -zxf openssl-1.0.1f.tar.gz
$ cd openssl-1.0.1f
$ patch -p0 < ../t1_lib.c.diff
$ ./config no-shared no-idea no-mdc2 no-rc5 zlib enable-tlsext no-ssl2 && make depend && make
$ apps/openssl s_client -tlsextdebug -connect <victim_server>:443
Once you connect, type 'B' to trigger a heartbeat then 'Q' to quit. You can send a few heartbeats per session if you want. At this point, many servers out there have disabled heartbeat support so don't be alarmed if you receive "peer does not accept heartbearts." This is a good thing!
We detect Heartbleed attacks whether they are encrypted or not by using detection_filter ("threshold") rules to discover too many heartbeat requests in a short amount of time as an attacker tries to gather memory dumps and by inspecting the TLS size in heartbeat responses for a value that is greater than the normal heartbeat response size.
More information about how the exploit works and our detection for it can be read at our original blog post on this subject, http://blog.talosintel.com/2014/04/heartbleed-memory-disclosure-upgrade.html
Heartbleed Continued - OpenSSL Client Memory Exposed
OpenSSL clients process heartbeats using the same vulnerable functions:
tls1_process_heartbeat()
and dtls1_process_heartbeat()
. The same memcpy()
overread detailed in our previous blog post allows malicious servers to read blocks of client memory. In internal testing we were able to extract memory from several client programs such as curl and wget, that link against the vulnerable OpenSSL versions. It is important to note the versions of these programs does not necessarily matter, if they are linking against the vulnerable OpenSSL versions.Research into other clients that link against the vulnerable versions of OpenSSL continues. Again, it is strongly recommended that you upgrade to OpenSSL version 1.0.1g or install a version of OpenSSL with heartbeats disabled.
We have released detection for the client side attack in SIDs 30520 through 30523, we have expanded detection port ranges to cover more vulnerable clients and servers, and last but not least, all Heartbleed rules have been added to the community ruleset - because we care.
Tuesday, April 8, 2014
Heartbleed Memory Disclosure - Upgrade OpenSSL Now!
-DOPENSSL_NO_HEARTBEATS
it is strongly recommended that you do so immediately.This vulnerability allows the attacker to read up to 64KB of heap memory from the victim without any privileged information or credentials. How is this possible? In short, OpenSSL's heartbeat processing functions use an attacker controlled length for copying data into heartbeat responses. Both DTLS and TLS heartbeat implementations are vulnerable.
The vulnerable functions are
tls1_process_heartbeat()
in ssl/t1_lib.c
(for TLS) and dtls1_process_heartbeat()
in ssl/d1_both.c
(for DTLS). Looking at these functions you can see that OpenSSL first reads the heartbeat type and length:hbtype = *p++;
n2s(p, payload);
pl = p;
n2s is a macro that takes two bytes from "p" and copies them to "payload". This is the length indicated by the SSL client for the heartbeat payload. Note: The actual length of the SSL record is not checked. The variable "pl" is a pointer to the heartbeat data sent by the client.
OpenSSL allocates as much memory as the client asked for (two byte length up to 65535 bytes) plus 1 byte for heartbeat type, 2 bytes for payload length, and 16 bytes for padding:
buffer = OPENSSL_malloc(1 + 2 + payload + padding);
bp = buffer;
Then it builds the heartbeat response by copying the payload size sent in the request to the response using the macro s2n (opposite of n2s). Finally (and here's the critical part), using the size supplied by the attacker rather than its actual length, it copies the request payload bytes to the response buffer.
*bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
memcpy(bp, pl, payload);
If the specified heartbeat request length is larger than its actual length, this
memcpy()
will read memory past the request buffer and store it in the response buffer which is sent to the attacker. In internal testing we were able to successfully retrieve usernames, passwords, and SSL certificates.To detect this vulnerability we use detection_filter ("threshold") rules to detect too many inbound heartbeat requests, which would be indicative of someone trying to read arbitrary blocks of data. Since OpenSSL uses hardcoded values that normally result in a 61 byte heartbeat message size, we also use rules to detect outbound heartbeat responses that are significantly above this size. Note: you can't simply compare the TLS record size with the heartbeat payload size since the heartbeat message (including the indicated payload size) is encrypted.
We have released detection in SIDs 30510 through 30517 to detect attacks targeting this vulnerability.
To keep people updated, Heartbleed rules have been added to the community ruleset.
Microsoft Update Tuesday: April 2014, two final XP and Office 2003 fixes
CVE-2014-1761, Oh did you mean CVE-2012-2539?
When the in the wild sample finally arrived I thought someone was playing an early April Fool's joke on us: I knew this vulnerability already. More than that, I had written the coverage for this almost a year and half ago! The vulnerability appeared to be CVE-2012-2539, which was released December 11th 2012 as Microsoft Security Bulletin MS12-079. I checked blogs, looked for any mistakes in the hash I had gotten but, no, this WAS the dreaded vulnerability that prompted Yahoo Finance to tell everyone not to open any RTF files. So I did some searching in my old research and found that I had written Snort rules 24974 and 24975 way back in December of 2012 for this vulnerability. The release posts on Snort.org's blog confirmed this (blog|rule changes). The rule even specifies the vulnerable element of the RTF specification, listoverridecount, in the message.
I enjoyed this hilarious state of affairs and we kept it to ourselves until someone else found it out, for dramatic effect if you will. Lo and behold, this week's blog posts by other security vendors popped up, pointing to listoverridecount as the exploitation vector. This confirmed what we already knew, that this vulnerability was centered around the listoverridecount value. The blog posts rightly deduced that the only legal values for this element are 0, 1 or 9 and other values could cause a crash. Our detection on both Snort and ClamAV already detected that. Interestingly though, there seems to be some programs that generate RTF out there that can generate values for listoverridecount that are not 0, 1 or 9, as we found out when someone submitted a sample to ClamAV that has the SHA256 hash:
3fbffe29252df6a87f37962afe72576ea2a7a5540d6c7993cbbff265fcd2734das a potential false positive for the a signature we have to detect attacks leveraging CVE-2012-2539.
ClamAV was the only vendor to detect it before we decided it was prudent to turn the signature into a PUA (Potentially Unwanted Application) signature since no one seemed to be exploiting it actively. The Snort rules have now been updated with new references and a non PUA ClamAV signature that references CVE-2014-1761 has gone out (I can only hope that alternate RTF generators stop using invalid values in their listoverridecounts).
All in all this 0-day has been a little bit disappointing since it was a rehash of a known vulnerability we already covered, but what I can console myself with is the fact that someone, somewhere is probably majorly annoyed because the exploit they built or bought is not working against Sourcefire/CISCO customers!
Monday, April 7, 2014
Dynamically Unpacking Malware With Pin
It is a fairly tedious task to follow execution in a debugger in order to retrieve unpacked code. You need to skim thousands of instructions, set breakpoints, watch calls to functions, unset breakpoints, accidentally allow the malware to execute, revert your VM, get back to where you were, read more disassembly, then finally dump memory and analyze that when you get to something interesting. This can take hours, sometimes days or more.
The Dropper
The dropper (MD5: 2E57C0CA7553263E7B6010B850FF2E48) is covered by an NDB signature, Win.Trojan.Zbot-30983. This signature targets bytes from the first stage’s unpacking loop as these bytes were seen to be consistent among all similar samples.Win.Trojan.Zbot-30983:1:*:8b95a0f6ffff33c08a8415a7f6ffff83f00233858cf6ffff8b8da0f6ffff88840da7f6ffff{-20}410f95c0ff75203bc68d8d6cfdffff59e815ffffff{-75}8b95a0f6ffff33c08a8415a7f6ffff83f0028b8da0f6ffff88840da7f6ffffThis initial unpacking function opens the binary (itself),
seeks
and ftells
for the size, mallocs a buffer, then reads its bytes into the buffer. Beginning at offset 0x4FD8
the function searches for the byte pattern:NN ?? (NN+1) ?? (NN+2) ?? (NN+3) ?? (NN+4)
Writing the same in Python we can identify the offset 0x51A9C, which places us 0x89D bytes from the end of the file. The matching pattern:
9C 54 9D 91 9E FB 9F 69 A0
There is then a loop that copies the 0x956 bytes immediately following that pattern to a local buffer. It then xor decodes the first 0x84A bytes of that buffer with the 6th byte of the 9 bytes extracted above, 0xFB. That is the variable labeled as xor_byte in the above screenshot. Once this memory is decoded, it is executed.
The Pintool
Pin enables you to instrument binaries. That is, you can write code to execute between each instruction, basic block, or routine, you can instrument threads, as well, there is a lot more functionality that would be difficult to list here like hooking system calls. The goal of this Pintool was to simply execute this malware and retrieve the unpacked code.To achieve this, I started with one of Pin’s examples which records memory reads and writes. I only cared about the writes, so I cut out the code for handling reads.
Any time an opcode for writing memory is detected, the program retrieves the write's target address. It then takes that write address and scans memory regions using
VirtualQuery()
in order to find the base address of the page that the write address belongs to. Once the owning page is found, that page's info is returned. The page's start and end addresses are stored in a map. Rather than storing every single address that was written to, we instead store ranges of memory, this saves a significant amount of space.// Records a memory write VOID RecordMemWrite(VOID * ip, VOID * addr) { map<VOID *, VOID *>::iterator i; for(i=writtenMap.begin(); i != writtenMap.end(); ++i) { if(addr >= i->first && addr < i->second) { return; } } WINDOWS::MEMORY_BASIC_INFORMATION *info = getAddrInfo(addr); if(info == NULL) return; writtenMap[info->BaseAddress] = ((UINT8 *)info->BaseAddress) + info->RegionSize; return; }
In addition to recording what memory is written to, the tool checks the address of every basic block executed. If this address falls within one of the memory regions that was previously written to, that memory is dumped to file. The tool then removes the record of that write so that the memory will not be dumped to file again unless it is subsequently written to then executed. This avoids writing to the disk as every single basic block inside a memory region is executed.
VOID checkBBL(ADDRINT addr) { map<VOID *, VOID *>::iterator i; FILE *memdump; char fname[30]; // Check if basic block (eip / rip) is in memory that was written to for(i=writtenMap.begin(); i != writtenMap.end(); ++i) { if(addr >= (ADDRINT)i->first && addr < (ADDRINT)i->second) { // Dump memory to file sprintf(fname, "dumps\\%p.dump", i->first); memdump = fopen(fname, "wb"); fwrite(i->first, sizeof(char), (size_t)((ADDRINT)i->second - (ADDRINT)i->first), memdump); fclose(memdump); // Remove write record so we don't dump at every bb writtenMap.erase(i->first); break; } } return; }
The Result
Running the Pintool on the sample 2E57C0CA7553263E7B6010B850FF2E48, we get a total of 12 memory files.Of the memory dumps highlighted above, the smallest two (0018D000 and 0018E000) contain the second stage of unpacking (first stage discussed above), and the two larger files are the third unpacking stage. In the third stage, there is one rather lengthy, hideous function. This function calls itself recursively in order to run through different stages. We see some anti-analysis from the strings vmtoolsd.exe, VBoxService.exe, and SbieDll.dll (Sandboxie). The first two are checked when the function is called with 6 as the first argument. Sandboxie is checked when it is called with a 5.
Eventually, the function calls itself with 9 and that leads to the last stage of unpacking. The final stage uses the RunPE method. It calls CreateProcess on InternetExplorer. It then calls WriteProcessMemory a few times in order to replace code in the newly created process. Finally it calls ResumeThread to begin execution.
The final Zbot payload is detected by a signature dating back to 2011.
Trojan.Spy.Zbot-142:1:*:4973576f77363450726f6365737300002200250073002200000000002200250073002200200025007300000075736572656e762e646c6c00437265617465456e7669726f6e6d656e74426c6f636b000044657374726f79456e7669726f6e6d656e74426c6f636b003a640d0a64656c20222573220d0a6966206578697374202225732220676f746f206400006200610074000000406563686f206f66660d0a25730d0a64656c202f4620222573220d0a000000002f006300200022002500730022
Conclusion
This Pintool was able to get me all of the stages of the unpacker, however, since the sample used RunPE as the final stage I had to dump that manually. The memory dumps did allow me to quickly identify where to break and reach the right functions. Jurriaan Bremer has done some work on unpacking RunPE malware with Pin by hooking the system calls that are used during this process. Another useful addition to this tool would be dumping the call stack when the unpacked code is called. This would allow rapid identification of the unpacking functions at each stage.Pin is a powerful tool for dynamic malware analysis. This Pintool acts as good proof of concept to justify further work in this area. Setting up an unpacking environment with a powerful, generic unpacker will speed up analysis and classification of malware samples.
Wednesday, April 2, 2014
Using the Immunity Debugger API to Automate Analysis
GetProcAddress()
to get the address to a function located within a library. That address is stored in a variable and saved for later use. This becomes an issue while analyzing the application and coming across a call instruction that references a generic memory address. There is barely any information to indicate which function is being called. Although I could make some intelligent guesses about what is being called, it would be better to know the exact function.GetProcAddress()
with both the library and the function name are passed as parameters. The return value is the address of the provided function, and it is stored in a variable. Figures 1, 2, and 3 will are pulled directly from the disassembly in IDA.Figure 1: Unknown Function Call |
Figure 2: 4266FC Memory Address |
dword_4266FC
” XREF link in IDA to show the memory address where the function address is stored. Figure 2, shows us the details at .data:004266FC.
GetProcAddress()
call. The function, sub_410610
, is the culprit. This function contains multiple GetProcAddress()
calls. Each call has a return value that is stored in a separate variable. Figure 3: GetProcAddress() |
GetProcAddress()
. The return value of GetProcAddress()
is the memory address of the specified function, and it is stored in the EAX
register. Looking at the first instruction in Figure 3, the address for GetProcAddress()
is moved into EDI
. A few instructions below that is the call to GetProcAddress()
(CALL EDI
). The next thing to figure out is what happens to EAX
after the function call. Within five instructions EAX
is moved into dword_4266FC
.GetProcAddress()
, why can’t I just grab the name of the function from another spot in memory? Well, the malware author has come up with a method to obfuscate the function names.sub_401610
). Once the decoding is complete, the name is stored in a variable that is used for the GetProcAddress()
call. In figure 3, that variable part of the ‘lea edx, [esp+0E4h+var_74’
instruction.Figure 4: Obfuscated Function Names |
- Hook the function that does the
GetProcAddress()
calls - Get a list of where the
GetProcAddress()
calls are being made - Look for where EAX is being stored in a variable
- Record the address of the variable
- Record the function name
- Dump this info into a file
- Use IDA Python to read the file and load the data into IDA
Figure 5: Command Box |
Hooking the Function
Figure 6: Hook Breakpoint in Function
|
GetProcAddress()
, scripting a solution to get the address of the calls was easily accomplished. It doesn’t do much for making the script work for multiple situations, but it solves this problem. Figure 3 shows the CALL EDI instruction. This is used for every GetProcAddress()
call within this function. It is also only used for the GetProcAddress()
call. In addition, this function is just one long basic block. Based on this, I felt the easiest way to grab what I needed was to parse a list of the call instructions being executed and then grabbing the instructions following those calls.Figure 7: Breakpoint at End of Function |
Figure 8: Current Address, Basic Blocks, Call Instructions
|
Figure 9: CALL EDI and MOV Instructions |
GetProcAddress()
. On the first run through, I used the following code to get the name of the function:Figure 10: Function Name Return Values |
Figure 11: Memory Address and Function Name |
Figures 12, 13, and 14 shows the renamed sections and calls in IDA. Compare these with figures 1, 2, and 3. The code is now much easier to understand.
Figure 13: Location of Function Call |
|
Figure 14: Location of Memory Address |
Figure 15: EAX being Stored at Memory Address |
Conclusion