首页
论坛
课程
招聘
[原创]打造 API 函数 -- API 绝密档案系列之四 (修正版)
2006-3-11 06:48 35853

[原创]打造 API 函数 -- API 绝密档案系列之四 (修正版)

2006-3-11 06:48
35853
打造 API 函数 -- API 绝密档案系列之四  告别众乡亲

重要更正,在这里感谢 heXer 指出 GetFunctionAddress 的 bug,现已修正。对于我的粗心,在这里向各位看官道歉。如果你已经下载此文,请以修正版为准。

题目:周谕的老母是谁?诸葛亮的老母是谁?张非的老母是谁?
可怜天下父母心,不幸被伊万大叔答中,“季氏、何氏、吴氏”,伊万大叔立马丢回来一个问题:“林妹妹是怎么死的”,线索越剧名曲。
想当年,徐玉兰、王文娟将宝玉和林妹妹演译到何等林漓至尽,徐玉兰一曲“天上掉下个林妹妹....” 更是唱的荡气回肠。
伊万大叔的答案就在里面,可是我想破脑袋也不得其要领。因为我不是什么斯坦,只好问结果,各位看官可座稳了,下面公布伊万大叔给的答案:
林妹妹是摔死的!!!!,何解,天上掉下来不就摔死了,哈哈哈哈,这都是什么东西吗。TMD(Theater Missile Defens)。
废话少说,言归正传。

前面三篇介绍了 GetModuleHandle 和 GetProcAddress 这两个函数,第一个函数获得进程启动时加载在内存的基地址,第二个获取函数的调用的地址。我在第二篇中说过:“可以使用其他的方法达到相同的目的,这样就是最高境界了。”现在我们就按这个思路去做,用自己的方法打造这两个函数。

我们先看看PE文件头中的 IMAGE_OPTIONAL_HEADER :
77E600C8           dw 10Bh              ; OptionalHeader.Magic
77E600C8           db 5                 ; OptionalHeader.MajorLinkerVersion
77E600C8           db 0Ch               ; OptionalHeader.MinorLinkerVersion
77E600C8           dd 59000h            ; OptionalHeader.SizeOfCode
77E600C8           dd 73E00h            ; OptionalHeader.SizeOfInitializedData
77E600C8           dd 0                 ; OptionalHeader.SizeOfUninitializedData
77E600C8           dd 7A40h             ; OptionalHeader.AddressOfEntryPoint
77E600C8           dd 1000h             ; OptionalHeader.BaseOfCode
77E600C8           dd 5A000h            ; OptionalHeader.BaseOfData
77E600C8           dd 77E60000h         ; OptionalHeader.ImageBase
77E600C8           dd 1000h             ; OptionalHeader.SectionAlignment
77E600C8           dd 200h              ; OptionalHeader.FileAlignment
......
......

我们感兴趣的不是 ImageBase 因为我们担心万一模块没有被装载在这个地址上,基于这点写的代码肯定土崩瓦解。我们感兴趣的是:
77E600C8           dd 1000h                        ; OptionalHeader.SectionAlignment

除非你自己修改 PE 文件头,不管什么编译器,SectionAlignment 总是等于 0x1000,有了这个认识我们就开始构造我们的GetModuleHandle,当然,我们不会使用这个名字,这样会把编译器给弄傻了,而且我也不喜欢这个名字,我们就使用我在上一篇中提过的名字 GetProcBaseAddress。代码非常简单,就是利用这个 0x1000 的对齐特性,随便找一个在这个模块中的函数调用地址,将这个地址异或 0xFFFF0000 ,也就是对齐到 0x1000,然后往前查找 DOS 文件头的 e_magic 和 PE 文件头的 Signature,按 SectionAlignment 的值 0x1000递减。

GetProcbaseAddress proc uses ebx esi FunAddr:DWORD
    .if !FunAddr
        ;如果 FunAddr = 0 随便 Call 一下,pop eax 就是当前的地址
        call $ + 5
        pop eax
    .else
        ;如果是某个函数的调用地址,函数调用地址一般有下面的格式:
        ; __stdcall MessageBoxA(x,x,x,x)
        ; MessageBoxA proc near
        ;FF 25 08 04 40 00     jmp     ds:__imp__MessageBoxA
        ; MessageBoxA endp
        ;其中 FF 25 是 jmp 的操作码,后面的 00400408 是函数调用地址
        ;这样就容易理解下面的代码了。
        mov eax, FunAddr
        mov eax, [eax+2]
        mov eax, [eax]
    .endif
    and eax, 0FFFF0000h
    .repeat
        .if word ptr [eax] != 'ZM'
            xor esi, esi
            sub eax, 1000h

        .else
            mov esi, [eax+IMAGE_DOS_HEADER.e_lfanew]
        .endif
        add esi, eax
    .until dword ptr [esi] == 'EP'

    ret
