PE教程6: Import Table(引入表)二

OriginalFirstThunk 指向的RVA数组始终不会改变,所以若还反过头来查找引入函数名,PE装载器还能找寻到。 当然再简单的事物都有其复杂的一面。有些情况下一些函数仅由序数引出,也就是说您不能用函数名来调用它们: 您只能用它们的位置来调用。此时,调用者模块中就不存在该函数的 IMAGE_IMPORT_BY_NAME 结构。不同的,对应该函数的 IMAGE_THUNK_DATA 值的低位字指示函数序数,而最高二进位 (MSB)设为1。例如,如果一个函数只由序数引出且其序数是1234h,那么对应该函数的 IMAGE_THUNK_DATA 值是80001234hMicrosoft提供了一个方便的常量来测试dword值的MSB位,就是 IMAGE_ORDINAL_FLAG32,其值为80000000h。 假设我们要列出某个PE文件的所有引入函数,可以照着下面步骤走:

  1. 校验文件是否是有效的PE
  2. DOS header 定位到 PE header
  3. 获取位于 OptionalHeader 数据目录地址。
  4. 转至数据目录的第二个成员提取其VirtualAddress值。
  5. 利用上值定位第一个 IMAGE_IMPORT_DESCRIPTOR 结构。
  6. 检查 OriginalFirstThunk值。若不为0,顺着 OriginalFirstThunk 里的RVA值转入那个RVA数组。若 OriginalFirstThunk 0,就改用FirstThunk值。有些连接器生成PE文件时会置OriginalFirstThunk值为0,这应该算是个bug。不过为了安全起见,我们还是检查 OriginalFirstThunk值先。
  7. 对于每个数组元素,我们比对元素值是否等于IMAGE_ORDINAL_FLAG32如果该元素值的最高二进位为1, 那么函数是由序数引入的,可以从该值的低字节提取序数。
  8. 如果元素值的最高二进位为0,就可将该值作为RVA转入 IMAGE_IMPORT_BY_NAME 数组,跳过 Hint 就是函数名字了。
  9. 再跳至下一个数组元素提取函数名一直到数组底部(它以null结尾)。现在我们已遍历完一个DLL的引入函数,接下去处理下一个DLL
  10. 即跳转到下一个 IMAGE_IMPORT_DESCRIPTOR 并处理之,如此这般循环直到数组见底。(IMAGE_IMPORT_DESCRIPTOR 数组以一个全0域元素结尾)

示例:

本例程打开一PE文件,将所有引入函数名读入一编辑控件,同时显示 IMAGE_IMPORT_DESCRIPTOR 结构各域值。

