One of the hardest things about reverse engineering Visual Basic applications is the obscurity of system calls. You can't simply patch the IAT to obtain a hook to your desired function. It is pretty obvious that the libraries are loaded dynamically because of the run-time errors generated when libraries aren't found. This quick guide will show you how VB makes library calls in assembly and then show you how to hook VB's DllFunctionCall() in order to call your own functions.
How does VB handle declarations? Well, let's look at some assembly!
Each declaration has a short stub that checks to see if the pointer to that function exists and if not, calls DllFunctionCall() to get it. For this example we'll see how GetWindowThreadProcessId() in user32.dll is called.
That function looks like this
DWORD GetWindowThreadProcessId(
HWND hWnd,
LPDWORD lpdwProcessId
);
When a VB application calls this function it will look like this
push lpdwProcessId
push hWnd
call sub_403208 ; stub for GetWindowThreadProcessId
The stub that it calls looks like this
.text:004031F0 dd offset aUser32 ; offset to "user32"
.text:004031F4 dd offset aGetWindowThreadProcessId ; offset to "GetWindowThreadProcessId"
.text:004031F8 dd 40000h, 41A3A0h, 2 dup(0)
.text:00403208
.text:00403208
.text:00403208 sub_403208 proc near
.text:00403208 mov eax, dword_41A3A8 ; move address of GetWindowThreadProcessId into eax
.text:0040320D or eax, eax ; sets zf if the function address == 0
.text:0040320F jz short loc_403213 ; if function address == 0 then load the library and get the address
.text:00403211 jmp eax ; else jmp to the function (no need to use call, the ret address is already on the stack)
.text:00403213 loc_403213:
.text:00403213 push offset off_4031F0 ; put the struct pointer argument on the stack
.text:00403218 mov eax, offset DllFunctionCall
.text:0040321D call eax ; DllFunctionCall
.text:0040321F jmp eax ; jmp to the returned function
.text:0040321F sub_403208 endp
Now, without looking at the internals of DllFunctionCall() you can guess what it does. It returns a pointer to the address of the function we are requesting. How then, does dword_41A3A8 get set to the address of our function? Well DllFunctionCall() must also modify the structure we passed to it earlier! We can see that structure looks like this in C:
struct DllCallData {
char * library;
char * funcName;
DWORD * unknown1;
DWORD * unknown2;
};
We can see from our data that unknown1 = 40000h and unknown2 = 41A3A0h. By observation we can see that dword_41A3A8 is only 8 bytes away from unknown2. So dword_41A3A8 must be the 2nd index of that array.
Now putting it all together our hooked function will look something like this:
DWORD __stdcall MyDllFunctionCall(struct DllCallData * callData)
{
DWORD ret = RealDllFunctionCall(callData); // call the actual DllFunctionCall from MSVBVM60.dll
if (strnicmp(callData->library, "user32", 6) == 0 &&
stricmp(callData->funcName, "GetWindowThreadProcessId") == 0) {
ret = callData->unknown2[2] = (DWORD)&MyGetWindowThreadProcessId;
}
return ret;
}
Every time GetWindowThreadProcessId() is called from within our application now your function will be called. Whooo!