GetProcbaseAddress  endp

是不是很简单?但使用的前提是必须有一个该模块函数的调用地址,其实这一点是非常容易实现的。在后面给出完整的代码中可以看到如何处理。
下面我们接着构造 GetFunctionAddress(GetProcAddress),在给出代码前我们首先回忆上一篇中给出的那个 ntdll.dll 的 PE 文件头和输出表的具体结构,详细请看上篇,这里只列出其中的输出表:
77EB4230 ExportTable     
77EB4230           dd 0                                ; Characteristics 
77EB4230           dd 41B04DB0h                        ; TimeDateStamp
77EB4230           dw 0                                ; MajorVersion
77EB4230           dw 0                                ; MinorVersion
77EB4230           dd offset NameStrBase               ; Name
77EB4230           dd 1                                ; Base
77EB4230           dd 33Dh                             ; NumberOfFunctions
77EB4230           dd 33Dh                             ; NumberOfNames
77EB4230           dd offset AddressOfFunctionsBase    ; AddressOfFunctions
77EB4230           dd offset AddressOfNamesBase        ; AddressOfNames
77EB4230           dd offset AddressOfNameOrdinalsBase ; AddressOfNameOrdinals

另外我们也仔细研究输出表中 AddressOfNames 所指向的函数名表 NameStrBase。
......
......
77EB8DC4 aRequestdevicew    db 'RequestDeviceWakeup',0
77EB8DD8 aRequestwakeupl    db 'RequestWakeupLatency',0
77EB8DED aResetevent        db 'ResetEvent',0
77EB8DF8 aResetwritewatc    db 'ResetWriteWatch',0
77EB8E08 aResumethread      db 'ResumeThread',0
77EB8E15 aRtlfillmemory     db 'RtlFillMemory',0
77EB8E23 RtlFillMemory      db 'NTDLL.RtlFillMemory',0
77EB8E37 aRtlmovememory     db 'RtlMoveMemory',0
77EB8E45 RtlMoveMemory      db 'NTDLL.RtlMoveMemory',0
77EB8E59 aRtlunwind         db 'RtlUnwind',0
77EB8E63 RtlUnwind          db 'NTDLL.RtlUnwind',0
77EB8E73 aRtlzeromemory     db 'RtlZeroMemory',0
77EB8E81 RtlZeroMemory      db 'NTDLL.RtlZeroMemory',0
77EB8E95 aScrollconsoles    db 'ScrollConsoleScreenBufferA',0
77EB8EB0 aScrollconsol_0    db 'ScrollConsoleScreenBufferW',0
77EB8ECB aSearchpatha       db 'SearchPathA',0
......
......

在这张表中,其中有几个函数名出现两次,另一个函数名前多了一个模块指引。我想大概原因是,一方面,老盖不希望用户直接调用许多底层的函数,另一方面也是为了用户使用方便,将常用函数放在一个模块中,前面的模块指引指出该函数所在的模块,这也就是 bug 所在。我们的 GetProcBaseAddress 运行的条件是必须先有一个该模块的函数调用地址,然后由这个地址搜索模块的基地址,以这种模式运行,不能解决直接从模块名获得模块基地址,为此,必须打造另一个具有类似 GetModuleHandle 功能的函数,这就是我们的新函数 GetDllBase ,GetDllBase 的工作方式和 GetProcBaseAddress 完全不同,所起的作用却是相同的,其实在打造了 GetDllBase 后,可以完全舍弃这个 GetProcBaseAddress 的函数,但我还是将他保留下来,我觉得这样比较好,让你可以看到完成同种的功能,可以使用不同的方法。下面是 GetDllBase 实现的代码:
GetDllBase proc uses ecx edx esi lpProcName:DWORD

    ;mov eax, large fs:18h
    xor eax, eax
    ; masm32 在编译普通exe文件时,不支持 mov eax, fs:[18] 指令
    ; 因此写成如下格式,编译后等同 mov eax, fs:[18]
    db 64h
    mov eax, ds:18h                               ; TEB.Self
                                                  
    mov eax, [eax].TEB.Peb                        ; 取 PEB 表的地址
    mov eax, [eax].PEB.Ldr                        ; 取 PEB_LDR_DATA 结构的地址
    ; PEB_LDR_DATA.InLoadOrderModuleList.Flink 指向 LDR_DATA_TABLE_ENTRY
    add eax, PEB_LDR_DATA.InLoadOrderModuleList.Flink
    ; 这个结构形成一个链表
    mov ecx, [eax].LDR_DATA_TABLE_ENTRY.InLoadOrderModuleList.Flink