.386 .model flat,stdcall option casemap:none include /masm32/include/windows.inc include /masm32/include/kernel32.inc include /masm32/include/comdlg32.inc include /masm32/include/user32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib includelib /masm32/lib/comdlg32.lib IDD_MAINDLG equ 101 IDC_EDIT equ 1000 IDM_OPEN equ 40001 IDM_EXIT equ 40003 DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD ShowImportFunctions proto :DWORD ShowTheFunctions proto :DWORD,:DWORD AppendText proto :DWORD,:DWORD SEH struct PrevLink dd ? ; the address of the previous seh structure CurrentHandler dd ? ; the address of the new exception handler SafeOffset dd ? ; The offset where it's safe to continue execution PrevEsp dd ? ; the old value in esp PrevEbp dd ? ; The old value in ebp SEH ends .data AppName db "PE tutorial no.6",0 ofn OPENFILENAME <> FilterString db "Executable Files (*.exe, *.dll)",0,"*.exe;*.dll",0              db "All Files",0,"*.*",0,0 FileOpenError db "Cannot open the file for reading",0 FileOpenMappingError db "Cannot open the file for memory mapping",0 FileMappingError db "Cannot map the file into memory",0 NotValidPE db "This file is not a valid PE",0 CRLF db 0Dh,0Ah,0 ImportDescriptor db 0Dh,0Ah,"================[ IMAGE_IMPORT_DESCRIPTOR ]=============",0 IDTemplate db "OriginalFirstThunk = %lX",0Dh,0Ah            db "TimeDateStamp = %lX",0Dh,0Ah            db "ForwarderChain = %lX",0Dh,0Ah            db "Name = %s",0Dh,0Ah            db "FirstThunk = %lX",0 NameHeader db 0Dh,0Ah,"Hint Function",0Dh,0Ah            db "-----------------------------------------",0 NameTemplate db "%u %s",0 OrdinalTemplate db "%u (ord.)",0 .data? buffer db 512 dup(?) hFile dd ? hMapping dd ? pMapping dd ? ValidPE dd ? .code start: invoke GetModuleHandle,NULL invoke DialogBoxParam, eax, IDD_MAINDLG,NULL,addr DlgProc, 0 invoke ExitProcess, 0 DlgProc proc hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD .if uMsg==WM_INITDIALOG invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETLIMITTEXT,0,0 .elseif uMsg==WM_CLOSE invoke EndDialog,hDlg,0 .elseif uMsg==WM_COMMAND .if lParam==0     mov eax,wParam     .if ax==IDM_OPEN       invoke ShowImportFunctions,hDlg     .else ; IDM_EXIT       invoke SendMessage,hDlg,WM_CLOSE,0,0     .endif    .endif .else mov eax,FALSE    ret .endif mov eax,TRUE ret DlgProc endp SEHHandler proc uses edx pExcept:DWORD, pFrame:DWORD, pContext:DWORD, pDispatch:DWORD    mov edx,pFrame    assume edx:ptr SEH    mov eax,pContext    assume eax:ptr CONTEXT    push [edx].SafeOffset    pop [eax].regEip    push [edx].PrevEsp    pop [eax].regEsp    push [edx].PrevEbp    pop [eax].regEbp    mov ValidPE, FALSE    mov eax,ExceptionContinueExecution    ret SEHHandler endp ShowImportFunctions proc uses edi hDlg:DWORD    LOCAL seh:SEH    mov ofn.lStructSize,SIZEOF ofn mov ofn.lpstrFilter, OFFSET FilterString mov ofn.lpstrFile, OFFSET buffer mov ofn.nMaxFile,512 mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY    invoke GetOpenFileName, ADDR ofn .if eax==TRUE     invoke CreateFile, addr buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL     .if eax!=INVALID_HANDLE_VALUE       mov hFile, eax       invoke CreateFileMapping, hFile, NULL, PAGE_READONLY,0,0,0       .if eax!=NULL         mov hMapping, eax         invoke MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0         .if eax!=NULL           mov pMapping,eax           assume fs:nothing           push fs:[0]           pop seh.PrevLink           mov seh.CurrentHandler,offset SEHHandler           mov seh.SafeOffset,offset FinalExit           lea eax,seh           mov fs:[0], eax           mov seh.PrevEsp,esp           mov seh.PrevEbp,ebp           mov edi, pMapping           assume edi:ptr IMAGE_DOS_HEADER           .if [edi].e_magic==IMAGE_DOS_SIGNATURE             add edi, [edi].e_lfanew             assume edi:ptr IMAGE_NT_HEADERS             .if [edi].Signature==IMAGE_NT_SIGNATURE                mov ValidPE, TRUE              .else               mov ValidPE, FALSE             .endif           .else             mov ValidPE,FALSE           .endif FinalExit:           push seh.PrevLink           pop fs:[0]           .if ValidPE==TRUE             invoke ShowTheFunctions, hDlg, edi           .else             invoke MessageBox,0, addr NotValidPE, addr AppName, MB_OK+MB_ICONERROR            .endif           invoke UnmapViewOfFile, pMapping        .else           invoke MessageBox, 0, addr FileMappingError, addr AppName, MB_OK+MB_ICONERROR        .endif        invoke CloseHandle,hMapping      .else       invoke MessageBox, 0, addr FileOpenMappingError, addr AppName, MB_OK+MB_ICONERROR     .endif     invoke CloseHandle, hFile    .else     invoke MessageBox, 0, addr FileOpenError, addr AppName, MB_OK+MB_ICONERROR    .endif .endif ret ShowImportFunctions endp AppendText proc hDlg:DWORD,pText:DWORD    invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,pText    invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,addr CRLF    invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETSEL,-1,0     ret AppendText endp RVAToOffset PROC uses edi esi edx ecx pFileMap:DWORD,RVA:DWORD    mov esi,pFileMap    assume esi:ptr IMAGE_DOS_HEADER    add esi,[esi].e_lfanew    assume esi:ptr IMAGE_NT_HEADERS    mov edi,RVA ; edi == RVA    mov edx,esi    add edx,sizeof IMAGE_NT_HEADERS    mov cx,[esi].FileHeader.NumberOfSections    movzx ecx,cx    assume edx:ptr IMAGE_SECTION_HEADER    .while ecx>0 ; check all sections      .if edi>=[edx].VirtualAddress        mov eax,[edx].VirtualAddress        add eax,[edx].SizeOfRawData        .if edi<eax ; The address is in this section           mov eax,[edx].VirtualAddress          sub edi,eax          mov eax,[edx].PointerToRawData          add eax,edi ; eax == file offset          ret        .endif      .endif      add edx,sizeof IMAGE_SECTION_HEADER      dec ecx    .endw    assume edx:nothing    assume esi:nothing    mov eax,edi    ret RVAToOffset endp ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORD    LOCAL temp[512]:BYTE    invoke SetDlgItemText,hDlg,IDC_EDIT,0    invoke AppendText,hDlg,addr buffer    mov edi,pNTHdr    assume edi:ptr IMAGE_NT_HEADERS    mov edi, [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].VirtualAddress    invoke RVAToOffset,pMapping,edi    mov edi,eax    add edi,pMapping    assume edi:ptr IMAGE_IMPORT_DESCRIPTOR    .while !([edi].OriginalFirstThunk==0 && [edi].TimeDateStamp==0 && [edi].ForwarderChain==0 && [edi].Name1==0 && [edi].FirstThunk==0)      invoke AppendText,hDlg,addr ImportDescriptor      invoke RVAToOffset,pMapping, [edi].Name1      mov edx,eax      add edx,pMapping      invoke wsprintf, addr temp, addr IDTemplate, [edi].OriginalFirstThunk,[edi].TimeDateStamp,[edi].ForwarderChain,edx,[edi].FirstThunk      invoke AppendText,hDlg,addr temp      .if [edi].OriginalFirstThunk==0         mov esi,[edi].FirstThunk      .else         mov esi,[edi].OriginalFirstThunk      .endif      invoke RVAToOffset,pMapping,esi      add eax,pMapping      mov esi,eax      invoke AppendText,hDlg,addr NameHeader      .while dword ptr [esi]!=0        test dword ptr [esi],IMAGE_ORDINAL_FLAG32        jnz ImportByOrdinal        invoke RVAToOffset,pMapping,dword ptr [esi]        mov edx,eax        add edx,pMapping        assume edx:ptr IMAGE_IMPORT_BY_NAME        mov cx, [edx].Hint        movzx ecx,cx        invoke wsprintf,addr temp,addr NameTemplate,ecx,addr [edx].Name1        jmp ShowTheText ImportByOrdinal:        mov edx,dword ptr [esi]        and edx,0FFFFh        invoke wsprintf,addr temp,addr OrdinalTemplate,edx ShowTheText:        invoke AppendText,hDlg,addr temp        add esi,4     .endw     add edi,sizeof IMAGE_IMPORT_DESCRIPTOR .endw    ret ShowTheFunctions endp end start

