Jump to content

Windows API Hooking, Injecting a DLL


wraithdu
 Share

Recommended Posts

IMPORTANT: For those who don't have them, please download and install the Microsoft VC++ 2008 SP1 Redistributable

If you don't have these runtimes, the example will crash.

This little project of mine got influnce from many places. First was monoceres' example of hooking a local API call by modifying the IAT. Next was Ward's MemoryDll UDF, which formed a basis for my method. Lastly was a CodeProject article here -

http://www.codeproject.com/KB/system/mini_hook_engine.aspx

which describes a hook engine, and from which I took the idea for the hook bridge, which allows a hooking function to call the real function without resetting the hook (makes this process thread-safe). I'm also using Distorm64 for the disassembly. The compiled distorm64 DLL is included and used via Ward's MemoryDll UDF.

What this archive contains -

Source for the testapp, example app, and DLL

The compiled DLL that is injected into the testapp to hook the MessageBoxW function

UDF source files include _WinApiHook (main UDF), and support files _Distorm, _Distorm_src (the DLL), _MemoryDll (from Ward)

In order to do remote hooking, a DLL must* be used. This method uses RtlCreateUserThread() to cause the remote process to call LoadLibraryA to load the remote DLL. Similarly, it can be made to call FreeLibrary, or any function in your injected DLL. This method can be used to retrieve a remote process's commandline, for example.

I hope the included sample DLL can show the functions needed and how to use them. 2 functions are requred for each API - 1 to do the actual hooking, and 1 to retrieve the Bridge address. This function must be called manually before setting the hook to provide the remote process with the address of the Bridge. Well, the 2nd function and manual call are only required if you want your hooking function to be able to call the original function.

Comments welcome!

* I said must above, but that's not 100% true. There is a way to do it without an external DLL using WriteProcessMemory, but it sill requires the binary data of compiled code, and the code must adhere to specific guidelines -

(among others, HERE under "Additional caveats")

- code must not use C/C++ runtimes

- code must not call other functions, unless they are also injected (those functions must adhere to the same guidelines)

This makes it pretty hard to do anything useful.

Update 1: 2009/01/06

- fixed small error in example

- added note about installing the VC++ 2008 SP1 Redistributable

Update 2: 2009/01/22

- fixed error in _HookApi_Get() trying to FreeLibrary the wrong address

- added function to find the base address of a module in a process (incorporated in main functions)

- _InjectDll and _FreeRemoteDll now search the remote process for the base address of kernel32.dll, instead of assuming the base address is constant in all processes

- removed compiled apps from archive to reduce its size

- _HookApi_Get creates hook backup by reading remote process memory, instead of assuming local code is identical

Update 3: 2009/09/25

- fixed virtual memory release errors: previously used _MemVirtualFree() with the $MEM_DECOMMIT flag instead of the correct $MEM_RELEASE flag

- changed inject/free functions to use RtlCreateUserThread() (original CreateRemoteThread option is still there, only commented out)

- minor changes to _MemoryDll UDF: now it allocates memory using _MemVirtualAlloc() with the $PAGE_EXECUTE_READWRITE flag to avoid any possible problems with DEP

Update 4: 2009/09/28

- added _GetPrivilege_SEDEBUG.au3 to zip file

Update 5: 2009/10/04

- updated injection functions to work on XP: it will use CreateRemoteThread() on XP and RtlCreateUserThread() on Vista+

Update 6: 2009/10/06

- new option in _InjectDll() and _FreeRemoteDll() to skip remotely locating the base address of 'kernel32.dll' - THIS IS DANGEROUS, USE AT YOUR OWN RISK!!!

- switched to use the CreateToolhelp32 API to enumerate remote modules (the PSAPI version is still included)

- much better error detection and return values in all UDFs

Update 7: 2009/10/06

- workaround for AutoIt < 3.3.1.0 bug

_WinApiHook.zip

Edited by wraithdu
Link to comment
Share on other sites

  • Replies 45
  • Created
  • Last Reply

Top Posters In This Topic

Top Posters In This Topic

This little project of mine got influnce from many places. First was monoceres' example of hooking a local API call by modifying the IAT. Next was Ward's MemoryDll UDF, which formed a basis for my method. Lastly was a CodeProject article here -

http://www.codeproject.com/KB/system/mini_hook_engine.aspx

which describes a hook engine, and from which I took the idea for the hook bridge, which allows a hooking function to call the real function without resetting the hook (makes this process thread-safe). I'm also using Distorm64 for the disassembly. The compiled distorm64 DLL is included and used via Ward's MemoryDll UDF.

What this archive contains -

A compiled testapp (the app that will be hooked)

A compiled example app which will show both local and remote hooking

