首页
社区
课程
招聘
[原创]HookApi中学习PE文件格式(一)[原创]
发表于: 2007-11-15 09:46 8904

[原创]HookApi中学习PE文件格式(一)[原创]

petnt 活跃值
12
2007-11-15 09:46
8904
【文章标题】: HookApi中学习PE文件格式(一)
【文章作者】: petnt
【作者邮箱】: petnt@sohu.com
【使用工具】: MASM32\OLLYDBG
【操作平台】: XP SP2
【作者声明】: 本文既不是讲解如何HOOK API的文章,也不是详细介绍PE格式的文章。只是初学PE格式和MASM编程的一点体会,希望和我一样的菜鸟们能从中有所收获。高手们如能从中指点一二,小菜将在此不胜感激。
--------------------------------------------------------------------------------
【详细过程】:
本文实现的是Hook已运行中程序的API,我们并不讨论这样做有没有实用价值。我们注重的应该是学习的过程,特别是对于我们这些初学的小菜们。其实自己动手写一个小程序,远比你看书看上十遍要刻骨铭心的多。
    
思路:
1、找到系统中指定的进程。  
2、搜取进程的导入表,找到指定的API。 
3、修改指定API的FirstThunK(此方法只适用于不加壳且常规调用API的应用程序)
4、写操作指令到目标进程空间。(如果加入的代码要用到API的话,那将是一个比较有趣的问题。)

好了,有了大体思路,我们就开始实现之路吧。ACTION!
  
一、找到系统中指定的进程。
  
这好像和Hook API或PE文件格式没有一点关系,实现起来也比较简单。我们只需记住和熟悉几个API就可以了,下面请他们隆重登场:CreateToolhelp32Snapshot、Process32First、Process32Next。(怎么没有掌声?原来他们并不是明星!)  
CreateToolhelp32Snapshot能获取系统现有进程的快照,快照的句柄返回在Eax中。
Process32First获取快照中的第一个进程有关信息,信息返回在一个结构中。  
Process32Next用来循环获取进程信息。
(以上函数具体用法请参考msdn)
  
有了这3个函数,我们就可以找到我们要找的目标进程了。我们用一个函数来实现他吧!
    
ffGetProcHandle proc lpAppName ;lpAppName为指向目标进程的文件名的指针
     local hSnapShot
     local stProcess:PROCESSENTRY32
     local dwReturn
    
     invoke RtlZeroMemory,addr stProcess,sizeof stProcess ;申请一块内存,用来存放系统进程快照
    mov stProcess.dwSize,sizeof stProcess     ;注意这个结构必须先填写 dwSize项
    invoke CreateToolhelp32Snapshot,TH32CS_SNAPPROCESS,0
    mov hSnapShot,eax                      ;呵呵,这里应该考虑eax=0的情况。
    invoke Process32First,hSnapShot,addr stProcess
    .while eax
        invoke ffStringCmp,addr stProcess.szExeFile,lpAppName
        .if eax
               jmp @Find
        .endif
        invoke Process32Next,hSnapShot,addr stProcess
    .endw
        mov eax,0               ;如果没发现我们的目标就老老实实的上报情况吧
        mov dwReturn,eax
        jmp @Ret
  @Find:  
        mov eax,stProcess.th32ProcessID
        invoke OpenProcess ,PROCESS_ALL_ACCESS,0,eax ;如果发现了,我们就以所有权限打开他
        mov  dwReturn,eax ;eax中就是我们要操作的进程的句柄了,这里也应该考虑eax=0的情况
  @Ret:
        invoke CloseHandle,hSnapShot
        mov eax,dwReturn
        ret
  ffGetProcHandle endp
  
其中的ffStringCmp是我自己写的字符串比较函数。说起来惭愧,因为初学asm,有都不知道怎么比较两个字符串,想了半天不知道怎么解决,只好硬着头皮写了一个。现在想来好像api中就有这样的函数。下面也把它贴出来,希望高手予以指点。
  
  ffStringCmp proc uses esi edi ,lpstring1,lpstring2
    
    mov esi,lpstring1
    xor ecx,ecx
    .repeat
      lodsb
      inc ecx
    .until  !al         ;因为不知道字符串的size,所以这里还煞费了我一番苦心
    mov esi,lpstring1
    mov edi,lpstring2
    repz cmpsb 
    jnz @No
    mov eax,1
    jmp @Ret
  @No:
    mov eax,0
  @Ret:
    
    ret
  ffStringCmp endp
  