Loop_SeachModule:
    cmp ecx, eax
    jz  Exit
    mov edx, ecx
    ; 获取下一个 LDR_DATA_TABLE_ENTRY 结构的地址
    mov ecx, [ecx].LDR_DATA_TABLE_ENTRY.InLoadOrderModuleList.Flink
    ; 比较该内存地址是否为空,如果是,则出错
    cmp [edx].LDR_DATA_TABLE_ENTRY.InMemoryOrderModuleList.Flink, 0
    jz  Loop_SeachModule
    ; 获取模块名的地址
    lea esi, [edx].LDR_DATA_TABLE_ENTRY.BaseDllName.UNICODE_STRING.woLength
    push eax
    ; 验证是否是我们需要的模块
    invoke ComparAnsiStrUnicodeStr, lpProcName, esi
    cmp eax, 1
    pop eax
    jnz Loop_SeachModule
    ; 如果是,取该模块的基地址
    mov eax, [edx].LDR_DATA_TABLE_ENTRY.DllBase
    jmp Exit1

Exit:
    xor eax, eax

Exit1:
    ret
GetDllBase endp

GetDllBAse 的工作原理是利用 LDR_DATA_TABLE_ENTRY 结构的特性,这个结构如下:
PEB_LDR_DATA STRUCT                 ; sizeof = 24h
    _Length                         DWORD       ?   ; original name Length
    Initialized                     BYTE        ?   ; 04h
                                    db  3 dup(?)    ; padding
    SsHandle                        PVOID       ?   ; 08h
    InLoadOrderModuleList           LIST_ENTRY  <>  ; 0Ch
    InMemoryOrderModuleList         LIST_ENTRY  <>  ; 14h
    InInitializationOrderModuleList LIST_ENTRY  <>  ; 1Ch
PEB_LDR_DATA ENDS
PPEB_LDR_DATA typedef PTR PEB_LDR_DATA

LIST_ENTRY STRUCT
    Flink DWORD ?
    Blink DWORD ?
LIST_ENTRY ENDS

LDR_DATA_TABLE_ENTRY_U0 union ; (sizeof=0X8, standard type)
    HashLinks                       LIST_ENTRY     <>
    SectionPointer                  DWORD          ?       ; offset (FFFFFFFF)
LDR_DATA_TABLE_ENTRY_U0 ends

LDR_DATA_TABLE_ENTRY_U1 union ; (sizeof=0X4, standard type)
    TimeDateStamp                   DWORD          ?
    LoadedImports                   DWORD          ?       ; offset (FFFFFFFF)
LDR_DATA_TABLE_ENTRY_U1 ends

LDR_DATA_TABLE_ENTRY struc ; (sizeof=0X54, standard type)
    InLoadOrderModuleList           LIST_ENTRY     <>
    InMemoryOrderModuleList         LIST_ENTRY     <>
    InInitializationOrderModuleList LIST_ENTRY     <>
    DllBase                         DWORD          ?       ; offset (FFFFFFFF)
    EntryPoint                      DWORD          ?       ; offset (FFFFFFFF)
    SizeOfImage                     DWORD          ?
    FullDllName                     UNICODE_STRING <>
    BaseDllName                     UNICODE_STRING <>
    Flags                           DWORD          ?
    LoadCount                       WORD           ?
    TlsIndex                        WORD           ?
    U0                              LDR_DATA_TABLE_ENTRY_U0 <>
    CheckSum                        DWORD          ?
    U1                              LDR_DATA_TABLE_ENTRY_U1 <>
    EntryPointActivationContext     DWORD          ?       ; offset (FFFFFFFF)
    PatchInformation                DWORD          ?       ; offset (FFFFFFFF)
LDR_DATA_TABLE_ENTRY ends

LDR_DATA_TABLE_ENTRY 结构的前三个元素都是链表结构,定义成 LIST_ENTRY 结构,通过 LIST_ENTRY.Flink (向前链界) 或 LIST_ENTRY.Blink (向后链接),和下一个 LDR_DATA_TABLE_ENTRY 链接,因此通过这个结构,可以遍列所有加载的模块,我们就是根据这个特性来得到所需模块的基地址。
PEB.Ldr 指向 PEB_LDR_DATA ,PEB_LDR_DATA.InLoadOrderModuleList.Flink 就指向我们关心的 LDR_DATA_TABLE_ENTRY 表。

