During malware analysis we can often encounter payloads in form of DLLs. Analyzing them dynamically may not be very handy, because they need some external loaders to run. Different researchers have different tricks to deal with them. In this post I will describe some of my tricks for patching DLLs, so that they can run as independent executables.
Usually we can encounter 2 cases:
- meaningful code starts in one of the exported functions
- meaningful code starts in DllMain
To illustrate those cases with real-life examples I will use 2 malware samples.
First case – represented by Bunitu Trojan (b0a91e1f91078bad48252edc989e868e) and second case: represented by Petya/Mischa dropper (c8e4829dcba8b288bd0ed75717214db6).
Those DLLs have been unpacked from their loaders/crypters – but in order to keep things simple I am not gonna describe here the full process of unpacking.
In both cases we will start from editing the field Characteristics in the File Header and removing the flag indicating that the file is a DLL. I will do it using PE-bear:
I changed the value:
Case #1: When the meaningful code starts in the exported function
In this case, one more modification is required before we save the file.
We have to change the entry point in order to point the appropriate function from export table – the one where the execution of the meaningful code starts. We need to find the Function RVA:
Then, follow it:
On the Disasm tab we can see the code of this function. Now, we should redirect Entry Point to it’s beginning:
And save the file as EXE:
In case of Bunitu Trojan, this function does not take any parameters, so we not need to fill anything more. Now, we can run the saved file like a normal executable (see the patched version at malwr).
Case #2 – meaningful code starts in DllMain
This time we not need to change the Entry Point – just after changing Characteristics in File Header save the file and load it under a debugger (I will use OllyDbg).
Similarly to the main function in typical executables, DLLs have their DllMain function that is executed automatically when they are loaded to the memory (the same function is also executed on few more events – i.e. on DLL unloading).
Let’s recall it’s header:
BOOL WINAPI DllMain( _In_ HINSTANCE hinstDLL, _In_ DWORD fdwReason, _In_ LPVOID lpvReserved );
As we can see, the function takes 3 arguments. The first one (hinstDLL) is the handle to the memory area where the DLL has been loaded. Second stores a value that indicates the reason why the DllMain has been triggered. Read more here.
To make our patched DLL run properly, we must take care that it’s arguments will be filled with proper values – especially important are the first two that I mentioned.
The first argument: hinstDLL – must contain the module handle (ImageBase). Second usually should be filled with 1 – to emulate DLL_PROCESS_ATTACH.
That’s how the Entry Point of the dumped executable looks:
Let’s add a code that will overwrite the arguments with valid values. I will utilize some free cave for this purpose. Fortunately, there is enough space at the end of the code section:
I am gonna do some patching in order to redirect execution to this place. We need 5 bytes for the jump – so, let’s remove/rearrange some code in order to gain the space (if we are lacking in space, some instructions can be also moved to the cave, along with the added code):
Patched – step 1:
Patched – step 2:
I redirected execution to the mentioned cave. We will copy aside the address that is just after the added jump, to go back to this place later.
Now let’s fill the cave with needed code.
To fill the first argument with the valid ImageBase I will copy the value from Process Environment Block (pointed by FS:) . This is example of the code that do this job:
MOV EAX, [FS:0X30] ; copy to EAX handle to PEB MOV EAX, [EAX+0X8] ; copy to EAX the field with ImageBase MOV [EBP+0X8], EAX ; copy the content of EAX into the first argument
Now, let’s fill the second argument with a vale 1, emulating that the DLL is loaded:
MOV DWORD [EBP+0XC], 0X1
The third argument can be filled with NULL:
MOV DWORD [EBP+0X10], 0
Now only returning jump is remaining:
And we can save the modified executable:
Now we can run/debug the saved file as a standalone executable.
Of course the the described techniques are not a silver bullet that will work for all the DLL payloads that we can encounter. My goal was just to provide some examples and inspiration for experiments. However, those simple tricks worked for me in many cases making the work much easier.