邪恶八进制信息安全团队技术讨论组's Archiver

金州 2006-8-20 08:37

[转载]挂钩Windows API

文章作者:SoBeIt

挂钩Windows API

创建时间:2004-03-06
文章属性:原创
文章提交:SoBeIt (kinsephi_at_hotmail.com)

===========================[ 挂钩Windows API ]==================

                        SoBeIt

                Author:  Holy_Father <[email]holy_father@phreaker.net[/email]>
                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.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        ;最后获得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   ExitProcess
new_sleep:           
   ret   004h
end 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 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:


; __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          ; 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为前缀的指令

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


   现在我们可以获取任意地址的指令长度。我们重复调用这个函数直到读取了5个字节。完成后把这些字节拷贝到old_hook。我们知道了开始这些指令的长度,所以我们可以在原始函数的下条指令填入跳转地址。

.386p
.model flat, stdcall

...

.data

kernel_name      db "kernel32.dll",0
sleep_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


.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      ;调用LDE32
   pop   ecx
   add   ecx,eax
   add   ebx,eax
   cmp   ecx,5
   jb   get_five_bytes
   mov   edi,offset old_sleep   ;计算跳转地址
   mov   [edi+011h],ebx
   sub   [edi+011h],edi
   sub   dword ptr [edi+011h],015h
   rep   movsb
   pop   edi

;下面的代码都是前面有的,所以不需要注解了

   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           ;重复延时
   push   eax
   mov   eax,offset old_sleep        ;调用原函数
   call   eax
   ret   004h


挂钩后看起来想这样:

   004010CC: 6888130000   push 000001388h
   004010D1: E818090000   call Sleep


   Sleep:    ;跳转到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

;这个指令在Kernel32.Sleep(77E86779)后1个字节

   Kernel32.77E8677F:   
   77E8677F: E803000000   call Kernel32.SleepEx
   ...        ;后面的已不重要

为了让这些看起来更清楚,这是原始版本的Kernel32.Sleep:

   Kernel32.Sleep:
   77E86779: 6A00      push 0
   77E8677B: FF742408   push dword ptr [esp+8]
   77E8677F: E803000000   call Kernel32.SleepEx
   77E86784: C20400   ret 00004h


   就象你看到的,在我们已经拷贝了第1和第2个指令(这里总共6个字节)和指向下一条指令的跳转指令后应该是怎样。这里我们得假设跳转指令并不象函数开始的指令那么放置,否则我们就会遇到问题。下一个问题就是诸如ntdll.DbgBreakPoint这样的API,它们太短了,所以不能用这种挂钩的方法。并且它是由Kernel32.DebugBreak调用,所以也不能通过修改IAT来挂钩。虽然说没有谁会去挂钩这个只有int 3的函数,但没有什么是做不到的,只要认真想想就能找到解决的方法。我的方法是挂钩它之后的那个函数(它可能会因为修改了前一个函数的开始5个字节而被破坏)。DbgBreakPoint函数长度为2个字节,所以这里我们可以设置一些标志,然后试着在第二个函数的开始写入条件跳转指令...但这不是我们现在的问题。
   保存原始函数的问题已经叙述完了,就到解除挂钩(unhook)。解除挂钩就是把被修改的字节恢复为原始状态。修改IAT的方法里,如果你想解除挂钩的话,你就需要在表里恢复原始的地址。修改入口点的方法里,你要做的就是把原始函数的开始指令拷贝回去。两种做法都很简单,所以不需要再讲了。


=====[ 3.2.4 挂钩其它进程 ]========================================
   
   现在我们来实践一下运行中挂钩。试想,谁会想只挂钩自己进程?这显然是非常不实用的。
   我来演示3种不同的挂钩其它进程的方法。其中两种都使用了CreateRemoteThread这个API,它只在使用了NT技术的Windows版本里有效。对我来说在较老的Windows里挂钩没那么有趣。忘了说我将介绍的这3个方法我都没有实践过,所以可能会出点问题。
   先介绍CreateRemoteThread。就象帮助里说的,这个函数可以在任意进程里创建新线程并运行它的代码。

   HANDLE CreateRemoteThread(
      HANDLE hProcess,
      LPSECURITY_ATTRIBUTES lpThreadAttributes,
      DWORD dwStackSize,
      LPTHREAD_START_ROUTINE lpStartAddress,
      LPVOID lpParameter,
      DWORD dwCreationFlags,
      LPDWORD lpThreadId
   );

   句柄hProcess可以通过OpenProcess获得。这里我们必须获得足够权限。lpStartAddress是指向目标进程地址空间里存放新线程第一条指令地址的指针,因为新线程是在目标进程里创建,所以它存在于目标进程的地址空间里。lpParameter是指向提交给新线程的参数的指针。


