Valhalla Legends Forums Archive | C/C++ Programming | Injecting a DLL via SetWindowsHookEx

AuthorMessageTime
NicoQwertyu
I've been trying to learn how to inject code into a running process for a long time now, and I still am not able to grasp it.  Yes, I've searched google numerous times  :-[.

This is a simple dll and loader I'm trying to write that will simply force Calculator.exe to load my DLL.

testdll.dll
[code]
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>


extern "C" __declspec(dllexport) LRESULT CALLBACK DummieCallback(int nCode, WPARAM wParam, LPARAM lParam);


BOOL APIENTRY DllMain(HANDLE hModule, DWORD  dwReason, LPVOID lpReserved)
{

switch (dwReason)
{
case DLL_PROCESS_ATTACH: MessageBox(NULL, "DLL injected!", "OK", MB_OK); break;
case DLL_PROCESS_DETACH: MessageBox(NULL, "DLL ejected!", "OK", MB_OK); break;
}

    return TRUE;
}



extern "C" __declspec(dllexport) LRESULT CALLBACK DummieCallback(int nCode, WPARAM wParam, LPARAM lParam)
{

///////////
// This function is used for SetWindowsHookEx.
// We don't really care what it returns, but it's needed for the
// way I'm trying to do this.
//////////

return CallNextHookEx(NULL, nCode, wParam, lParam);

}
[/code]



launcher
[code]
#include <windows.h>
#include <stdio.h>

int main()
{
HWND hWndProcess;
DWORD dwThreadId;
HMODULE hInjected;
HOOKPROC hDummieProc;

hWndProcess = FindWindow(NULL, "Calculator");
dwThreadId = GetWindowThreadProcessId(hWndProcess, NULL);

if (hWndProcess == 0 || dwThreadId == 0)
{
MessageBox(NULL, "Unable to gain thread id!", "ERROR", MB_OK);
return 0;
}

hInjected = LoadLibrary("testdll.dll");
hDummieProc = (HOOKPROC)GetProcAddress(hInjected, "DummieCallback");

if (hInjected == NULL || hDummieProc == NULL)
{
MessageBox(NULL, "Unable to inject DLL!", "ERROR", MB_OK);
FreeLibrary(hInjected);
return 0;
}

SetWindowsHookEx(WH_CALLWNDPROC, hDummieProc, (HINSTANCE)hInjected, dwThreadId);
return 0;
}

[/code]


The few message boxes that pop up are:

DLL Injected!  (LoadLibrary returned)
Unable to inject DLL! (dwThreadId is NULL)
DLL ejected! (my program ends?)


The problem I'm having is: GetProcAddress always returns NULL, and I don't know why.  Any help would be appreciated.



[Edit]
P.S. I'm using SetWindowsHookEx instead of the other couple alternatives for injecting code because I'm on Win ME.
November 16, 2004, 12:28 AM
Myndfyr
[quote author=NicoQwertyu link=topic=9566.msg88977#msg88977 date=1100564933]
[Edit]
P.S. I'm using SetWindowsHookEx instead of the other couple alternatives for injecting code because I'm on Win ME.
[/quote]

That's the problem.  You're running on antiquated, unreliable, piece-of-trash code.  :P

I'll take a look at your actual code a little bit later.  For now I have to voice my disdain at Windows "Me".
November 16, 2004, 12:32 AM
kamakazie
Does hInjected return NULL as well?
November 16, 2004, 12:32 AM
NicoQwertyu
[quote author=dxoigmn link=topic=9566.msg88979#msg88979 date=1100565146]
Does hInjected return NULL as well?
[/quote]

No, it doesn't.

Damn you guys are fast...
November 16, 2004, 12:33 AM
Adron
Make testdll.def:

library testdll
exports
    DummieCallback

Or, use dumpbin /exports to find what your function is being exported as and pass that exact string to GetProcAddress.
November 16, 2004, 9:54 PM
NicoQwertyu
Thanks a bunch, Adron.  I used the second suggestion and found out the export was "_DummieCallback@12".  Now calc.exe is loading my DLL, but I'm having a new problem.

I want to use inline asm to push a string onto the stack, then jmp to an address in calc.exe where it pushes the hWnd of a child window and calls SetWindowText.  I'm having a compile issue though, and I don't understand what's wrong.

Here's the asm that's giving me hell:
(This is called via my Hook)

[code]


// ...

char sBuffer[] = "Test";

// ...


__asm
{
mov eax, offset sBuffer
push eax
jmp 0x01004189
}  // <-- Line 38
[/code]

This is the compile error I get:
testdll.cpp(38) : error C2415: improper operand type


Sorry if this is off-topic in the C++ forum, but I wasn't sure if I should start a new topic similar to this in the asm forum, or add it to this one.
November 17, 2004, 1:44 AM
iago
If you want to reasearch, the book "Programming Applications in Microsoft Windows" by Jeffrey Richter has a chapter dedicated to the various methods of dll injection.  SetWindowsHookEx is one, CreateRemoteThread is another (but that's only on 2k+), and I forget the others.
November 17, 2004, 1:49 AM
Myndfyr
[quote author=NicoQwertyu link=topic=9566.msg89057#msg89057 date=1100655869]
[code]
// ...
char sBuffer[] = "Test";
// ...
__asm
{
mov eax, offset sBuffer
push eax
jmp 0x01004189
}  // <-- Line 38
[/code]

This is the compile error I get:
testdll.cpp(38) : error C2415: improper operand type
[/quote]

Compiler error C2415 is discussed here on MSDN.

I can't imagine that Microsoft's implementation of Assembler in the C/++ compiler wouldn't let you use 0x notation for hex numbers.  Perhaps you need to "cast" it to a pointer type?

[code]
jmp dword ptr 0x01004189
[/code]

Let me know how that works out for you. :)
November 17, 2004, 3:04 AM
NicoQwertyu
[quote author=MyndFyre link=topic=9566.msg89066#msg89066 date=1100660691]
[code]
jmp dword ptr 0x01004189
[/code]

Let me know how that works out for you. :)
[/quote]


*sigh* No change, yet. :(

This is so aggrivating...
November 17, 2004, 4:20 AM
drivehappy
Does this work?
[code]
mov ip, 0x01004189
[/code]
November 17, 2004, 6:43 AM
Myndfyr
[quote author=drivehappy link=topic=9566.msg89085#msg89085 date=1100673802]
Does this work?
[code]
mov ip, 0x01004189
[/code]
[/quote]

I didn't know that could work.  What ever happend to CS, and didn't they make an ECS or EIP?

It seems like CS/IP would be somewhat deprecated.
November 17, 2004, 9:03 AM
NicoQwertyu
[quote author=drivehappy link=topic=9566.msg89085#msg89085 date=1100673802]
Does this work?
[code]
mov ip, 0x01004189
[/code]
[/quote]

No.  :-[


I can get the DLL to compile by using:

[code]
mov ecx, 0x01004189
jmp ecx
[/code]

But this makes CALC.exe crash, so I must still be doing something wrong. :/
November 17, 2004, 12:06 PM
iago
You can't jump straight to an address, because jmp is relative.  You either move it into a register and jump to the register or, if you want to preserve registers:
[code]push 0x010004189
ret[/code]
November 17, 2004, 1:31 PM
Kp
[quote author=iago link=topic=9566.msg89097#msg89097 date=1100698310]You can't jump straight to an address, because jmp is relative.[/quote]

Actually, you can.  It just requires GNU tools and a bit of cleverness. :)
November 17, 2004, 3:59 PM
Myndfyr
[quote author=iago link=topic=9566.msg89097#msg89097 date=1100698310]
You can't jump straight to an address, because jmp is relative.  You either move it into a register and jump to the register or, if you want to preserve registers:
[code]push 0x010004189
ret[/code]
[/quote]

Since when is jmp relative?  I thought there were a plethora of adressing modes, where the one he's using is an absolute.  :/

Edit: nvm, I'm a retard.  I guess I just don't get 32-bit addressing.  I remember that when you jmp'd on the 8086, you did it within the code segment.  It just seems to me that when you're running a 32-bit processor with 32-bit memory address registers, you ought to be able to jump to an absolute memory address.  Of course I can see where protection issues would arise...

:-/
November 17, 2004, 5:33 PM
NicoQwertyu
Do you guys know of anything else that could be wrong with the code I have thus far?  After the DLL is injected, two message boxes pop up informing me that "CALC.exe caused in error in <unknown>", then closes.  

I'm at school, but when I get home I'll try to debug it.  I'm pretty new to this though, so any input would be appreciated.
November 17, 2004, 5:34 PM
iago
There are tons of things that can go wrong when injecting assembly, but the major things are:
- Make sure ESP DOESN'T CHANGE
- Make sure you preserve all registers that are used after your patch
- Make sure you're returning to the correct address

The first two are the same, basically, but it's VERY important that the stack be EXACTLY the same when you return.  Also don't forget that call and ret both modify the stack pointer.

Debugging it, and stepping through the area where you made the patch, keeping track of registers, is probably your best bet.
November 17, 2004, 6:24 PM
Skywing
I typically use one of the following:

[code]
const unsigned long jmpdest = 0xnnnnnnnn;

.
.
.

jmp dword ptr [jmpdest]
[/code]

Or...

[code]
mov eax, 0xnnnnnnnn
jmp eax
[/code]
November 17, 2004, 7:24 PM
NicoQwertyu
[quote author=iago link=topic=9566.msg89106#msg89106 date=1100715861]
There are tons of things that can go wrong when injecting assembly, but the major things are:
- Make sure ESP DOESN'T CHANGE
- Make sure you preserve all registers that are used after your patch
- Make sure you're returning to the correct address

The first two are the same, basically, but it's VERY important that the stack be EXACTLY the same when you return.  Also don't forget that call and ret both modify the stack pointer.

Debugging it, and stepping through the area where you made the patch, keeping track of registers, is probably your best bet.
[/quote]

After stepping through it in a debugger: my code executes, jumps to the correct address, does what I want it to, hits a "retn 0", then ends up somewhere else where it, according to the debugger, has an "access violation when reading [FFFFFFFF]".  How can I fix whatever's going wrong?  :-[


[Edit]
The instruction it keeps getting stuck on is:
0x0056F362  0000  ADD BYTE PTR DS:[EAX], AL
November 17, 2004, 7:37 PM
drivehappy
@NicoQwertyu: Can you post stack information before your code is executed and then again at the jmp? Can you also post some more instructions before the access violation one?
@MyndFyre: I had meant EIP, it could very well be incorrect though (I'm used to 8051).
November 17, 2004, 8:04 PM
NicoQwertyu
[quote author=drivehappy link=topic=9566.msg89121#msg89121 date=1100721869]
@NicoQwertyu: Can you post stack information before your code is executed and then again at the jmp? Can you also post some more instructions before the access violation one?
@MyndFyre: I had meant EIP, it could very well be incorrect though (I'm used to 8051).
[/quote]

I'd be more than happy to.

...

This is around my 4th time using a debugger, what exactly am I posting when you ask for "stack information?"

Sorry for my ignorance. :/
November 17, 2004, 8:28 PM
Myndfyr
[quote author=NicoQwertyu link=topic=9566.msg89116#msg89116 date=1100720224]
After stepping through it in a debugger: my code executes, jumps to the correct address, does what I want it to, hits a "retn 0", then ends up somewhere else where it, according to the debugger, has an "access violation when reading [FFFFFFFF]".  How can I fix whatever's going wrong?  :-[
[/quote]

It's hitting a ret instruction?  Perhaps you need to use call rather than jmp in your code.
November 17, 2004, 9:03 PM
NicoQwertyu
[quote author=MyndFyre link=topic=9566.msg89131#msg89131 date=1100725405]
[quote author=NicoQwertyu link=topic=9566.msg89116#msg89116 date=1100720224]
After stepping through it in a debugger: my code executes, jumps to the correct address, does what I want it to, hits a "retn 0", then ends up somewhere else where it, according to the debugger, has an "access violation when reading [FFFFFFFF]".  How can I fix whatever's going wrong?  :-[
[/quote]

It's hitting a ret instruction?  Perhaps you need to use call rather than jmp in your code.
[/quote]

That wouldn't work for what I want it to do.  And it should hit a ret, shouldn't it?  The address I jump to is in the middle of a function.  :o
November 17, 2004, 9:13 PM
drivehappy
[quote author=NicoQwertyu link=topic=9566.msg89134#msg89134 date=1100725984]
[quote author=MyndFyre link=topic=9566.msg89131#msg89131 date=1100725405]
[quote author=NicoQwertyu link=topic=9566.msg89116#msg89116 date=1100720224]
After stepping through it in a debugger: my code executes, jumps to the correct address, does what I want it to, hits a "retn 0", then ends up somewhere else where it, according to the debugger, has an "access violation when reading [FFFFFFFF]".  How can I fix whatever's going wrong?  :-[
[/quote]

It's hitting a ret instruction?  Perhaps you need to use call rather than jmp in your code.
[/quote]

That wouldn't work for what I want it to do. And it should hit a ret, shouldn't it? The address I jump to is in the middle of a function. :o
[/quote]
That's probably your problem. When that function is called it pushes values onto the stack that correspond to parameters and the address at which it was called. Return I *believe* will revert EIP to the original address after which the call statement was made, any parameters should be popped off within the function. Again, I'm not 100% sure of the order. So when you jump inside the function and ret is reached it will try popping values off the stack that are incorrect. You should always have pairs of pushes and pops for the stack.
November 17, 2004, 9:51 PM
Myndfyr
[quote author=NicoQwertyu link=topic=9566.msg89134#msg89134 date=1100725984]
[quote author=MyndFyre link=topic=9566.msg89131#msg89131 date=1100725405]
[quote author=NicoQwertyu link=topic=9566.msg89116#msg89116 date=1100720224]
After stepping through it in a debugger: my code executes, jumps to the correct address, does what I want it to, hits a "retn 0", then ends up somewhere else where it, according to the debugger, has an "access violation when reading [FFFFFFFF]".  How can I fix whatever's going wrong?  :-[
[/quote]

It's hitting a ret instruction?  Perhaps you need to use call rather than jmp in your code.
[/quote]

That wouldn't work for what I want it to do.  And it should hit a ret, shouldn't it?  The address I jump to is in the middle of a function.  :o
[/quote]

Sure it would.  You can call an address directly last I checked.

When CALL is used, a pointer to the next instruction in memory is saved (and code segment?) on the stack.  RET pops them off of the stack.  So, you're probably trying to read the instruction at 0xffffffff, because that was the value popped off the stack, and are subsequently getting an access violation.
November 17, 2004, 9:58 PM
iago
When you do a call, it does this:
push return address;
jump location

When you do a return, it does:
pop return address->current location

You need to have a "call" before you can have a "ret", otherwise it has no idea where to return to.
November 17, 2004, 10:07 PM
NicoQwertyu
Ok, I understand the importance of call now, bust it still errors; now there's an access violation at 0x24291EF1 (doesn't exist), which is where it goes after the return.
November 17, 2004, 10:18 PM
Skywing
Perhaps you might post your entire hook, where you are installing it (and what instructions you are patching in), and a disassembly of the target function in calc.exe.
November 17, 2004, 10:42 PM
NicoQwertyu
[quote author=Skywing link=topic=9566.msg89163#msg89163 date=1100731348]
Perhaps you might post your entire hook, where you are installing it (and what instructions you are patching in), and a disassembly of the target function in calc.exe.
[/quote]




DLL to be injected:

[code]
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>


extern "C" __declspec(dllexport) LRESULT CALLBACK DummieCallback(int nCode, WPARAM wParam, LPARAM lParam);


HWND hWnd = FindWindow(NULL, "Calculator");
char sBuffer[] = "test";

BOOL APIENTRY DllMain(HANDLE hModule, DWORD  dwReason, LPVOID lpReserved)
{
    return TRUE;
}



extern "C" __declspec(dllexport) LRESULT CALLBACK DummieCallback(int nCode, WPARAM wParam, LPARAM lParam)
{

///////////
//////////

MSG *msg = (MSG*)lParam;

if (nCode < 0)
{
return CallNextHookEx((HHOOK)0, nCode, wParam, lParam);
} else {

_asm
{
mov eax, offset sBuffer
push eax
mov ecx, 0x01004189
call ecx
}

MessageBeep(MB_OK);

return CallNextHookEx((HHOOK)0, nCode, wParam, lParam);
}



}
[/code]

(Right now it's only purpose to put the text "Test" in whatever window the calulator uses to display it's stuff evertime calc.exe recieves a message)


Code that injects DLL:

[code]
#include <windows.h>
#include <stdio.h>

int main()
{
HWND hWndProcess;
DWORD dwThreadId;
HMODULE hInjected;
HOOKPROC hDummieProc;

hWndProcess = FindWindow(NULL, "Calculator");
dwThreadId = GetWindowThreadProcessId(hWndProcess, NULL);

if (hWndProcess == 0 || dwThreadId == 0)
{
MessageBox(NULL, "Unable to gain thread id!", "ERROR", MB_OK);
return 0;
}

hInjected = LoadLibrary("testdll.dll");
hDummieProc = (HOOKPROC)GetProcAddress(hInjected, "_DummieCallback@12");

if (hInjected == NULL || hDummieProc == NULL)
{
MessageBox(NULL, "Unable to inject DLL!", "ERROR", MB_OK);
FreeLibrary(hInjected);
return 0;
}

SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)hDummieProc, (HINSTANCE)hInjected, dwThreadId);
return 0;
}
[/code]



Target function:

[quote]

01004166 PUSH ESI
01004167 PUSH EDI
01004168 PUSH 193
0100416D PUSH DWORD PTR SS:[ESP+10]
01004171 CALL DWORD PTR DS:[<&USER32.GetDlgItem>]

01004177 MOV ESI,EAX
01004179 PUSH DWORD PTR SS:[ESP+10]
0100417D CALL DWORD PTR DS:[<&KERNEL32.lstrlenA>]

01004183 MOV EDI,EAX
01004185 PUSH DWORD PTR SS:[ESP+10] ; Push Text
01004189 PUSH ESI ; <---- I jump in here (push hWnd)
0100418A CALL DWORD PTR DS:[<&USER32.SetWindowTextA>]

01004190 PUSH ESI
01004191 CALL DWORD PTR DS:[<&USER32.SetFocus>]

01004197 PUSH EDI
01004198 PUSH EDI
01004199 PUSH 0B1
0100419E PUSH ESI
0100419F CALL DWORD PTR DS:[<&USER32.SendMessageA>]

010041A5 PUSH 1
010041A7 POP EAX
010041A8 POP EDI
010041A9 POP ESI

010041AA RETN 8
[/quote]
November 17, 2004, 11:02 PM
Kp
Based on a cursory look, your hook just won't work at all the way you wrote it.  For one thing, you jump to an instruction which pushes esi, but you didn't set up esi first.  Therefore, the wrong window handle will be passed to SetWindowTextA.  The original function sets esi using GetDlgItem, but your jump bypasses that.  From the look of that function, you ought to be able to call the function from the beginning.
November 17, 2004, 11:25 PM
NicoQwertyu
[quote author=Kp link=topic=9566.msg89174#msg89174 date=1100733923]
Based on a cursory look, your hook just won't work at all the way you wrote it.  For one thing, you jump to an instruction which pushes esi, but you didn't set up esi first.  Therefore, the wrong window handle will be passed to SetWindowTextA.  The original function sets esi using GetDlgItem, but your jump bypasses that.  From the look of that function, you ought to be able to call the function from the beginning.
[/quote]

But if I called the function from the beginning, I wouldn't be able to change what text it puts in the textbox, which was the goal I was trying to reach.  :-\
November 18, 2004, 4:28 AM
Kp
[quote author=NicoQwertyu link=topic=9566.msg89214#msg89214 date=1100752113]But if I called the function from the beginning, I wouldn't be able to change what text it puts in the textbox, which was the goal I was trying to reach.  :-\[/quote]

Why not?  If you look at that function, the text to place in the window is passed as an argument already.  Just put a pointer to it on the stack and call the code from the beginning.
November 18, 2004, 4:46 AM
NicoQwertyu
[quote]
If you look at that function, the text to place in the window is passed as an argument already.
[/quote]

Perhaps understanding what "dword ptr ss:[esp+10]" is will help..

*Google*

[Edit]

Random Google Result:
[quote]
Lets take a look at our program entry point. It is defined as:

WinMain( hInstance, hPrevInstance, lpszCmdLine, nCmdShow);

Thus our program is just treated as an ordinary function with the following dword values on the stack:

[esp+16]: nCmdShow    ;value determining if the program window should be displayed normal, fullscreen or minimized
[esp+12]: lpszCmdLine ;address of the command line
[esp+8]:  0           ;always 0 in Win32
[esp+4]:  hInstance   ;instance handle of the current process
[esp+0]:  stacked EIP ;return address
[/quote]


So I guess dword ptr ss:[esp+10] is the argument being passed that you're talking about.  But why +10?  How is it esp+10??  :-[
November 18, 2004, 5:10 AM
iago
You need to learn how to pass argument on the stack.  It's a very important concept.  Basically, to call:
void func(int a, int b, int c);
with the arguments:
func(1, 2, 3)

it does:
push 3
push 2
push 1
call func
(by convention)

When you're in the function
[esp] = return address
[esp+4] = 1
[esp+8] = 2
esp+c] = 3

Of course, this also depends on optimizations and stuff.  If optimizations are off, esp is usually changed and ebp is what is used to access the parameters.  I'd recommend you read up on how the stack and frame pointers work.
November 18, 2004, 12:09 PM
NicoQwertyu
How do they get to esp+10, though?  Doesn't it go by 4's?
November 18, 2004, 2:39 PM
Myndfyr
[quote author=NicoQwertyu link=topic=9566.msg89249#msg89249 date=1100788757]
How do they get to esp+10, though?  Doesn't it go by 4's?
[/quote]

esp+10 -- are you in hex?  That's 16 decimal.

If it's +10 and you're NOT in hex, that means that you've got a short integer being passed by value somewhere.  :)
November 18, 2004, 4:48 PM
Skywing
More likely it would mean the program is broken because every calling convention I know of keeps the stack aligned.
November 18, 2004, 6:59 PM
K
[quote author=Skywing link=topic=9566.msg89268#msg89268 date=1100804374]
More likely it would mean the program is broken because every calling convention I know of keeps the stack aligned.
[/quote]

The compiler we use to write programs for the MC68000 has a -Zp2 option which passes shorts (and chars/bytes) as 2 bytes. ;)
November 20, 2004, 12:25 AM
Skywing
[quote author=K link=topic=9566.msg89425#msg89425 date=1100910338]
[quote author=Skywing link=topic=9566.msg89268#msg89268 date=1100804374]
More likely it would mean the program is broken because every calling convention I know of keeps the stack aligned.
[/quote]

The compiler we use to write programs for the MC68000 has a -Zp2 option which passes shorts (and chars/bytes) as 2 bytes. ;)
[/quote]To clarify: Every compiler-supported calling convention used by x86-Win32 that I know of.
November 20, 2004, 2:06 AM
Adron
[quote author=NicoQwertyu link=topic=9566.msg89249#msg89249 date=1100788757]
How do they get to esp+10, though?  Doesn't it go by 4's?
[/quote]

Like others have said, it's hex. And note that you need to take into account the pushes in the function:

01004166  PUSH ESI ; here return address is [esp] and arg is [esp+4]
01004167  PUSH EDI ; here return address is [esp+4] and arg is [esp+8]
01004168  PUSH 193 ; here return address is [esp+8] and arg is [esp+c]
0100416D  PUSH DWORD PTR SS:[ESP+10] ; here return address is [esp+c] and arg is [esp+10]

November 27, 2004, 2:34 PM

Search