===========================[ 挂钩Windows API ]==================
SoBeIt
Author: Holy_Father <holy_father@phreaker.net> Version: 1.1 english Date: 6.10.2002
=====[ 1. 内容 ]=============================================
1. 内容2. 介绍3. 挂钩方法 3.1 运行前挂钩 3.2 运行时挂钩 3.2.1 使用IAT挂钩本进程 3.2.2 改写入口点挂钩本进程 3.2.3 保存原始函数 3.2.4 挂钩其它进程 3.2.4.1 DLL注入 3.2.4.2 独立的代码 3.2.4.3 原始修改4. 结束语
=====[ 2. 介绍 ]====================================================
这篇文章是有关在OS Windows下挂钩API函数的方法。所有例子都在基于NT技术的Windows版本NT 4.0及以上有效(Windows NT 4.0, Windows 2000, Windows XP)。可能在其它Windows系统也会有效。 你应该比较熟悉Windows下的进程、汇编器、PE文件结构和一些API函数,才能明白这篇文章里的内容。 这里使用"Hooking API"这个术语表示对API的完全修改。当调用被挂钩的API时,我们的代码能立刻被执行。我将写下完全的挂钩过程。
=====[ 3. 挂钩方法 ]==============================================
一般来说我们的目的是用我们的代码取代一些函数里的代码。这些问题有时可以在进程运行前解决。这些大多数时候可以用我们运行的用户级进程来完成,目的可以是修改程序的行为。举个例子应用程序的破解,比方说有些程序会在启动时需要原光盘,我们想要不用光盘就启动它。如果我们修改获取驱动类型的函数我们就可以让程序从硬盘启动。 当我们挂钩系统进程时(比如说服务)这些不可能做到或者我们不打算这么做,或者在这个例子里我们不知道哪个进程才是目标。这时我们就要用到动态挂钩(在运行时挂钩)的技术。使用的例子有rootkit或者病毒里的反杀毒软件的技术。
=====[ 3.1 运行前挂钩 ]===========================================
这里修改我们想要修改函数来自的物理模块(大多数时候是.exe或.dll)。在这里我们至少有3种可能的做法。 第一种可能是找到函数的入口点然后重写它的代码。这会因为函数的大小而受限制,但我们能动态加载其它一些模块(API LoadLibrary),所以应该足够了。 内核函数(kernel32.dll)是通用的因为Windows中每个进程都有这个模块的拷贝。另一个好处是如果我们知道哪些模块在某版本中会修改,我们可以在一些API如LoadLibraryA中使用直接的指针。这是因为kernel模块在内存中地址在相同Windows版本中是固定的。我们同样也能用动态加载的模块的作用。在这里它的初始化部分在加载进内存后立刻就运行。在新模块的初始化部分我们不受限制。 第二种可能是在模块中被代替的函数只是原函数的扩展。然后我们选择要么修改开始的5个字节为跳转指令或者改写IAT。如果改为跳转指令,那么将会改变指令执行流程转为执行我们的代码。如果调用了IAT记录被修改的函数,我们的代码能在调用结束后被执行。但模块的扩展没那么容易,因为我们必须注意DLL首部。 下一个是修改整个模块。这意味着我们创建自己的模块版本,它能够加载原始的模块并调用原始的函数,当然我们对这个不感兴趣,但重要的函数都是被更新的。这种方法对于有的模块过大有几百个导出函数的很不方便。
=====[ 3.2 运行时挂钩 ]==========================================
在运行前挂钩通常都非常特殊,并且是在内部面向具体的应用程序(或模块)。如果我们更换了kernel32.dll或ntdll.dll里的函数(只在NT操作系统里),我们就能完美地做到在所有将要运行的进程中替换这个函数。但说来容易做起来却非常难,因为我们不但得考虑精确性和需要编写比较完善的新函数或新模块,但主要问题是只有将要运行的进程才能被挂钩(要挂钩所有进程只能重启电脑)。另一个问题是如何进入这些文件,因为NT操作系统保护了它们。比较好的解决方法在进程正在运行时挂钩。这需要更多的有关知识,但最后的结果相当不错。在运行中挂钩只对能够写入它们的内存的进程能成功。为了能写入它自己我们使用API函数WriteProcessMemory。现在我们开始运行中挂钩我们的进程。
=====[ 3.2.1 使用IAT挂钩本进程 ]===================================
这里有很多种可能性。首先介绍如何用改写IAT挂钩函数的方法。接下来这张图描述了PE文件的结构:
+-------------------------------+ - offset 0 | MS DOS标志("MZ") 和 DOS块 | +-------------------------------+ | PE 标志 ("PE") | +-------------------------------+ | .text | - 模块代码 | 程序代码 | | | +-------------------------------+ | .data | - 已初始化的(全局静态)数据 | 已初始化的数据 | | | +-------------------------------+ | .idata | - 导入函数的信息和数据 | 导入表 | | | +-------------------------------+ | .edata | - 导出函数的信息和数据 | 导出表 | | | +-------------------------------+ | 调试符号 | +-------------------------------+
这里对我们比较重要的是.idata部分的导入地址表(IAT)。这个部分包含了导入的相关信息和导入函数的地址。有一点很重要的是我们必须知道PE文件是如何创建的。当在编程语言里间接调用任意API(这意味着我们是用函数的名字来调用它,而不是用它的地址),编译器并不直接把调用连接到模块,而是用jmp指令连接调用到IAT,IAT在系统把进程调入内存时时会由进程载入器填满。这就是我们可以在两个不同版本的Windows里使用相同的二进制代码的原因,虽然模块可能会加载到不同的地址。进程载入器会在程序代码里调用所使用的IAT里填入直接跳转的jmp指令。所以我们能在IAT里找到我们想要挂钩的指定函数,我们就能很容易改变那里的jmp指令并重定向代码到我们的地址。完成之后每次调用都会执行我们的代码了。这种方法的缺点是经常有很多函数要被挂钩(比方说如果我们要在搜索文件的API中改变程序的行为我们就得修改函数FindFirstFile和FindNextFile,但我们要知道这些函数都有ANSI和WIDE版本,所以我们不得不修改FindFirstFileA、FindFirstFileW、FindNextFileA和FileNextFileW的IAT地址。但还有其它类似的函数如FindFirstFileExA和它的WIDE版本FindFirstFileExW,也都是由前面提到的函数调用的。我们知道FindFirstFileW调用FindFirstFileExW,但这是直接调用,而不是使用IAT。再比如说ShellAPI的函数SHGetDesktopFolder也会直接调用FindFirstFilwW或FindFirstFileExW)。如果我们能获得它们所有,结果就会很完美。 我们通过使用imagehlp.dll里的ImageDirectoryEntryToData来很容易地找到IAT。
PVOID ImageDirectoryEntryToData( IN LPVOID Base, IN BOOLEAN MappedAsImage, IN USHORT DirectoryEntry, OUT PULONG Size );
在这里Base参数可以用我们程序的Instance(Instance通过调用GetModuleHandle获得):
hInstance = GetModuleHandleA(NULL);
DirectoryEntry我们可以使用恒量IMAGE_DIRECTORY_ENTRY_IMPORT。
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1
函数的结果是指向第一个IAT记录指针。IAT的所有记录是由IMAGE_IMPORT_DESCRIPTOR定义的结构。所以函数结果是指向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;
IMAGE_IMPORT_DESCRIPTOR里的Name成员变量是模块名字的指针。如果我们想要挂钩某个函数比如是来自kernel32.dll我们就在导入表里找属于名字kernel32.dll的描述符号。我们先调用ImageDirectoryEntryToData然后找到名字是"kernel32.dll"的描述符号(可能不只一个描述符号是这个名字),最后我们在这个模块的记录里所有函数的列表里找到我们想要的函数(函数地址通过GetProcAddress函数获得)。如果我们找到了就必须用VirtualProtect函数来改变内存页面的保护属性,然后就可以在内存中的这些部分写入代码了。在改写了地址之后我们要把保护属性改回来。在调用VirtualProtect之前我们还要先知道有关页面的信息,这通过VirtualQuery来实现。我们可以加入一些测试以防某些函数会失败(比方说如果第一次调用VirtualProctect就失败了,我们就没办法继续)。
PCSTR pszHookModName = "kernel32.dll",pszSleepName = "Sleep"; HMODULE hKernel = GetModuleHandle(pszHookModName); PROC pfnNew = (PROC)0x12345678, //这里存放新地址 pfnHookAPIAddr = GetProcAddress(hKernel,pszSleepName);
ULONG ulSize; PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData( hKernel, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize );
while (pImportDesc->Name) { PSTR pszModName = (PSTR)((PBYTE) hKernel + pImportDesc->Name); if (stricmp(pszModName, pszHookModName) == 0) break; pImportDesc++; }
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)((PBYTE) hKernel + 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++; }
调用Sleep(1000)的结果如例子所示:
00407BD8: 68E8030000 push 0000003E8h 00407BDD: E812FAFFFF call Sleep
Sleep: ;这是跳转到IAT里的地址 004075F4: FF25BCA14000 jmp dword ptr [00040A1BCh]
原始表: 0040A1BC: 79 67 E8 77 00 00 00 00 新表: 0040A1BC: 78 56 34 12 00 00 00 00
所以最后会跳转到0x12345678。
=====[ 3.2.2 改写入口点挂钩本进程 ]==================
改写函数入口点开始的一些字节这种方法相当简单。就象改变IAT里的地址一样,我们也要先修改页面属性。在这里对我们想要挂钩的函数是一开始的5个字节。为了之后的使用我们用动态分配MEMORY_BASIC_INFORMATION结构。函数的起始地址也是用GetProcAddress来获得。我们在这个地址里插入指向我们代码的跳转指令。接下来程序调用Sleep(5000)(所以它会等待5秒钟),然后Sleep函数被挂钩并重定向到new_sleep,最后它再次调用Sleep(5000)。因为新的函数new_sleep什么都不做并直接返回,所以整个程序只需要5秒钟而不是10秒种。
.386p.model flat, stdcall
includelib lib/kernel32.libSleep PROTO :DWORDGetModuleHandleA PROTO :DWORDGetProcAddress PROTO :DWORD,:DWORDVirtualQuery PROTO :DWORD,:DWORD,:DWORDVirtualProtect PROTO :DWORD,:DWORD,:DWORD,:DWORDVirtualAlloc PROTO :DWORD,:DWORD,:DWORD,:DWORDVirtualFree PROTO :DWORD,:DWORD,:DWORDFlushInstructionCache PROTO :DWORD,:DWORD,:DWORDGetCurrentProcess PROTOExitProcess PROTO :DWORD
.data
kernel_name db "kernel32.dll",0sleep_name db "Sleep",0old_protect dd ?
MEMORY_BASIC_INFORMATION_SIZE equ 28
PAGE_READWRITE dd 000000004hPAGE_EXECUTE_READWRITE dd 000000040hMEM_COMMIT dd 000001000hMEM_RELEASE dd 000008000h
.codestart: push 5000 call Sleep
do_hook: push offset kernel_name call GetModuleHandleA push offset sleep_name push eax call GetProcAddress mov edi,eax ;最后获得Sleep地址
push PAGE_READWRITE push MEM_COMMIT push MEMORY_BASIC_INFORMATION_SIZE push 0 call VirtualAlloc test eax,eax jz do_sleep mov esi,eax ;为MBI结构分配内存
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 ExitProcessnew_sleep: ret 004hend start
第二次调用Sleep的结果是这样:
004010A4: 6888130000 push 000001388h 004010A9: E80A000000 call Sleep
Sleep: ;这里是跳转到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 保存原始函数 ]=====================================
更多时候我们需要的不仅仅是挂钩函数。比方说也许我们并不想取代给定的函数而只是想检查一下它的结果,或者也许我们只是想在函数被使用特定的参数来调用时才取代原函数。比较好的例子有前面提过的通过取代FindXXXFile函数来完成隐藏文件。所以如果我们想要隐藏指定的文件并且不想被注意的话,就得对其它所有文件只调用没有被修改过的原始函数。这对使用修改IAT的方法时是很简单的,为调用原始函数我们可以用GetProcAddress获得它的原始地址,然后直接调用。但修改入口点的方法就会有问题,因为修改了函数入口点的5个字节,使我们破坏了原函数。所以我们必须保存开始的那些指令。这将用到以下的技术。 我们知道我们要修改开始的5个字节但不知道里面包含多少条指令以及指令的长度。我们得为开始那些指令保留足够的内存空间。16个字节应该足够了,因为函数开始时通常没有多长的指令,很可能根本就用不到16个字节。整个被保留的内存用0x90(0x90=nop)来填满。下一个5个字节预留给将在之后填入的跳转指令。
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
现在我们已准备好拷贝开始的指令。为获得指令长度的代码相当麻烦,这就是我们得使用已完成的引擎的原因。它是由Z0MBiE写的。传入参数是我们要获得长度的指令的地址。输出参数在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 simultaneouslyC_MEM4 equ 0004h ; |C_DATA1 equ 0100h ; |C_DATA2 equ 0200h ; |may be used simultaneouslyC_DATA4 equ 0400h ; |C_67 equ 0010h ; used with C_PREFIXC_MEM67 equ 0020h ; C_67 ? C_MEM2 : C_MEM4C_66 equ 1000h ; used with C_PREFIXC_DATA66 equ 2000h ; C_66 ? C_DATA2 : C_DATA4C_PREFIX equ 0008h ; prefix. take opcode againC_MODRM equ 4000h ; MODxxxR/MC_DATAW0 equ 8000h ; opc&1 ? C_DATA66 : C_DATA1
p386 model flat locals @@
.code
public disasm_mainpublic _disasm_mainpublic @disasm_mainpublic DISASM_MAIN
disasm_main:_disasm_main:@disasm_main:DISASM_MAIN:
; __fastcall EAX; __cdecl [ESP+4]
;这是我的第一处修改,它只是这个函数的声明get_instr_len:
mov ecx, [esp+4] ; ECX = opcode ptr
xor edx, edx ; 标志 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
;这里是我的第二处修改,只有在原始版本这里是retn@@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 -- 在代码中分析,不需要标志(也就是标志(flag)必须为0);F6,F7 -- --//-- (ttt=000 -- 3 字节, 否则为2字节);CD -- --//-- (如果为 CD 20 为6字节, 否则为2字节)
table_1 label dword ; 一般的指令
dd C_MODRM ; 00dd C_MODRM ; 01dd C_MODRM ; 02dd C_MODRM ; 03dd C_DATAW0 ; 04dd C_DATAW0 ; 05dd 0 ; 06dd 0 ; 07dd C_MODRM ; 08dd C_MODRM ; 09dd C_MODRM ; 0Add C_MODRM ; 0Bdd C_DATAW0 ; 0Cdd C_DATAW0 ; 0Ddd 0 ; 0Edd 0 ; 0Fdd C_MODRM ; 10dd C_MODRM ; 11dd C_MODRM ; 12dd C_MODRM ; 13dd C_DATAW0 ; 14dd C_DATAW0 ; 15dd 0 ; 16dd 0 ; 17dd C_MODRM ; 18dd C_MODRM ; 19dd C_MODRM ; 1Add C_MODRM ; 1Bdd C_DATAW0 ; 1Cdd C_DATAW0 ; 1Ddd 0 ; 1Edd 0 ; 1Fdd C_MODRM ; 20dd C_MODRM ; 21dd C_MODRM ; 22dd C_MODRM ; 23dd C_DATAW0 ; 24dd C_DATAW0 ; 25dd C_PREFIX ; 26dd 0 ; 27dd C_MODRM ; 28dd C_MODRM ; 29dd C_MODRM ; 2Add C_MODRM ; 2Bdd C_DATAW0 ; 2Cdd C_DATAW0 ; 2Ddd C_PREFIX ; 2Edd 0 ; 2Fdd C_MODRM ; 30dd C_MODRM ; 31dd C_MODRM ; 32dd C_MODRM ; 33dd C_DATAW0 ; 34dd C_DATAW0 ; 35dd C_PREFIX ; 36dd 0 ; 37dd C_MODRM ; 38dd C_MODRM ; 39dd C_MODRM ; 3Add C_MODRM ; 3Bdd C_DATAW0 ; 3Cdd C_DATAW0 ; 3Ddd C_PREFIX ; 3Edd 0 ; 3Fdd 0 ; 40dd 0 ; 41dd 0 ; 42dd 0 ; 43dd 0 ; 44dd 0 ; 45dd 0 ; 46dd 0 ; 47dd 0 ; 48dd 0 ; 49dd 0 ; 4Add 0 ; 4Bdd 0 ; 4Cdd 0 ; 4Ddd 0 ; 4Edd 0 ; 4Fdd 0 ; 50dd 0 ; 51dd 0 ; 52dd 0 ; 53dd 0 ; 54dd 0 ; 55dd 0 ; 56dd 0 ; 57dd 0 ; 58dd 0 ; 59dd 0 ; 5Add 0 ; 5Bdd 0 ; 5Cdd 0 ; 5Ddd 0 ; 5Edd 0 ; 5Fdd 0 ; 60dd 0 ; 61dd C_MODRM ; 62dd C_MODRM ; 63dd C_PREFIX ; 64dd C_PREFIX ; 65dd C_PREFIX+C_66 ; 66dd C_PREFIX+C_67 ; 67dd C_DATA66 ; 68dd C_MODRM+C_DATA66 ; 69dd C_DATA1 ; 6Add C_MODRM+C_DATA1 ; 6Bdd 0 ; 6Cdd 0 ; 6Ddd 0 ; 6Edd 0 ; 6Fdd C_DATA1 ; 70dd C_DATA1 ; 71dd C_DATA1 ; 72dd C_DATA1 ; 73dd C_DATA1 ; 74dd C_DATA1 ; 75dd C_DATA1 ; 76dd C_DATA1 ; 77dd C_DATA1 ; 78dd C_DATA1 ; 79dd C_DATA1 ; 7Add C_DATA1 ; 7Bdd C_DATA1 ; 7Cdd C_DATA1 ; 7Ddd C_DATA1 ; 7Edd C_DATA1 ; 7Fdd C_MODRM+C_DATA1 ; 80dd C_MODRM+C_DATA66 ; 81dd C_MODRM+C_DATA1 ; 82dd C_MODRM+C_DATA1 ; 83dd C_MODRM ; 84dd C_MODRM ; 85dd C_MODRM ; 86dd C_MODRM ; 87dd C_MODRM ; 88dd C_MODRM ; 89dd C_MODRM ; 8Add C_MODRM ; 8Bdd C_MODRM ; 8Cdd C_MODRM ; 8Ddd C_MODRM ; 8Edd C_MODRM ; 8Fdd 0 ; 90dd 0 ; 91dd 0 ; 92dd 0 ; 93dd 0 ; 94dd 0 ; 95dd 0 ; 96dd 0 ; 97dd 0 ; 98dd 0 ; 99dd C_DATA66+C_MEM2 ; 9Add 0 ; 9Bdd 0 ; 9Cdd 0 ; 9Ddd 0 ; 9Edd 0 ; 9Fdd C_MEM67 ; A0dd C_MEM67 ; A1dd C_MEM67 ; A2dd C_MEM67 ; A3dd 0 ; A4dd 0 ; A5dd 0 ; A6dd 0 ; A7dd C_DATA1 ; A8dd C_DATA66 ; A9dd 0 ; AAdd 0 ; ABdd 0 ; ACdd 0 ; ADdd 0 ; AEdd 0 ; AFdd C_DATA1 ; B0dd C_DATA1 ; B1dd C_DATA1 ; B2dd C_DATA1 ; B3dd C_DATA1 ; B4dd C_DATA1 ; B5dd C_DATA1 ; B6dd C_DATA1 ; B7dd C_DATA66 ; B8dd C_DATA66 ; B9dd C_DATA66 ; BAdd C_DATA66 ; BBdd C_DATA66 ; BCdd C_DATA66 ; BDdd C_DATA66 ; BEdd C_DATA66 ; BFdd C_MODRM+C_DATA1 ; C0dd C_MODRM+C_DATA1 ; C1dd C_DATA2 ; C2dd 0 ; C3dd C_MODRM ; C4dd C_MODRM ; C5dd C_MODRM+C_DATA1 ; C6dd C_MODRM+C_DATA66 ; C7dd C_DATA2+C_DATA1 ; C8dd 0 ; C9dd C_DATA2 ; CAdd 0 ; CBdd 0 ; CCdd 0 ; CDdd 0 ; CEdd 0 ; CFdd C_MODRM ; D0dd C_MODRM ; D1dd C_MODRM ; D2dd C_MODRM ; D3dd C_DATA1 ; D4dd C_DATA1 ; D5dd 0 ; D6dd 0 ; D7dd C_MODRM ; D8dd C_MODRM ; D9dd C_MODRM ; DAdd C_MODRM ; DBdd C_MODRM ; DCdd C_MODRM ; DDdd C_MODRM ; DEdd C_MODRM ; DFdd C_DATA1 ; E0dd C_DATA1 ; E1dd C_DATA1 ; E2dd C_DATA1 ; E3dd C_DATA1 ; E4dd C_DATA1 ; E5dd C_DATA1 ; E6dd C_DATA1 ; E7dd C_DATA66 ; E8dd C_DATA66 ; E9dd C_DATA66+C_MEM2 ; EAdd C_DATA1 ; EBdd 0 ; ECdd 0 ; EDdd 0 ; EEdd 0 ; EFdd C_PREFIX ; F0dd 0 ; F1dd C_PREFIX ; F2dd C_PREFIX ; F3dd 0 ; F4dd 0 ; F5dd 0 ; F6dd 0 ; F7dd 0 ; F8dd 0 ; F9dd 0 ; FAdd 0 ; FBdd 0 ; FCdd 0 ; FDdd C_MODRM ; FEdd C_MODRM ; FF
table_0F label dword ; 0F为前缀的指令
dd C_MODRM ; 00dd C_MODRM ; 01dd C_MODRM ; 02dd C_MODRM ; 03dd -1 ; 04dd -1 ; 05dd 0 ; 06dd -1 ; 07dd 0 ; 08dd 0 ; 09dd 0 ; 0Add 0 ; 0Bdd -1 ; 0Cdd -1 ; 0Ddd -1 ; 0Edd -1 ; 0Fdd -1 ; 10dd -1 ; 11dd -1 ; 12dd -1 ; 13dd -1 ; 14dd -1 ; 15dd -1 ; 16dd -1 ; 17dd -1 ; 18dd -1 ; 19dd -1 ; 1Add -1 ; 1Bdd -1 ; 1Cdd -1 ; 1Ddd -1 ; 1Edd -1 ; 1Fdd -1 ; 20dd -1 ; 21dd -1 ; 22dd -1 ; 23dd -1 ; 24dd -1 ; 25dd -1 ; 26dd -1 ; 27dd -1 ; 28dd -1 ; 29dd -1 ; 2Add -1 ; 2Bdd -1 ; 2Cdd -1 ; 2Ddd -1 ; 2Edd -1 ; 2Fdd -1 ; 30dd -1 ; 31dd -1 ; 32dd -1 ; 33dd -1 ; 34dd -1 ; 35dd -1 ; 36dd -1 ; 37dd -1 ; 38dd -1 ; 39dd -1 ; 3Add -1 ; 3Bdd -1 ; 3Cdd -1 ; 3Ddd -1 ; 3Edd -1 ; 3Fdd -1 ; 40dd -1 ; 41dd -1 ; 42dd -1 ; 43dd -1 ; 44dd -1 ; 45dd -1 ; 46dd -1 ; 47dd -1 ; 48dd -1 ; 49dd -1 ; 4Add -1 ; 4Bdd -1 ; 4Cdd -1 ; 4Ddd -1 ; 4Edd -1 ; 4Fdd -1 ; 50dd -1 ; 51dd -1 ; 52dd -1 ; 53dd -1 ; 54dd -1 ; 55dd -1 ; 56dd -1 ; 57dd -1 ; 58dd -1 ; 59dd -1 ; 5Add -1 ; 5Bdd -1 ; 5Cdd -1 ; 5Ddd -1 ; 5Edd -1 ; 5Fdd -1 ; 60dd -1 ; 61dd -1 ; 62dd -1 ; 63dd -1 ; 64dd -1 ; 65dd -1 ; 66dd -1 ; 67dd -1 ; 68dd -1 ; 69dd -1 ; 6Add -1 ; 6Bdd -1 ; 6Cdd -1 ; 6Ddd -1 ; 6Edd -1 ; 6Fdd -1 ; 70dd -1 ; 71dd -1 ; 72dd -1 ; 73dd -1 ; 74dd -1 ; 75dd -1 ; 76dd -1 ; 77dd -1 ; 78dd -1 ; 79dd -1 ; 7Add -1 ; 7Bdd -1 ; 7Cdd -1 ; 7Ddd -1 ; 7Edd -1 ; 7Fdd C_DATA66 ; 80dd C_DATA66 ; 81dd C_DATA66 ; 82dd C_DATA66 ; 83dd C_DATA66 ; 84dd C_DATA66 ; 85dd C_DATA66 ; 86dd C_DATA66 ; 87dd C_DATA66 ; 88dd C_DATA66 ; 89dd C_DATA66 ; 8Add C_DATA66 ; 8Bdd C_DATA66 ; 8Cdd C_DATA66 ; 8Ddd C_DATA66 ; 8Edd C_DATA66 ; 8Fdd C_MODRM ; 90dd C_MODRM ; 91dd C_MODRM ; 92dd C_MODRM ; 93dd C_MODRM ; 94dd C_MODRM ; 95dd C_MODRM ; 96dd C_MODRM ; 97dd C_MODRM ; 98dd C_MODRM ; 99dd C_MODRM ; 9Add C_MODRM ; 9Bdd C_MODRM ; 9Cdd C_MODRM ; 9Ddd C_MODRM ; 9Edd C_MODRM ; 9Fdd 0 ; A0dd 0 ; A1dd 0 ; A2dd C_MODRM ; A3dd C_MODRM+C_DATA1 ; A4dd C_MODRM ; A5dd -1 ; A6dd -1 ; A7dd 0 ; A8dd 0 ; A9dd 0 ; AAdd C_MODRM ; ABdd C_MODRM+C_DATA1 ; ACdd C_MODRM ; ADdd -1 ; AEdd C_MODRM ; AFdd C_MODRM ; B0dd C_MODRM ; B1dd C_MODRM ; B2dd C_MODRM ; B3dd C_MODRM ; B4dd C_MODRM ; B5dd C_MODRM ; B6dd C_MODRM ; B7dd -1 ; B8dd -1 ; B9dd C_MODRM+C_DATA1 ; BAdd C_MODRM ; BBdd C_MODRM ; BCdd C_MODRM ; BDdd C_MODRM ; BEdd C_MODRM ; BFdd C_MODRM ; C0dd C_MODRM ; C1dd -1 ; C2dd -1 ; C3dd -1 ; C4dd -1 ; C5dd -1 ; C6dd -1 ; C7dd 0 ; C8dd 0 ; C9dd 0 ; CAdd 0 ; CBdd 0 ; CCdd 0 ; CDdd 0 ; CEdd 0 ; CFdd -1 ; D0dd -1 ; D1dd -1 ; D2dd -1 ; D3dd -1 ; D4dd -1 ; D5dd -1 ; D6dd -1 ; D7dd -1 ; D8dd -1 ; D9dd -1 ; DAdd -1 ; DBdd -1 ; DCdd -1 ; DDdd -1 ; DEdd -1 ; DFdd -1 ; E0dd -1 ; E1dd -1 ; E2dd -1 ; E3dd -1 ; E4dd -1 ; E5dd -1 ; E6dd -1 ; E7dd -1 ; E8dd -1 ; E9dd -1 ; EAdd -1 ; EBdd -1 ; ECdd -1 ; EDdd -1 ; EEdd -1 ; EFdd -1 ; F0dd -1 ; F1dd -1 ; F2dd -1 ; F3dd -1 ; F4dd -1 ; F5dd -1 ; F6dd -1 ; F7dd -1 ; F8dd -1 ; F9dd -1 ; FAdd -1 ; FBdd -1 ; FCdd -1 ; FDdd -1 ; FEdd -1 ; FF
end
现在我们可以获取任意地址的指令长度。我们重复调用这个函数直到读取了5个字节。完成后把这些字节拷贝到old_hook。我们知道了开始这些指令的长度,所以我们可以在原始函数的下条指令填入跳转地址。
.386p.model flat, stdcall
...
.data
kernel_name db "kernel32.dll",0sleep_name db "Sleep",0
...
MEM_RELEASE dd 000008000h
;16 nops + 一个跳转指令old_sleep db 090h,090h,090h,090h,090h,090h,090h,090h, 090h,090h,090h,090h,090h,090h,090h,090h, 0E9h,000h,000h,000h,000h