分析:

本例中,用户点击打开菜单显示文件打开对话框,检验文件的PE有效性后调用 ShowTheFunctions

ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORD    LOCAL temp[512]:BYTE

保留512字节堆栈空间用于字符串操作。

   invoke SetDlgItemText,hDlg,IDC_EDIT,0

清除编辑控件内容。

   invoke AppendText,hDlg,addr buffer

PE文件名插入编辑控件。 AppendText 通过传递一个 EM_REPLACESEL 消息以通知向编辑控件添加文本。然后它又向编辑控件发送一个设置了 wParam=-1lParam=0EM_SETSEL 消息,使光标定位到文本末。

   mov edi,pNTHdr    assume edi:ptr IMAGE_NT_HEADERS    mov edi, [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].VirtualAddress

获取import symbolsRVAedi起初指向 PE header,以此我们可以定位到数据目录数组的第二个数组元素来得到虚拟地址值。

   invoke RVAToOffset,pMapping,edi    mov edi,eax    add edi,pMapping

这儿对PE编程初学者来说可能有点困难。在PE文件中大多数地址多是RVAs 而 RVAs只有当PE文件被PE装载器装入内存后才有意义。 本例中,我们直接将文件映射到内存而不是通过PE装载器载入,因此我们不能直接使用那些RVAs。必须先将那些RVAs转换成文件偏移量,RVAToOffset函数就起到这个作用。 这里不准备详细分析。指出的是,它还将给定的RVA和PE文件所有节的始末RVA作比较(检验RVA的有效性),然后通过IMAGE_SECTION_HEADER 结构中的PointerToRawData域(当然是所在节的那个PointerToRawData域啦)将RVA转换成文件偏移量。 函数使用需要传递两个参数: 内存映射文件指针和所要转换的RVA。eax里返回文件偏移量。上面代码中,我们必须将文件偏移量加上内存映射文件指针以转换成虚拟地址。是不是有点复杂? :)

   assume edi:ptr IMAGE_IMPORT_DESCRIPTOR    .while !([edi].OriginalFirstThunk==0 && [edi].TimeDateStamp==0 && [edi].ForwarderChain==0 && [edi].Name1==0 && [edi].FirstThunk==0)

