sample_j DllMain
So last time we finished some exercises from the book pratical reverse engineering. Now, we have page 35 exercises. I wont cover exercise here because some are really straight forward.
without useless intros lets get started.
Chaper 1, page 35⌗
We are starting with the second one, because first one is pretty easy. 2nd question asks us to decompile DllMain.
as we can see, IDA has generated us some information of the stack as well as a nice graph view. From that, we can decompile it down to,
BOOL __stdcall APIENTRY DllMain(HMODULE *hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
IDTR idtr;
PROCESSENTRY32 pe;
HANDLE handle;
}
next we can the function prologue, where stack frame is initialized. using sub esp, 130h
instruction, we can confirm that stack will be 0x130 bytes large. next we can see a sidt fword[ebp+idtr]
.
if you have some low level debugging/development experience, you might know what sidt
does. in case you dont, it reads idtr register to the operand location. in this case, fword[ebp+idr]
will be filled with idtr register. So whats this idtr register? well, idtr register is a 6byte sized register. it stores length of the interrupt desciptor table in the last 2 bytes and base of interrupt desciptor table in the top 4 bytes. Now, how can we call sidt
from C/C++?
before that, here we build the idtr structure
typedef struct idtr {
DWORD idt_base;
short idt_size;
} IDTR, *PIDTR;
inside DllMain we do this.
IDTR idtr;
__sidt(&idtr);
then DllMain compares idtr+2
with some 2 random hex values that looks like memory addresses. Using the above explaination, it is pretty clear that whoever wrote this malware compares base address of interrupt desciptor table with 2 hardcoded memory addresses, first as we can see in the above image, 0x8003f400 and 0x80047400 in the below image. Tbh, this is bad. Reason is that, in multi core processors each core gets a different base address for interrupt desciptor table. And when using hardcoded addresses… Anyway. lets decompile that part too.
if (idtr.idt_base > 0x8003f400 && idtr.idt_base < 0x80047400){
return FALSE;
}
From above snippet it is clear that it does check for some range. looks like it validates IDT base…
After the comparision if the range requirements are satisfied, we can see that DllMain creates a snapshot of all the running processes.
we can decompile that stub into
handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (handle == INVALID_HANDLE_VALUE) {
return FALSE;
}
it also zero out pe
using stosd
instruction.
memset(&pe.cntUsage, 0, 0x49);
Next, DllMain calls Process32First
with arguements &pe
(eax) and handle
(edi), a string comparison with explorer.exe
and then if comparison fails, we can see a call to Process32Next
.
Basically this stub go through each process of the snapshot until it finds explorer.exe
.
decompiled version of that stub may look like
pe.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(handle, &pe) != 0) {
// code here
}
else {
while (Process32Next(handle, &pe) != 0) {
if (wcscmp(pe.szExeFile, L"explorer.exe") == 0) {
// code here
}
continue;
}
return FALSE;
}
Once it found the explorer.exe, it compares the process’s pid to it’s parents pid.
here if they are equal, we simply exit out.
else, we create a thread in the current virtual memory space with start address set to 0x100032d0
.
do_evil_stuff:
if (pe.th32ParentProcessID == pe.th32ProcessID) {
return FALSE;
}
else {
if (ul_reason_for_call == DLL_PROCESS_ACCESS) {
CreateThread(
NULL, NULL, (LPTHREAD_START_ROUTINE)0x100032d0, NULL, NULL, NULL
);
return TRUE;
}
return TRUE:
}
with that info, we can fill out the stub that searches for explorer.exe like this
pe.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(handle, &pe) != 0) {
if (wcscmp(pe.szExeFile, L"explorer.exe") == 0)
goto do_evil_stuff;
else {
while (Process32Next(handle, &pe) != 0) {
if (wcscmp(pe.szExeFile, L"explorer.exe") == 0) {
goto do_evil_stuff;
}
continue;
}
return FALSE;
}
}
combining all into a single stub, we will get something like this as the DllMain
typedef struct idtr {
DWORD idt_base;
short idt_size;
} IDTR, *PIDTR;
BOOL __stdcall APIENTRY DllMain(HMODULE *hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
IDTR idtr;
PROCESSENTRY32 pe;
HANDLE handle;
__sidt(&idtr);
if (idtr.idt_base < 0x80047400 && idtr.idt_base > 0x8003f400){
return FALSE;
}
handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (handle == INVALID_HANDLE_VALUE) {
return FALSE;
}
memset(&pe.cntUsage, 0, 0x49);
pe.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(handle, &pe) != 0) {
if (wcscmp(pe.szExeFile, L"explorer.exe") == 0)
goto do_evil_stuff;
else {
while (Process32Next(handle, &pe) != 0) {
if (wcscmp(pe.szExeFile, L"explorer.exe") == 0) {
goto do_evil_stuff;
}
continue;
}
return FALSE;
}
}
do_evil_stuff:
if (pe.th32ParentProcessID == pe.th32ProcessID) {
return FALSE;
}
else {
if (ul_reason_for_call == DLL_PROCESS_ACCESS) {
CreateThread(
NULL, NULL, (LPTHREAD_START_ROUTINE)0x100032d0, NULL, NULL, NULL
);
return TRUE;
}
return TRUE:
}
}
#Spread Anarchy!