各位还记得上一篇中那个“象鼻子吸盘”的代码吗,这段代码最后返回的是某个函数的序号,AddrOfNameOrdinalsBase 序号表单位是 WORD ,根据这个序号,在 AddressOfFunctions 相应的位置上就是函数的调用地址了,就这么简单,只要理解了输出表的结构中几个元素的含义,就非常容易打造我们自己的函数:
GetFunctionAddress proc uses ecx ebx esi edi BaseAddress:DWORD, lpProcName:DWORD
    LOCAL Count                 :DWORD
    LOCAL NumberOfNames         :DWORD
    LOCAL AddressOfNames        :DWORD
    LOCAL AddressOfNameOrdinals :DWORD
    LOCAL AddressOfFunctions    :DWORD
    LOCAL ExportSize            :DWORD
    LOCAL ProcAddr              :DWORD
    LOCAL ExportAddr            :DWORD

    mov ebx, BaseAddress
    movzx eax, word ptr [ebx].IMAGE_DOS_HEADER.e_lfanew
    add ebx, eax

    mov eax, [ebx].IMAGE_NT_HEADERS1.OptionalHeader.DirectoryExport.isize
    mov ExportSize, eax

    mov ebx, [ebx].IMAGE_NT_HEADERS1.OptionalHeader.DirectoryExport.VirtualAddress
    add ebx, BaseAddress
    mov ExportAddr, ebx

    mov eax, [ebx].IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals
    add eax, BaseAddress

    mov  AddressOfNameOrdinals, eax
    mov eax, [ebx].IMAGE_EXPORT_DIRECTORY.NumberOfNames
    mov NumberOfNames, eax

    mov eax, [ebx].IMAGE_EXPORT_DIRECTORY.AddressOfNames
    add eax, BaseAddress
    mov  AddressOfNames, eax

    mov eax, [ebx].IMAGE_EXPORT_DIRECTORY.AddressOfFunctions
    add eax, BaseAddress
    mov AddressOfFunctions, eax

    mov eax, NumberOfNames
    and Count, 0
    lea ecx, [eax-1]              ; ecx = NumberOfNames - 1
    test ecx, ecx
    jl CheckEnd

Loop1:
    mov eax, Count
    mov edi, lpProcName
    lea esi, [eax+ecx]            ; ecx = NumberOfName - 1
                                                                ; esi = Count + ecx
    mov eax, AddressOfNames       ; eax = AddressOfNamesBase
    sar esi, 1                    ; esi / 2

    mov eax, [eax+esi*4]          ; 在AddressOfNames偏移地址表中取函数名的偏移量
    add eax, BaseAddress          ; 加上基地址得到函数名的地址

Loop_If_dl_IsNotTerminalChar:     ; 从函数名中取一个字符
    mov bl, [edi]
    mov dl, bl
    cmp bl, [eax]                 ; 和函数名列表中的函数名在相应的位置上进行比较,特别
                                  ; 需要注意,当两个字符进行比较时:
                                  ;  if bl >= [eax]
                                  ;     CF = 0          //这里将CF标志位置 0
                                  ;  else               //bl < [eax]
                                  ;     CF = 1
                                  ;  end
                                  ; 这里将 CF(进位标志) 置位,根据函数名排序的条件,即
                                  ; 决定了是向前搜索,还是向后搜索:
                                  ;   jnz short IfCharNotEqu
                                  ; 不影响标志位,这个标志位将在后面的代码中使用
    jnz IfCharNoEqu
    test dl, dl
    jz If_dl_IsTerminalChar
    mov bl, [edi+1]               ; 取下一个字符
    mov dl, bl
    cmp bl, [eax+1]               ; 和下一个函数名字符进行比较,这里和上面的比较相同,
                                  ; 通过设置 CF 标志位来决定搜索的方向。
    jnz  IfCharNoEqu
    inc edi                       ; 调整指针,指向下二个字符
    inc edi
    inc eax
    inc eax
    test dl, dl
    jnz Loop_If_dl_IsNotTerminalChar

If_dl_IsTerminalChar:
    xor eax, eax
    jmp CheckDirection

IfCharNoEqu:                      ; 指令
    sbb eax, eax                  ;   sbb eax, eax    //带进位减
                                  ; 其结果只有两种可能
                                  ;   if CF == 0
                                  ;      eax = 0
                                  ;      set CF = 0
                                  ;   else        //CF = 1
                                  ;      eax = 0xFFFFFFFF
                                  ;      set CF = 1
                                  ;   end;
    sbb eax, 0FFFFFFFFh           ; 指令
                                  ;   sbb eax, 0FFFFFFFFh    //带进位减
                                  ; 其结果也是两种可能
                                  ;   if  CF == 0
                                  ;      eax = 0
                                  ;      set SF = 0
                                  ;   else         //CF = 1
                                  ;      eax = 0xFFFFFFFF
                                  ;      set SF = 1
                                  ;   end
                                  ; 这里 SF 是符号标志位,即判断结果是正数还是负数。