好了,第一部分就算完成了。

二、搜取进程的导入表,找到指定的API。

我们有了指定进程的句柄,下面就要拿这个句柄开刀了。谁来主刀呢,下面有请:WriteProcessMemory和ReadProcessMemory兄弟俩!(来点掌声鼓励,这两个人还是比较有名气的)
看名字就知道这兄弟俩是用来读、写指定进程的地址空间数据的。手术刀有了,下面我们来了解一下我们的目标的内部结构吧:
  一般情况下,PE文件总能很顺利的被载入到默认的位置上。PE的文件头部分是按原样搬进内存的,所以我们可以从文件头下手找到我们感兴趣的部分。介绍PE头格式的文章很多,都介绍得比较详细。可是其中我们感兴趣的部分并不多。仔细分析完PE格式,你就会发现其实他并没有表面上看上去那么复杂。好了,废话少说,PE头中包含的我们感兴趣的部分有:节的数目、大小、每个节的RVA、输入表的RVA、输出表的RVA等等,我们总能够在固定的位置上发现这些我们感兴趣的东西。好像又要跑题,我们现在感兴趣的地方是输入表,所以我们的矛头要快速的指向他。
如果一个程序被顺利的载入到了默认地址,那么我们就可以在这个地址上发现这个文件的PE头。(大多数情况下,这个地址是00400000,同样这个值我们可以在头中发现他。)PE头以一个标志性的“MZ”开头,在从文件头开始的偏移为3ch处,藏着另一个重要的偏移,这个偏移指向的是另一个标志——“PE”。如果发现这两个标志我们都对上了,我们就有99%的把握说,我们发现了载入内存中文件头。
从“PE”开始偏移80h处,藏着我们今天要挨刀的主角,输入表的RVA。顺着这个RVA,我们就可以发现我们的主角。我们有必要隆重的介绍一下我们的主角:
我们的主角由一系列的IMAGE_IMPORT_DESCRIPTOR结构组成,这个结构有5个成员:OriginalFirstThunk 、TimeDateStamp 、ForwarderChain 、Name1、FirstThunk。简而言之,每个结构对应了一个导入库的信息。这个程序用到了多少DLL库,就有多少个IMAGE_IMPORT_DESCRIPTOR结构与之相对应。这一系列的结构以一个全零的IMAGE_IMPORT_DESCRIPTOR结构结束。
OriginalFirstThunk 、FirstThunk 同样是一个RVA,两个RVA又分别指向了一系列的双字结构。这一系列的双字结构同样以一个全零的双字作为结构的结束。一个DLL中导入了多少函数,就会有多少个这样的双字结构与之对应。文件中的OriginalFirstThunk 、FirstThunk指向的位置存放的仍然是个RVA(如果导入函数是按照名称导入的话),他们又同时指向了一个结构,这个结构存放的就是导入函数的序号和函数名。载入内存中的FirstThunk所指向的位置存放的就是导入函数的实际地址组成的序列。这个序列与OriginalFirstThunk最终所指向的函数名序列相对应。
我们的任务,就是在所有的IMAGE_IMPORT_DESCRIPTOR结构中搜索OriginalFirstThunk所指向的函数名,如果发现了我们要找的函数,再找到与之对应的FirstThunk所指向的地址,我们就找到了我们要动手术的地方了。
好了,同样以一个函数实现我们的目标。

.data
DOS_HEADER equ 00400000h         ;我们估且认为是这个默认值,其实为了安全起见,我们应该在PE头是把这个值读出来。

