Today I’m going to tell you something about the last malware I checked (MD5 0C17E03F41289E47EEB5D0F3F1F48C9C).
The exe file imports few functions only, but the malware calls a lot of APIs. The author uses a special trick to call an API function, he creates a sort of bridge between the first instruction and the rest of the code of the function itself. The first instruction is executed directly from the stack, then a jmp instruction (the bridge) will lead you to the second instruction (and the rest of the code) of the function. I think I’ve already seen the trick somewhere but unfortunately I don’t remember where… maybe a specific packer or just something similar, I don’t know. If you have seen this trick before just drop me a comment, thx!
The malware is packed, but inside the unpacked file there’s something strange:
The exe is full of calls to NULL value. I’m pretty sure that it’s not an error occorred during the unpacking process; there’s something at the beginning of the exe able to fix the addresses. I started my analysis from the first lines of the unpacked file.
The Import Table is really small but spying inside the strings window I found a lot of common API strings. There’s a big list of functions, and they are divided into some groups; one group containing kernel32 functions, another one with user32 and so on. Working a little with some cross references I got the point I was looking for. It’s time to describe how the malware changes all the “call NULL” instructions.
First of all the malware gains access to kernel32 base address:
4013D5 mov edi, large fs:30h ; PEB 4013DC mov edi, [edi+0Ch] ; PEB+0x00c Ldr : Ptr32 _PEB_LDR_DATA 4013DF mov edi, [edi+0Ch] ; +0x00c InLoadOrderModuleList : _LIST_ENTRY 4013E2 jmp short loc_401404 4013E4 check_current_module: 4013E4 mov eax, edi ; eax points to current _LDR_MODULE structure 4013E6 add eax, 2Ch 4013E9 push [ebp+arg_0] ; unicode "kernel32.dll" 4013EC push dword ptr [eax+4] ; current module name inside InLoadOrderModuleList 4013EF call Compare_UNICODE_Strings 4013F4 or eax, eax 4013F6 jnz short strings_are_not_equal 4013F8 mov eax, edi ; LDR_MODULE of the module I was looking for 4013FA mov eax, [eax+18h] ; He gets the BaseAddress!!! 4013FD pop edi 4013FE leave 4013FF retn 4 401402 strings_are_not_equal: 401402 mov edi, [edi] ; jump to next module structure 401404 loc_401404: 401404 cmp dword ptr [edi+18h], 0 ; Is BaseAddress 0? 401408 jnz short check_current_module 40140A xor eax, eax 40140C pop edi 40140D leave 40140E retn 4
Quite common way, but quite uncommon inside a malware… at least from my not so experienced perspective. Anyway, once it has the right BaseAddress tha malware starts bridge-ing all the necessary functions.
It’s everything inside this call. It takes four parameters, we can ignore the first one pushed into the stack. What about the others?
- eax represents the BaseAddress of a module, in this case ntdll
- 404040 points to a sequence of strings, in this case the first one is “RtlZeroMemory”
- 406000 represents an address inside the malware
The procedure is called each time the malware needs to bridge a group of functions, all of them belong to a specific module. In this specific case it works with ntdll’s functions. The list of the functions starts from 0×404040 address:
The routine contains a loop running until all the functions inside the current group are not all bridged. Here is how the first function is bridged (the sub routine starts at 0×401411):
1. It takes VirtualAlloc starting address via Kernel32′s ExportTable
2. It gets the number of bytes of the first instruction of VirtualAlloc. On my XP machine VirtualAlloc starts with a two byte length instruction “MOV EDI, EDI”
3. It subtracts 7 from ESP value. 7 is obtained adding 5 (a fixed value) to the number of bytes of VirtualAlloc first instruction (2+5=7)
4. It copies the first two bytes of VirtualAlloc’s code inside the word pointed by the new stack pointer value
5. If the length of the first instruction is one byte only the malware checks for a possible active breakpoint comparing the byte with 0xCC value; quite useless check at this point…
6. It sets the byte pointed by ESP+2 to 0xE9
7. It fills the final 4 of 7 bytes obtaining:
You have the first instruction of VirtualAlloc at 0x12FDF5, then the jump instruction will lead you directly at the second instruction of VirtualAlloc. Now you understand why it decreases ESP value by 7, two bytes for the first instruction and 5 for the jump. Starting from 0x12FDFC you have the old untouched stack.
8. It gets the length of the first instruction of the function to bridge. In this specific case the name of the function is RtlZeroMemory and the length is 1
9. It adds 5 to the obtained value
10. It calls VirtualAlloc passing trought the stack. It calls 0x12FDF5, and it allocs 6 bytes. 6 is the value that comes from point #9 (5+1=6)
11. It copies the first instruction of RtlZeroMemory inside the allocated memory space
12. An anti breakpoint check occours this time because the first instruction is one byte only
13. It sets the second byte inside the allocated memory space to 0xE9 (again, a jmp instruction)
14. It fills the rest of the bytes:
Here is the bridge! It’s like what happened to VirtualAlloc, the allocated memory space contains the first instruction of RtlZeroMemory and a jump to the rest of the code
15. It restores the original stack pointer value simply adding 7 to the current ESP value
16. It returns the starting address of the allocated memory space (in this case 0×320000)
To sum up, the routine bridges the function and returns a memory address. The address will be saved starting from 0×406000 which is another parameter passed to the routine. If you don’t remind all the parameters you can take a look some lines above.
So, starting from 0×406000 you’ll have a series of dwords, each one containing a pointer to a memory allocated space; these are the values used to replace all the NULL calls. Now I finally know why after the unpacking process I still had a series of “call NULL” instructions.
Is it really necessary to bridge everything?
Yes if the author wants to fool an automatic analysis. I don’t know if the trick works or not, but it’s the only reasonable thing I can think of.
On the other hand, he can’t stop a complete human analysis because once you know how it works it’s pretty easy to convert all the “call NULL” instructions into the right ones; a simple idc script will solve the puzzle.
It’s a nice piece of malware to analyse btw, it has some interesting routines inside!