CheckDirection:                     ; 指令
    test eax, eax                 ;   test eax, eax
                                  ; 一般我们用来测试某一位是 1 还是 0,但这个操作同样影
                                  ; 响符号标志位:
                                  ;   if eax >= 0
                                  ;     set SF = 0
                                  ;   else
                                  ;     set SF = 1
                                  ;   end
    jge ForewardSearch            ; Jump if Greater or Equal (SF=0)

BackwardSearch:
    lea ecx, [esi-1]
    jmp NextSearch

ForewardSearch:
    jle SearchEnd
    lea eax, [esi+1]
    mov Count, eax

NextSearch:
    cmp ecx, Count
    jge Loop1
    jmp SearchEnd

CheckEnd:
    mov esi, NumberOfNames

SearchEnd:
    cmp ecx, Count
    jge GetOrdinal
    or ax, 0FFFFh
    jmp Exit

GetOrdinal:
    mov eax, AddressOfNameOrdinals
    mov ax, [eax+esi*2]
    movzx ecx, word ptr ax      ; 序号列表的单位是 WORD 所以 esi*2 即得到序号的偏移
                                ; 地址,加上基地址,即得到函数的序号。
    mov eax, AddressOfFunctions ; 函数地址表起始地址
    lea eax, [eax+ecx*4]        ; 加上序号乘4,因为函数地址表的单位是 DWORD
    mov ecx, [eax]              ; 取函数地址的偏移量
    add ecx, BaseAddress        ; 加上基地址即函数的调用地址
    mov ProcAddr, ecx
    mov edx, ExportSize
    add edx, ExportAddr
    ; 如果返回的函数调用地址在 ExportAddr 之间,则该函数在外部模块
    .if ((ecx > ExportAddr) && (ecx <= edx))
        ; 验证是否为有效的字符串
        invoke CheckIsStr, ecx
        .if eax
            ; 如果是,根据模块前部的指引获得该模块的基地址
            invoke GetDllBase, eax
            ; 调用自己,获得函数调用地址
            invoke GetFunctionAddress, eax, lpProcName
            mov ProcAddr, eax
        .endif
    .endif

Exit:
    mov eax, ProcAddr
    ret
GetFunctionAddress endp

有了这三个个函数,下面是实战了,我在目录 \masm32\EXAMPLE1\FILTINPT 中随便选了段代码 FILTINPT 来演示我们自己打造的函数,因为代码太长,所以只摘录部分来讲解,需要仔细研究的可以下载。
首先将该程序的所有 API 函数列表:
    .data
    ......
    ;API 函数名列表
    szCallWindowProcA      db 'CallWindowProcA',0
    szCreateWindowExA      db 'CreateWindowExA',0
    szDefWindowProcA       db 'DefWindowProcA',0
    ......
    ......
    szGetModuleHandleA     db 'GetModuleHandleA',0
                           dd  0
    EVEN
    ;API 函数名引用表
    OffCallWindowProcA     dd  offset szCallWindowProcA
    OffCreateWindowExA     dd  offset szCreateWindowExA
    OffDefWindowProcA      dd  offset szDefWindowProcA
    ......
    ......

    ;自建输入表
    ; Imports from user32
    _CallWindowProcA       dd  ?
    _CreateWindowExA       dd  ?
    _DefWindowProcA        dd  ?

    ;注意下面是代码段
    .code
    
    ;下面这条宏命令是不让 proc 宏自动生成 ebp 框架的代码
    option prologue:none


; 自建输入表引用表
; __stdcall CallWindowProcA(x,x,x,x,x)
MyCallWindowProc    proc lpPrevWndFunc:DWORD,   \
                         hWndl:DWORD,           \
                         Msg:DWORD,             \
                         wParam:DWORD,          \
                         lParam:DWORD
                jmp     _CallWindowProcA    ; CallWindowProcA(x,x,x,x,x)
MyCallWindowProc    endp

; __stdcall CreateWindowExA(x,x,x,x,x,x,x,x,x,x,x,x)
MyCreateWindowEx    proc dwExStyle:DWORD,       \
                         lpClassName:DWORD,     \
                         lpWindowName:DWORD,    \
                         dwStyle:DWORD,x:DWORD, \
                         y:DWORD,               \
                         nWidth:DWORD,          \
                         nHeight:DWORD,         \
                         hWndParent:DWORD,      \
                         hMenu:DWORD,           \
                         hInstance1:DWORD,      \
                         lpParam:DWORD
                jmp     _CreateWindowExA    ; CreateWindowExA(x,x,x,x,x,x,x,x,x,x,x,x)
