Valhalla Legends Forums Archive | Advanced Programming | API hooking and code injection

AuthorMessageTime
St0rm.iD
I've asked this several times on the other boards to no avail.

Basically, what I'm trying to do is change the thunk in-memory of another process to point to one of my dlls. I've figured out how to inject a dll using SetWindowsHookEx(), but now I just need to hook the API call. The function Jeffrey Richter provided doesn't work for some reason. I've googled it countless times.

Anyone have any help?
June 2, 2003, 1:52 AM
TheMinistered
While I'm getting ready to leave, I will post a simple but watered down explanation. You basically use the WriteProcessMemory function to write your machine code somewhere in the process that is actually useful. Note: You might have to write some data, such as, the filename/path of the dll somewhere inside the process too. The machine code loads your dll using the LoadLibrary function. You basically have the Loader and the DLL. The loader injects the code/data that tricks the process into loading your DLL. I'm sorry I didn't have enough time to explain this in detail, but I have to go!
June 2, 2003, 2:00 AM
St0rm.iD
I know, its the WriteProcessMemory params I need ;)

And also, you can just do a rundll32 entrypoint instead of a loader, makes things slightly less confusing (or more...depending on how you look at it ;))
June 2, 2003, 7:40 PM
Adron
Try to separate the problems of 1. getting the dll into the process 2. getting the process to call your dll's functions.

From what I understand, you've already solved #1 by using SetWindowsHookEx. The answer by TheMinistered seems to deal mostly with #1.

To do #2, you need to tell us a bit more about exactly what you want to hook. The easiest possibility would be if you're hooking something in the import table of the program. That can be done by a search/replace through the memory of the application from 401000 up to the first memory block that isn't commited. Search for the (pointer to the) function you want to hook and replace it with the address of your function.

It's also easy to modify internal calls in the application, but you have to beware that the address there is supposed to be relative to the location of the instruction following the call. You can of course also patch any location with some machine code to call your function, but then you're getting into the trickier code.

Things to think about: Memory protection, preserving registers, calling conventions. You don't need to use WriteProcessMemory if you do the patching from your dll after it's loaded in the target application.
June 2, 2003, 7:50 PM
indulgence
If you are wanting to hijack a function midstream you will want to do the following:
1. Redirect To a stub
- you need to find 5+ bytes you can use to redirect (N*B* if you use more you must nop after the call -- dont leave machine instructions half overwritten)
- you WILL need to use VirtualProtect to protect the memory before you do any patching...
- you WILL need to read the bytes you plan to overwrite to call your stub and WRITE them into the beggining of your stub
- you *could* make a macro to return the relative address you need to create a call although its not difficult to do without the macro
- your stub MUST be "naked" (only asm) and can then call a clean c function in your dll
2. Your stub will want to look like the following:

// begin code
nop
nop
nop
// ^ etc.. you get the point -- as many as you will need to overwrite with the bytes from the redirect

// must preserve registers (including the flags register)
pushad
pushfd

// set up any registers you might need (fastcall uses registers -- if you are using fastcall calling convention on your clean c function)
// if not using fastcall - you will want to push your arguments
call your_clean_c_fxn

// restore the registers
popfd
popad
// -- end code
June 3, 2003, 10:22 AM
indulgence
dangit hit reply instead of modify -- surry :'(
June 3, 2003, 10:38 AM
St0rm.iD
OK. How would I get the address of an imported function, say, USER32!MessageBoxA, so I could put my stub in there?
June 3, 2003, 11:09 AM
TheMinistered
GetProcAddress
June 3, 2003, 5:23 PM
Adron
In C/C++, the name is the address... You can do something like:

[code]
typedef int __stcall MessageBoxFunc(HWND, LPCTSTR, LPCTSTR, UINT);

for(MessageBoxFunc **x = (MessageBoxFunc**)0x401000; <insert condition>; x++) {
if(*x == MessageBoxA)
*x = YourFunction;
}
[/code]

Like I said before, a possible condition would be to check the size of the allocated memory starting at 0x401000. You can do that using VirtualQuery.

If you're patching a specific version of a program you could also just look up the offset to patch in the disassembly. Calls to api functions will generally look like this:

[code]
call dword ptr[<some offset>]
[/code]

Then all you need to do is

[code]
*(MessageBoxFunc**)<some offset> = YourFunction;
[/code]


If on the other hand you wish to be more generic, you could walk the import table of the program. The structure definitions for that can be found in winnt.h.


June 3, 2003, 5:34 PM
St0rm.iD
Adron you genius :)

What would happen if it wasn't imported? Would it loop forever? At which point would I stop looping?

Sorry for all of these questions.