edi现在指向第一个 IMAGE_IMPORT_DESCRIPTOR 结构。接下来我们遍历整个结构数组直到遇上一个全0结构,这就是数组末尾了。

     invoke AppendText,hDlg,addr ImportDescriptor      invoke RVAToOffset,pMapping, [edi].Name1      mov edx,eax      add edx,pMapping

我们要显示当前 IMAGE_IMPORT_DESCRIPTOR 结构的值。Name1 不同于其他结构成员,它含有指向相关dll名的RVA。因此必须先将其转换成虚拟地址。

     invoke wsprintf, addr temp, addr IDTemplate, [edi].OriginalFirstThunk,[edi].TimeDateStamp,[edi].ForwarderChain,edx,[edi].FirstThunk      invoke AppendText,hDlg,addr temp

显示当前 IMAGE_IMPORT_DESCRIPTOR 结构的值。

     .if [edi].OriginalFirstThunk==0         mov esi,[edi].FirstThunk      .else         mov esi,[edi].OriginalFirstThunk      .endif

接下来准备遍历 IMAGE_THUNK_DATA 数组。通常我们会选择OriginalFirstThunk指向的那个数组,不过,如果某些连接器错误地将OriginalFirstThunk 0,这可以通过检查OriginalFirstThunk值是否为0判断。这样的话,只要选择FirstThunk指向的数组了。

     invoke RVAToOffset,pMapping,esi      add eax,pMapping      mov esi,eax

同样的,OriginalFirstThunk/FirstThunk值是一个RVA。必须将其转换为虚拟地址。

     invoke AppendText,hDlg,addr NameHeader      .while dword ptr [esi]!=0

现在我们准备遍历 IMAGE_THUNK_DATAs 数组以查找该DLL引入的函数名,直到遇上全0项。

       test dword ptr [esi],IMAGE_ORDINAL_FLAG32        jnz ImportByOrdinal

第一件事是校验IMAGE_THUNK_DATA 是否含有IMAGE_ORDINAL_FLAG32标记。检查IMAGE_THUNK_DATA MSB是否为1,如果是1,则函数是通过序数引出的,所以不需要更进一步处理了。直接从 IMAGE_THUNK_DATA 提取低字节获得序数,然后是下一个IMAGE_THUNK_DATA 双字。

       invoke RVAToOffset,pMapping,dword ptr [esi]        mov edx,eax        add edx,pMapping        assume edx:ptr IMAGE_IMPORT_BY_NAME

如果IMAGE_THUNK_DATA MSB0,那么它包含了IMAGE_IMPORT_BY_NAME 结构的RVA。需要先转换为虚拟地址。

       mov cx, [edx].Hint        movzx ecx,cx        invoke wsprintf,addr temp,addr NameTemplate,ecx,addr [edx].Name1        jmp ShowTheText

Hint 是字类型,所以先转换为双字后再传递给wsprintf,然后我们将hint和函数名都显示到编辑控件中。

ImportByOrdinal:        mov edx,dword ptr [esi]        and edx,0FFFFh        invoke wsprintf,addr temp,addr OrdinalTemplate,edx

在仅用序数引出函数的情况中,先清空高字再显示序数。

ShowTheText:        invoke AppendText,hDlg,addr temp        add esi,4

在编辑控件中插入相应的函数名/序数后,跳转到下个 IMAGE_THUNK_DATA

    .endw     add edi,sizeof IMAGE_IMPORT_DESCRIPTOR

处理完当前IMAGE_THUNK_DATA 数组里的所有双字,跳转到下个IMAGE_IMPORT_DESCRIPTOR 开始处理其他DLLs的引入函数了。

附录:

让我们再来讨论一下bound import。当PE装载器装入PE文件时,检查引入表并将相关DLLs映射到进程地址空间。然后象我们这样遍历IMAGE_THUNK_DATA 数组并用引入函数的真实地址替换IMAGE_THUNK_DATAs 值。这一步需要很多时间。如果程序员能事先正确预测函数地址,PE装载器就不用每次装入PE文件时都去修正IMAGE_THUNK_DATAs 值了。Bound import就是这种思想的产物。 为了方便实现,Microsoft出品的类似Visual Studio的编译器多提供了bind.exe这样的工具,由它检查PE文件的引入表并用引入函数的真实地址替换IMAGE_THUNK_DATA 值。当文件装入时,PE装载器必定检查地址的有效性,如果DLL版本不同于PE文件存放的相关信息,或则DLLs需要重定位,那么装载器认为原先计算的地址是无效的,它必定遍历OriginalFirstThunk指向的数组以获取引入函数新地址。 Bound import在本课中并非很重要,我们确省就是用到了OriginalFirstThunk。要了解更多信息可参见LUEVELSMEYERpe.txt

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值