MyCreateWindowEx    endp

; __stdcall DefWindowProcA(x,x,x,x)
MyDefWindowProc     proc hWnd1:DWORD,Msg:DWORD,wParam:DWORD,lParam:DWORD
                jmp     _DefWindowProcA    ; DefWindowProcA(x,x,x,x)
MyDefWindowProc     endp
......
......
    ;恢复 proc 宏自动生成堆栈框架
    option prologue:PrologueDef

下面是原来的程序,将程序中所有的 API 调用前面都加了 My 前缀,引用我们自建的 API 表:
WndProc proc hWin   :DWORD,
             uMsg   :DWORD,
             wParam :DWORD,
             lParam :DWORD

    .if uMsg == WM_COMMAND
    ;======== menu commands ========
        .if wParam == 1000
            invoke  MySendMessage,hWin,WM_SYSCOMMAND,SC_CLOSE,NULL
        .elseif wParam == 1900
            szText TheMsg,"Assembler, Pure & Simple"
            invoke  MyMessageBox,hWin,ADDR TheMsg,ADDR szDisplayName,MB_OK
        .endif
    ;====== end menu commands ======
......
......

最后是启动部分,初始化自建的API表:
start:
    jmp S1
    ;这里加了一句永远也不会执行的 API 调用,目的是为了获得 user32.dll 的基地址
    invoke  MessageBox,0,ADDR TheText,ADDR szDisplayName,MB_OK
S1:
    mov eax, offset ExitProcess          ;取ExitProcess函数地址
    invoke GetProcbaseAddress, eax       ;获取kernel32.dll基地址
;    invoke GetDllBase, addr szKernel32  ;测试
    mov MyImageBase, eax                 ;保存
    mov esi, offset OffExitProcess       ;取kernel32.dll API 函数名引用表的首地址
    mov edi, offset _ExitProcess         ;取kernel32.dll 自建输入表首地址
    .while (dword ptr [esi] != 0)
        invoke GetFunctionAddress, MyImageBase, [esi]
        stosd                            ;建立kernel32.dll的输入表
        lodsd
    .endw

    mov eax, offset MessageBox           ;取MessageBox函数的地址
    invoke GetProcbaseAddress, eax       ;获取user32.dll基地址
    mov MyImageBase, eax                 ;保存
    mov esi, offset OffCallWindowProcA   ;取user32.dll API 函数名引用表首地址
    mov edi, offset _CallWindowProcA     ;取user32.dll 自建输入表首地址
    .while (dword ptr [esi] != 0)
        invoke GetFunctionAddress, MyImageBase, [esi]
        stosd                            ;建立user32.dll的输入表
        lodsd
    .endw

    ;下面是原程序的代码,所有 API 函数都家里 My 前缀
    invoke  MyGetModuleHandle, NULL
    mov hInstance, eax

    invoke  MyGetCommandLine
    mov CommandLine, eax
    invoke  WinMain,hInstance,NULL,CommandLine,SW_SHOWDEFAULT
    ;这里保留是为了取 Kernel32.dll 的基地址
    invoke  ExitProcess,eax
    ret
end start

这里给出的代码和壳实际使用的代码差别并不大,就是少了垃圾代码,当然,函数名子也需要简单的处理一下。这一点上 Themida 的 GetFunctionAddress 写的比较有水平,隐蔽性相当强,垃圾代码也写的比较好,基本做到了真中有假,假中有真,但基本原理和上面的代码相同(比我们这个要简单,只是从头到尾的查表,不过加上垃圾代码有几千行,Themida 最有效的战略手段就是疲劳战,技术上虽然比较先进,但也不是不可突破的,包括那个许多人害怕的驱动,实际也是很容易突破的),Themida 的 GetFunctionAddress 几个入口参数中有一个是函数名的第一个字母,还有一个密码数,当然还有函数的基地址。先用这个首字母快速定位,然后将获取的函数名加密,和参数的密码比较而确定是否是需要的函数,整个过程中不出现函数名字,给出一个字母的目的可能是为了加快搜索速度,不过我认为没有必要,你只要找到这个GetFunctionAddr的地址下断点 Themida 什么时候调用什么函数就一清二楚了,对于其他的函数,Themida 并没有处理。另外在实际使用中也不会将函数列表,用一个取一个,用完就删掉,要用再取。

说明: 代码中给出的 PEB 结构得自于 WinDGB,适用 win2k 系统,对于其他系统,请自行改造。TEB 结构我没有完整的(虽然我自己弄了一个所谓完整的,但可能有许多错误,所以没敢列出),只列出前部,省略了大部分,4F 给出的 TEB 表也只有前部少数是正确的,(我跟踪系统程序时验证过)。
这个系列就暂时告一段落,以后有兴趣再写点什么垃圾来玩玩,希望大家喜欢,多顶一下。

