Recent

Author Topic: Windows API Hooking/DLL Injection/Function Hooking - Need some pointers  (Read 1775 times)

tooknox

  • New Member
  • *
  • Posts: 15
Re: Windows API Hooking - Need some pointers
« Reply #15 on: April 27, 2026, 05:29:52 pm »
Yeah I did not mean a permanently patched executable. Honestly looked at the SecureUXTheme project and wondered how the guy did it and have spent 2 weekends try to figure it out that's the whole gist of it.

Such answers are to be found though, at https://www.delphibasics.info
Note that is mostly for 32 bit windows - your favourite - , but I guess that it if you feed it into DeepSeek or Claude.ai you will get a working 64 bit version too.
(Even in the free versions)

Look for the Func snippets, covers both runtime patching as exe patching, IIRW.
If you have learned all the code on that website - and understand it - you are sufficiently trained to do an attempt at writing malware.

Thanks for sharing this  :D, good to learn more of this while I am at it.

A C function in a C program will simply also be patched with my Pascal code when put in a dll: it is a jump instruction.....It will jump....
Ohh ok, I thought I would have to go through a ritual but if I can refer to it by name in DLL then it should be straight forward. Will test after work :)

Also really appreciate you guys helping me out O:-)

440bx

  • Hero Member
  • *****
  • Posts: 6459
Re: Windows API Hooking - Need some pointers
« Reply #16 on: April 27, 2026, 07:33:38 pm »
Note that is mostly for 32 bit windows
that works equally well in 64 bit as it does in 32 bit.  The only difference is the code the programmer has to inspect to determine the address to patch.

The simplest, which should be the choice, is patching the IAT which is available if the function is exported, if it is not then the disassembly avenue is the way to determine the target address.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Thaddy

  • Hero Member
  • *****
  • Posts: 19114
  • Glad to be alive.
Re: Windows API Hooking - Need some pointers
« Reply #17 on: April 28, 2026, 06:08:14 am »
The post important thing that is not in the older code, but is necessary nowadays, is flushing the instruction cache as per my example. That should be added to any of the other examples too.
objects are fine constructs. You can even initialize them with constructors.

tooknox

  • New Member
  • *
  • Posts: 15
Re: Windows API Hooking - Need some pointers
« Reply #18 on: May 04, 2026, 03:00:32 pm »
@Thaddy

I think I am close, have done the boilerplate work. Testing in progress. Just a quick query once my DLL is injected, it should technically have full access to the target process, how do I get the base address of the target process in memory,

should calling GetModuleHandle(nil) get me the address??

Thanks,
tooknox

Thaddy

  • Hero Member
  • *****
  • Posts: 19114
  • Glad to be alive.
Re: Windows API Hooking - Need some pointers
« Reply #19 on: May 04, 2026, 06:35:49 pm »
You don't need the address before hand: you are patching @runtime.
That means the pointer is known and you can simply take the address of the procedure or function you want to patch. As per my example.
objects are fine constructs. You can even initialize them with constructors.

tooknox

  • New Member
  • *
  • Posts: 15
Re: Windows API Hooking - Need some pointers
« Reply #20 on: May 04, 2026, 09:49:50 pm »
Ok so I have successfully done the runtime patching will share the details/code once I have cleaned the scattered mess in my code, here is the gist though

1. I have a 'C' binary called "target.exe" compiled from the C source code I shared. I want to patch the "sum" function in this exe at runtime to multiply instead of adding the scanf values.
2. I write an injector in Lazarus that does the boilerplate work of LoadLibrary, WriteProcessMemory etc...the whole shebang for injecting the DLL.
3. Then there is the library/DLL in Pascal/Lazarus which once injected will hook the sum function with a function defined in the DLL.

How I did it
1. Since DLL is in the process itself GetModuleHandle(nil) will return the base address of the target.exe. (I verified it does)
2. Got the relative virtual address of the sum function in target.exe via disassembler and added it to base address and then called the intercept/hook function from DDetour Library and voila it works as expected, after scanf values, the printf outputs the multiplication instead of sum like magic. And since the base address is dynamically calculated, the code works with address randomization.

My questions
1. what do you mean by getting pointer directly in my DLL code?? I mean can you share short snippet on how to refer to the "sum" function from target.exe in my DLL pascal code. This would have saved me a lot of head scratching. Either way learned a lot in the process though.
2. I was trying to avoid DDetour CUZ I understood @Khrys and your code and tried to use the functions as is. @Khrys code returns -1 (more 2G Rel Address) and yours forced the target.exe to exit after second scanf. Doesn't mean anything is wrong with your code, just sharing the observation on what I have done till now. Next thing I am trying to do is to use your function instead of the intercept function from DDetour to make it work now that I have a baseline.
« Last Edit: May 04, 2026, 09:57:11 pm by tooknox »

