ITPub博客

首页 > 大数据 > Hadoop > PE教程7: Export Table(软件加密技术内幕)

PE教程7: Export Table(软件加密技术内幕)

Hadoop 作者:yaozpok 时间:2013-11-26 12:58:00 0 删除 编辑

当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)。

并行遍历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,stdcalloption casemap:noneinclude masm32includewindows.incinclude masm32includekernel32.incinclude masm32includecomdlg32.incinclude masm32includeuser32.incincludelib masm32libuser32.libincludelib masm32libkernel32.libincludelib masm32libcomdlg32.libIDD_MAINDLG equ 101IDC_EDIT
equ 1000IDM_OPEN equ 40001IDM_EXIT equ 40003DlgProc proto
:DWORD,:DWORD,:DWORD,:DWORDShowExportFunctions proto :DWORDShowTheFunctions proto :DWORD,:DWORDAppendText proto :DWORD,:DWORD

SEH struct   PrevLink dd ?   CurrentHandler
dd ?   SafeOffset dd ?   PrevEsp dd ?   PrevEbp dd ?SEH ends.dataAppName db "PE tutorial no.7",0ofn OPENFILENAME <>FilterString db "Executable Files (*.exe, *.dll)",0,"*.exe;*.dll",0             db "All Files",0,"*.*",0,0FileOpenError db "Cannot open
the file for reading",0FileOpenMappingError db "Cannot open the file for
memory mapping",0FileMappingError db "Cannot map the file into memory",0NotValidPE db "This file is not a valid PE",0NoExportTable db "No
export information in this file",0CRLF db 0Dh,0Ah,0ExportTable 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,0Header db "RVA Ord. Name",0Dh,0Ah       db "----------------------------------------------",0template db
"%lX %u %s",0.data?buffer db 512 dup(?)hFile dd ?hMapping dd ?pMapping dd ?ValidPE dd ?.codestart:invoke GetModuleHandle,NULLinvoke DialogBoxParam, eax,
IDD_MAINDLG,NULL,addr DlgProc, 0invoke ExitProcess, 0DlgProc 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.endifmov eax,TRUEretDlgProc
endpSEHHandler proc uses edx pExcept:DWORD, pFrame:DWORD,
pContext:DWORD, pDispatch:DWORDmov edx,pFrameassume edx:ptr SEHmov eax,pContextassume eax:ptr CONTEXTpush [edx].SafeOffsetpop [eax].regEippush [edx].PrevEsppop [eax].regEsppush
[edx].PrevEbppop [eax].regEbpmov ValidPE, FALSEmov
eax,ExceptionContinueExecutionretSEHHandler endpShowExportFunctions proc uses edi hDlg:DWORDLOCAL seh:SEHmov
ofn.lStructSize,SIZEOF ofnmov ofn.lpstrFilter, OFFSET FilterStringmov
ofn.lpstrFile, OFFSET buffermov ofn.nMaxFile,512mov ofn.Flags,
OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or
OFN_HIDEREADONLYinvoke 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         .endifFinalExit:         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.endifretShowExportFunctions endpAppendText proc
hDlg:DWORD,pText:DWORDinvoke
SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,pTextinvoke
SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,addr CRLFinvoke
SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETSEL,-1,0retAppendText endpRVAToFileMap PROC uses edi esi edx ecx pFileMap:DWORD,RVA:DWORDmov
esi,pFileMapassume esi:ptr IMAGE_DOS_HEADERadd esi,[esi].e_lfanewassume esi:ptr IMAGE_NT_HEADERSmov edi,RVA ; edi == RVAmov edx,esiadd edx,sizeof IMAGE_NT_HEADERSmov cx,[esi].FileHeader.NumberOfSectionsmovzx ecx,cxassume edx:ptr IMAGE_SECTION_HEADER.while
ecx>0   .if edi>=[edx].VirtualAddress     mov
eax,[edx].VirtualAddress     add eax,[edx].SizeOfRawData     .if
ediedi,eax       mov eax,[edx].PointerToRawData       add
eax,edi       add eax,pFileMap       ret     .endif   .endif   add edx,sizeof IMAGE_SECTION_HEADER   dec ecx.endwassume
edx:nothingassume esi:nothingmov eax,ediretRVAToFileMap endpShowTheFunctions proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORDLOCAL temp[512]:BYTELOCAL NumberOfNames:DWORDLOCAL Base:DWORDmov edi,pNTHdrassume edi:ptr IMAGE_NT_HEADERSmov edi,
[edi].OptionalHeader.DataDirectory.VirtualAddress.if edi==0  invoke
MessageBox,0, addr NoExportTable,addr AppName,MB_OK+MB_ICONERROR  ret.endifinvoke SetDlgItemText,hDlg,IDC_EDIT,0invoke
AppendText,hDlg,addr bufferinvoke RVAToFileMap,pMapping,edimov edi,eaxassume edi:ptr IMAGE_EXPORT_DIRECTORYmov eax,[edi].NumberOfFunctionsinvoke RVAToFileMap, pMapping,[edi].nNameinvoke wsprintf, addr
temp,addr ExportTable, eax, [edi].nBase, [edi].NumberOfFunctions,
[edi].NumberOfNames, [edi].AddressOfFunctions, [edi].AddressOfNames,
[edi].AddressOfNameOrdinalsinvoke AppendText,hDlg,addr tempinvoke
AppendText,hDlg,addr Headerpush [edi].NumberOfNamespop
NumberOfNamespush [edi].nBasepop Baseinvoke
RVAToFileMap,pMapping,[edi].AddressOfNamesmov esi,eaxinvoke
RVAToFileMap,pMapping,[edi].AddressOfNameOrdinalsmov ebx,eaxinvoke
RVAToFileMap,pMapping,[edi].AddressOfFunctionsmov 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.endwretShowTheFunctions endpend start

分析:

mov edi,pNTHdrassume edi:ptr IMAGE_NT_HEADERSmov 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].NumberOfFunctionsinvoke RVAToFileMap,
pMapping,[edi].nNameinvoke wsprintf, addr temp,addr ExportTable, eax,
[edi].nBase, [edi].NumberOfFunctions, [edi].NumberOfNames,
[edi].AddressOfFunctions, [edi].AddressOfNames, [edi].AddressOfNameOrdinalsinvoke AppendText,hDlg,addr temp

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

push [edi].NumberOfNamespop NumberOfNamespush
[edi].nBasepop Base

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

invoke RVAToFileMap,pMapping,[edi].AddressOfNamesmov
esi,eaxinvoke RVAToFileMap,pMapping,[edi].AddressOfNameOrdinalsmov
ebx,eaxinvoke RVAToFileMap,pMapping,[edi].AddressOfFunctionsmov
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

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

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/26259646/viewspace-1111960/,如需转载,请注明出处,否则将追究法律责任。

上一篇: 没有了~
下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2011-09-10