24.09.2009

API Hooking Tricks of the Hackers: 2 API Hooking and DLL Injection Intercepting API calls is a mechanism for testing Dr. Wolfgang Koch monitoring

Friedrich Schiller University Jena and reverse engineering

Department of Mathematics and as well as for altering the behavior of the Computer Science Jena, Germany or of 3rd party products, [email protected] without having their source code available.

API Hooking Literature Books 3 4

Intercepting API calls is a mechanism for “The Windows-API Book” : Jeffrey Richter, altering the behavior of programs or of the Christophe Nasarre operating system WINDOWS via C/C++ 5th edition widely used by hackers and other “bad guys” Redmond, Wash : Press, 2008 ISBN-13: 978-0-7356-2424-5

820 p. + Companion content Web page

1 24.09.2009

Literature Books Literature 5 6

Ivo Ivanov: API hooking revealed, 2002 The WDM Bible : http://www.codeproject.com/KB/system/hooksys.aspx Walter Oney Robert Kuster: Three Ways to Inject Your Code Programming the Driver Model into Another , 2003 2nd edition http://www.codeproject.com/KB/threads/winspy.aspx

Redmond, Wash : Microsoft Press, 2003 Seung-Woo Kim - Intel® Software Network: Intercepting ISBN: 0-7356-1803-8 System API Calls, 2004 http://software.intel.com/en-us/articles/ intercepting-system--calls/ 846 p. + CD-ROM

Literature Literature Executable File Format 7 8

Anton Bassov: Microsoft MSDN man pages and “ white papers ”: Process-wide API spying - an ultimate hack, 2004 http://msdn.microsoft.com/library http://www.codeproject.com/KB/system/api_spying_hack.aspx An In-Depth Look into the Win32 Kernel-mode API spying - an ultimate hack, 2005 File Format, Matt Pietrek, MSDN Magazine, Feb. 2002 http://www.codeproject.com/KB/system/kernelspying.aspx http://msdn.microsoft.com/en-us/magazine/cc301805.aspx Newsgroups: comp.os.ms-windows.programmer.nt.kernel-mode Microsoft Portable Executable and Common Object microsoft.public.development.device.drivers File Format Specification, Revision 8.0 - May 2006 http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx

2 24.09.2009

API Hooking API Hooking 9 10

API Routine Call: API - Application Programming Interface

Wikipedia: ... push Parameter_2 push Parameter_1 API - is an interface that defines the ways by which an call application program may request services from mov eax, Result libraries and/or operating systems . ... An API determines the vocabulary and calling // API Routine (in DLL) conventions the programmer should employ to use push ebp mov esp, ebp the services. It may include specifications for routines , ... // service data structures , object classes and protocols … ret

API Hooking API Hooking 11 12

API Routine Call - hooked: API Routine Call – hooked (2): // proxy function //proxy function push ebp push ebp ...... // other stuff ...... // pre proc. push Parameter_2 ret push Parameter_2 call original API push Parameter_1 push Parameter_1 ... // post proc. ret callcall call mov eax, Result mov eax, Result ...... //API Routine (in DLL) //API Routine (in DLL) push ebp push ebp mov esp, ebp mov esp, ebp We would have to overwrite every ... // service • Parameters of the API routine ... // service call to the API routine in the code ret • Win32 API : _stdcall – ret n ret

3 24.09.2009

API Hooking API Hooking 13 14 API Routine Call – hooked Hooking API Routine Calls different way – overwriting code // proxy function call (0xE8) push ebp ...... // other stuff we would have to overwrite every call to the push Parameter_2 ret push Parameter_1 API routine in the code call mov eax, Result In Windows executables indirect calls are applied: ... call ind(Ptr_To_Addr_of_API_Routine ) //API Routine (in DLL) jmppush< Addr_of_proxy_ebp The Pointer points to the address of the API Routine mov esp,Routine ebp > Difficulties when calling or jumping ... // service in the Import Address Table (IAT), where the addresses to the original API routine. ret of all functions, imported by the module, are stored.

