Click here to Skip to main content
Click here to Skip to main content

DLL Injection and function interception tutorial

, 23 Oct 2003
Rate this:
Please Sign up or sign in to vote.
How to inject a DLL into a running process and then intercept function calls in statically linked DLLs.
Revision 4


The source files depend a lot on function pointers. Overview is recommended.

DLL Injection is similar to 'Injecting' code into an already running process. Many things have been taken from Matt Pietrek's book 'Secrets of Windows 95'.

Function interception means 'intercepting' an API function that was loaded by a statically linked DLL by modifying the address of the beginning of the function's code, resulting in the application to call your 'intercepting' function instead of the original 'intercepted' function. This is similar to the idea in "APIHijack - A Library for easy DLL function hooking" article posted by Wade Brainerd.

DLL Injection

"DLL Injection" is not an accurate name for what my content will actually be. My code will 'inject' a series of assembled assembly language instructions [Code] into some available space in the running process and alters the registers to point at the offset of the 'injected' [code]. The process will of course execute the instructions which will load a certain DLL, which is the DLL that is being injected. Note that this code 'Injects' [code]. The code can be anything, it doesn't necessarily have to load a DLL. Hence, the inaccurate title.

There are two ways to 'Inject' a series of bytes into an already running process. VirtualAllocEx() - which isn't supported in Win9x/ME - will allow a process to reserve or commit a region of memory within the virtual address space of a separate specified process. Use WriteProcessMemory() to write the data in the reserved/committed area of the target process' memory. The other way is to directly use ReadProcessMemory() and WriteProcessMemory() - which is supported in all versions of Windows - to search for some accessible area of the target process' memory and replace the bytes within the area size equal to the size of the code. Of course, you will be saving a backup of the replaced bytes in order to put them all back later on.

