Saturday, February 28, 2015

Shellcode Analysis of a CVE-2009-4324 Exploit

While researching a malware campaign during my day job, I stumbled across a PDF laced with CVE-2009-4324. As a lesson in learning, I spent a few weekend hours slogging through the shellcode assembly and these are my notes.

The MD5 of the PDF is a674f8b451634c156c0fbc748cec2093.

Shellcode Extraction

1) Extract the JavaScript from the PDF using peepdf:











2) Copy and paste the contents of the sc variable into a scratch Python program and run the following regex on it to convert from %u909" to \x90\x90 format:

re.sub("[\\%]u([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})", r"\\x\2\\x\1", sc)

3) Write it out as binary to sc.bin.

Start of Shellcode











1) NOP sled to ep

2) Some anti-analysis where the 5 byte call calls ep+4 which is part of the call's opcodes

3) GetPC function sticks EIP into EDI

4) call find_kernel32

5) Following the call is a string containing:

kernel32.dll GetProcAddressLoadLibraryAws2_32.dll

Find Kernel32












1) Pop the address of the above string into EBP (address was pushed onto the stack by the call)

2) PEB

3) In module load order list

4) Loop through the modules starting from the last loaded module










1) Compare lowercase DLL name with kernel32.dll<space> (from string)

2) Once found, ESI will contain kernel32.dll's base address

Resolve Initial Functions









1) resolv_func_addr("GetProcAddress", 14)

2) resolve_func_addr("LoadLibraryA", 12)

3) LoadLibraryA("ws2_32.dll")
  • ESI = address of kernel32.dll
  • ECX = address of ws32_32.dll
  • EDX = address of LoadLibraryA
  • EDI = address of GetProcAddress
resolve_func_addr














1) Skip over PE DOS header

2) RVA and address of export table

3) Number of exports

4) RVA and address of function array

5) RVA and address of names array

6) RVA and address of ordinals array













7) Loop to compare the function name and length with names array entries

8) If found, index into ordinals array to get ordinal

9) Index into function array with ordinal to get function RVA

10) Add kernel32.dll base address and return function address

Disable DEP













1) LoadLibraryA("ntdll.dll")

2) GetProcAddress(ntdll.dll, "NtSetInformationProcess")

3) Bypass DEP via NtSetInformation(-1, 0x22, 2, 4)

Resolve Process Injection Functions









1) GetProcAddress(kernel32.dll, "OpenProcess")

2) GetProcAddress(kernel32.dll, "VirtualAllocEx")

3) GetProcAddress(kernel32.dll, "WriteProcessMemory")

4) GetProcAddress(kernel32.dll, "CreateRemoteThread")

5) GetProcAddress(kernel32.dll, "CloseHandle")

Set SeDebugPrivilege










1) Setup TOKEN_PRIVILEGES for later AdjustTokenPrivileges

2) LoadLibraryA("advapi32.dll")

3) GetProcAddress(advapi32.dll, "LookupPrivilegeValueA")

4) LookupPrivilegeA(0, "SeDebugPrivilege", Luid)

5) GetProcAddress(advapi32.dll, "OpenProcessToken")

6) OpenProcessToken(-1, TOKEN_ADJUST_PRIVILEGES, TokenHandle)

7) GetProcAddress(advapi32.dll, "AdjustTokenPrivileges")

8) AdjustTokenPrivileges() -- enable SeDebugPrivileges

9) LoadLibraryA("psapi.dll")

Find Explorer.exe













1) GetProcAddress(psapi.dll, "GetModuleBaseNameA")

2) LoadLibraryA("ntdll.dll")

3) GetProcAddress(ntdll.dll, "_stricmp")

4) GetProcAddress(psapi.dll, "EnumProcesses")

5) EnumProcesses()

6) Loop through processes

7) OpenProcess(each pid)

8) GetModuleBaseNameA(each pid) -- get filename

9) If process is explorer.exe

Inject into Explorer.exe












1) VirtualAllocEx in explorer.exe

2) WriteProcessMemory() in explorer.exe -- copy from find_kernel32 (0x1a) to right before call proc_inj (0x15c)

3) WriteProcessMemory() in explorer.exe starting from remote buffer+0x142 (right at call proc_inj) -- saved return pointer after call proc_inj to 0x6f9 (rest of the code).

2/3b Boils down to more or less copying itself into explorer.exe sans the process injection code

4) CreateRemoteThead() starting at find_kernel32

5) Some cleanup

6) ExitProcess() -- code is running in a thread in explorer.exe

Resolve Network Functions










1) GetProcAddress(ws2_32.dll, "WSAStartup")

2) WSAStartup()

3) GetProcAddress(ws2_32.dll, "socket")

4) socket(2, 1, 6) -- TCP

5) GetProcAddress(ws2_32.dll, "connect")

6) connect(66.192.70.35:443)

7) GetProcAddress(ws2_32.dll, "send")

8) GetProcAddress(ws2_32.dll, "recv")

Send







1) Send buffer is inline with the code, and its meaning is unknown to me:

"7fc2fff99bc9c21b54417ed85769".decode("hex")

1) send(socket, buffer, 14, 0)

Here is the packet on the wire:
















Jump Over Send Buffer









Recv


1) Receive 4 bytes -- size of the next recv

2) VirtualAlloc() -- of X size

3) Receive X bytes into buffer

4) Jump to buffer

Second Stage Shellcode

While the IP and port were open at the time of writing, I couldn't solicit a response. My educated guess is that it would be a second stage shellcode.

66.192.70.35

At the time of writing 66.192.70.35 resolves to 66-192-70-35.static.twtelecom.net. VirusTotal has hits on the IP for:
  • hXXp://www.banksrb.com/rpt/0e1bc7e54ae82b82/RHP-01MTuQ3xAOUnjvYMN11vNbcwaa/index.html
  • hXXp://banksrb.com/rpt
  • hXXp://banksrb.com/
Fancy VirusTotal Intelligence has a source of the PDF as:
  • hXXp://www.banksrb.com/rpt/9bc9c21b54417ed8/RHP039skcV1eik07SKYYFJEw2rgaa/index.html
#totalhash has 2 hits on the IP:

0x00000000 (00000)   7fcbffcf 6e6f636f 6f6b6965            ....nocookie

which is interesting because it starts with the same "7fc".

My IDA database is at https://github.com/tildedennis/exploits/tree/master/CVE-2009-4324.