后话:肺腑之言
许多朋友认为自己能用别人提供的方法就算学会破解,或能使用某种语言就认为理解了操作系统,其实这是远远不够的,正所谓知其然,不知其所以然。深刻的理解系统底层的一些方法,可以让你做到知其所以然,将来不管你从事那类开发,都会获益不浅。尤其是通过自己对底层代码的分析,在这个过程中,本身就是最好的学习,尤其是能锻炼你分析代码的能力,理解每一句代码的深层含义。
通往高手的路不是一条平坦的路,一定会遇到许多你根本就想不到的困难,一定要做好充分的思想准备,不要怕挫折,有些问题实在想不通可以放放,也许你在研究别的东西时豁然开朗,触类旁通,那些问题无意中就解决了。
朝思暮想,一招交手,乃门外汉也。不管多好的文章,光看是没有用的,要变成自己的东西,就要实践,不断的写。
在北美你去找一个地产经纪,问买房子的要点是什么,这个经纪会告诉你:
1、Location
2、Location
3、Location
意思是,位置,位置,位置,也就是决定一个房子的好坏最重要的就是房子的位置。
同样,如何才能成为真正的高手:
1、实践
2、实践
3、实践
金大侠在笑傲江湖有这样一段话(不是原话,是意思),那是发生在华山绝顶,风清杨祖师指点令狐冲武功时,随便从地上拣起一根不知是那个魔教长老的大腿骨,象令狐冲一比划,问这个怎么破,令狐冲说这个不是招啊,这就引出一个绝世真理,无招胜有招。无招胜有招并不是说你什么都不学乱来一气,就能胜过任何人了,这样理解就完全错了,这里的无招是建立在无数招数的艰苦锻炼上而达到的,到你真正理解了这些招数之后,返朴归真而达到从有招到无招的一种境界。写程序也一样,当你真正能从有招过度到无招,那时就是无招胜有招了。那就是当年独孤求败大侠的境界了。这非常符合辩证唯物主义的否定之否定规律,当历史螺旋发展到高级阶段时,可以见到初级阶段的雏影。也就是从什么都不会(无招)学到来去无踪(重返无招)。
[I][CODE]

看雪2022 KCTF 秋季赛 防守篇规则,征题截止日期11月12日!(iPhone 14等你拿!)

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (37)
雪    币: 584
活跃值: 活跃值 (11132)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
kanxue 活跃值 8 2006-3-11 08:30
2
0
谢谢gzgzlxg给大家带来这么好的文章!
我想gzgzlxg另一个用意是告诉初学者,研究加解密目的并不是为了破解几个软件,而是通过这门技术,可以让自己朝各个方向发展。在这几篇文章里,以API函数为例,讲解如何研究系统底层的一些方法,并打造自己的函数。
雪    币: 110
活跃值: 活跃值 (90)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
xzchina 活跃值 1 2006-3-11 08:50
3
0
从1到4,每一篇写的都很精彩!
雪    币: 159
活跃值: 活跃值 (51)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
gzgzlxg 活跃值 11 2006-3-11 09:03
4
0
谢谢所有关心此帖的朋友,谢谢看雪老大。
雪    币: 207
活跃值: 活跃值 (23)
能力值: ( LV9,RANK:970 )
在线值:
发帖
回帖
粉丝
simonzh2000 活跃值 24 2006-3-11 10:19
5
0
写的很详细.

可是 GetFunctionAddress 却有 bug,

没有处理特殊情况, 论坛上很多人都提到过, 自己搜一下.
雪    币: 331
活跃值: 活跃值 (141)
能力值: ( LV9,RANK:690 )
在线值:
发帖
回帖
粉丝
winndy 活跃值 17 2006-3-11 10:28
6
0
最初由 gzgzlxg 发布
通往高手的路不是一条平坦的路,一定会遇到许多你根本就想不到的困难,一定要做好充分的思想准备,不要怕挫折,有些问题实在想不通可以放放,也许你在研究别的东西时豁然开朗,触类旁通,那些问题无意中就解决了。
朝思暮想,一招交手,乃门外汉也。不管多好的文章,光看是没有用的,要变成自己的东西,就要实践,不断的写。
在北美你去找一个地产经纪,问买房子的要点是什么,这个经纪会告诉你:
1、Location
........


受教。
“艺多不压身”好像也是你说的。
教训的好。
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
菜儿 活跃值 2006-3-11 10:36
7
0
最初由 winndy 发布
受教。
“艺多不压身”好像也是你说的。
教训的好。