Khrys

  • Sr. Member
  • ****
  • Posts: 433
2. I was trying to avoid DDetour CUZ I understood @Khrys and your code and tried to use the functions as is. @Khrys code returns -1 (more 2G Rel Address) and yours forced the target.exe to exit after second scanf. Doesn't mean anything is wrong with your code, just sharing the observation on what I have done till now. Next thing I am trying to do is to use your function instead of the intercept function from DDetour to make it work now that I have a baseline.

That's strange... how exactly are you calling the function?

tooknox

  • New Member
  • *
  • Posts: 15
So here is the code with steps to follow.

1. FPCDLLInjector.zip: This is a small tool/project I wrote during my testing. It's just for DLL injection and has nothing to do with hooking code/DLL. Helped simplify the testing process.. Just select the Payload DLL from OpenDialog button at the bottom, then select the process from the ListView and hit Inject. You can keep this window opened and just refresh to keep testing. Built with Lazarus 4.6/FPC 3.2.2

Compiled on x86_64 works fine.

2. PayloadDLL.zip: Contains the code for the DLL to be injected. Once you open the *.lpi file in lazarus, go to
Project Options > Compiler Options > Paths > Other Unit Files (-Fu)
and add the DDetours Library to path. Ofc there are other ways to do this, if you want.

@Khrys, I have included both yours and Thaddy's code in the DLL source along with how I was calling them. I didn't debug any further because it worked right away with DDetour.

Compiled on x86_64 works fine.

3. TargetCBinary.zip: contains the c source file and the binary compiled using MSVC compiler (had to use MSVC instead of GCC cuz DWARF symbols were not working with x64dbg). I suggest you use the binary directly because the offset address is hardcoded in the DLL code for this binary. Recompiling may or may not change this. If you are paranoid like me and don't wanna use the .exe file, build the C source file yourself and replace the offset address in the Payload DLL.

Steps:
Run "target.exe" and input the two numbers, you should see the sum of the number by default. Run again and let the CLI stay on the first scanf, try injecting the "payload.dll", once done continue entering the scanf values and you will see a MessageBox along with the sum replaced by multiplication.
« Last Edit: May 05, 2026, 11:52:43 am by tooknox »

Khrys

  • Sr. Member
  • ****
  • Posts: 433
Code: Text  [Select][+][-]
  1. Patch Result: -1 | Target Func at: 00007FF7EBC015E2 | Hooked Func at: 0000000110001750

Oh, now I see - look at the addresses! They are almost 128 terabytes apart because the  .exe  and the  .dll  have completely different base addresses.
Both @Thaddy's version and mine use a simplistic  jmp rel32,  which can't handle such large jumps.

I stuck with the relative jump because (1) it works independently of architecture & ABI and (2) I mostly used it to debug/profile 32-bit code.
You could try replacing that instruction with  mov rax, imm64  followed by  jmp rax  (should be fine on x64 Windows).

tooknox

  • New Member
  • *
  • Posts: 15
@Khrys,

Yeah I dumped the addresses and they were too far apart. Just wanted to double-check If I was doing anything wrong. Will replace jmp rel32 with mov rax, imm64 and test it out.

However I am trying a few other things

1. The Detour:
    a) I tried to use the Microsoft Detours (My OCD kicked in and now I wanna try it) now that I have the boilerplate stuff in place. The thing is, it is designed to be built/used as a static library and the default Makefile provided only works with MSVC. Is there any way to make MSVC *.lib work with FPC/Lazarus? Parallelly I wrote my own Makefile and compiled it with MinGW got the libdetours.a, but trying to link it with {$LINKLIB} throws a lot of undefined symbols in libdetours.a itself. Tried adding libkernel32.a and got most of them fixed but in the end can't get a few symbols resolved, linking libstdc++.a fails with some COFF error.
    https://github.com/microsoft/detours

    How do I proceed with this now??

    b) Will also try to modify your code with mov rax, imm64, just so I have a way to do it without any external dependency.

2. The Payload:
    a) Is there any other way to get the address/pointer of the sum function from payload code besides what I did?
    b) Is it possbile to calculate even the offset dynamically at runtime??

BTW, in case someone wants to setup (Only the) MSVC compiler without the full visual studio BS, helped me setup an MSVC build environment quickly and easy to get rid off.