.code
ffFindApi proc hProcess , lpApiName;传来的参数为目标进程的句柄和指向API名字的指针
    local BufferW:WORD
    local lpPEheader:DWORD
    local BufferDW:DWORD
    local Import_VirtualAddress:DWORD
    local Import_Size:DWORD
    local szBuffer[512]:byte
    local szName[64]:byte
    local Import_FirstThunk:DWORD
    local Import_OriginalFirstThunk:DWORD
    
    invoke ReadProcessMemory,hProcess,DOS_HEADER,addr BufferW,2,NULL
    .if BufferW!='ZM'             ;比较这个位置上放的是不是著名的“MZ”
      jmp @Error
    .endif
    invoke ReadProcessMemory,hProcess,DOS_HEADER+3ch,addr BufferW,2,NULL
    .if !eax             ;读取偏移3ch处的值,这个值是个偏移
      jmp @Error          ;这个偏移处应该放着另一个著名的标志。
    .endif
    movzx eax,BufferW
    add eax,DOS_HEADER
    mov lpPEheader,eax        
    invoke ReadProcessMemory,hProcess,lpPEheader,addr BufferW,2,NULL
    .if BufferW!='EP'        ;看看是不是著名的“PE”
      jmp @Error
    .endif
    mov edx,lpPEheader
    add edx,80h            ;如果一切顺利,这里存放的一个双字就是我们要找的输入表
    invoke ReadProcessMemory,hProcess,edx,addr BufferDW,4,NULL ;的偏移,紧接着的一个双字
    .if !eax                                   ;是输入表的大小
      jmp @Error
    .endif
    mov eax,BufferDW
    mov Import_VirtualAddress,eax
    
    mov edx,lpPEheader
    add edx,84h                         ;这个位置存放的就是输入表的大小
    invoke ReadProcessMemory,hProcess,edx,addr BufferDW,4,NULL
    .if !eax
      jmp @Error
    .endif
    mov eax,BufferDW
    mov Import_Size,eax
    
    mov edx,Import_VirtualAddress
    add edx,DOS_HEADER
    
    invoke ReadProcessMemory,hProcess,edx,addr szBuffer,Import_Size,NULL
    .if !eax             ;将输入表读入我们预留的位置,以便于我们操作(千万别大于512),
      jmp @Error       ;因为我们给他留的地方就那么大,再多就住不下啦。:)
    .endif
    lea edi,szBuffer
    assume edi:ptr IMAGE_IMPORT_DESCRIPTOR
    
    .while  [edi].OriginalFirstThunk || [edi].TimeDateStamp || [edi].ForwarderChain || [edi].Name1 || [edi].FirstThunk  
      .if [edi].OriginalFirstThunk!=0
        mov edx,[edi].OriginalFirstThunk
        add edx,DOS_HEADER        
        mov Import_OriginalFirstThunk,edx
        mov edx,[edi].FirstThunk
        add edx,DOS_HEADER
        mov Import_FirstThunk,edx
        mov edx,[edi].OriginalFirstThunk      
        add edx,DOS_HEADER
        invoke ReadProcessMemory,hProcess,edx,addr BufferDW,4,NULL
        .if !eax
          jmp @Error
        .endif
        .if BufferDW & IMAGE_ORDINAL_FLAG32     ;看是不是以序号导出的,是的话我们什么
        .else                     ;也不做,因为我们不关心他
          .while BufferDW
            mov edx,BufferDW
            add edx,2                ;因为前两个字节代表的是函数序号,我们关心的
            add edx,DOS_HEADER       ;是紧随其后的函数名
            invoke ReadProcessMemory,hProcess,edx,addr szName,64,NULL
            .if !eax  ;名字大于64同样会住不下:),希望能够他们住。
              jmp @Error
            .endif
            invoke ffStringCmp, addr szName ,lpApiName
                                      ;比较是否和我们要找的名字相同
            .if eax
              mov eax,Import_FirstThunk
              jmp @Ret                ;如果找到就返回这个函数地址在内存的RVA
            .endif
            assume ebx:nothing    ;没找到就读取序列中的下一个继续比较
            mov eax,Import_FirstThunk
            add eax,4
            mov Import_FirstThunk,eax
            mov eax,Import_OriginalFirstThunk
            add eax,4
            mov Import_OriginalFirstThunk,eax
            invoke ReadProcessMemory,hProcess,Import_OriginalFirstThunk,addr BufferDW,4,NULL
            .if !eax
              jmp @Error
            .endif
          .endw  
        .endif
      .endif
      
      add edi,sizeof IMAGE_IMPORT_DESCRIPTOR   ;还没找到就找另一个DLL
    .endw  
    
@Error:
    mov eax,0
@Ret:
    assume ebx:nothing
    assume edi:nothing
    ret
ffFindApi endp
至此,第二任务完成。
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!(未完,待续)

[课程]Linux pwn 探索篇!

收藏
免费 7
支持
分享
最新回复 (5)
雪    币: 134
活跃值: (84)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
2
很不错,谢谢学习了。
2008-2-25 15:40
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
想懂,还得继续努力
2008-2-27 20:10
0
雪    币: 14
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
顶下,下载收藏!
2008-2-28 20:01
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
好文章

顶楼主

谢谢
2008-3-17 18:38
0
雪    币: 334
活跃值: (47)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
不懂
2008-3-18 10:59
0
游客
登录 | 注册 方可回帖
返回
//