Implementing all sort of methods to bypass anti-virus (AV) scanners and/or to make the analysis of a malware sample a lot harder, at least from a static point of view, is an old dog’s trick.
At Nettitude, we see a lot of these techniques in evidence in malware that we come across during client engagements or that we personally collect via honeypots and other means.
Dealing with packers, either known or custom ones, is quite often an issue that any malware analyst has to deal with. Successfully unpacking and isolating the malware from the top protection layers can be really useful since it allows the analyst to concentrate only at the code that matters, and perform more detailed static analysis on the sample itself.
The custom layer
It doesn’t take much to realise that something is ‘wrong’ with this sample. Just by looking at the entry point of the module, an experienced eye will definitely blink.
MOVD MM5, EBX
MOVD ECX, MM5
ADD ECX, 2C6
This jump takes us to a custom decryption code block. Not surprisingly, this is heavily obfuscated with a lot of junk instructions.
The following code block shows a few effective instructions surrounded by junk code.
For example, if you take a look at the first three instructions, the ‘ADD ECX,EBP’ instruction is a junk instruction. These three instructions can be re-written with just one: “MOV ECX,EDX”.
ADD ECX, EBP
MOV EAX, ECX
MOV CL, 0CA
XCHG CH, CH
ADD EBX, EAX
ADD EBX, DWORD PTR SS:[ESP]
AND ECX, D73E1285
LEA ECX, DWORD PTR DS:[E58CFC96]
MOV EAX, DWORD PTR DS:[EBX]
XOR ECX, EAX
CMP EDI, 0E2E4
00401D4C BD 6149A0AD MOV EBP, ADA04961
MOV ECX, EDI
MOV EBP, EBP
IMUL EBP, EDI, CE86F98B
SUB EAX, ECX
MOV CH, AL
MOV ECX, F7F0D4F3
MOV DWORD PTR DS:[EBX], EAX
MOV CL, 0CC
LEA ECX, DWORD PTR DS:[15F75C0D]
SBB CL, BH
Once the custom decryption algorithm has finished its job, the execution will be transferred on the decrypted code block.
This stage of the custom packer acts as a loader. It will make use of a combination of CreateFileMapping/MapViewOfFile functions to allocate memory and copy there at the very same stage.
In the meantime, the authors didn’t forget to use some ‘funny’ names for the file mapping object: “Sessions1BaseNamedObjectspurity_control_7728”.
However, execution will never be transferred on that memory block and it will continue executing that stage from the original memory pages.
The next step is to create a new thread that will handle the final stage of this custom packing layer.
Thread entry point:
ENTER 0, 0
MOV EBP, DWORD PTR SS:[EBP+8]
CMP BYTE PTR SS:[EBP+402773], 1
MOV ECX, DWORD PTR SS:[EBP+402774]
TEST ECX, ECX
JE SHORT 00402A15
This stage will finally make use of VirtualAlloc to copy the decrypted UPX-packed malware and pass execution to the entry point of the UPX packer.
However, there is a problem to solve at this stage. The execution is transferred out of the PE image memory range of the executable we are debugging. This can further confuse some unpacking tools since they will be trying to read information from the PE header of the original module in memory.
Even though there are techniques that an experienced unpacker can use to force those tools to read the information that they want, there are also cases like this one in which a simple trick can solve a big problem.
As already mentioned, the entire decrypted UPX-packed malware is now copied to another memory location. It is basically an entire PE file loaded in memory in the same way that would be if we had read the file from disk into a buffer.
So before proceeding into the next packing layer, we can easily dump and isolate the UPX-packed malware from memory so that we won’t have to deal with the first custom layer again.
After we have dumped the UPX-packed malware from memory we can directly load this back to the debugger since it is basically a fully functional PE file. In summary, the custom packing layer is totally out of the game at this point.
Unpacking UPX is straight forward, and you can easily find a number of tutorials online that explain how it can be done, but since we are here let’s show this one more time.
UPX entry point:
MOV ESI, 004AD000
LEA EDI, DWORD PTR DS:[ESI+FFF54000]
OR EBP, FFFFFFFF
JMP SHORT 004B7AE2
The above code block is a common UPX entry point. Nothing really interesting here, apart from the fact that if you are familiar with UPX you can easily identify what you are dealing with.
Finding your way to the original entry point of the packed application is really easy. All you need to do is to scroll down the code until you find the following instructions.
Jmp to OEP:
LEA EAX, DWORD PTR SS:[ESP-80]
CMP ESP, EAX
JNZ SHORT 004B7C9C
SUB ESP, -80
JMP 00434567 JUMP to Original Entry Point. Place BP here and run.
MOV EBP, ESP
SUB ESP, 194
MOV DWORD PTR SS:[EBP-194], 0
CALL DWORD PTR DS:[40113C] ; kernel32.SetErrorMode
LEA EAX, DWORD PTR SS:[EBP-190]
CALL DWORD PTR DS:[4011EC] ; WS2_32.WSAStartup
In order to dump the unpacked PE file from memory we used the OllyDump plugin, but you can you can use others as well. However, if you use this one, remember to copy the RVA of the entry point (highlighted in the Figure 2) and also uncheck the ‘Rebuild Import’ since we will be using another tool to do so.
After we have saved the output in a file called ‘dump.pe’, we need to fix the imports table so that the dumped executable can load and run.
We will be using another tool, called Import REConstructor and in a few simple steps (Figure 3) we can have a fully functional and unpacked executable to start analysing.
Going through the process of manually unpacking and isolating the original malware from the top protection layers might not be something that you will always need to do in order to have an overview of what a malware sample is doing. However, when more detailed analysis is needed for sophisticated and complex malware, having the original executable isolated from all the packing layers might be a privilege rather than just a convenience. Keep in mind though, that some malware might check on runtime if they have been unpacked or modified.
To contact Nettitude’s editor, please email email@example.com.