A compiled DLL that is injected into the testapp to hook the MessageBoxW function

Source for the testapp, example app, and DLL

UDF source files include _WinApiHook (main UDF), and support files _Distorm, _Distorm_src (the DLL), _MemoryDll (from Ward)

In order to do remote hooking, a DLL must* be used. This method uses CreateRemoteThread to cause the remote process to call LoadLibraryA to load the remote DLL. Similarly, it can be made to call FreeLibrary, or any function in your injected DLL. This method can be used to retrieve a remote process's commandline, for example.

I hope the included sample DLL can show the functions needed and how to use them. 2 functions are requred for each API - 1 to do the actual hooking, and 1 to retrieve the Bridge address. This function must be called manually before setting the hook to provide the remote process with the address of the Bridge. Well, the 2nd function and manual call are only required if you want your hooking function to be able to call the original function.

Comments welcome!

* I said must above, but that's not 100% true. There is a way to do it without an external DLL using WriteProcessMemory, but it sill requires the binary data of compiled code, and the code must adhere to specific guidelines -

(among others, HERE under "Additional caveats")

- code must not use C/C++ runtimes

- code must not call other functions, unless they are also injected (those functions must adhere to the same guidelines)

This makes it pretty hard to do anything useful.

the hooked msgboxw crashed the testap before the 10 seconds ran out.. im using vista.

ps: tested with adminrights without and with compatibility mode. everytime crash befor i could press the button.

pss: but anyway a nice and worthful try. keep on the good work.

Edited by JRSmile
$a=StringSplit("547275737420796F757220546563686E6F6C75737421","")
For $b=1 To UBound($a)+(-1*-1*-1)step(2^4/8);&$b+=1*2/40*µ&Asc(4)
Assign("c",Eval("c")&Chr(Dec($a[$b]&$a[$b+1])));''Chr("a")&"HI"
Next ;time_U&r34d,ths,U-may=get$the&c.l.u.e;b3st-regards,JRSmile;
MsgBox(0x000000,"",Eval("c"));PiEs:d0nt+*b3.s4d.4ft3r.1st-try:-)
Link to comment
Share on other sites

That's very odd. I'm also on Vista SP1 32-bit, and it works fine here, both in compiled form and from Scite.

How are you running the test? You should extract the 2 compiled EXEs and the DLL to the same directory, and run ApiHookExample.exe.

Also works well on an XP box I just tested.

Edited by wraithdu
Link to comment
Share on other sites

Ok, so I did get it to crash on an XP system I have at home. Unfortunately I have no idea why it would crash on some systems and not others. Any ideas would be welcome.

I've tested on 1 Vista sys (OK), 2 XP SP3 system (1 OK, 1 crash).

Edited by wraithdu
Link to comment
Share on other sites

Ok, duh, I figured it out. I did find one error in the example, but it wasn't necessarily related to the crash.

For those who had problems with the test crashing, please download the VC++ 2008 SP1 Redistributable and install the runtimes. The msgdll.dll was failing to load on systems without the runtimes and crashing the testapp. See first post for an updated archive and download link for the runtimes.

Edited by wraithdu
Link to comment
Share on other sites

Eh, 722 downloads??? That's gotta be a board error...there's not even that many views!

i downloaded 5 times: laptop, iphone,rootserver,work pc,svn-server... maybe others did the same?

$a=StringSplit("547275737420796F757220546563686E6F6C75737421","")
For $b=1 To UBound($a)+(-1*-1*-1)step(2^4/8);&$b+=1*2/40*µ&Asc(4)
Assign("c",Eval("c")&Chr(Dec($a[$b]&$a[$b+1])));''Chr("a")&"HI"
Next ;time_U&r34d,ths,U-may=get$the&c.l.u.e;b3st-regards,JRSmile;
MsgBox(0x000000,"",Eval("c"));PiEs:d0nt+*b3.s4d.4ft3r.1st-try:-)
Link to comment
Share on other sites

this is my normal procedure if i find interresting code for further development: download, categorize (function name includes dlls used etc..) upload to svn, backup on nas and a zipped working example on all of my machines. sorry to dissapoint you :-)

$a=StringSplit("547275737420796F757220546563686E6F6C75737421","")
For $b=1 To UBound($a)+(-1*-1*-1)step(2^4/8);&$b+=1*2/40*µ&Asc(4)
Assign("c",Eval("c")&Chr(Dec($a[$b]&$a[$b+1])));''Chr("a")&"HI"
Next ;time_U&r34d,ths,U-may=get$the&c.l.u.e;b3st-regards,JRSmile;
MsgBox(0x000000,"",Eval("c"));PiEs:d0nt+*b3.s4d.4ft3r.1st-try:-)
Link to comment
Share on other sites

that's the thing i tried to do when monoceres did his script for local api hook, but i never managed to do it, and whe i see the complexity of all, i understand why :)

do you think you can make a hook for the function SHFileOperation used in shell32.dll with the process explorer.exe ?

i'm trying to edit your code to make it woek but it's a little bit hard with the dll's part.

awesome job anyway :o that open lot of possibilities for monitoring.

Edited by arcker

-- Arck System _ Soon -- Ideas make everything

"La critique est facile, l'art est difficile"

Projects :

[list] [*]Au3Service : Run your exe as service V3 / Updated 29/07/2013 Get it Here [/list]
Link to comment
Share on other sites

Where exactly are you having trouble, injecting the DLL, hooking the API, or coding the DLL to do what you want? What do you want the hook to do? Also, are you hooking the Ansi or Unicode version of the function, or both?

FYI, the script already gets the SE_DEBUG privilege if it can, so you should be OK to open explorer. Be careful though, if ya mess up, you're taking explorer down with you :)

The basic DLL layout would be something like -

// main.h
#ifndef __MAIN_H__
#define __MAIN_H__

#include <windows.h>

/*  To use this exported function of dll, include this header
 *  in your project.
 */

#ifdef BUILD_DLL
    #define DLL_EXPORT __declspec(dllexport) WINAPI
#else
    #define DLL_EXPORT __declspec(dllimport) WINAPI
#endif


#ifdef __cplusplus
extern "C"
{
#endif

int DLL_EXPORT MySHFileOpW(LPSHFILEOPSTRUCT lpFileOp);
int DLL_EXPORT GetSHFileOpW(void *p);

#ifdef __cplusplus
}
#endif

#endif // __MAIN_H__

// main.cpp
#include "main.h"

int (WINAPI *pMySHFileOpW) (LPSHFILEOPSTRUCT); // pointer to the bridge

// SHFileOpW hook function
int DLL_EXPORT MySHFileOpW(LPSHFILEOPSTRUCT lpFileOp)
{
    if (pMySHFileOpW == NULL)
        return 1;
    
    // do something
    
    
    // call the original SHFileOpW function through the Bridge
    return pMySHFileOpW(lpFileOp);
}

// obtain the address to the Bridge
int DLL_EXPORT GetSHFileOpW(void *p)
{
    // this function must be called before setting the hook to provide the hooking function with the bridge address
    pMySHFileOpW = (int (WINAPI *)(LPSHFILEOPSTRUCT)) p;
    return 0;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            pMySHFileOpW = NULL;
        case DLL_PROCESS_DETACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
            break;
    }
    return TRUE; // succesful
}
Link to comment
Share on other sites

thx for the answer !

i've a problem coding the dll, that's all ... for now

i want to hook, for now, just if the Api is called. After i will try to get the pointer information and launch my own "copyfile", lile supercopier does.

i'm using VC++ 2005

thx again :)

-- Arck System _ Soon -- Ideas make everything

"La critique est facile, l'art est difficile"

Projects :

[list] [*]Au3Service : Run your exe as service V3 / Updated 29/07/2013 Get it Here [/list]
Link to comment
Share on other sites

mmm the autoit code returns a bad parameter ( error 87 ) when using openprocess with explorer.exe....

don't know why.

-- Arck System _ Soon -- Ideas make everything

"La critique est facile, l'art est difficile"

Projects :

[list] [*]Au3Service : Run your exe as service V3 / Updated 29/07/2013 Get it Here [/list]
Link to comment
Share on other sites

yes this code works at home . ( not tested at work )

Now i try to run the test but it crashes explorer.exe. :/ ( so if there is just one explorer.exe, it crash the desktop but fortunately windows manage to restore him )

here is what i have :

#NoTrayIcon
#AutoIt3Wrapper_Change2CUI=y
#include <_WinApiHook.au3>
#include <_MemoryDll.au3>

Local $MyMsgBox = DllCallbackRegister("_MyCopyFile", "none", "ptr")
Local $pMyMsgBox = DllCallbackGetPtr($MyMsgBox)

Local $targetmodule = "shell32.dll"
Local $targetfunction = "SHFileOperationW"

$DISTORM_DEBUG = True


;; remote hook test
;MsgBox(0 + 64, "Info", "Press OK.  You then have 10 seconds to test the Message button.")
;$pid = Run("explorer.exe")
;ProcessWait($pid)
;Sleep(500)
;test by using existing process
$pid = ProcessExists("explorer.exe")
ConsoleWrite("pid: " & $pid & @CRLF)