https://gist.github.com/mmozeiko/7f3162ec2988e81e56d5c4e22cde9977
« Last Edit: May 06, 2026, 11:05:15 am by tooknox »

tooknox

  • New Member
  • *
  • Posts: 15
@Khrys,

I changed the jmp rel32 to 64-bit indirect absolute RIP relative jump, JMP [RIP+0] (6-Bytes: FF 25 00 00 00 00) followed by 8-Bytes of address of jump to. I also wrote a procedure to restore patch too. There are a few drawbacks through

1. The target function should be bigger than 14 bytes (duh!!)
2. The restore patch only works if the 14-bytes overwritten don't contain any RIP relative instruction cuz that will change for obvious reasons and I haven't taken that into account.

Here is the excerpt from the zip attached.

Code: Pascal  [Select][+][-]
  1. // @tooknox's implementation ///////////////////////////////////////////////////
  2. procedure SetMemoryWritable(Addr: Pointer; Size: Integer; Enable: Boolean; var OldProtect: DWORD);
  3. begin
  4.   if Enable then
  5.     VirtualProtect(Addr, Size, PAGE_EXECUTE_READWRITE, @OldProtect)
  6.   else
  7.     VirtualProtect(Addr, Size, OldProtect, @OldProtect);
  8. end;
  9.  
  10. procedure ApplyPatch(Source: Pointer; Destination: Pointer; out Backup: TPatchBackup);
  11. var
  12.   Patch: array[0..13] of Byte;
  13.   OldProtect: DWORD;
  14. begin
  15.   // Step 1: 14-byte indirect absolute RIP-Relative JMP [RIP+0]
  16.   Patch[0] := $FF; Patch[1] := $25;
  17.   Patch[2] := $00; Patch[3] := $00; Patch[4] := $00; Patch[5] := $00;
  18.   PPointer(@Patch[6])^ := Destination;
  19.  
  20.   // Step 2: Backup the original bytes
  21.   Move(Source^, Backup, 14);
  22.  
  23.   // Step 3: Apply the patch
  24.   SetMemoryWritable(Source, 14, True, OldProtect);
  25.   Move(Patch, Source^, 14);
  26.   SetMemoryWritable(Source, 14, False, OldProtect);
  27. end;
  28.  
  29. procedure RemovePatch(Source: Pointer; const Backup: TPatchBackup);
  30. var
  31.   OldProtect: DWORD;
  32. begin
  33.   // Restore the original bytes from the backup
  34.   SetMemoryWritable(Source, 14, True, OldProtect);
  35.   Move(Backup, Source^, 14);
  36.   SetMemoryWritable(Source, 14, False, OldProtect);
  37. end;
  38.  
  39. ////////////////////////////////////////////////////////////////////////////////
  40.  

Attached the new payload DLL code, still contains everything it had before, just added my implementation.
« Last Edit: May 08, 2026, 09:00:04 am by tooknox »

Khrys

  • Sr. Member
  • ****
  • Posts: 433
Good to know you solved it.

Do note however that  RIP-relative addressing is  x86_64-only (there is no such thing as  EIP-relative addressing on plain x86). An architecture-independent method would be to use  48 B8 00 00 00 00 90 90 90 90 FF E0  (12 bytes) as the patch, inserting the target address using  PPointer(@Patch[2])^ := Destination.  This yields the following instruction sequences on  x86_64  and  x86,  respectively:

Code: ASM  [Select][+][-]
  1. movabs rax, <imm64>
  2. jmp    rax

Code: ASM  [Select][+][-]
  1. dec eax
  2. mov eax, <imm32>
  3. nop
  4. nop
  5. nop
  6. nop
  7. jmp eax

tooknox

  • New Member
  • *
  • Posts: 15
@Khrys

Wow this is way better, added it to PayloadDLL code (tested and works great) along with everything else, in case someone wants to try all the ways to hook. Also copied the procedure to restore just like with RIP relative, and unlike RIP-Relative, this restore patch won't have the previous restrictions :D (target function must be >= 12 bytes ofc)

A few things still bothering me though
.
1. How do i go about using the Microsoft Detour library with freepascal??. I mean this is purely academic ofc.
2. Is there any other recommended way to get the address of the target function because what I have done now seems a bit janky....??

I think NOW I will go ahead and look at the "SecureUXTheme" source code to see how they did it :)

Edit: Also added some QoL improvements to FPCDLLInjector
« Last Edit: May 08, 2026, 01:12:06 pm by tooknox »

 

TinyPortal © 2005-2018