API Hooking API Hooking Locating the IAT 15 16

In Windows executables indirect calls are applied: see Portable Executable File Format:

call ind(Ptr_To_Addr_of_API_Routine ) IMAGE_DOS_HEADER * dosheader = (IMAGE_DOS_HEADER *) hMod; (0xFF,0x15) IMAGE_OPTIONAL_HEADER * opthdr = (IMAGE_OPTIONAL_HEADER *) Reason: ((BYTE*)hMod + dosheader->e_lfanew + 24); The actual address of the API Routine is not known at compile time. IMAGE_IMPORT_DESCRIPTOR * descriptor = (IMAGE_IMPORT_DESCRIPTOR *) ((BYTE*) hMod + opthdr-> The Import Address Table is filled in, when a DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]. (a DLL) is loaded. VirtualAddress );

Now we can change the addresses in the IAT while (descriptor->FirstThunk){ (only one per API routine) to our proxy functions. char * dllname = (char *)((BYTE*)hMod + descriptor->Name);

4 24.09.2009

API Hooking IAT_Entry_Addresses API Hooking IAT_Entry_Addresses 17 18

while (descriptor->FirstThunk){ IATentryaddress = (DWORD*) char * dllname = (char *)((BYTE*)hMod + descriptor->Name); ((BYTE*) hMod + descriptor->FirstThunk) + x; IMAGE_THUNK_DATA * thunk = (IMAGE_THUNK_DATA *) ((BYTE*) hMod + descriptor->OriginalFirstThunk); Now we can overwrite IAT entries of the target module with

int x=0; the addresses of our user-defined proxy functions. while (thunk->u1.Function) { Each IAT entry replacement normally requires a separate char * functionname = (char*) ((BYTE*) hMod + (DWORD)thunk->u1.AddressOfData + 2); proxy function - a proxy function must know which particular DWORD * IATentryaddress = (DWORD*) ((BYTE*) hMod + API function it replaces so that it can invoke the original callee. descriptor->FirstThunk) + x; x++; thunk++; } Anton Bassov proposed the following method descriptor++; }

API Hooking API Hooking 19 20

Anton Bassov proposed the following method 4 (unique) functions, one structure, 4 (unique) functions, one structure : 22 bytes of allocated (executable) memory per IAT entry: ProxyProlog(), ProxyEpilog() - Assembler Prolog(), Epilog() - normal C code 0xFF15 Addr_RelFkt struct RelocatedFunction 2 4 16 Bytes struct RelocatedFunction{ DWORD proxyptr; 0xFF15 Addr_RelFkt – DWORD functionptr; indirect call: call ind(Addr_RelFkt) char * dllname; i.e. calls RelocatedFunction.proxyptr char * functionname; Trick: The return address of this proxy function – on top of the }; stack – is the address of our struct RelocatedFunction !!!

5 24.09.2009

API Hooking Modifying IAT Entries API Hooking Modifying IAT Entries 21 22

while(thunk->u1.Function) { uchar * mptr = malloc(22); // char * functionname = ... ; // DWORD * IATentryaddress = ... + x; struct RelocatedFunction * reloc = (struct RelocatedFunction *) (mptr+6); if ( alter(functionname) ){ reloc->proxyptr = (DWORD) ProxyProlog; reloc->functionptr = *IATentryaddress; // orig. reloc->functionname = functionname; } reloc->dllname = dllname;

mptr[0]= 0xFF; mptr[1]= 0x15; // JMP ind x++; thunk++; memmove( &mptr[2], &reloc, 4); }

API Hooking Modifying IAT Entries API Hooking Modifying IAT Entries 23 24

uchar * mptr = malloc(22); Usually the IAT is writeable … . . . if (! WriteProcessMemory( GetCurrentProcess(), // < modify *IATentryaddress > IATentryaddress, &mptr, 4, NULL)) { DWORD byteswritten; DWORD dwOldProt; if ( VirtualProtect (IATentryaddress, 4, WriteProcessMemory ( GetCurrentProcess(), PAGE_READWRITE, &dwOldProt)) { IATentryaddress, WriteProcessMemory( GetCurrentProcess(), &mptr, 4, &byteswritten); IATentryaddress, &mptr, 4, NULL); VirtualProtect(IATentryaddress, 4, dwOldProt, Usually the IAT is writeable (it is filled in, when a Library &dwOldProt); } is loaded), but not the Export Directory. }

6 24.09.2009

API Hooking ProxyProlog API Hooking ProxyProlog 25 26

ProxyProlog() saves registers and flags and calls Prolog(). ProxyProlog() saves registers and flags and calls Prolog(). Then it restores the registers and flags and calls (in fact: returns to) __declspec(naked) void ProxyProlog() the original API routine (Prolog modifies the return address). creates pure function code without a stack frame __declspec(naked) void ProxyProlog() – intended for assembler code. { _asm{ push eax; push ebx; push ecx; push edx } I work with cygwin and gcc, there is no __declspec(naked). _asm{ mov ebx,esp; pushf; add ebx,16 } I just define void ProxyProlog() _asm{ push ebx; call Prolog; pop ebx; popf } and instad of reloc->proxyptr = (uint) ProxyProlog; _asm ( pop edx; pop ecx; pop ebx; pop eax; ret) I have } reloc->proxyptr = (uint)((BYTE*)ProxyProlog +3);

API Hooking ProxyProlog API Hooking Prolog 27 28

ProxyProlog() saves registers and flags and calls Prolog(). Prolog () does the preprocessing part to the hooked routines

_asm{ mov ebx,esp; pushf; add ebx,16 } – for example monitoring all API calls. _asm{ push ebx; call Prolog ; pop ebx; popf } It has access to the actual parameters of the routine as well as to the DLL name and the function name. Prolog is defined as void Prolog(DWORD * stackptr); – it has one parameter – If necessary Prolog() prepares for the call of the ebx , which holds the address of the top of stack post-processing part – ProxyEpilog() and Epilog() (the contents of esp ) just before saving the registers – Prolog() returns to ProxyProlog(), which in turn has no valid i.e. the address of the return address of ProxyProlog(). return address. Prolog replaces it with the original address As you remember this in fact is the address of the corresponding of the hooked routine – so return is just a jump to this routine. struct RelocatedFunction which contains all necessary data.

7 24.09.2009

API Hooking Prolog API Hooking Prolog 29 30

Prolog() has access to the actual parameters of the routine Stack: | ... | as well as to the DLL name and the function name. +------+ void Prolog(DWORD * stackptr); | EAX | It has one parameter – the address of the return address of +------+ ProxyProlog(), which in fact is the address of the companion | Ret ProxyPr | <==== stackptr +------+ struct RelocatedFunction . | Ret API | +------+ void Prolog(DWORD * stackptr) { | Param 1 | // DWORD param1 = *(stackptr+2); struct RelocatedFunction *reloc_prol, *reloc_epil; +------+ reloc_prol = (struct RelocatedFunction *) *stackptr; | Param 2 | // DWORD param2 = *(stackptr+3); +------+ // use reloc_prol->functionname, reloc_prol->dllname); high | ... |

API Hooking Prolog API Hooking Prolog 31 32

Stack: | ... | … API returns regularly using RET API . +------+ void Prolog(DWORD * stackptr); | EAX | If necessary Prolog() prepares for the call of the +------+ post-processing part – ProxyEpilog() and Epilog() | Ret ProxyPr | <==== stackptr +------+ – it overwrites RET API with the address of a new Prolog replaces the return address of | Ret API | struct RelocatedFunction (in fact of all the 22 bytes) ProxyProlog with the original address +------+ of the hooked routine → jmp to API which works the same way as with ProxyProlog | Param 1 | and holds data, important for Epilog(). +------+ *stackptr = reloc_prol->functionptr; | Param 2 | Epilog() then must repair the return address RET API +------+ If there is no Epilog, API returns regularly on the stack. high | ... | using RET API

8 24.09.2009

API Hooking Prolog API Hooking Prolog 33 34

Prolog() overwrites RET API with the address of a new Prolog() overwrites RET API with the address of a new struct

struct RelocatedFunction . uchar * mptr = malloc(22);

We cannot re-use the original struct , reloc_epil = (struct RelocatedFunction *) (mptr+6);

since we are in a preemptive, multithreaded environment reloc_epil->proxyptr = (DWORD) Proxy Epilog ; – several calls to the same routine may be pending. reloc_epil->functionname = reloc_prol->functionname; reloc_epil->dllname = reloc_prol->dllname; A. Bassov uses local storage (TLS) reloc_epil->functionptr = *(stackptr+1); // RET API – and has a lot of trouble with it mptr[0]= 0xFF; mptr[1]= 0x15; (scanning all DLLs, DLL injection) memmove( &mptr[2], &reloc_epil, 4);

My method (using malloc) is much easier and just as good. *(stackptr+1) = (DWORD) mptr;

API Hooking Epilog API Hooking Epilog 35 36

ProxyEpilog() saves registers and flags and calls Epilog(). Epilog() does the post-processing part to the hooked routines. Then it restores the registers and flags and returns to the caller void Epilog(DWORD * stackptr); of the original API routine (Epilog repairs the return address).

__declspec(naked) void Proxy Epilog () It has access to the parameters of the routine { – though they are of no much use now, _asm{ push eax; push ebx; push ecx; push edx } to the result of the original routine ( in eax – on the stack) and, again to the DLL name and the function name. _asm{ mov ebx,esp; pushf; add ebx,16 } _asm{ push ebx; call Epilog ; pop ebx; popf } DWORD result = *(stackptr-1);

_asm ( pop edx; pop ecx; pop ebx; pop eax; ret) DWORD Param1 = *(stackptr +1 ); // +1 now, not +2 } Epilog repairs the return address of ProxyEpilog()

9 24.09.2009

API Hooking Epilog API Hooking Multiple Modules 37 38

In order to intercept every call to certain API routines Epilog repairs the return address of ProxyEpilog() on a process-wide base, and frees the memory, used for struct RelocatedFunction. we have to walk through all modules that are currently loaded into the address space of the target process, struct RelocatedFunction * reloc_epil; and, in each loaded module, overwrite the IAT entries

reloc_epil = (struct RelocatedFunction *) *stackptr; while ( modules ){ HANDLE hMod = GetModuleHandle(DLL_Name); ModuleScanIAT(hMod); } *stackptr = reloc_epil->functionptr;

free((uchar *)reloc_epil -6); This doesn´t work, if a module is dynamically loaded afterwards, then we must overwrite the Export Directory of the API module.

API Hooking DLL DLL Injection 4 Ways 39 40

We put Prolog() etc. in a DLL, which we inject into the In order to apply our hooking tool to a (possibly yet running) target process. target process, we put the tool in a DLL and inject the DLL When the DLL is loaded, its DLLMain Routine is called into that process. with reason = DLL_PROCESS_ATTACH . There are different possible ways of injection: int WINAPI DllMain(HANDLE h, DWORD reason, void *foo)

{ ¤ Using the Registry if (reason == DLL_PROCESS_ATTACH) ¤ Using Windows Hooks { HANDLE hMod = GetModuleHandle(NULL); ¤ Using CreateRemoteThread() the way I used ModuleScanIAT(hMod); ¤ Using CreateProcess( …, CREATE_SUSPENDED, … ) < possibly scans of more DLLs and Export Dirs > for a process we launch see A. Bassow } }

10 24.09.2009

DLL Injection Registry DLL Injection Windows Hooks 41 42

There are different possible ways of injection There are different possible ways of injection

Using the Registry Using Windows Hooks

if in the registry key The primary role of windows hooks is to monitor the message HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows\ traffic of some thread. the value AppInit_DLLs contains the name of one or more DLLs, these DLLs are loaded by each Windows-based application running If the hooked thread belongs to another process, the hook within the current logon session ( LoadAppInit_DLLs = 1 ) procedure must reside in a DLL. The system then maps the entire DLL containing the hook procedure into the address ¤ Injection only into processes that use USER32.DLL space of the hooked thread. ¤ Injection into every single GUI application, regardless we want it I never used Windows Hooks.

DLL Injection CreateRemoteThread DLL Injection CreateRemoteThread 43 44

There are different possible ways of injection CreateRemoteThread() is identical to CreateThread() except for one additional parameter: HANDLE hProcess Using CreateRemoteThread() We get this handle using OpenProcess() with the ProcessID Any process can load a DLL dynamically using LoadLibrary . of the target process. But, how do we force an external process to call this function? The addresses LPTHREAD_START_ROUTINE lpStartAddress Answer: CreateRemoteThread() and LPVOID lpParameter

It was originally designed for debuggers and such tools. are addresses in the target process now, we have to copy the thread code and additional data there using It is identical to CreateThread() except for one additional VirtualAllocEx(hProcess, …) and parameter: hProcess , the process that will own the new thread WriteProcessMemory(hProcess, …) .

11 24.09.2009

DLL Injection CreateRemoteThread DLL Injection CreateRemoteThread 45 46

We have to copy the thread code and additional data there … Thread code:

typedef HINSTANCE (*fpLoadLibrary) (char*); DWORD WINAPI threadcode(LPVOID addr) { typedef struct _INJECTSTRUCT INJECTSTRUCT *is = addr; { fpLoadLibrary LoadLibrary; is->LoadLibrary(is->path); char path[120]; return 0; } INJECTSTRUCT; }

INJECTSTRUCT will contain additional data: void threadend() a function pointer for LoadLibrary() { and a string that holds the path of the DLL we want to inject } // for computing the size of the thread code

DLL Injection CreateRemoteThread DLL Injection CreateRemoteThread 47 48

int main() funcsize = (DWORD)threadend -(DWORD)threadcode; { printf("\n PID: "); scanf("%d", (int *) &pid); HANDLE hProc, hThread; LPVOID start, thread; hProc = OpenProcess(PROCESS_ALL_ACCESS, DWORD funcsize, pid; FALSE, pid); INJECTSTRUCT is; if(!hProc){ printf(” ... %d”, pid); return 1; }

is.LoadLibrary = (fpLoadLibrary) GetProcAddress( start = VirtualAllocEx(hProc, NULL, funcsize + GetModuleHandle("Kernel32"), sizeof(INJECTSTRUCT), // 4096 "LoadLibraryA"); MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); strcpy(is.path, "Hook.dll");

12 24.09.2009

DLL Injection CreateRemoteThread DLL Injection CreateRemoteThread 49 50

WriteProcessMemory(hProc, start, &is, hThread = CreateRemoteThread(hProc, ...); sizeof(INJECTSTRUCT), NULL); WaitForSingleObject(hThread, INFINITE); thread = (LPVOID) // new Addr of Thread ((DWORD)start + sizeof(INJECTSTRUCT)); VirtualFreeEx(hProc, start, 0, MEM_RELEASE);

WriteProcessMemory(hProc, thread, threadcode, CloseHandle(hThread); funcsize, NULL); CloseHandle(hProc); return 0; hThread = CreateRemoteThread(hProc, NULL, 0, } thread, start, 0, NULL);

we can find the PID using the console program tasklist

DLL Injection DebugPrivilege DLL Injection DebugPrivilege 51 52

We cannot inject code into every other process, { OpenProcessToken(GetCurrentProcess(), unless the injecting process has “DebugPriviledge” TOKEN_ADJUST_PRIVILEGES, &hToken); #include LookupPrivilegeValue(NULL, "seDebugPrivilege", &luid); void EnableDebugPrivilege() { priv.PrivilegeCount = 1; TOKEN_PRIVILEGES priv; priv.Privileges[0].Luid = luid; HANDLE hToken; priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; LUID luid; //Locally Unique Identifier AdjustTokenPrivileges(hToken, FALSE, &priv, 0,0,0); OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken); CloseHandle(hToken); }

13