; for remote processes, the Bridge element will be NULL
; the BridgePtr will be a pointer in the remote process to the memory allocated
$MyHook = _HookApi_Get($targetmodule, $targetfunction, $pid)
ConsoleWrite("-------------" & @CRLF)
ConsoleWrite("HookAddress: " & DllStructGetData($MyHook, "HookAddress") & @CRLF)
ConsoleWrite("HookBak: " & DllStructGetData($MyHook, "HookBak") & @CRLF)
ConsoleWrite("Bridge: " & DllStructGetData($MyHook, "Bridge") & @CRLF)
ConsoleWrite("BridgePtr: " & DllStructGetData($MyHook, "BridgePtr") & @CRLF)
ConsoleWrite("Status after get: " & DllStructGetData($MyHook, "Status") & @CRLF)
ConsoleWrite("Process: " & DllStructGetData($MyHook, "Process") & @CRLF)

; verify bridge
$hProcess = _GetProcHandle($pid)
$s = DllStructCreate("byte[64]")
DllCall("kernel32.dll", "int", "ReadProcessMemory", "ptr", $hProcess, "ptr", DllStructGetData($MyHook, "BridgePtr"), "ptr", DllStructGetPtr($s), "uint", 64, "uint*", 0)
ConsoleWrite("-------------------" & @CRLF)
ConsoleWrite("remote bridge: " & DllStructGetData($s, 1) & @CRLF)

; inject dll
$hModule = _InjectDll(@ScriptDir & "\mine.dll", $pid)
;; set hook
; call the remote function to set the bridge address
; if you know the offset, then add it to $hModule
; if not, load the dll locally to get hMod and use GetProcAddress to get the funcaddres
; then the offset = funcaddress - hMod

; get the bridge setting function offset
$hMod = _WinAPI_LoadLibrary("mine.dll")
$fnAddress = _GetProcAddress($hMod, "_GetSHFileOp@4")
$offset = $fnAddress - $hMod
; set the bridge
$ret = DllCall("kernel32.dll", "ptr", "CreateRemoteThread", "ptr", $hProcess, "ptr", 0, "uint", 0, "ptr", $hModule + $offset, "ptr", DllStructGetData($MyHook, "BridgePtr"), "dword", 0, "ptr", 0)
_WinAPI_WaitForSingleObject($ret[0])
_WinAPI_CloseHandle($ret[0])

; get hook function offset
$fnAddress = _GetProcAddress($hMod, "_MySHFileOp@16")
$offset = $fnAddress - $hMod
; free the locally loaded library
_WinAPI_FreeLibrary($hMod)

_HookApi_Set($MyHook, $hModule + $offset)
$s = 0
$s = DllStructCreate("byte[10]")
DllCall("kernel32.dll", "int", "ReadProcessMemory", "ptr", $hProcess, "ptr", DllStructGetData($MyHook, "HookAddress"), "ptr", DllStructGetPtr($s), "uint", 10, "uint*", 0)
ConsoleWrite("remote hook: " & DllStructGetData($s, 1) & @CRLF)

_WinAPI_CloseHandle($hProcess)

; test out the hooked function
Sleep(10000)
MsgBox(0 + 64, "Info", "Time is up.  Please close any testapp message boxes, or it may crash.")

; unset the hook
_HookApi_UnSet($MyHook)
; optionally free the injected library
; if the remote hooked message box is open when we do this, the remote app will crash
; leaving the injected library loaded will prevent the crash, and won't do any harm
_FreeRemoteDll($hModule, $pid)
MsgBox(0 + 64, "Unhooked", "MessageBoxW is now unhooked, and the dll has been unloaded.")

DllCallbackFree($MyMsgBox)

Func _MyCopyFile($LpFileOp)
    MemoryFuncCall("int", DllStructGetData($MyHook, "BridgePtr"), "hwnd", 0, _
                                "wstr", "This is the hook intercepting the MessageBoxW call, then calling the bridge to the real function." & @CRLF & _
                                "Here's what we got:" & @CRLF & @CRLF & _                               
                                "wstr", "Hooked Message Box", _
                                "uint", 48)
EndFunc

thx for your great support ! :)

Edited by arcker

-- Arck System _ Soon -- Ideas make everything

"La critique est facile, l'art est difficile"

Projects :

[list] [*]Au3Service : Run your exe as service V3 / Updated 29/07/2013 Get it Here [/list]
Link to comment
Share on other sites

Could you post the source for your DLL, and if you can figure it out, at what step exactly Explorer is crashing.

From the name of your function, something doesn't look right. Both the SHFileOp hook function and the _Get function should only have 1 parameter, and so should be named at @4. Post the source and I'll look at it.

Vista or XP?

Edited by wraithdu
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...