科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网安全频道一步一步教你加密解密技术——压缩与脱壳(1)

一步一步教你加密解密技术——压缩与脱壳(1)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

PE 的意思就是 Portable Executable(可移植的执行体)。它是 Win32环境自身所带的执行体文件格式。它的一些特性继承自 Unix的 Coff (common object file format)文件格式。

作者:看雪 来源:看雪 2008年10月16日

关键字: 压缩 脱壳 加密解密

  • 评论
  • 分享微博
  • 分享邮件

在本页阅读全文(共2页)

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

  理论:

  首先,您得了解什么是引入函数。一个引入函数是被某模块调用的但又不在调用者模块中的函数,因而命名为"import(引入)"。引入函数实际位于一个或者更多的DLL里。调用者模块里只保留一些函数信息,包括函数名及其驻留的DLL名。现在,我们怎样才能找到PE文件中保存的信息呢? 转到data directory寻求答案吧。再回顾一把,下面就是 PE header:

  IMAGE_NT_HEADERS STRUCT

  Signature dd ?

  FileHeader IMAGE_FILE_HEADER <>

  OptionalHeader IMAGE_OPTIONAL_HEADER <>

  IMAGE_NT_HEADERS ENDS

  optional header 最后一个成员就是 data directory(数据目录):

  IMAGE_OPTIONAL_HEADER32 STRUCT

  ....

  LoaderFlags dd ?

  NumberOfRvaAndSizes dd ?

  DataDirectory IMAGE_DATA_DIRECTORY 16 dup(<>)

  IMAGE_OPTIONAL_HEADER32 ENDS

  data directory 是一个 IMAGE_DATA_DIRECTORY结构数组,共有16个成员。如果您还记得节表可以看作是PE文件各节的根目录的话,也可以认为 data directory 是存储在这些节里的逻辑元素的根目录。明确点,data directory 包含了PE文件中各重要数据结构的位置和尺寸信息。 每个成员包含了一个重要数据结构的信息。

  

Member Info inside
0 Export symbols
1 Import symbols
2 Resources
3 Exception
4 Security
5 Base relocation
6 Debug
7 Copyright string
8 Unknown
9 Thread local storage (TLS)
10 Load configuration
11 Bound Import
12 Import Address Table
13 Delay Import
14 COM descriptor

  上面那些金色显示的是我熟悉的。了解 data directory 包含域后,我们可以仔细研究它们了。data directory 的每个成员都是 IMAGE_DATA_DIRECTORY结构类型的,其定义如下所示:

  IMAGE_DATA_DIRECTORY STRUCT

  VirtualAddress dd ?

  isize dd ?

  IMAGE_DATA_DIRECTORY ENDS

  VirtualAddress实际上是数据结构的相对虚拟地址(RVA)。比如,如果该结构是关于import symbols的,该域就包含指向IMAGE_IMPORT_DESCRIPTOR 数组的RVA。

  isize 含有VirtualAddress所指向数据结构的字节数。

  下面就是如何找寻PE文件中重要数据结构的一般方法:

  从 DOS header 定位到 PE header

  从 optional header 读取 data directory 的地址。

  IMAGE_DATA_DIRECTORY 结构尺寸乘上找寻结构的索引号: 比如您要找寻import symbols的位置信息,必须用IMAGE_DATA_DIRECTORY结构尺寸(8 bytes)乘上1(import symbols在data directory中的索引号)。

  将上面的结果加上data directory地址,我们就得到包含所查询数据结构信息的 IMAGE_DATA_DIRECTORY结构项。

  现在我们开始真正讨论引入表了。data directory数组第二项的VirtualAddress包含引入表地址。引入表实际上是一个 IMAGE_IMPORT_DESCRIPTOR结构数组。每个结构包含PE文件引入函数的一个相关DLL的信息。比如,如果该PE文件从10个不同的DLL中引入函数,那么这个数组就有10个成员。该数组以一个全0的成员结尾。下面详细研究结构组成:

  IMAGE_IMPORT_DESCRIPTOR STRUCT

  union

  Characteristics dd ?

  OriginalFirstThunk dd ?

  ends

  TimeDateStamp dd ?

  ForwarderChain dd ?

  Name1 dd ?

  FirstThunk dd ?

  IMAGE_IMPORT_DESCRIPTOR ENDS

  结构第一项是一个union子结构。 事实上,这个union子结构只是给 OriginalFirstThunk增添了个别名,您也可以称其为"Characteristics"。 该成员项含有指向一个 IMAGE_THUNK_DATA结构数组的RVA。

  什么是 IMAGE_THUNK_DATA? 这是一个dword类型的集合。通常我们将其解释为指向一个 IMAGE_IMPORT_BY_NAME结构的指针。注意 IMAGE_THUNK_DATA包含了指向一个 IMAGE_IMPORT_BY_NAME结构的指针: 而不是结构本身。

  请看这里: 现有几个 IMAGE_IMPORT_BY_NAME结构,我们收集起这些结构的RVA (IMAGE_THUNK_DATAs)组成一个数组,并以0结尾,然后再将数组的RVA放入 OriginalFirstThunk

  此 IMAGE_IMPORT_BY_NAME结构存有一个引入函数的相关信息。再来研究 IMAGE_IMPORT_BY_NAME结构到底是什么样子的呢:

  IMAGE_IMPORT_BY_NAME STRUCT

  Hint dw ?

  Name1 db ?

  IMAGE_IMPORT_BY_NAME ENDS

  Hint 指示本函数在其所驻留DLL的引出表中的索引号。该域被PE装载器用来在DLL的引出表里快速查询函数。该值不是必须的,一些连接器将此值设为0。

  Name1含有引入函数的函数名。函数名是一个ASCIIZ字符串。注意这里虽然将Name1的大小定义成字节,其实它是可变尺寸域,只不过我们没有更好方法来表示结构中的可变尺寸域。The structure is provided so that you can refer to the data structure with descriptive names.

  TimeDateStampForwarderChain可是高级东东: 让我们精通其他成员后再来讨论它们吧。

  Name1含有指向DLL名字的RVA,即指向DLL名字的指针,也是一个ASCIIZ字符串。

  FirstThunkOriginalFirstThunk非常相似,它也包含指向一个 IMAGE_THUNK_DATA结构数组的RVA(当然这是另外一个IMAGE_THUNK_DATA结构数组)。

  好了,如果您还在犯糊涂,就朝这边看过来: 现在有几个 IMAGE_IMPORT_BY_NAME 结构,同时您又创建了两个结构数组,并同样寸入指向那些 IMAGE_IMPORT_BY_NAME结构的RVAs,这样两个数组就包含相同数值了(可谓相当精确的复制啊)。 最后您决定将第一个数组的RVA赋给 OriginalFirstThunk第二个数组的RVA赋给 FirstThunk,这样一切都很清楚了。

  OriginalFirstThunk   IMAGE_IMPORT_BY_NAME   FirstThunk

  |       |

  

IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
...
IMAGE_THUNK_DATA

  --->

  --->

  --->

  --->

  --->

  --->

  

Function 1
Function 2
Function 3
Function 4
...
Function n

  <---

<  <---

<  <---

<  <---

<  <---

<  <---

IMAGE_THUNK_DATA

IMAGE_THUNK_DATA

IMAGE_THUNK_DATA

IMAGE_THUNK_DATA

...

IMAGE_THUNK_DATA

现在您应该明白我的意思。不要被<  IMAGE_THUNK_DATA

  IMAGE_THUNK_DATA

  IMAGE_THUNK_DATA

  IMAGE_THUNK_DATA

  ...

  IMAGE_THUNK_DATA

  现在您应该明白我的意思。不要被IMAGE_THUNK_DATA这个名字弄糊涂: 它仅是指向 IMAGE_IMPORT_BY_NAME 结构的RVA。 如果将 IMAGE_THUNK_DATA字眼想象成RVA,就更容易明白了。OriginalFirstThunkFirstThunk所指向的这两个数组大小取决于PE文件从DLL中引入函数的数目。比如,如果PE文件从kernel32.dll中引入10个函数,那么IMAGE_IMPORT_DESCRIPTOR结构的 Name1域包含指向字符串"kernel32.dll"的RVA,同时每个IMAGE_THUNK_DATA数组有10个元素。

  下一个问题是: 为什么我们需要两个完全相同的数组? 为了回答该问题,我们需要了解当PE文件被装载到内存时,PE装载器将查找IMAGE_THUNK_DATAIMAGE_IMPORT_BY_NAME这些结构数组,以此决定引入函数的地址。然后用引入函数真实地址来替代由FirstThunk指向的IMAGE_THUNK_DATA数组里的元素值。因此当PE文件准备执行时,上图已转换成:

  OriginalFirstThunk   IMAGE_IMPORT_BY_NAME   FirstThunk

  |       |

  

IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
...
IMAGE_THUNK_DATA

  --->

  --->

  --->

  --->

  --->

  --->

  

Function 1
Function 2
Function 3
Function 4
...
Function n

  

Address of Function 1
Address of Function 2
Address of Function 3
Address of Function 4
...
Address of Function n

  由OriginalFirstThunk指向的RVA数组始终不会改变,所以若还反过头来查找引入函数名,PE装载器还能找寻到。

  当然再简单的事物都有其复杂的一面。有些情况下一些函数仅由序数引出,也就是说您不能用函数名来调用它们: 您只能用它们的位置来调用。此时,调用者模块中就不存在该函数的IMAGE_IMPORT_BY_NAME结构。不同的,对应该函数的 IMAGE_THUNK_DATA值的低位字指示函数序数,而最高二进位 (MSB)设为1。例如,如果一个函数只由序数引出且其序数是1234h,那么对应该函数的 IMAGE_THUNK_DATA值是80001234h。Microsoft提供了一个方便的常量来测试dword值的MSB位,就是 IMAGE_ORDINAL_FLAG32,其值为80000000h。

  假设我们要列出某个PE文件的所有引入函数,可以照着下面步骤走:

  校验文件是否是有效的PE。

  从 DOS header 定位到 PE header。

  获取位于 OptionalHeader 数据目录地址。

  转至数据目录的第二个成员提取其VirtualAddress值。

  利用上值定位第一个 IMAGE_IMPORT_DESCRIPTOR结构。

  检查 OriginalFirstThunk值。若不为0,顺着 OriginalFirstThunk里的RVA值转入那个RVA数组。若 OriginalFirstThunk为0,就改用FirstThunk值。有些连接器生成PE文件时会置OriginalFirstThunk值为0,这应该算是个bug。不过为了安全起见,我们还是检查 OriginalFirstThunk值先。

  对于每个数组元素,我们比对元素值是否等于IMAGE_ORDINAL_FLAG32如果该元素值的最高二进位为1, 那么函数是由序数引入的,可以从该值的低字节提取序数。

  如果元素值的最高二进位为0,就可将该值作为RVA转入 IMAGE_IMPORT_BY_NAME数组,跳过 Hint就是函数名字了。

  再跳至下一个数组元素提取函数名一直到数组底部(它以null结尾)。现在我们已遍历完一个DLL的引入函数,接下去处理下一个DLL。

  即跳转到下一个 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=-1和lParam=0的EM_SETSEL消息,使光标定位到文本末。

  mov edi,pNTHdr

  assume edi:ptr IMAGE_NT_HEADERS

  mov edi, [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].VirtualAddress

  获取import symbols的RVA。edi起初指向 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的MSB是0,那么它包含了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。

  摘要:PE 的意思就是 Portable Executable(可移植的执行体)。它是 Win32环境自身所带的执行体文件格式。它的一些特性继承自 Unix的 Coff (common object file format)文件格式。

  标签:加密 解密 crack 破解

  PE教程7: Export Table(引出表)

  理论:

  当PE装载器执行一个程序,它将相关DLLs都装入该进程的地址空间。然后根据主程序的引入函数信息,查找相关DLLs中的真实函数地址来修正主程序。PE装载器搜寻的是DLLs中的引出函数。

  DLL/EXE要引出一个函数给其他DLL/EXE使用,有两种实现方法: 通过函数名引出或者仅仅通过序数引出。比如某个DLL要引出名为"GetSysConfig"的函数,如果它以函数名引出,那么其他DLLs/EXEs若要调用这个函数,必须通过函数名,就是GetSysConfig。另外一个办法就是通过序数引出。什么是序数呢? 序数是唯一指定DLL中某个函数的16位数字,在所指向的DLL里是独一无二的。例如在上例中,DLL可以选择通过序数引出,假设是16,那么其他DLLs/EXEs若要调用这个函数必须以该值作为GetProcAddress调用参数。这就是所谓的仅仅靠序数引出。

  我们不提倡仅仅通过序数引出函数这种方法,这会带来DLL维护上的问题。一旦DLL升级/修改,程序员无法改变函数的序数,否则调用该DLL的其他程序都将无法工作。

  现在我们开始学习引出结构。象引出表一样,可以通过数据目录找到引出表的位置。这儿,引出表是数据目录的第一个成员,又可称为IMAGE_EXPORT_DIRECTORY。该结构中共有11 个成员,常用的列于下表。

  

Field Name Meaning
nName 模块的真实名称。本域是必须的,因为文件名可能会改变。这种情况下,PE装载器将使用这个内部名字。
nBase 基数,加上序数就是函数地址数组的索引值了。
NumberOfFunctions 模块引出的函数/符号总数。
NumberOfNames 通过名字引出的函数/符号数目。该值不是模块引出的函数/符号总数,这是由上面的NumberOfFunctions给出。本域可以为0,表示模块可能仅仅通过序数引出。如果模块根本不引出任何函数/符号,那么数据目录中引出表的RVA为0。
AddressOfFunctions 模块中有一个指向所有函数/符号的RVAs数组,本域就是指向该RVAs数组的RVA。简言之,模块中所有函数的RVAs都保存在一个数组里,本域就指向这个数组的首地址。
AddressOfNames 类似上个域,模块中有一个指向所有函数名的RVAs数组,本域就是指向该RVAs数组的RVA。
AddressOfNameOrdinals RVA,指向包含上述 AddressOfNames数组中相关函数之序数的16位数组。

  上面也许无法让您完全理解引出表,下面的简述将助您一臂之力。

  引出表的设计是为了方便PE装载器工作。首先,模块必须保存所有引出函数的地址以供PE装载器查询。模块将这些信息保存在AddressOfFunctions域指向的数组中,而数组元素数目存放在NumberOfFunctions域中。 因此,如果模块引出40个函数,则AddressOfFunctions指向的数组必定有40个元素,而NumberOfFunctions值为40。现在如果有一些函数是通过名字引出的,那么模块必定也在文件中保留了这些信息。这些 名字的RVAs存放在一数组中以供PE装载器查询。该数组由AddressOfNames指向,NumberOfNames包含名字数目。考虑一下PE装载器的工作机制,它知道函数名,并想以此获取这些函数的地址。至今为止,模块已有两个模块: 名字数组和地址数组,但两者之间还没有联系的纽带。因此我们还需要一些联系函数名及其地址的东东。PE参考指出使用到地址数组的索引作为联接,因此PE装载器在名字数组中找到匹配名字的同时,它也获取了指向地址表中对应元素的索引。 而这些索引保存在由AddressOfNameOrdinals域指向的另一个数组(最后一个)中。由于该数组是起了联系名字和地址的作用,所以其元素数目必定和名字数组相同,比如,每个名字有且仅有一个相关地址,反过来则不一定: 每个地址可以有好几个名字来对应。因此我们给同一个地址取"别名"。为了起到连接作用,名字数组和索引数组必须并行地成对使用,譬如,索引数组的第一个元素必定含有第一个名字的索引,以此类推。

  AddressOfNames   AddressOfNameOrdinals

  |   |

  

RVA of Name 1
RVA of Name 2
RVA of Name 3
RVA of Name 4
...
RVA of Name N

  <-->

  <-->

  <-->

  <-->

  ...

  <-->

  

Index of Name 1
Index of Name 2
Index of Name 3
Index of Name 4
...
Index of Name N

  下面举一两个例子说明问题。如果我们有了引出函数名并想以此获取地址,可以这么做:

  定位到PE header。

  从数据目录读取引出表的虚拟地址。

  定位引出表获取名字数目(NumberOfNames)。

  并行遍历AddressOfNamesAddressOfNameOrdinals指向的数组匹配名字。如果在AddressOfNames指向的数组中找到匹配名字,从AddressOfNameOrdinals指向的数组中提取索引值。例如,若发现匹配名字的RVA存放在AddressOfNames数组的第77个元素,那就提取AddressOfNameOrdinals数组的第77个元素作为索引值。如果遍历完NumberOfNames个元素,说明当前模块没有所要的名字。

  从AddressOfNameOrdinals数组提取的数值作为AddressOfFunctions数组的索引。也就是说,如果值是5,就必须读取AddressOfFunctions数组的第5个元素,此值就是所要函数的RVA。

  现在我们在把注意力转向IMAGE_EXPORT_DIRECTORY结构的nBase成员。您已经知道AddressOfFunctions数组包含了模块中所有引出符号的地址。当PE装载器索引该数组查询函数地址时,让我们设想这样一种情况,如果程序员在.def文件中设定起始序数号为200,这意味着AddressOfFunctions数组至少有200个元素,甚至这前面200个元素并没使用,但它们必须存在,因为PE装载器这样才能索引到正确的地址。这种方法很不好,所以又设计了nBase域解决这个问题。如果程序员指定起始序数号为200,nBase值也就是200。当PE装载器读取nBase域时,它知道开始200个元素并不存在,这样减掉一个nBase值后就可以正确地索引AddressOfFunctions数组了。有了nBase,就节约了200个空元素。

  注意nBase并不影响AddressOfNameOrdinals数组的值。尽管取名"AddressOfNameOrdinals",该数组实际包含的是指向AddressOfFunctions数组的索引,而不是什么序数啦。

  讨论完nBase的作用,我们继续下一个例子。

  假设我们只有函数的序数,那么怎样获取函数地址呢,可以这么做:

  定位到PE header。

  从数据目录读取引出表的虚拟地址。

  定位引出表获取nBase值。

  减掉nBase值得到指向AddressOfFunctions数组的索引。

  将该值与NumberOfFunctions作比较,大于等于后者则序数无效。

  通过上面的索引就可以获取AddressOfFunctions数组中的RVA了。

  可以看出,从序数获取函数地址比函数名快捷容易。不需要遍历AddressOfNamesAddressOfNameOrdinals这两个数组。然而,综合性能必须与模块维护的简易程度作一平衡。

  总之,如果想通过名字获取函数地址,需要遍历AddressOfNamesAddressOfNameOrdinals这两个数组。如果使用函数序数,减掉nBase值后就可直接索引AddressOfFunctions数组。

  如果一函数通过名字引出,那在GetProcAddress中可以使用名字或序数。但函数仅由序数引出情况又怎样呢? 现在就来看看。

  "一个函数仅由序数引出"意味着函数在AddressOfNames AddressOfNameOrdinals数组中不存在相关项。记住两个域,NumberOfFunctionsNumberOfNames。这两个域可以清楚地显示有时某些函数没有名字的。函数数目至少等同于名字数目,没有名字的函数通过序数引出。比如,如果存在70个函数但AddressOfNames数组中只有40项,这就意味着模块中有30个函数是仅通过序数引出的。现在我们怎样找出那些仅通过序数引出的函数呢?这不容易,必须通过排除法,比如,AddressOfFunctions的数组项在AddressOfNameOrdinals数组中不存在相关指向,这就说明该函数RVA只通过序数引出。

  示例:

  本例类似上课的范例。然而,在显示IMAGE_EXPORT_DIRECTORY结构一些成员信息的同时,也列出了引出函数的RVAs,序数和名字。注意本例没有列出仅由序数引出的函数。

  .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

  ShowExportFunctions proto :DWORD

  ShowTheFunctions proto :DWORD,:DWORD

  AppendText proto :DWORD,:DWORD

  SEH struct

  PrevLink dd ?

  CurrentHandler dd ?

  SafeOffset dd ?

  PrevEsp dd ?

  PrevEbp dd ?

  SEH ends

  .data

  AppName db "PE tutorial no.7",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

  NoExportTable db "No export information in this file",0

  CRLF db 0Dh,0Ah,0

  ExportTable db 0Dh,0Ah,"======[ IMAGE_EXPORT_DIRECTORY ]======",0Dh,0Ah

  db "Name of the module: %s",0Dh,0Ah

  db "nBase: %lu",0Dh,0Ah

  db "NumberOfFunctions: %lu",0Dh,0Ah

  db "NumberOfNames: %lu",0Dh,0Ah

  db "AddressOfFunctions: %lX",0Dh,0Ah

  db "AddressOfNames: %lX",0Dh,0Ah

  db "AddressOfNameOrdinals: %lX",0Dh,0Ah,0

  Header db "RVA Ord. Name",0Dh,0Ah

  db "----------------------------------------------",0

  template db "%lX %u %s",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 ShowExportFunctions,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

  ShowExportFunctions 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

  ShowExportFunctions 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

  RVAToFileMap 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

  .if edi>=[edx].VirtualAddress

  mov eax,[edx].VirtualAddress

  add eax,[edx].SizeOfRawData

  .if edi<eax

  mov eax,[edx].VirtualAddress

  sub edi,eax

  mov eax,[edx].PointerToRawData

  add eax,edi

  add eax,pFileMap

  ret

  .endif

  .endif

  add edx,sizeof IMAGE_SECTION_HEADER

  dec ecx

  .endw

  assume edx:nothing

  assume esi:nothing

  mov eax,edi

  ret

  RVAToFileMap endp

  ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORD

  LOCAL temp[512]:BYTE

  LOCAL NumberOfNames:DWORD

  LOCAL Base:DWORD

  mov edi,pNTHdr

  assume edi:ptr IMAGE_NT_HEADERS

  mov edi, [edi].OptionalHeader.DataDirectory.VirtualAddress

  .if edi==0

  invoke MessageBox,0, addr NoExportTable,addr AppName,MB_OK+MB_ICONERROR

  ret

  .endif

  invoke SetDlgItemText,hDlg,IDC_EDIT,0

  invoke AppendText,hDlg,addr buffer

  invoke RVAToFileMap,pMapping,edi

  mov edi,eax

  assume edi:ptr IMAGE_EXPORT_DIRECTORY

  mov eax,[edi].NumberOfFunctions

  invoke RVAToFileMap, pMapping,[edi].nName

  invoke wsprintf, addr temp,addr ExportTable, eax, [edi].nBase, [edi].NumberOfFunctions, [edi].NumberOfNames, [edi].AddressOfFunctions, [edi].AddressOfNames, [edi].AddressOfNameOrdinals

  invoke AppendText,hDlg,addr temp

  invoke AppendText,hDlg,addr Header

  push [edi].NumberOfNames

  pop NumberOfNames

  push [edi].nBase

  pop Base

  invoke RVAToFileMap,pMapping,[edi].AddressOfNames

  mov esi,eax

  invoke RVAToFileMap,pMapping,[edi].AddressOfNameOrdinals

  mov ebx,eax

  invoke RVAToFileMap,pMapping,[edi].AddressOfFunctions

  mov edi,eax

  .while NumberOfNames>0

  invoke RVAToFileMap,pMapping,dword ptr [esi]

  mov dx,[ebx]

  movzx edx,dx

  mov ecx,edx

  shl edx,2

  add edx,edi

  add ecx,Base

  invoke wsprintf, addr temp,addr template,dword ptr [edx],ecx,eax

  invoke AppendText,hDlg,addr temp

  dec NumberOfNames

  add esi,4

  add ebx,2

  .endw

  ret

  ShowTheFunctions endp

  end start

  分析:

  mov edi,pNTHdr

  assume edi:ptr IMAGE_NT_HEADERS

  mov edi, [edi].OptionalHeader.DataDirectory.VirtualAddress

  .if edi==0

  invoke MessageBox,0, addr NoExportTable,addr AppName,MB_OK+MB_ICONERROR

  ret

  .endif

  程序检验PE有效性后,定位到数据目录获取引出表的虚拟地址。若该虚拟地址为0,则文件不含引出符号。

  mov eax,[edi].NumberOfFunctions

  invoke RVAToFileMap, pMapping,[edi].nName

  invoke wsprintf, addr temp,addr ExportTable, eax, [edi].nBase, [edi].NumberOfFunctions, [edi].NumberOfNames, [edi].AddressOfFunctions, [edi].AddressOfNames, [edi].AddressOfNameOrdinals

  invoke AppendText,hDlg,addr temp

  在编辑控件中显示IMAGE_EXPORT_DIRECTORY结构的一些重要信息。

  push [edi].NumberOfNames

  pop NumberOfNames

  push [edi].nBase

  pop Base

  由于我们要枚举所有函数名,就要知道引出表里的名字数目。nBase在将AddressOfFunctions数组索引转换成序数时派到用场。

  invoke RVAToFileMap,pMapping,[edi].AddressOfNames

  mov esi,eax

  invoke RVAToFileMap,pMapping,[edi].AddressOfNameOrdinals

  mov ebx,eax

  invoke RVAToFileMap,pMapping,[edi].AddressOfFunctions

  mov edi,eax

  将三个数组的地址相应存放到esi,,ebx,edi中。准备开始访问。

  .while NumberOfNames>0

  直到所有名字都被处理完毕。

  invoke RVAToFileMap,pMapping,dword ptr [esi]

  由于esi指向包含名字字符串RVAs的数组,所以[esi]含有当前名字的RVA,需要将它转换成虚拟地址,后面wsprintf要用的。

  mov dx,[ebx]

  movzx edx,dx

  mov ecx,edx

  add ecx,Base

  ebx指向序数数组,值是字类型的。因此我们先要将其转换成双字,此时edx和ecx含有指向AddressOfFunctions数组的索引。我们用edx作为索引值,而将ecx加上nBase得到函数的序数值。=

  shl edx,2

  add edx,edi

  索引乘以4 (AddressOfFunctions数组中每个元素都是4字节大小) 然后加上数组首地址,这样edx指向的就是所要函数的RVA了。

  invoke wsprintf, addr temp,addr template,dword ptr [edx],ecx,eax

  invoke AppendText,hDlg,addr temp

  在编辑控件中显示函数的RVA, 序数, 和名字。

  dec NumberOfNames

  add esi,4

  add ebx,2

  .endw

  修正计数器,AddressOfNamesAddressOfNameOrdinals两数组的当前指针,继续遍历直到所有名字全都处理完毕。

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章