WIN32汇编语言教程:第17章 PE文件 · 17.6 应用实例(1)

本章的前5节介绍了PE文件的结构,所举的例子只涉及对PE文件的静态分析,但在实际的应用中还有很多其他方面的内容,比如对PE文件加密、压缩,编写杀毒软件等都涉及修改及重组PE文件,另外,象API Hook,PE文件的内存映像Dump等应用则涉及分析内存中的PE映像。

本节将用另外的两个例子来简单说明这些方面的应用,17.6.1节将演示如何从内存中动态获取某个API的地址;17.6.2节将演示如何在PE文件上添加一段可执行代码。

17.6.1 动态获取API入口地址

学习这个例子是为了加深对PE文件到内存的映射和使用导出表这两方面的知识的理解。

在Win32环境下编程,不使用API几乎是不可能的事情,一般情况下,在代码中使用API不外乎两种办法:第一是编译链接的时候使用导入库,那么生成的PE文件中就会包含导入表,这样程序执行时会由Windows装载器根据导入表中的信息来修正API调用语句中的地址;第二种方法是使用LoadLibrary函数动态装入某个DLL模块,并使用GetProcAddress函数从被装入的模块中获取API函数的地址。

假如有一段代码由于特殊的原因无法(或者实现的难度很大)在PE文件中使用导入表,比如17.6.2节中被加到其他PE文件上的代码就是如此,在这种代码中,如何使用API函数呢?有人可能会说,那就用第二种办法好了!听起来不错,但这里有一个“先有鸡还是先有蛋”的问题,固然所有的API函数都可以用LoadLibrary函数和GetProcAddress函数配合来动态获取,但这两个函数本身也是API,又怎样首先得到它们的地址呢?

本节的内容讲述如何用一种变通的办法来解决这个问题。

1. 原理

在DOS环境下,一个可执行文件既可以用INT 21h/4ch来结束程序,也可以用一个Ret指令来结束程序,实际上,在Win32下也可以用这种方法来结束程序,虽然大部分的Win32程序都使用ExitProcess函数来终止执行,但是使用Ret指令确实也是有效的。

如图17.9所示,当父进程要创建一个子进程的时候,它会调用Kernel32.dll中的CreateProcess函数,CreateProcess函数在完成装载应用程序后,会将一个返回地址压入堆栈并转而执行应用程序,如果应用程序用ExitProcess函数来终止,那么这个返回地址没有什么用途,但如果应用程序使用Ret指令的话,程序就会返回CreateProcess函数设定的地址。也就是说,应用程序的主程序可以看作是被Windows调用的一个子程序。


图17.9 Win32可执行文件退出的示意图

那么Ret指令返回到的地址上究竟有什么指令呢?用Soft-ICE看看就会发现,它包含一句push eax指令和一句call ExitThread,也就是说,假如用Ret指令返回的话,Windows会替程序去调用ExitThread函数,如果这是进程的最后一个线程的话,ExitThread函数又会自动去调用ExitProcess,这样程序就会被终止执行。

从这个过程可以得到一个很重要的数据,那就是堆栈中的返回地址,这个地址只要在程序入口的地方用[esp]就可以将它读出,说它重要是因为它位于Kernel32.dll模块中,而LoadLibrary和GetProcAddress函数正是处于Kernel32.dll模块中,换句话说就是,我们得到的地址和这两个函数近在咫尺,完全可以从这个地址经过某种算法来找到这两个函数的入口地址,得到这两个函数的入口地址以后,什么问题都解决了。

结合本章前面内容中提到过的两个事实,可以确定这种想法是可行的。

首先,PE文件被装入内存后(包括Kernel32.dll文件),除了一些可丢弃的节如重定位节以外,其他的内容都会被装入内存,这样获取导出函数地址所需的PE文件头、导出表等数据都存在于内存中;第二,PE文件被装入内存时是按内存页对齐的,只要从Ret指令返回的地址按照页对齐的边界一页页地向低地址搜寻,就必然可以找到Kernel32.dll文件的文件头位置。

好了,有了Kernel32.dll的基址,接下来的事情就是按照17.3.1节的第4点所列的过程去操作了!

2. 例子

例子演示了上面所设想的功能,全部的源代码包含在本书所附光盘的Chapter17\NoImport目录中,主程序NoImport.asm的内容如下:

                      .386
                      .model flat,stdcall
                      option casemap:none
;####################################################################
include           windows.inc
 
_ProtoGetProcAddress      typedef proto  :dword,:dword
_ProtoLoadLibrary     typedef proto  :dword
_ProtoMessageBox   typedef proto  :dword,:dword,:dword,:dword
_ApiGetProcAddress    typedef ptr    _ProtoGetProcAddress
_ApiLoadLibrary       typedef ptr    _ProtoLoadLibrary
_ApiMessageBox        typedef ptr    _ProtoMessageBox
;####################################################################
; 数据段
;####################################################################
                  .data?
hDllKernel32      dd ?
hDllUser32    dd ?
_GetProcAddress _ApiGetProcAddress ?
_LoadLibrary      _ApiLoadLibrary    ?
_MessageBox    _ApiMessageBox     ?
 
                  .const
szLoadLibrary  db    'LoadLibraryA',0
szGetProcAddress db    'GetProcAddress',0
szUser32   db    'user32',0
szMessageBox      db    'MessageBoxA',0
 
szCaption     db    'A MessageBox !',0
szText        db    'Hello, Win32 ASM !',0
;####################################################################
; 代码段
;####################################################################
                  .code
include       _GetKernel.asm
start:
;********************************************************************
; 从堆栈中的 Ret 地址转换 Kernel32.dll 的基址,并在 Kernel32.dll
; 的导出表中查找 GetProcAddress 函数的入口地址
;********************************************************************
                  invoke _GetKernelBase,[esp]
       .          .if    eax
                         mov    hDllKernel32,eax
                          invoke _GetApi,hDllKernel32,addr szGetProcAddress
                          mov _GetProcAddress,eax
                  .endif
;********************************************************************
; 用得到的 GetProcAddress 函数得到 LoadLibrary 函数地址并装入其他 Dll
;********************************************************************
                  .if    _GetProcAddress
                         invoke _GetProcAddress,hDllKernel32,\
                                 addr szLoadLibrary
                          mov    _LoadLibrary,eax
                          .if    eax
                                 invoke _LoadLibrary,addr szUser32
                                 mov    hDllUser32,eax
                                  invoke _GetProcAddress,hDllUser32,\
                                         addr szMessageBox
                                 mov    _MessageBox,eax
                          .endif
                  .endif
;********************************************************************
                  .if    _MessageBox
                          invoke _MessageBox,NULL,offset szText,\
                                  offset szCaption,MB_OK
                  .endif
                  ret
;####################################################################
                  end    start

上页:第17章 PE文件 · 17.5 重定位表(2) 下页:第17章 PE文件 · 17.6 应用实例(2)

第17章 PE文件

版权所有 © 中山市飞娥软件工作室 证书:粤ICP备09170368号