“艺多不压身”好像也是你说的。
教训的好。 [/QUOTE]
雪    币: 203
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yujinjianx 活跃值 2006-3-11 10:47
8
0
写的很详细。
雪    币: 159
活跃值: 活跃值 (51)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
gzgzlxg 活跃值 11 2006-3-11 11:21
9
0
最初由 simonzh2000 发布
可是 GetFunctionAddress 却有 bug,

没有处理特殊情况, 论坛上很多人都提到过, 自己搜一下.

你是指没有检验基地址的合法性?还是别的?谢谢指点。
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
guess 活跃值 2006-3-11 11:33
10
0
好文章,很高兴看到
雪    币: 226
活跃值: 活跃值 (11)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
heXer 活跃值 3 2006-3-11 12:30
11
0
不知下面2个能否正确取出:
kernel32.dll 的 RtlZeroMemory
msvcrt.dll 的 _acmdln
雪    币: 1331
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Ivanov 活跃值 2006-3-11 12:56
12
0
支持
雪    币: 207
活跃值: 活跃值 (23)
能力值: ( LV9,RANK:970 )
在线值:
发帖
回帖
粉丝
simonzh2000 活跃值 24 2006-3-11 13:49
13
0
最初由 gzgzlxg 发布
你是指没有检验基地址的合法性?还是别的?谢谢指点。


hexer 已经给出了具体的例子,  用你的代码调试一下就知道了.
雪    币: 2266
活跃值: 活跃值 (496)
能力值: (RANK:990 )
在线值:
发帖
回帖
粉丝
CCDebuger 活跃值 24 2006-3-11 14:31
14
0
gzgzlxg 辛苦,支持!
雪    币: 159
活跃值: 活跃值 (51)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
gzgzlxg 活跃值 11 2006-3-11 14:57
15
0
问题已经解决,现在可以获得 herXer 所指的这类函数。详细内容请看上面的修正版。
雪    币: 206
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
macol 活跃值 2006-3-11 17:27
16
0
努力学习,谢谢!
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
usa 活跃值 2006-3-11 20:46
17
0
最初由 gzgzlxg 发布
同样,如何才能成为真正的高手:
1、实践
2、实践
3、实践
金大侠在笑傲江湖有这样一段话(不是原话,是意思),那是发生在华山绝顶,风清杨祖师指点令狐冲武功时,随便从地上拣起一根不知是那个魔教长老的大腿骨,象令狐冲一比划,问这个怎么破,令狐冲说这个不是招啊,这就引出一个绝世真理,无招胜有招。无招胜有招并不是说你什么都不学乱来一气,就能胜过任何人了,这样理解就完全错了,这里的无招是建立在无数招数的艰苦锻炼上而达到的,到你真正理解了这些招数之后,返朴归真而达到从有招到无招的一种境界。写程序也一样,当你真正能从有招过度到无招,那时就是无招胜有招了。那就是当年独孤求败大侠的境界了。这非常符合辩证唯物主义的否定之否定规律,当历史螺旋发展到高级阶段时,可以见到初级阶段的雏影。也就是从什么都不会(无招)学到来去无踪(重返无招)。

........

多么富有哲理性的一段话!楼主应该去当政治老师!
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
huziyi 活跃值 2006-3-12 10:39
18
0
写得很详细,先收下来,慢慢学习。
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
viviann 活跃值 2006-3-12 12:56
19
0
特别支持!
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
qqpoly 活跃值 2006-3-12 13:03
20
0
学学,楼主的敬业精神。等我会了,我也这样发给大家.
雪    币: 375
活跃值: 活跃值 (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
gegon 活跃值 2006-3-12 18:23
21
0
我是初学者,现在还看不太懂 !!!!!!!!!1
雪    币: 159
活跃值: 活跃值 (51)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
gzgzlxg 活跃值 11 2006-3-12 18:47
22
0
最初由 usa 发布
多么富有哲理性的一段话!楼主应该去当政治老师!

谢谢,在真实的生活中,我有两个外号,一个叫“大师”,一个叫“大侠”,从外号可以看出,我当个政治老师应该没有问题,读过很多书,包括老马的那三块砖头,哈哈。
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
linklimit 活跃值 2006-3-13 07:38
23
0
强烈支持,感谢楼主
雪    币: 326
活跃值: 活跃值 (15)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
快雪时晴 活跃值 4 2006-3-13 09:29
24
0
强人,语言也很精彩!

拜读-收藏
雪    币: 193
活跃值: 活跃值 (13)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
林海雪原 活跃值 6 2006-3-13 09:30
25
0
大侠就是大侠,不但文章好,教人的法子也绝好!受教了
游客
登录 | 注册 方可回帖
返回