=====[ 3.2.4.1 DLL注入 ]==================================================

   我们可以在目标进程地址空间里任意地方运行我们的新线程。这看起来没什么用,除非在里面有我们完整的代码。第一种方法就是这么实现。它调用GetProcAddress获取LoadLibrary地址。然后把LoadLibrary赋值给参数lpStartAddress。LoadLibrary函数只有一个参数,就和目标进程里新线程的函数一样。

   HINSTANCE LoadLibrary(
      LPCTSTR lpLibFileName
   );

   我们可以使用这点相似性,把lpParameter参数赋为我们的DLL库的名字。在新线程运行后lpParameter的位置就是lpLibFileName的位置。这里最重要的东西前面已经讲过了。在加载了新的模块到目标进程后就开始执行初始化部分。如果我们在这里放置了能够挂钩其它函数的特殊函数就OK了。在执行了初始化部分后,这个线程就什么都不做并被关闭,但我们的模块仍然在地址空间中。这种方法很不错而且很容易实现,它的名字叫DLL注入。但如果你和我一样不喜欢还得多个DLL库的话,请看下面的方法。但如果不介意多个DLL库的话这确实是最快的方法(从程序员的角度来看)。


=====[ 3.2.4.2 独立的代码 ]===============================================

   实现独立的代码比较困难,但也容易给人深刻印象。独立的代码是不需要任何静态地址的代码。它里面所有东西都是互相联系地指向代码里面某些特定的地方。如果我们不知道这段代码开始执行的地址它也能自己完成 。当然,也有可能先获得地址然后重新链接我们的代码这样它可以完全正常地在新地址工作,但这比编写独立的代码更困难。这类型代码的例子比方说病毒的代码。病毒通过这种方法感染可执行文件,它把它自己的代码加入到可执行文件中的某个地方。在不同的可执行文件中放置病毒代码的位置也不一样,这取决于比方说文件结构的长度。
   首先将我们的代码插入目标进程,然后CreateRemoteThread函数就会负责运行我们的代码。所以第一步我们要做的就是通过OpenProcess函数获取目标进程的信息和句柄,接着调用VirtualAllocEx在目标进程地址空间里分配一些内存给我们的代码,最后调用WriteProcessMemory把我们的代码写入分配的内存里并运行它。调用CreateRemoteThread的参数lpStartAddress设置为分配的内存地址,lpParameter可以随便设置。因为我不喜欢附加什么不必要的文件所以我使用了这种方法。


=====[ 3.2.4.3 原始修改 ]=====================================================

   在非NT内核的老版本Windows里是没有CreateRemoteThread函数的,所以我们不能用以上的方法。可能会有比我现在介绍的这种方法好很多的方法,事实上我的这种方法还没有经过实践,但理论上来说是可行的。
   我们其实根本不需要把我们代码放到目标进程里来挂钩它的函数。有两个函数WriteProcessMemory和OpenProcess,它们在所有版本的Windows中都有效。我们还需要的函数是VirtualProtectEx,用来修改进入目标进程的内存页。我找不到任何不直接从我们的进程挂钩目标进程的的理由...


=====[ 4. 结束语 ]================================================

   我欢迎任何人提出更多的这里没有提到的挂钩方法,我肯定那会有很多。同样欢迎补充我介绍得不是很详细的方法。也可以把我懒得写的代码部分完成,把源代码发给我。这篇文档的目的是演示挂钩技术的细节,我希望我做到了。
   特别感谢Z0MBiE的代码,为我节省了很多宝贵的时间。

============================[ End ]========================

   水平有限,欢迎大家指出错漏之处。QQ:27324838 Email:kinvis@hotmail.com

页: [1]
© 1999-2008 EvilOctal Security Team