Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ===========================[ Hooking Windows API ]==============================
- Technics of hooking API functions on Windows
- --------------------------------------------
- Author: Holy_Father <[email protected]>
- Version: 1.1 english
- Date: 6.10.2002
- =====[ 1. Contents ]============================================================
- 1. Contents
- 2. Introduction
- 3. Hooking methods
- 3.1 Hooking before running
- 3.2 Hooking during running
- 3.2.1 Own process hooking using IAT
- 3.2.2 Own process hooking using entry point rewriting
- 3.2.3 Original function saving
- 3.2.4 Other process hooking
- 3.2.4.1 DLL Injection
- 3.2.4.2 Independent code
- 3.2.4.3 Raw change
- 4. Ending
- =====[ 2. Introduction ]========================================================
- This text is about hooking API functions on OS Windows. All examples
- here completely works on Windows systems based on NT technology version NT 4.0
- and higher (Windows NT 4.0, Windows 2000, Windows XP). Probably will also work
- on others Windows systems.
- You should be familiar with processes on Windows, assembler, PE files
- structure and some API functions to understand whole text.
- When using term "Hooking API" here, I mean the full change of API.
- So, when calling hooked API, our code is run immediately. I do not deal with
- cases of API monitoring only. I will write about complete hooking.
- =====[ 3. Hooking methods ]=====================================================
- Our goal is generally to replace the code of some function with our
- code. This problem can be sometimes solved before running the process. This can
- be done mostly with user level process which are run by us and the goal is e.g.
- to change the program behaviour. Example of this can be application crack.
- E.g. program which wants original CD-ROM during startup (this was in the game
- Atlantis) and we want to run it without CDs. If we change the function for
- getting a drive type we would be able to run this program from the hard drive.
- This can not be done or we do not want to do this when want to hook
- system process (e.g. services) or in the case we do not know which process will
- be the target. Then we will use hooking during running technic. Example of
- using this can be rootkit or virus with anti-antivirus technics.
- =====[ 3.1 Hooking before running ]=============================================
- This is about physical module change (mostly .exe or .dll) when the
- function, which we want to change, is. We've got three possibilities at least
- here on how to do this.
- The first is to find entry point of that function and basically to
- rewrite its code. This is limited by the function size but we can load some
- other modules dynamically (API LoadLibrary), so it could be enought.
- Kernel functions (kernel32.dll) can be used in all cases because
- each process in windows has its own copy of this module. Other advantage
- is if we know on which OS will be changed module run. We can use direct
- pointers in this case for e.g. API LoadLibraryA. This is because the address
- of kernel module in memory is static in the scope of one OS Windows version.
- We can also use behaviour of dynamically loaded module. In this case its
- initialization part is run immediately after loading to the memory. We are not
- limited in initialization part of new module.
- Second possibility of replacing function in module is its extension.
- Then we have to choose between replacing first 5 bytes by relative jump
- or rewriting IAT. In the case of relative jump, this will redirect the code
- execution to our code. When calling function which IAT record is changed,
- our code will be executed directly after this call. But extension of the module
- is not so easy because we have to care about DLL header.
- Next one is replacing the whole module. That means we create own
- version of the module which can load the original one and call original
- functions which we are not interested in. But important functions will be
- totally new. This method is not so good for big modules which can contain
- hundreds of exports.
- =====[ 3.2 Hooking during running ]=============================================
- Hooking before running is mostly very special and intimately oriented
- for concrete application (or module). If we replace function in kernel32.dll
- or in ntdll.dll (only on NT OS) we will get perfect replace of this function
- in all processes which will be run later, but it is so difficult to make it
- because we have to take care about accuracy and code prefection of new
- functions or whole new modules, but the main problem is that only process
- which will be run later will be hooked (so for all process we have to reboot
- system). Next problem could be access to these files because NT OS tries to
- protect them. Much more pretty solution is to hook process during running. This
- method require more knowledge but the result is perfect. Hooking during running
- can be done only on process for which we have writing access to their memory.
- For the writing in itself we will use API function WriteProcessMemory. We will
- start from hooking our own process during running.
- =====[ 3.2.1 Own process hooking using IAT ]====================================
- There are many possibilities here. At first I will show you how to hook
- function by rewriting IAT. Following picture shows structure of PE file:
- +-------------------------------+ - offset 0
- | MS DOS Header ("MZ") and stub |
- +-------------------------------+
- | PE signature ("PE") |
- +-------------------------------+
- | .text | - module code
- | Program Code |
- | |
- +-------------------------------+
- | .data | - initialized (global static) data
- | Initialized Data |
- | |
- +-------------------------------+
- | .idata | - information for imported functions
- | Import Table | and data
- | |
- +-------------------------------+
- | .edata | - information for exported functions
- | Export Table | and data
- | |
- +-------------------------------+
- | Debug symbols |
- +-------------------------------+
- Important part for us here is Import Address Table (IAT) in the .idata
- part. This part contains description of imports and mainly imported functions
- addresses. Now it is important to know how are PE files created. When calling
- arbitrary API indirectly in programming language (that means we call it using
- its name, no using its OS specific address) the compiler does not link direct
- calls to the module but it links call to IAT on jmp instruction which will be
- filled by process loader while OS is loading process to the memory. This is why
- we can use the same binary on two different version os Windows where modules
- can be loaded to another addresses. Process loader will fill out direct jmp
- instructions in IAT which is used by our calls from the program code. So,
- if we are able to find out specific function in IAT which we want to hook,
- we can easily change jmp instruction there and redirect code to our address.
- Every call after doing this will execute our code. Advantage of this method
- is its perfection. Disadvantage is often amount of functions which should be
- hooked (e.g. if we want to change program behaviour in the file searching APIs
- we will have to change functions FindFirstFile and FindNextFile, but we have to
- know that these functions have its ANSI and WIDE version, so we have to change
- IAT address for FindFirstFileA, FindFirstFileW, FindNextFileA and also
- FileNextFileW. But there still some others like FindFirstFileExA and its WIDE
- version FindFirstFileExW which are called by previous mentioned functions.
- We know that FindFirstFileW calls FindFirstFileExW but this is done directly
- - not usinig IAT. And still some others to go. There are e.g. ShellAPI
- functions like SHGetDesktopFolder which also directly calls FindFirstFileW or
- FindFirstFileExW). But if we will get all of them, the result will be perfect.
- We can use ImageDirectoryEntryToData from imagehlp.dll to find out IAT
- easily.
- PVOID ImageDirectoryEntryToData(
- IN LPVOID Base,
- IN BOOLEAN MappedAsImage,
- IN USHORT DirectoryEntry,
- OUT PULONG Size
- );
- We will use Instance of our application as Base (Instance can be get by calling
- GetModuleHandle:
- hInstance = GetModuleHandleA(NULL);
- ), and as DirectoryEntry we will use constant IMAGE_DIRECTORY_ENTRY_IMPORT.
- #define IMAGE_DIRECTORY_ENTRY_IMPORT 1
- Result of this function is pointer to the first IAT record. IAT records are
- structures which are defined by I IMAGE_IMPORT_DESCRIPTOR. So, the result
- is a pointer on IMAGE_IMPORT_DESCRIPTOR.
- typedef struct _IMAGE_THUNK_DATA {
- union {
- PBYTE ForwarderString;
- PDWORD Function;
- DWORD Ordinal;
- PIMAGE_IMPORT_BY_NAME AddressOfData;
- } ;
- } IMAGE_THUNK_DATA,*PIMAGE_THUNK_DATA;
- typedef struct _IMAGE_IMPORT_DESCRIPTOR {
- union {
- DWORD Characteristics;
- PIMAGE_THUNK_DATA OriginalFirstThunk;
- } ;
- DWORD TimeDateStamp;
- DWORD ForwarderChain;
- DWORD Name;
- PIMAGE_THUNK_DATA FirstThunk;
- } IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;
- The Name value in IMAGE_IMPORT_DESCRIPTOR is a relative reference to
- the name of module. If we want to hook a function e.g. from kernel32.dll
- we have to find out in imports which belongs to the descriptor with name
- kernel32.dll. We will call ImageDirectoryEntryToData at first and than we will
- try to find descriptor with name "kernel32.dll" (there can be more than one
- descriptor with this name). Finally we will have to find our function in
- the list of all functions in the record (address of our function can be get
- by GetProcAddress function). If we find it we must use VirtualProtect to change
- memory page protection and after then we can write to this part of memory.
- After rewriting the address we have to change the protection back. Before
- calling VirtualProtect we have to know some information about this memory page.
- This is done by VirtualQuery. We can add some tests in case some calls will
- fail (e.g. we will not continue if the first VirtualProtect call failed, etc)>
- PCSTR pszHookModName = "kernel32.dll",pszSleepName = "Sleep";
- HMODULE hKernel = GetModuleHandle(pszHookModName);
- PROC pfnNew = (PROC)0x12345678, //new address will be here
- pfnHookAPIAddr = GetProcAddress(hKernel,pszSleepName);
- ULONG ulSize;
- PIMAGE_IMPORT_DESCRIPTOR pImportDesc =
- (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(
- hInstance,
- TRUE,
- IMAGE_DIRECTORY_ENTRY_IMPORT,
- &ulSize
- );
- while (pImportDesc->Name)
- {
- PSTR pszModName = (PSTR)((PBYTE) hInstance + pImportDesc->Name);
- if (stricmp(pszModName, pszHookModName) == 0)
- break;
- pImportDesc++;
- }
- PIMAGE_THUNK_DATA pThunk =
- (PIMAGE_THUNK_DATA)((PBYTE) hInstance + pImportDesc->FirstThunk);
- while (pThunk->u1.Function)
- {
- PROC* ppfn = (PROC*) &pThunk->u1.Function;
- BOOL bFound = (*ppfn == pfnHookAPIAddr);
- if (bFound)
- {
- MEMORY_BASIC_INFORMATION mbi;
- VirtualQuery(
- ppfn,
- &mbi,
- sizeof(MEMORY_BASIC_INFORMATION)
- );
- VirtualProtect(
- mbi.BaseAddress,
- mbi.RegionSize,
- PAGE_READWRITE,
- &mbi.Protect)
- )
- *ppfn = *pfnNew;
- DWORD dwOldProtect;
- VirtualProtect(
- mbi.BaseAddress,
- mbi.RegionSize,
- mbi.Protect,
- &dwOldProtect
- );
- break;
- }
- pThunk++;
- }
- Result of calling Sleep(1000) can be for example this:
- 00407BD8: 68E8030000 push 0000003E8h
- 00407BDD: E812FAFFFF call Sleep
- Sleep: ;this is jump on address in IAT
- 004075F4: FF25BCA14000 jmp dword ptr [00040A1BCh]
- original table:
- 0040A1BC: 79 67 E8 77 00 00 00 00
- new table:
- 0040A1BC: 78 56 34 12 00 00 00 00
- So the final jump is to 0x12345678.
- =====[ 3.2.2 Own process hooking using entry point rewriting ]==================
- The method of rewriting first few instructions on the function entry
- point is realy simple. As in the case of rewritng address in IAT we have to
- change a page protection at first. Here it will be first 5 bytes of the given
- function which we want to hook. For later usage we will use dynamical alocation
- of MEMORY_BASIC_INFORMATION structure. The beginning of the function is get
- by GetProcAddress again. On this address we will insert relative jump to our
- code. Following program calls Sleep(5000) (so it will wait for 5 seconds), than
- the Sleep functions is hooked and redirected to new_sleep, finally it calls
- Sleep(5000) again. Because new function new_sleep does nothing and returns
- immediately the whole program will take only 5 in place of 10 seconds.
- .386p
- .model flat, stdcall
- includelib lib\kernel32.lib
- Sleep PROTO :DWORD
- GetModuleHandleA PROTO :DWORD
- GetProcAddress PROTO :DWORD,:DWORD
- VirtualQuery PROTO :DWORD,:DWORD,:DWORD
- VirtualProtect PROTO :DWORD,:DWORD,:DWORD,:DWORD
- VirtualAlloc PROTO :DWORD,:DWORD,:DWORD,:DWORD
- VirtualFree PROTO :DWORD,:DWORD,:DWORD
- FlushInstructionCache PROTO :DWORD,:DWORD,:DWORD
- GetCurrentProcess PROTO
- ExitProcess PROTO :DWORD
- .data
- kernel_name db "kernel32.dll",0
- sleep_name db "Sleep",0
- old_protect dd ?
- MEMORY_BASIC_INFORMATION_SIZE equ 28
- PAGE_READWRITE dd 000000004h
- PAGE_EXECUTE_READWRITE dd 000000040h
- MEM_COMMIT dd 000001000h
- MEM_RELEASE dd 000008000h
- .code
- start:
- push 5000
- call Sleep
- do_hook:
- push offset kernel_name
- call GetModuleHandleA
- push offset sleep_name
- push eax
- call GetProcAddress
- mov edi,eax ;finally got Sleep address
- push PAGE_READWRITE
- push MEM_COMMIT
- push MEMORY_BASIC_INFORMATION_SIZE
- push 0
- call VirtualAlloc
- test eax,eax
- jz do_sleep
- mov esi,eax ;alocation for MBI
- push MEMORY_BASIC_INFORMATION_SIZE
- push esi
- push edi
- call VirtualQuery ;inforamtion about the memory page
- test eax,eax
- jz free_mem
- call GetCurrentProcess
- push 5
- push edi
- push eax
- call FlushInstructionCache ;just to be sure :)
- lea eax,[esi+014h]
- push eax
- push PAGE_EXECUTE_READWRITE
- lea eax,[esi+00Ch]
- push [eax]
- push [esi]
- call VirtualProtect ;we will change protection for a moment
- ;so we will be able to write there
- test eax,eax
- jz free_mem
- mov byte ptr [edi],0E9h ;to write relative jump
- mov eax,offset new_sleep
- sub eax,edi
- sub eax,5
- inc edi
- stosd ;this is relative address for jump
- push offset old_protect
- lea eax,[esi+014h]
- push [eax]
- lea eax,[esi+00Ch]
- push [eax]
- push [esi]
- call VirtualProtect ;return back the protection of page
- free_mem:
- push MEM_RELEASE
- push 0
- push esi
- call VirtualFree ;free memory
- do_sleep:
- push 5000
- call Sleep
- push 0
- call ExitProcess
- new_sleep:
- ret 004h
- end start
- Result of the second call Sleep is this:
- 004010A4: 6888130000 push 000001388h
- 004010A9: E80A000000 call Sleep
- Sleep: ;toto je jump na adresu v IAT
- 004010B8: FF2514204000 jmp dword ptr [000402014h]
- tabulka:
- 00402014: 79 67 E8 77 6C 7D E8 77
- Kernel32.Sleep:
- 77E86779: E937A95788 jmp 0004010B5h
- new_sleep:
- 004010B5: C20400 ret 004h
- =====[ 3.2.3 Original function saving ]=========================================
- Mostly we need more than a function hook. For example in case when we
- don't want to replace the given function but only to check its result, or in
- case when we want to replace the function only sometimes e.g. when it is called
- with specific arguments. Good example of this is already mentioned files hiding
- done by replacing FindXXXFile functions. So if we want to hide specific files
- and don't want to be noticeable we have to leave original function for all
- other files without changing the functions behavior. This is simple when using
- method of rewriting IAT. For calling original function we can get its original
- address with GetProcAddress and then call it directly. But the problem occurs
- when using rewriting entry point method. By rewriting those 5 bytes at the
- functions entry point we lost original function irrecoverably. So we need to
- save first instructions. We can use following technic.
- We know we will rewrite only first 5 bytes but don't know how many
- instructions there are or how long they are. We have to reserve enough memory
- for first instructions. 16 bytes could be enought because there are usually not
- long instructions at the begin of function. Probably we can use less then 16.
- Whole reserverd memory will be filled with 0x90 (0x90 = nop) in case there
- are shorter instructions. Next 5 bytes will be relative jump which will be
- filled later.
- old_hook: db 090h,090h,090h,090h,090h,090h,090h,090h
- db 090h,090h,090h,090h,090h,090h,090h,090h
- db 0E9h,000h,000h,000h,000h
- Now we are ready to copy first instructions. It is a long stuff to get
- instruction length, this is why we will work with the complete engine. This
- was made by Z0MBiE. Input argument is instruction address for which we want to
- get length. Output is commonly in eax.
- ; LDE32, Length-Disassembler Engine, 32-bit, (x) 1999-2000 Z0MBiE
- ; special edition for REVERT tool
- ; version 1.05
- C_MEM1 equ 0001h ; |
- C_MEM2 equ 0002h ; |may be used simultaneously
- C_MEM4 equ 0004h ; |
- C_DATA1 equ 0100h ; |
- C_DATA2 equ 0200h ; |may be used simultaneously
- C_DATA4 equ 0400h ; |
- C_67 equ 0010h ; used with C_PREFIX
- C_MEM67 equ 0020h ; C_67 ? C_MEM2 : C_MEM4
- C_66 equ 1000h ; used with C_PREFIX
- C_DATA66 equ 2000h ; C_66 ? C_DATA2 : C_DATA4
- C_PREFIX equ 0008h ; prefix. take opcode again
- C_MODRM equ 4000h ; MODxxxR/M
- C_DATAW0 equ 8000h ; opc&1 ? C_DATA66 : C_DATA1
- p386
- model flat
- locals @@
- .code
- public disasm_main
- public _disasm_main
- public @disasm_main
- public DISASM_MAIN
- disasm_main:
- _disasm_main:
- @disasm_main:
- DISASM_MAIN:
- ; returns opcode length in EAX or -1 if error
- ; input: pointer to opcode
- ; __fastcall EAX
- ; __cdecl [ESP+4]
- ;this is my first change here, it's the label only for calling this function
- get_instr_len:
- mov ecx, [esp+4] ; ECX = opcode ptr
- xor edx, edx ; flags
- xor eax, eax
- @@prefix: and dl, not C_PREFIX
- mov al, [ecx]
- inc ecx
- or edx, table_1[eax*4]
- test dl, C_PREFIX
- jnz @@prefix
- cmp al, 0F6h
- je @@test
- cmp al, 0F7h
- je @@test
- cmp al, 0CDh
- je @@int
- cmp al, 0Fh
- je @@0F
- @@cont:
- test dh, C_DATAW0 shr 8
- jnz @@dataw0
- @@dataw0done:
- test dh, C_MODRM shr 8
- jnz @@modrm
- @@exitmodrm:
- test dl, C_MEM67
- jnz @@mem67
- @@mem67done:
- test dh, C_DATA66 shr 8
- jnz @@data66
- @@data66done:
- mov eax, ecx
- sub eax, [esp+4]
- and edx,C_MEM1+C_MEM2+C_MEM4+C_DATA1+C_DATA2+C_DATA4
- add al, dl
- add al, dh
- ;my second change heer, there was retn only in original version
- @@exit: ret 00004h
- @@test: or dh, C_MODRM shr 8
- test byte ptr [ecx], 00111000b ; F6/F7 -- test
- jnz @@cont
- or dh, C_DATAW0 shr 8
- jmp @@cont
- @@int: or dh, C_DATA1 shr 8
- cmp byte ptr [ecx], 20h
- jne @@cont
- or dh, C_DATA4 shr 8
- jmp @@cont
- @@0F: mov al, [ecx]
- inc ecx
- or edx, table_0F[eax*4]
- cmp edx, -1
- jne @@cont
- @@error: mov eax, edx
- jmp @@exit
- @@dataw0: xor dh, C_DATA66 shr 8
- test al, 00000001b
- jnz @@dataw0done
- xor dh, (C_DATA66+C_DATA1) shr 8
- jmp @@dataw0done
- @@mem67: xor dl, C_MEM2
- test dl, C_67
- jnz @@mem67done
- xor dl, C_MEM4+C_MEM2
- jmp @@mem67done
- @@data66: xor dh, C_DATA2 shr 8
- test dh, C_66 shr 8
- jnz @@data66done
- xor dh, (C_DATA4+C_DATA2) shr 8
- jmp @@data66done
- @@modrm: mov al, [ecx]
- inc ecx
- mov ah, al ; ah=mod, al=rm
- and ax, 0C007h
- cmp ah, 0C0h
- je @@exitmodrm
- test dl, C_67
- jnz @@modrm16
- @@modrm32: cmp al, 04h
- jne @@a
- mov al, [ecx] ; sib
- inc ecx
- and al, 07h
- @@a: cmp ah, 40h
- je @@mem1
- cmp ah, 80h
- je @@mem4
- cmp ax, 0005h
- jne @@exitmodrm
- @@mem4: or dl, C_MEM4
- jmp @@exitmodrm
- @@mem1: or dl, C_MEM1
- jmp @@exitmodrm
- @@modrm16: cmp ax, 0006h
- je @@mem2
- cmp ah, 40h
- je @@mem1
- cmp ah, 80h
- jne @@exitmodrm
- @@mem2: or dl, C_MEM2
- jmp @@exitmodrm
- endp
- .data
- ;0F -- analyzed in code, no flags (i.e.flags must be 0)
- ;F6,F7 -- --//-- (ttt=000 -- 3 bytes, otherwise 2 bytes)
- ;CD -- --//-- (6 bytes if CD 20, 2 bytes otherwise)
- table_1 label dword ; normal instructions
- dd C_MODRM ; 00
- dd C_MODRM ; 01
- dd C_MODRM ; 02
- dd C_MODRM ; 03
- dd C_DATAW0 ; 04
- dd C_DATAW0 ; 05
- dd 0 ; 06
- dd 0 ; 07
- dd C_MODRM ; 08
- dd C_MODRM ; 09
- dd C_MODRM ; 0A
- dd C_MODRM ; 0B
- dd C_DATAW0 ; 0C
- dd C_DATAW0 ; 0D
- dd 0 ; 0E
- dd 0 ; 0F
- dd C_MODRM ; 10
- dd C_MODRM ; 11
- dd C_MODRM ; 12
- dd C_MODRM ; 13
- dd C_DATAW0 ; 14
- dd C_DATAW0 ; 15
- dd 0 ; 16
- dd 0 ; 17
- dd C_MODRM ; 18
- dd C_MODRM ; 19
- dd C_MODRM ; 1A
- dd C_MODRM ; 1B
- dd C_DATAW0 ; 1C
- dd C_DATAW0 ; 1D
- dd 0 ; 1E
- dd 0 ; 1F
- dd C_MODRM ; 20
- dd C_MODRM ; 21
- dd C_MODRM ; 22
- dd C_MODRM ; 23
- dd C_DATAW0 ; 24
- dd C_DATAW0 ; 25
- dd C_PREFIX ; 26
- dd 0 ; 27
- dd C_MODRM ; 28
- dd C_MODRM ; 29
- dd C_MODRM ; 2A
- dd C_MODRM ; 2B
- dd C_DATAW0 ; 2C
- dd C_DATAW0 ; 2D
- dd C_PREFIX ; 2E
- dd 0 ; 2F
- dd C_MODRM ; 30
- dd C_MODRM ; 31
- dd C_MODRM ; 32
- dd C_MODRM ; 33
- dd C_DATAW0 ; 34
- dd C_DATAW0 ; 35
- dd C_PREFIX ; 36
- dd 0 ; 37
- dd C_MODRM ; 38
- dd C_MODRM ; 39
- dd C_MODRM ; 3A
- dd C_MODRM ; 3B
- dd C_DATAW0 ; 3C
- dd C_DATAW0 ; 3D
- dd C_PREFIX ; 3E
- dd 0 ; 3F
- dd 0 ; 40
- dd 0 ; 41
- dd 0 ; 42
- dd 0 ; 43
- dd 0 ; 44
- dd 0 ; 45
- dd 0 ; 46
- dd 0 ; 47
- dd 0 ; 48
- dd 0 ; 49
- dd 0 ; 4A
- dd 0 ; 4B
- dd 0 ; 4C
- dd 0 ; 4D
- dd 0 ; 4E
- dd 0 ; 4F
- dd 0 ; 50
- dd 0 ; 51
- dd 0 ; 52
- dd 0 ; 53
- dd 0 ; 54
- dd 0 ; 55
- dd 0 ; 56
- dd 0 ; 57
- dd 0 ; 58
- dd 0 ; 59
- dd 0 ; 5A
- dd 0 ; 5B
- dd 0 ; 5C
- dd 0 ; 5D
- dd 0 ; 5E
- dd 0 ; 5F
- dd 0 ; 60
- dd 0 ; 61
- dd C_MODRM ; 62
- dd C_MODRM ; 63
- dd C_PREFIX ; 64
- dd C_PREFIX ; 65
- dd C_PREFIX+C_66 ; 66
- dd C_PREFIX+C_67 ; 67
- dd C_DATA66 ; 68
- dd C_MODRM+C_DATA66 ; 69
- dd C_DATA1 ; 6A
- dd C_MODRM+C_DATA1 ; 6B
- dd 0 ; 6C
- dd 0 ; 6D
- dd 0 ; 6E
- dd 0 ; 6F
- dd C_DATA1 ; 70
- dd C_DATA1 ; 71
- dd C_DATA1 ; 72
- dd C_DATA1 ; 73
- dd C_DATA1 ; 74
- dd C_DATA1 ; 75
- dd C_DATA1 ; 76
- dd C_DATA1 ; 77
- dd C_DATA1 ; 78
- dd C_DATA1 ; 79
- dd C_DATA1 ; 7A
- dd C_DATA1 ; 7B
- dd C_DATA1 ; 7C
- dd C_DATA1 ; 7D
- dd C_DATA1 ; 7E
- dd C_DATA1 ; 7F
- dd C_MODRM+C_DATA1 ; 80
- dd C_MODRM+C_DATA66 ; 81
- dd C_MODRM+C_DATA1 ; 82
- dd C_MODRM+C_DATA1 ; 83
- dd C_MODRM ; 84
- dd C_MODRM ; 85
- dd C_MODRM ; 86
- dd C_MODRM ; 87
- dd C_MODRM ; 88
- dd C_MODRM ; 89
- dd C_MODRM ; 8A
- dd C_MODRM ; 8B
- dd C_MODRM ; 8C
- dd C_MODRM ; 8D
- dd C_MODRM ; 8E
- dd C_MODRM ; 8F
- dd 0 ; 90
- dd 0 ; 91
- dd 0 ; 92
- dd 0 ; 93
- dd 0 ; 94
- dd 0 ; 95
- dd 0 ; 96
- dd 0 ; 97
- dd 0 ; 98
- dd 0 ; 99
- dd C_DATA66+C_MEM2 ; 9A
- dd 0 ; 9B
- dd 0 ; 9C
- dd 0 ; 9D
- dd 0 ; 9E
- dd 0 ; 9F
- dd C_MEM67 ; A0
- dd C_MEM67 ; A1
- dd C_MEM67 ; A2
- dd C_MEM67 ; A3
- dd 0 ; A4
- dd 0 ; A5
- dd 0 ; A6
- dd 0 ; A7
- dd C_DATA1 ; A8
- dd C_DATA66 ; A9
- dd 0 ; AA
- dd 0 ; AB
- dd 0 ; AC
- dd 0 ; AD
- dd 0 ; AE
- dd 0 ; AF
- dd C_DATA1 ; B0
- dd C_DATA1 ; B1
- dd C_DATA1 ; B2
- dd C_DATA1 ; B3
- dd C_DATA1 ; B4
- dd C_DATA1 ; B5
- dd C_DATA1 ; B6
- dd C_DATA1 ; B7
- dd C_DATA66 ; B8
- dd C_DATA66 ; B9
- dd C_DATA66 ; BA
- dd C_DATA66 ; BB
- dd C_DATA66 ; BC
- dd C_DATA66 ; BD
- dd C_DATA66 ; BE
- dd C_DATA66 ; BF
- dd C_MODRM+C_DATA1 ; C0
- dd C_MODRM+C_DATA1 ; C1
- dd C_DATA2 ; C2
- dd 0 ; C3
- dd C_MODRM ; C4
- dd C_MODRM ; C5
- dd C_MODRM+C_DATA1 ; C6
- dd C_MODRM+C_DATA66 ; C7
- dd C_DATA2+C_DATA1 ; C8
- dd 0 ; C9
- dd C_DATA2 ; CA
- dd 0 ; CB
- dd 0 ; CC
- dd 0 ; CD
- dd 0 ; CE
- dd 0 ; CF
- dd C_MODRM ; D0
- dd C_MODRM ; D1
- dd C_MODRM ; D2
- dd C_MODRM ; D3
- dd C_DATA1 ; D4
- dd C_DATA1 ; D5
- dd 0 ; D6
- dd 0 ; D7
- dd C_MODRM ; D8
- dd C_MODRM ; D9
- dd C_MODRM ; DA
- dd C_MODRM ; DB
- dd C_MODRM ; DC
- dd C_MODRM ; DD
- dd C_MODRM ; DE
- dd C_MODRM ; DF
- dd C_DATA1 ; E0
- dd C_DATA1 ; E1
- dd C_DATA1 ; E2
- dd C_DATA1 ; E3
- dd C_DATA1 ; E4
- dd C_DATA1 ; E5
- dd C_DATA1 ; E6
- dd C_DATA1 ; E7
- dd C_DATA66 ; E8
- dd C_DATA66 ; E9
- dd C_DATA66+C_MEM2 ; EA
- dd C_DATA1 ; EB
- dd 0 ; EC
- dd 0 ; ED
- dd 0 ; EE
- dd 0 ; EF
- dd C_PREFIX ; F0
- dd 0 ; F1
- dd C_PREFIX ; F2
- dd C_PREFIX ; F3
- dd 0 ; F4
- dd 0 ; F5
- dd 0 ; F6
- dd 0 ; F7
- dd 0 ; F8
- dd 0 ; F9
- dd 0 ; FA
- dd 0 ; FB
- dd 0 ; FC
- dd 0 ; FD
- dd C_MODRM ; FE
- dd C_MODRM ; FF
- table_0F label dword ; 0F-prefixed instructions
- dd C_MODRM ; 00
- dd C_MODRM ; 01
- dd C_MODRM ; 02
- dd C_MODRM ; 03
- dd -1 ; 04
- dd -1 ; 05
- dd 0 ; 06
- dd -1 ; 07
- dd 0 ; 08
- dd 0 ; 09
- dd 0 ; 0A
- dd 0 ; 0B
- dd -1 ; 0C
- dd -1 ; 0D
- dd -1 ; 0E
- dd -1 ; 0F
- dd -1 ; 10
- dd -1 ; 11
- dd -1 ; 12
- dd -1 ; 13
- dd -1 ; 14
- dd -1 ; 15
- dd -1 ; 16
- dd -1 ; 17
- dd -1 ; 18
- dd -1 ; 19
- dd -1 ; 1A
- dd -1 ; 1B
- dd -1 ; 1C
- dd -1 ; 1D
- dd -1 ; 1E
- dd -1 ; 1F
- dd -1 ; 20
- dd -1 ; 21
- dd -1 ; 22
- dd -1 ; 23
- dd -1 ; 24
- dd -1 ; 25
- dd -1 ; 26
- dd -1 ; 27
- dd -1 ; 28
- dd -1 ; 29
- dd -1 ; 2A
- dd -1 ; 2B
- dd -1 ; 2C
- dd -1 ; 2D
- dd -1 ; 2E
- dd -1 ; 2F
- dd -1 ; 30
- dd -1 ; 31
- dd -1 ; 32
- dd -1 ; 33
- dd -1 ; 34
- dd -1 ; 35
- dd -1 ; 36
- dd -1 ; 37
- dd -1 ; 38
- dd -1 ; 39
- dd -1 ; 3A
- dd -1 ; 3B
- dd -1 ; 3C
- dd -1 ; 3D
- dd -1 ; 3E
- dd -1 ; 3F
- dd -1 ; 40
- dd -1 ; 41
- dd -1 ; 42
- dd -1 ; 43
- dd -1 ; 44
- dd -1 ; 45
- dd -1 ; 46
- dd -1 ; 47
- dd -1 ; 48
- dd -1 ; 49
- dd -1 ; 4A
- dd -1 ; 4B
- dd -1 ; 4C
- dd -1 ; 4D
- dd -1 ; 4E
- dd -1 ; 4F
- dd -1 ; 50
- dd -1 ; 51
- dd -1 ; 52
- dd -1 ; 53
- dd -1 ; 54
- dd -1 ; 55
- dd -1 ; 56
- dd -1 ; 57
- dd -1 ; 58
- dd -1 ; 59
- dd -1 ; 5A
- dd -1 ; 5B
- dd -1 ; 5C
- dd -1 ; 5D
- dd -1 ; 5E
- dd -1 ; 5F
- dd -1 ; 60
- dd -1 ; 61
- dd -1 ; 62
- dd -1 ; 63
- dd -1 ; 64
- dd -1 ; 65
- dd -1 ; 66
- dd -1 ; 67
- dd -1 ; 68
- dd -1 ; 69
- dd -1 ; 6A
- dd -1 ; 6B
- dd -1 ; 6C
- dd -1 ; 6D
- dd -1 ; 6E
- dd -1 ; 6F
- dd -1 ; 70
- dd -1 ; 71
- dd -1 ; 72
- dd -1 ; 73
- dd -1 ; 74
- dd -1 ; 75
- dd -1 ; 76
- dd -1 ; 77
- dd -1 ; 78
- dd -1 ; 79
- dd -1 ; 7A
- dd -1 ; 7B
- dd -1 ; 7C
- dd -1 ; 7D
- dd -1 ; 7E
- dd -1 ; 7F
- dd C_DATA66 ; 80
- dd C_DATA66 ; 81
- dd C_DATA66 ; 82
- dd C_DATA66 ; 83
- dd C_DATA66 ; 84
- dd C_DATA66 ; 85
- dd C_DATA66 ; 86
- dd C_DATA66 ; 87
- dd C_DATA66 ; 88
- dd C_DATA66 ; 89
- dd C_DATA66 ; 8A
- dd C_DATA66 ; 8B
- dd C_DATA66 ; 8C
- dd C_DATA66 ; 8D
- dd C_DATA66 ; 8E
- dd C_DATA66 ; 8F
- dd C_MODRM ; 90
- dd C_MODRM ; 91
- dd C_MODRM ; 92
- dd C_MODRM ; 93
- dd C_MODRM ; 94
- dd C_MODRM ; 95
- dd C_MODRM ; 96
- dd C_MODRM ; 97
- dd C_MODRM ; 98
- dd C_MODRM ; 99
- dd C_MODRM ; 9A
- dd C_MODRM ; 9B
- dd C_MODRM ; 9C
- dd C_MODRM ; 9D
- dd C_MODRM ; 9E
- dd C_MODRM ; 9F
- dd 0 ; A0
- dd 0 ; A1
- dd 0 ; A2
- dd C_MODRM ; A3
- dd C_MODRM+C_DATA1 ; A4
- dd C_MODRM ; A5
- dd -1 ; A6
- dd -1 ; A7
- dd 0 ; A8
- dd 0 ; A9
- dd 0 ; AA
- dd C_MODRM ; AB
- dd C_MODRM+C_DATA1 ; AC
- dd C_MODRM ; AD
- dd -1 ; AE
- dd C_MODRM ; AF
- dd C_MODRM ; B0
- dd C_MODRM ; B1
- dd C_MODRM ; B2
- dd C_MODRM ; B3
- dd C_MODRM ; B4
- dd C_MODRM ; B5
- dd C_MODRM ; B6
- dd C_MODRM ; B7
- dd -1 ; B8
- dd -1 ; B9
- dd C_MODRM+C_DATA1 ; BA
- dd C_MODRM ; BB
- dd C_MODRM ; BC
- dd C_MODRM ; BD
- dd C_MODRM ; BE
- dd C_MODRM ; BF
- dd C_MODRM ; C0
- dd C_MODRM ; C1
- dd -1 ; C2
- dd -1 ; C3
- dd -1 ; C4
- dd -1 ; C5
- dd -1 ; C6
- dd -1 ; C7
- dd 0 ; C8
- dd 0 ; C9
- dd 0 ; CA
- dd 0 ; CB
- dd 0 ; CC
- dd 0 ; CD
- dd 0 ; CE
- dd 0 ; CF
- dd -1 ; D0
- dd -1 ; D1
- dd -1 ; D2
- dd -1 ; D3
- dd -1 ; D4
- dd -1 ; D5
- dd -1 ; D6
- dd -1 ; D7
- dd -1 ; D8
- dd -1 ; D9
- dd -1 ; DA
- dd -1 ; DB
- dd -1 ; DC
- dd -1 ; DD
- dd -1 ; DE
- dd -1 ; DF
- dd -1 ; E0
- dd -1 ; E1
- dd -1 ; E2
- dd -1 ; E3
- dd -1 ; E4
- dd -1 ; E5
- dd -1 ; E6
- dd -1 ; E7
- dd -1 ; E8
- dd -1 ; E9
- dd -1 ; EA
- dd -1 ; EB
- dd -1 ; EC
- dd -1 ; ED
- dd -1 ; EE
- dd -1 ; EF
- dd -1 ; F0
- dd -1 ; F1
- dd -1 ; F2
- dd -1 ; F3
- dd -1 ; F4
- dd -1 ; F5
- dd -1 ; F6
- dd -1 ; F7
- dd -1 ; F8
- dd -1 ; F9
- dd -1 ; FA
- dd -1 ; FB
- dd -1 ; FC
- dd -1 ; FD
- dd -1 ; FE
- dd -1 ; FF
- end
- Now we are able to get instruction length on arbitrary address. We will
- repeat this call until 5 bytes are read. After this we will copy these bytes
- to old_hook. We know how long are first instructions, so we can fill out the
- relative jump address on the next instruction in original function.
- .386p
- .model flat, stdcall
- ...
- .data
- kernel_name db "kernel32.dll",0
- sleep_name db "Sleep",0
- ...
- MEM_RELEASE dd 000008000h
- ;16 nops + one relative jump
- old_sleep db 090h,090h,090h,090h,090h,090h,090h,090h,
- 090h,090h,090h,090h,090h,090h,090h,090h,
- 0E9h,000h,000h,000h,000h
- .code
- start:
- push 5000
- call Sleep
- do_hook:
- push offset kernel_name
- call GetModuleHandleA
- push offset sleep_name
- push eax
- call GetProcAddress
- push eax
- mov esi,eax
- xor ecx,ecx
- mov ebx,esi
- get_five_bytes:
- push ecx
- push ebx
- call get_instr_len ;calling LDE32
- pop ecx
- add ecx,eax
- add ebx,eax
- cmp ecx,5
- jb get_five_bytes
- mov edi,offset old_sleep ;counting relative jump address
- mov [edi+011h],ebx
- sub [edi+011h],edi
- sub dword ptr [edi+011h],015h
- rep movsb
- pop edi
- ;following code was above, so without comments
- push PAGE_READWRITE
- push MEM_COMMIT
- push MEMORY_BASIC_INFORMATION_SIZE
- push 0
- call VirtualAlloc
- test eax,eax
- jz do_sleep
- mov esi,eax
- push MEMORY_BASIC_INFORMATION_SIZE
- push esi
- push edi
- call VirtualQuery
- test eax,eax
- jz free_mem
- call GetCurrentProcess
- push 5
- push edi
- push eax
- call FlushInstructionCache
- lea eax,[esi+014h]
- push eax
- push PAGE_EXECUTE_READWRITE
- lea eax,[esi+00Ch]
- push [eax]
- push [esi]
- call VirtualProtect
- test eax,eax
- jz free_mem
- mov byte ptr [edi],0E9h
- mov eax,offset new_sleep
- sub eax,edi
- sub eax,5
- inc edi
- stosd
- push offset old_protect
- lea eax,[esi+014h]
- push [eax]
- lea eax,[esi+00Ch]
- push [eax]
- push [esi]
- call VirtualProtect
- free_mem:
- push MEM_RELEASE
- push 0
- push esi
- call VirtualFree
- do_sleep:
- push 5000
- call Sleep
- push 0
- call ExitProcess
- new_sleep:
- mov eax,dword ptr [esp+004h]
- add eax,eax ;doubling timeout
- push eax
- mov eax,offset old_sleep ;calling old function
- call eax
- ret 004h
- After the hook it will look like this:
- 004010CC: 6888130000 push 000001388h
- 004010D1: E818090000 call Sleep
- Sleep: ;this is jump on address in IAT
- 004019EE: FF2514204000 jmp dword ptr [000402014h]
- tabulka:
- 00402014: 79 67 E8 77 6C 7D E8 77
- Kernel32.Sleep:
- 77E86779: E95FA95788 jmp 0004010DDh
- new_sleep:
- 004010DD: 8B442404 mov eax,dword ptr [esp+4]
- 004010E1: 03C0 add eax,eax
- 004010E3: 50 push eax
- 004010E4: B827304000 mov eax,000403027h
- 004010E9: FFD0 call eax
- old_sleep:
- 00403027: 6A00 push 0
- 00403029: FF742408 push dword ptr [esp+8]
- 0040302D: 90 nop
- 0040302E: 90 nop
- 0040302F: 90 nop
- 00403030: 90 nop
- 00403031: 90 nop
- 00403032: 90 nop
- 00403033: 90 nop
- 00403034: 90 nop
- 00403035: 90 nop
- 00403036: 90 nop
- 00403037: E94337A877 jmp Kernel32.77E8677F
- ;this instruction is placed 1 byte after first instruction at Kernel32.Sleep
- (77E86779)
- Kernel32.77E8677F:
- 77E8677F: E803000000 call Kernel32.SleepEx
- ... ;following is unimportant
- To make this clearer, this is how the original version of Kernel32.Sleep looks:
- Kernel32.Sleep:
- 77E86779: 6A00 push 0
- 77E8677B: FF742408 push dword ptr [esp+8]
- 77E8677F: E803000000 call Kernel32.SleepEx
- 77E86784: C20400 ret 00004h
- As you can see we copied first and second instruction (it was 6 bytes
- here) and the relative jump pointed on the next instruction and that is how
- it should be. We have to supposed here that relative jumps are not placed
- as the first bytes of functions. If there would be we've got a problem. Next
- problem is with APIs like ntdll.DbgBreakPoint. These are too short for this
- method of hooking. And forasmuch as it is called by Kernel32.DebugBreak, it is
- not hookable by changing IAT. But who want to hook function which does only
- the int 3 call? But nothing is impossible. You can think about it and you can
- find how to solve this. As I was thinking about this you can hook the following
- function after this one (it would be damaged by rewritng first 5 bytes of the
- previous function). Function DbgBreakPoint is 2 bytes long, so we can set some
- flags here and try to write conditional jump on the begining of the second
- function ... But this is not our problem now.
- With the problem of saving original function relates then unhooking.
- Unhooking is changing replaces bytes back to the original state. When rewriting
- IAT you will have to return original address to the table if you want to do
- unhooking. When using the five byte patch you will have to copy first original
- instructions back. Both ways are realy simple and no need to write more about
- this.
- =====[ 3.2.4 Other process hooking ]============================================
- Now we will do something practical with hooking during running. Who
- want to deal with hooking own process? That is good only for learning basics
- but it is not much practical.
- I'll show you three methods of other process hooking. Two of them use
- API CreateRemoteThread which is only in Windows with NT technology. The problem
- of hooking is not so interesing for older windows version for me. After all
- I will try to explain third method which I didn't practise, so it could be
- unfunctional.
- At first few about CreateRemoteThread. As the help says this function
- creates new thread in any process and runs its code.
- HANDLE CreateRemoteThread(
- HANDLE hProcess,
- LPSECURITY_ATTRIBUTES lpThreadAttributes,
- DWORD dwStackSize,
- LPTHREAD_START_ROUTINE lpStartAddress,
- LPVOID lpParameter,
- DWORD dwCreationFlags,
- LPDWORD lpThreadId
- );
- The handle hProcess can be get by OpenProcess. Here we have to have
- necessary rights. The pointer lpStartAddress points on memory place in TARGET
- process where the first instruction for new thread is. Because new thread is
- created in target process it is in memory of target process. The pointer
- lpParameter points on argument which will be refered to the new thread.
- =====[ 3.2.4.1 DLL Injection ]==================================================
- We are able to run new thread from any place in target process memory.
- This is useless unless we have own code in it. The first method cheats on this.
- It uses GetProcAddress to get actual address for LoadLibrary. Then routes
- lpStartAddress to the address of LoadLibrary. Function LoadLibary has only one
- parameter like the function for new thread in target process.
- HINSTANCE LoadLibrary(
- LPCTSTR lpLibFileName
- );
- We will use this similarity and we will refer the name of our DLL
- library as lpParameter. After running new thread lpParameter will be on a place
- of lpLibFileName. The most important thing here is behavior decribed above.
- After loading new module into target process memory the initialization part is
- executed. If we place specific functions which will hook functions we want to
- we will win this stuff. After execution of initialization part, the thread will
- have nothing to do and close but our module is still in memory. This method is
- realy nice and easy to implement. This is called DLL Injection. But if you are
- like I am, you don't like must of having DLL library. But if one doesn't care
- about having this library it is the easiest and the fastest method (from the
- programmers sight).
- =====[ 3.2.4.2 Independent code ]===============================================
- Going on the way of independent code is very difficult but also very
- impressive thing. Independent code is the code without any statical addresses.
- Everything is relative in it towards some specific place in itself. This code
- is mostly done if we don't know the address where this code will be executed.
- Sure, it is possible to get this address and then to relink our code so as it
- will behave on the new address without errors but this is even harder than
- coding independent code. Example of this kind of code can be the virus code.
- The virus which infects executables in the way it adds itself somewhere into
- this executable. In different executables will be the virus code on different
- places depended e.g. on file structure on length.
- At first we have to insert our code into target process. Then function
- CreateRemoteThread will take care of running our code. So, at first we have to
- get some information about target process and get handle with OpenProcess.
- Then VirtualAllocEx will alloc some space in remote process memory for our
- code. Finally we will use WriteProcessMemory to write our code on allocated
- memory and run it. In CreateRemoteThread lpStartAddress will refer to allocated
- memory and lpParameter can be whatever we want. Because I realy don't like any
- unnecesarry files I use this method.
- =====[ 3.2.4.3 Raw change ]=====================================================
- There is not CreateRemoteThread in older windows version (without NT).
- So we can't use this for hooking. There are probably other and better methods
- how to hook than the method I will talk about now. In fact I don't know if this
- will work in practice (one never know when use Windows) but theoretically is
- everything ok.
- We don't need to have our code in target process to hook its functions
- at all. We have function WriteProcessMemory (this should be in all Windows
- version) and we have OpenProcess, too. Last thing we need is VirtualProtectEx
- which can change access to memory pages in target process. I can't see any
- reason why not to hook target process functions directly from our process...
- =====[ 4. Ending ]==============================================================
- This small document ends. I will greet any extension which will
- describe unmentionde methods of hook, I am sure there are a lot of them. I will
- also greet any extensions in the parts which were not described so intimately.
- You can also send me some source codes if they are fecund for the problem of
- hooking e.g. for parts where I was lazy to write the code. The goal of this
- document is to show deatails of every technics of hooking. I hope I've done
- the part of this.
- Special thanks to Z0MBiE for his work, so I haven't to code it myself
- and spend ages by studying tables for getting instruction length.
- ===================================[ End ]======================================
Advertisement
Add Comment
Please, Sign In to add comment