EDIT: missed the part about VirtualQuery...oops
June 3, 2003, 10:41 PM
St0rm.iD
It doesn't seem to find the proc in the target proces (Notepad.exe). Any suggestions? Perhaps it is dynamically loaded?
June 4, 2003, 11:03 AM
Kp
[quote author=St0rm.iD link=board=23;threadid=1514;start=0#msg11532 date=1054724626]
It doesn't seem to find the proc in the target proces (Notepad.exe). Any suggestions? Perhaps it is dynamically loaded?
[/quote]Post the loop you're using. It's easier to debug when we know exactly what you're doing.
June 4, 2003, 6:25 PM
St0rm.iD
I found the problem. The injected DLL doesn't have priviledges to read the memory at 0x401000. I VirtualProtect()'d the memory as well.

Any suggestions?
June 5, 2003, 7:21 PM
Skywing
[quote author=St0rm.iD link=board=23;threadid=1514;start=0#msg11641 date=1054840895]
I found the problem. The injected DLL doesn't have priviledges to read the memory at 0x401000. I VirtualProtect()'d the memory as well.

Any suggestions?
[/quote]Maybe you gave bad parameters to VirtualProtect? Did you check if it succeeded, and what the error code was if not?
June 5, 2003, 7:24 PM
Camel
you might want to take a peek at the source code for mousepad's d2 maphack
June 5, 2003, 10:29 PM
TheMinistered
d2hackit would be a better reference
June 5, 2003, 10:35 PM
indulgence
Skywing offered better advice...

Just check the return of the VirtualProtect function... If it is returning 0 that is bad. Call GetLastError to check to see what exactly went wrong
June 6, 2003, 7:35 PM
salty
;DHow about reading the win32 reference :o ? :-X
June 9, 2003, 12:11 PM
St0rm.iD
Now, using Adron's loop, it isn't finding any imported functions in notepad.exe. Any suggestions? This is really difficult for me :/
June 9, 2003, 7:17 PM
Skywing
[quote author=St0rm.iD link=board=23;threadid=1514;start=15#msg11936 date=1055186235]
Now, using Adron's loop, it isn't finding any imported functions in notepad.exe. Any suggestions? This is really difficult for me :/
[/quote]
Mind posting some code? You of all people should know that it's pretty hard to help somebody when they don't show what they're doing ;)
June 9, 2003, 7:55 PM
St0rm.iD
I am pretty much using his code verbatim, but here you go.

[code]
      for(MessageBoxFunc **x = (MessageBoxFunc**)startAddr; (DWORD)x < meminfo.RegionSize + startAddr; x++) {
         if (*x == MessageBoxA) {
            fprintf(logfile,"Replacing API call at 0x%x\n", (DWORD)x);
            *x = MyMessageBoxA;
            fprintf(logfile,"Replaced API call\n");
            replacedAPI = TRUE;
            break;
         }
      }

      VirtualProtect((LPVOID)startAddr, meminfo.RegionSize, oldProtect, &dummy);

      fprintf(logfile,"%s\n", replacedAPI ? "Replaced API call successfully." : "Unable to replace API call.");
[/code]
June 9, 2003, 10:40 PM
Camel
[quote author=TheMinistered link=board=23;threadid=1514;start=15#msg11666 date=1054852548]
d2hackit would be a better reference
[/quote]
d2hackit is open source?
June 9, 2003, 10:46 PM
Adron
What's meminfo? You may have to loop a few times with virtualquery to find the first non-committed page, the first few boundaries are likely to be just changes of page protection.
June 9, 2003, 11:40 PM
St0rm.iD
Forgot to say, startAddr = 0x401000

meminfo is the result of VirtualQuery.
June 10, 2003, 2:25 AM
Adron
[quote author=St0rm.iD link=board=23;threadid=1514;start=15#msg11961 date=1055211943]
Forgot to say, startAddr = 0x401000

meminfo is the result of VirtualQuery.
[/quote]

Code for meminfo? Perhaps something like:

[code]
   HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, 0, 1104);

   void *endaddress = (void*)0x401000;
   MEMORY_BASIC_INFORMATION mbi;
   while(1) {
      VirtualQueryEx(h, endaddress, &mbi, sizeof mbi);
      printf("Memory at %08x-%08x: ", mbi.BaseAddress, (unsigned)mbi.BaseAddress + mbi.RegionSize - 1);
      switch(mbi.State) {
      case MEM_COMMIT:
         printf("Committed ");
         break;
      case MEM_RESERVE:
         printf("Reserved ");
         break;
      case MEM_FREE:
         printf("Free ");
         break;
      }
      switch(mbi.Protect) {
         case PAGE_READONLY:
            printf("READONLY");
            break;
         case PAGE_READWRITE:
            printf("READWRITE");
            break;
         case PAGE_WRITECOPY:
            printf("WRITECOPY");
            break;
         case PAGE_EXECUTE:
            printf("EXECUTE");
            break;
         case PAGE_EXECUTE_READ:
            printf("EXECUTE_READ");
            break;
         case PAGE_EXECUTE_READWRITE:
            printf("EXECUTE_READWRITE");
            break;
         case PAGE_EXECUTE_WRITECOPY:
            printf("EXECUTE_WRITECOPY");
            break;
         case PAGE_GUARD:
            printf("GUARD");
            break;
         case PAGE_NOACCESS:
            printf("NOACCESS");
            break;
         case PAGE_NOCACHE:
            printf("NOCACHE");
            break;
      }
      printf("\n");
      if(mbi.State != MEM_COMMIT)
         break;
      endaddress = (char*)mbi.BaseAddress + mbi.RegionSize;
   }
   
   printf("Suggest to search from %08x to %08x\n", 0x401000, (unsigned)endaddress - 1);
[/code]

edit: display inclusive ranges (-1)
June 10, 2003, 3:41 PM
Skywing
You could further refine on Adron's method by only scanning areas with the SEC_IMAGE flag, or areas with an access that allows EXECUTE.
June 10, 2003, 6:34 PM
Adron
[quote author=Skywing link=board=23;threadid=1514;start=15#msg12004 date=1055270093]
You could further refine on Adron's method by only scanning areas with the SEC_IMAGE flag, or areas with an access that allows EXECUTE.
[/quote]

That's possible. Does the import table always have to have execute access though? Seems to me like read would be enough.
June 10, 2003, 10:06 PM
St0rm.iD
Loop returns 0x401000 :(
June 12, 2003, 9:52 PM
EvilCheese
[quote]
Loop returns 0x401000
[/quote]

Could you post the exact code you're using for your loop?
June 13, 2003, 2:58 AM
Arta
What exactly are you trying to do? Hook an imported function by changing it's address in the IAT? If so you'll find it *much* easier to look it up properly by parsing the exe's PE header. I can post some code if you'd like.
June 13, 2003, 11:31 AM
Brolly
Yes, the IAT has to have execute.
It's a collection of jumps to the actual APIs.
August 3, 2003, 9:44 PM
Skywing
[quote author=Brolly link=board=23;threadid=1514;start=30#msg16935 date=1059947047]
Yes, the IAT has to have execute.
It's a collection of jumps to the actual APIs.
[/quote]
IIRC, the IAT is indirected through by those jumps. The actual jumps themselves are typically located in .text.
August 4, 2003, 6:07 AM
St0rm.iD
Ah, thanks for resurrecting :)

Here's my code.

[code]
fprintf(logfile,"Replacing API call...\n");

      DWORD oldProtect;
      DWORD dummy;
      DWORD startAddr = 0x401000;
      DWORD endAddr = 0x401000;
      MEMORY_BASIC_INFORMATION meminfo;
      BOOL replacedAPI = FALSE;
      //HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());

      //VirtualQuery((LPVOID)startAddr,&meminfo,sizeof(meminfo));

      //fprintf(logfile,"Page size is 0x%x\n",meminfo.RegionSize);
      // scan the region of memory after 0x401000 until we hit stuff that isnt readable or empty
      while (1) {
         VirtualQuery((LPVOID)endAddr,&meminfo,sizeof(meminfo));

         if (meminfo.Protect != PAGE_EXECUTE)
            break;

         endAddr = (DWORD)meminfo.BaseAddress + meminfo.RegionSize;
      }

      VirtualProtect((LPVOID)startAddr, meminfo.RegionSize, PAGE_READWRITE,&oldProtect);
      fprintf(logfile,"searching 0x%x to 0x%x.\n",startAddr,endAddr);
      //VirtualProtectEx(hProcess, (LPVOID)startAddr, meminfo.RegionSize, PAGE_READWRITE,&oldProtect); // thanks d2hackit
      //for(MessageBoxFunc **x = (MessageBoxFunc**)startAddr; (DWORD)x < meminfo.RegionSize + startAddr; x++) {
      for(MessageBoxFunc **x = (MessageBoxFunc**)startAddr; (DWORD)x < endAddr; x++) {
         if (*x == MessageBoxA) {
            fprintf(logfile,"Replacing API call at 0x%x\n", (DWORD)x);
            *x = MyMessageBoxA;
            fprintf(logfile,"Replaced API call\n");
            replacedAPI = TRUE;
            break;
         }
      }

      VirtualProtect((LPVOID)startAddr, meminfo.RegionSize, oldProtect, &dummy);
      //VirtualProtectEx(hProcess, (LPVOID)startAddr, meminfo.RegionSize, oldProtect, &dummy);

      fprintf(logfile,"%s\n", replacedAPI ? "Replaced API call successfully." : "Unable to replace API call.");
[/code]
August 6, 2003, 7:54 PM

Search