(Of course, you can use CreateRemoteThread() instead of all this, but it's not supported in all versions of Windows.)

One good yet slow method of injecting the code is using Windows' debugging functions. Suspend the threads of the running process (using the debugging functions) and use GetThreadContext() and SetThreadContext() to save a backup of all the registers and then modify the EIP register, which is the register that contains the offset of the current to-be-executed code, to point it to the 'Injected' code. The injected code block will have a breakpoint set at the end of it (Interrupt 3h -int 3h-). Again, use the debugging functions to resume the threads, which will then continue executing till the first breakpoint is reached. Once your application receives the notification, all you have to do is restore the modified bytes and/or un-allocate any allocated space in memory, and finally restore the registers (SetThreadContext()). That's all there is to it. The application has no idea of what has happened! The code was executed, and probably loaded a DLL. As you know, loaded DLLs are in an application's address space, therefore, the DLLs can access all memory and control the whole application. Very interesting.

Lookup the MSDN library for more information (MSDN).

Useful points to lookup:

  1. Memory need to know how Windows manages its memory.
  2. How DLLs tick - MSDN - I suggest you read it. Might help in inspirations. Revising isn't bad.
  3. PE/COFF Headers specifications... The most important thing if you're doing this in Win9x/ME - MSDN.
  4. Basic debugging APIs...those are some APIs that allow you to debug certain applications. Lookup the section "Debugging and Error Handling" in MSDN.
  5. Enough knowledge of ASM is required of course...and OPCODES of instructions.

Have a look at the accompanied files:


Google Groups - A Message board thread on CreateRemoteThread()'s method.

Function Interception

Notice the DLL project in the zip file. This function is in HookApi.h.

// Macro for adding pointers/DWORDs together without C arithmetic interfering 
// -- Taken from Matt Pietrek's book
// Thought it'd be great to use..
#define MakePtr( cast, ptr, addValue ) (cast)( (DWORD)(ptr)+(DWORD)(addValue))
//This code is very similar to Matt Pietrek's, except that it is written 
//according to my understanding...
//And Matt Pietrek's also handles Win32s 
//--(Because they it has some sort of a problem)
PROC WINAPI HookImportedFunction(HMODULE hModule,
			         //Module to intercept calls from
     PSTR FunctionModule, //The dll file that contains the function you want to 
			  //hook(ex: "USER32.dll")
     PSTR FunctionName,   //The function that you want to hook 
			  //(ex: "MessageBoxA")
     PROC pfnNewProc)     //New function, this gets called instead
    PROC pfnOriginalProc; //The intercepted function's original location
    IMAGE_DOS_HEADER *pDosHeader; 
    // Verify that a valid pfn was passed

    if ( IsBadCodePtr(pfnNewProc) ) return 0; 

    pfnOriginalProc = GetProcAddress(GetModuleHandle(FunctionModule), 
    if(!pfnOriginalProc) return 0;
    pDosHeader = (PIMAGE_DOS_HEADER)hModule; 
    //kindly read the ImgHelp function reference 
    //in the Image Help Library section in MSDN
    //hModule is the Process's Base address  (GetModuleHandle(0)) 
    //even if called in the dll, it still gets the hModule of the calling process
    //---That's you should save the hInstance of the DLL as a global variable, 
    //in DllMain(), because it's the only way to get it(I think)
    // Tests to make sure we're looking at a module image (the 'MZ' header)
    if ( IsBadReadPtr(pDosHeader, sizeof(IMAGE_DOS_HEADER)) )
        return 0;
    if ( pDosHeader->e_magic != IMAGE_DOS_SIGNATURE ) 
	//Image_DOS_SIGNATURE is a WORD (2bytes, 'M', 'Z' 's values)
        return 0;
    // The MZ header has a pointer to the PE header
    pNTHeader = MakePtr(PIMAGE_NT_HEADERS, pDosHeader, pDosHeader->e_lfanew); 
    //it's like doing pDosHeader + pDosHeader->e_lfanew
    // e_lfanew contains a RVA to the 'PE\0\0' Header...An rva means, offset,
    // relative to the BaseAddress of module 
    // -pDosHeader is the base address..and e_lfanew is the RVA, 
    // so summing them, will give you the Virtual Address..
    // More tests to make sure we're looking at a "PE" image
    if ( IsBadReadPtr(pNTHeader, sizeof(IMAGE_NT_HEADERS)) )
        return 0;
    if ( pNTHeader->Signature != IMAGE_NT_SIGNATURE ) 
	//IMAGE_NT_SIGNATURE is a DWORD (4bytes, 'P', 'E', '\0', '\0' 's values)
        return 0;
    // We now have a valid pointer to the module's PE header. 
    // Now get a pointer to its imports section
    pImportDesc = MakePtr(PIMAGE_IMPORT_DESCRIPTOR, 
      pDosHeader, //IMAGE_IMPORT_DESCRIPTOR *pImportDesc;

    //What i just did was get the imports section by getting the RVA of it
    //(like i did above), then adding the base addr to it.
    //// pNTHeader->OptionalHeader.DataDirectory
    ///     [IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
    //// IMAGE_DIRECTORY_ENTRY_IMPORT==1 -- Look at that PE documentation. 
    //// Pietrek's articles in MSJ and MSDN Magazine will be real helpful!
    //Go out if imports table doesn't exist
    if ( pImportDesc == (PIMAGE_IMPORT_DESCRIPTOR)pNTHeader )
        return 0; //pImportDesc will ==pNTHeader. 
	//if the RVA==0, cause pNTHeader+0==pNTHeader -> stored in pImportDesc
	//Therefore, pImportDesc==pNTHeader
    // Iterate through the array of imported module descriptors, looking
    // for the module whose name matches the FunctionModule parameter
    while ( pImportDesc->Name ) //Name is a DWORD (RVA, to a DLL name)
        PSTR pszModName = MakePtr(PSTR, pDosHeader, pImportDesc->Name);

        if ( stricmp(pszModName, FunctionModule) == 0 ) 
	    //str"i"cmp,,, suggest you to ignore cases when comparing,
            break; //or strcmpi() in some compilers
        pImportDesc++;  // Advance to next imported module descriptor
    // Get out if we didn't find the Dll name. 
    // pImportDesc->Name will be non-zero if we found it.
    if ( pImportDesc->Name == 0 )
        return 0;
 // Get a pointer to the found module's import address table (IAT)
 //           =====IMAGE_THUNK_DATA *pThunk;
    pThunk = MakePtr(PIMAGE_THUNK_DATA, pDosHeader, pImportDesc->FirstThunk);
 //This is what i was talkin about earlier...
 //In pThunk, if it was image loaded in memory, you'll get the address to 
 //entry point of functions
 //but in a disk file, It's a function name

 // Look through the table of import addresses, of the found 
 // DLL, looking for the function's entry point that matches 
 // the address we got back from GetProcAddress above.
    while ( pThunk->u1.Function )
       if ( (DWORD)pThunk->u1.Function == (DWORD)pfnOriginalProc )
       // We found it!  Overwrite the original address with the
       // address of the interception function.  Return the original
       // address to the caller so that they can chain on to it.
            pThunk->u1.Function = (PDWORD)pfnNewProc; 
	    // pfnNewProc is in the parameters of the function
	    //pfnOriginalProc = (PROC)(DWORD)pdw1;
            return pfnOriginalProc;

        pThunk++;   // Advance to next imported function address
    return 0; //function not found!!!!!

Also notice:

  • HANDLE OpenLog(char *Filename)
  • BOOL CloseLog(HANDLE h)
  • DWORD AppendLog(char *str, DWORD uSize, HANDLE h)

They are the functions to write to the LOG file. What the whole project does is inject a DLL into an already running process (mIRC.exe - mIRC chatting program (mIRC)) [in my case]. It then creates a Log file of all the intercepted Winsock functions. Have a look at Successlog.txt. It is highly recommended that you apply the program on mIRC only, since it has been created for it. Have fun coding your own Smile | :)

I think you got the idea. I hope this is useful.



Nasser R. Rowhani -


This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author


Qatar Qatar
Nasser R. Rowhani
Programming simply pumps my adrenaline..
Okay... I like people critisizing me...
Let me fix this article...

Comments and Discussions

GeneralA very interesting Link collection PinmemberElmue29-Sep-08 17:04 
Questionwhy do you call your method 'good yet slow'? Pinmembercode_discuss4-Dec-07 20:07 
AnswerRe: why do you call your method 'good yet slow'? PinmemberAndrewWood7-Jan-08 22:43 
QuestionIntercept functions from kernel32.dll PinmemberHigginz6-Sep-06 8:13 
GeneralW32SKRNL.DLL Pinmembervmelkon7-Jun-05 20:03 
GeneralWinsock functions hooking doesn't work !! PinmemberWessam Fathi28-Apr-05 12:04 
GeneralCreateWindowEx & CreateWindow Pinmemberdj013-Feb-05 10:14 
GeneralProtect NET PinsussAnonymous8-Nov-04 2:44 
GeneralRe: Protect NET PinsussAnonymous9-Nov-04 12:29 
GeneralHooked function address is replaced by Original one Pinmemberkeenalex23-Sep-04 20:03 
GeneralRe: Hooked function address is replaced by Original one PinmemberTarmo26-Jan-05 1:24 
QuestionHas anyone solved the NT/XP Issue? Pinmemberdj018-Dec-03 19:38 
AnswerRe: Has anyone solved the NT/XP Issue? PinmemberCrankHank18-Dec-03 22:02 
GeneralRe: Has anyone solved the NT/XP Issue? Pinmemberdj019-Dec-03 8:48 
Generalquestion PinsussBrandon Sneed24-Oct-03 5:37 
GeneralRe: question PinmemberCrankHank28-Dec-03 4:23 
GeneralRe: question PinmemberBrandon Sneed28-Dec-03 10:07 
GeneralRe: question PinmemberCrankHank29-Dec-03 2:08 
Ah yes.. That is an interesting question.
It really depends on the DLL file itself and the compiler that it has been compiled with. Logically speakin, I don't see any reason for why a DLL file would call one of its internal functions through the export table. Unless someone was bored of life and thought of twisting things a bit..
One solution that I have adapted and polished personally was to either modify the binary it self, or its image by injecting/invoking a jmp instruction, at the entry point of the original function, to another internally defined function's location by myself which will -in my case- log the parameters passed to the original function through the stack, and allow execution to continue by restoring the modified bytes and using the call instruction back to the beggining of the original function.
Problems with C++ functions are the prolog and epilog parts. Since they cause stack modifications, and thus cause trouble. We need to be clean. That's what Naked functions are made for. A naked function, is a function that does not have any of epilog or prolog.
Use __declspec( naked ) void Function_Name, in VC++.
Back to the subject. After the original function has returned execution, it will return the to instruction after the call instruction of the created function. There happens the logging of the return values of the function. Normal execution is then resumed by reinjecting the jmp instructions to the beginning of the function and returning in a way that the stack and registers remain the same as they must originally be if there were no interception.
Hope I was descriptive enough. I can provide a better explanation if you'de like.
It is hard work, yet fun. Remember that different applications differ according to the programmer him/her self. Also, don't forget that functions differ alot too. Alot of assembly knowledge is required in order to be good at it. Care must be taken. In my case, my problem was over what i must do if the to-be-intercepted DLL file was called dynamically rather than statically. Very similar. GetProcAddress() is always very helpful. Love that function!
Hope that was helpful, any more help? Elaboration?
Nasser R. Rowhani
GeneralFinally! Problem solved.. PinmemberCrankHank23-Oct-03 20:33 
GeneralRe: Finally! Problem solved.. Pinmemberyuxc25-Oct-03 19:26 
GeneralWin98 is successed but win2000 is faliure! Pinmemberyuxc19-Oct-03 16:56 
GeneralRe: Win98 is successed but win2000 is faliure! PinmemberCrankHank20-Oct-03 3:26 
QuestionDllMain never got called? PinmemberPeter Leng17-Oct-03 9:13 
AnswerRe: DllMain never got called? PinmemberCrankHank18-Oct-03 5:17 
AnswerRe: DllMain never got called? Pinmemberyuxc18-Oct-03 21:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 24 Oct 2003
Article Copyright 2003 by CrankHank
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid