pe脱壳:压缩和脱壳-PE文件格式 5

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

  上课我们已经学习了动态联接中有关引入表那部分知识现在继续另外部分那就是引出表

理论 :   当PE装载器执行它将相关DLLs都装入该进程地址空间然后根据主引入信息查找相关DLLs中真实地址来修正主PE装载器搜寻是DLLs中引出

  DLL/EXE要引出给其他DLL/EXE使用有两种实现思路方法: 通过名引出或者仅仅通过序数引出比如某个DLL要引出名为"GetSysConfig"如果它以名引出那么其他DLLs/EXEs若要这个必须通过就是GetSysConfig另外个办法就是通过序数引出什么是序数呢? 序数是唯指定DLL中某个16位数字在所指向DLL里是独无 2例如在上例中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本域就是指向该RVAsRVA简言的模块中所有RVAs都保存在本域就指向这个首地址
AddressOfNames 类似上个域模块中有个指向所有RVAs本域就是指向该RVAsRVA
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 ) 并行遍历 AddressOfNames 和 AddressOfNameOrdinals 指向匹配名字如果在 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了   可以看出从序数获取地址比名快捷容易不需要遍历 AddressOfNames 和 AddressOfNameOrdinals 这两个然而综合性能必须和模块维护简易程度作平衡

  总的如果想通过名字获取地址需要遍历 AddressOfNames 和 AddressOfNameOrdinals 这两个如果使用序数减掉nBase值后就可直接索引 AddressOfFunctions

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

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

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

.386
.model flat,stdcall
option map:none
masm32windows.inc
masm32kernel32.inc
masm32comdlg32.inc
masm32user32.inc
lib masm32libuser32.lib
lib masm32libkernel32.lib
lib masm32libcomdlg32.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 ?
SafeOff 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 o 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
. uMsgWM_INITDIALOG
invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETLIMITTEXT,0,0
. uMsgWM_CLOSE
invoke EndDialog,hDlg,0
. uMsgWM_COMMAND
. lParam0
mov eax,wParam
. axIDM_OPEN
invoke ShowExportFunctions,hDlg
. ; IDM_EXIT
invoke SendMessage,hDlg,WM_CLOSE,0,0
.end
.end
.
mov eax,FALSE
ret
.end
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].SafeOff
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
. eaxTRUE
invoke CreateFile, addr buffer,
GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
. eax!=INVALID_HANDLE_VALUE
mov hFile, eax
invoke CreateFileMapping, hFile, NULL, PAGE_READONLY,0,0,0
. eax!=NULL
mov hMapping, eax
invoke MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0
. eax!=NULL
mov pMapping,eax
assume fs:nothing
push fs:[0]
pop seh.PrevLink
mov seh.CurrentHandler,off SEHHandler
mov seh.SafeOff,off 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
. [edi].e_magicIMAGE_DOS_SIGNATURE
add edi, [edi].e_lfa
assume edi:ptr IMAGE_NT_HEADERS
. [edi].SignatureIMAGE_NT_SIGNATURE
mov ValidPE, TRUE
.
mov ValidPE, FALSE
.end
.
mov ValidPE,FALSE
.end
FinalExit:
push seh.PrevLink
pop fs:[0]
. ValidPETRUE
invoke ShowTheFunctions, hDlg, edi
.
invoke MessageBox,0, addr NotValidPE, addr AppName, MB_OK+MB_ICONERROR
.end
invoke UnmapViewOfFile, pMapping
.
invoke MessageBox, 0, addr FileMappingError, addr AppName, MB_OK+MB_ICONERROR
.end
invoke CloseHandle,hMapping
.
invoke MessageBox, 0, addr FileOpenMappingError, addr AppName, MB_OK+MB_ICONERROR
.end
invoke CloseHandle, hFile
.
invoke MessageBox, 0, addr FileOpenError, addr AppName, MB_OK+MB_ICONERROR
.end
.end
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_lfa
assume esi:ptr IMAGE_NT_HEADERS
mov edi,RVA ; edi RVA
mov edx,esi
add edx, IMAGE_NT_HEADERS
mov cx,[esi].FileHeader.NumberOfSections
movzx ecx,cx
assume edx:ptr IMAGE_SECTION_HEADER
.while ecx>0
. edi>=[edx].VirtualAddress
mov eax,[edx].VirtualAddress
add eax,[edx].SizeOfRawData
. edi <eax
mov eax,[edx].VirtualAddress
sub edi,eax
mov eax,[edx].PoerToRawData
add eax,edi
add eax,pFileMap
ret
.end
.end
add edx, 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
. edi0
invoke MessageBox,0, addr NoExportTable,addr AppName,MB_OK+MB_ICONERROR
ret
.end
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 wsprf, 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 wsprf, 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
. edi0
invoke MessageBox,0, addr NoExportTable,addr AppName,MB_OK+MB_ICONERROR
ret
.end
  检验PE有效性后定位到数据目录获取引出表虚拟地址若该虚拟地址为0则文件不含引出符号

mov eax,[edi].NumberOfFunctions
invoke RVAToFileMap, pMapping,[edi].nName
invoke wsprf, addr temp,addr ExportTable,
eax, [edi].nBase, [edi].NumberOfFunctions, [edi].NumberOfNames, [edi].AddressOfFunctions, [edi].AddressOfNames, [edi].AddressOfNameOrdinals
invoke AppendText,hDlg,addr temp
  在编辑Control控件中显示 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
  将 3个地址相应存放到esi,ebxedi中准备开始访问

  .while NumberOfNames>0

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

  invoke RVAToFileMap,pMapping,dword ptr [esi]

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

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 wsprf, addr temp,addr template,dword ptr [edx],ecx,eax
invoke AppendText,hDlg,addr temp
  在编辑Control控件中显示RVA, 序数, 和名字

dec NumberOfNames
add esi,4
add ebx,2
.endw
  修正计数器 AddressOfNames 和 AddressOfNameOrdinals 两当前指针继续遍历直到所有名字全都处理完毕

Tags:  压缩文件是什么格式 压缩文件格式 pe文件格式 pe脱壳

延伸阅读

最新评论

发表评论