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 个成员,常用的列于下表。
上面也许无法让您完全理解引出表,下面的简述将助您一臂之力。
引出表的设计是为了方便PE装载器工作。首先,模块必须保存所有引出函数的地址以供PE装载器查询。模块将这些信息保存在 AddressOfFunctions 域指向的数组中,而数组元素数目存放在 NumberOfFunctions 域中。 因此,如果模块引出40个函数,则 AddressOfFunctions 指向的数组必定有40个元素,而 NumberOfFunctions 值为40。现在如果有一些函数是通过名字引出的,那么模块必定也在文件中保留了这些信息。这些 名字的RVAs存放在一数组中以供PE装载器查询。该数组由 AddressOfNames 指向, NumberOfNames 包含名字数目。考虑一下PE装载器的工作机制,它知道函数名,并想以此获取这些函数的地址。至今为止,模块已有两个模块: 名字数组和地址数组,但两者之间还没有联系的纽带。因此我们还需要一些联系函数名及其地址的东东。PE参考指出使用到地址数组的索引作为联接,因此PE装载器在名字数组中找到匹配名字的同时,它也获取了 指向地址表中对应元素的索引 。 而这些索引保存在由 AddressOfNameOrdinals 域指向的另一个数组(最后一个)中。由于该数组是起了联系名字和地址的作用,所以其元素数目必定和名字数组相同,比如,每个名字有且仅有一个相关地址,反过来则不一定: 每个地址可以有好几个名字来对应。因此我们给同一个地址取"别名"。为了起到连接作用,名字数组和索引数组必须并行地成对使用,譬如,索引数组的第一个元素必定含有第一个名字的索引,以此类推。
下面举一两个例子说明问题。如果我们有了引出函数名并想以此获取地址,可以这么做:
定位到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,序数和名字。注意本例没有列出仅由序数引出的函数
分析 :
程序检验PE有效性后,定位到数据目录获取引出表的虚拟地址。若该虚拟地址为0,则文件不含引出符号。
在编辑控件中显示 IMAGE_EXPORT_DIRECTORY 结构的一些重要信息。
由于我们要枚举所有函数名,就要知道引出表里的名字数目。 nBase 在将 AddressOfFunctions 数组索引转换成序数时派到用场。
将三个数组的地址相应存放到esi,,ebx,edi中。准备开始访问。
.while NumberOfNames>0
直到所有名字都被处理完毕。
invoke RVAToFileMap,pMapping,dword ptr [esi]
由于esi指向包含名字字符串RVAs的数组,所以[esi]含有当前名字的RVA,需要将它转换成虚拟地址,后面wsprintf要用的。
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 两数组的当前指针,继续遍历直到所有名字全都处理完毕。
上一课我们已经学习了动态联接中关于引入表那部分知识,现在继续另外一部分,那就是引出表。
理论 :
当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 个成员,常用的列于下表。
上面也许无法让您完全理解引出表,下面的简述将助您一臂之力。
引出表的设计是为了方便PE装载器工作。首先,模块必须保存所有引出函数的地址以供PE装载器查询。模块将这些信息保存在 AddressOfFunctions 域指向的数组中,而数组元素数目存放在 NumberOfFunctions 域中。 因此,如果模块引出40个函数,则 AddressOfFunctions 指向的数组必定有40个元素,而 NumberOfFunctions 值为40。现在如果有一些函数是通过名字引出的,那么模块必定也在文件中保留了这些信息。这些 名字的RVAs存放在一数组中以供PE装载器查询。该数组由 AddressOfNames 指向, NumberOfNames 包含名字数目。考虑一下PE装载器的工作机制,它知道函数名,并想以此获取这些函数的地址。至今为止,模块已有两个模块: 名字数组和地址数组,但两者之间还没有联系的纽带。因此我们还需要一些联系函数名及其地址的东东。PE参考指出使用到地址数组的索引作为联接,因此PE装载器在名字数组中找到匹配名字的同时,它也获取了 指向地址表中对应元素的索引 。 而这些索引保存在由 AddressOfNameOrdinals 域指向的另一个数组(最后一个)中。由于该数组是起了联系名字和地址的作用,所以其元素数目必定和名字数组相同,比如,每个名字有且仅有一个相关地址,反过来则不一定: 每个地址可以有好几个名字来对应。因此我们给同一个地址取"别名"。为了起到连接作用,名字数组和索引数组必须并行地成对使用,譬如,索引数组的第一个元素必定含有第一个名字的索引,以此类推。
下面举一两个例子说明问题。如果我们有了引出函数名并想以此获取地址,可以这么做:
定位到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 casemap:none include masm32includewindows.inc include masm32includekernel32.inc include masm32includecomdlg32.inc include masm32includeuser32.inc includelib masm32libuser32.lib includelib masm32libkernel32.lib includelib 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 ? 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
修正计数器, AddressOfNames 和 AddressOfNameOrdinals 两数组的当前指针,继续遍历直到所有名字全都处理完毕。
发表评论
-
压缩与脱壳-PE文件格式 六 续
2009-04-13 11:39 1077assume edi:ptr IMAGE_IMPORT_D ... -
压缩与脱壳-PE文件格式 六 续一
2009-04-13 11:33 976示例 : 本例程打开一 PE 文件,将所有引入函数名读入一 ... -
压缩与脱壳-PE文件格式 六
2009-04-13 11:29 897Import Table (引入表) 本课我们将学习引入表。 ... -
压缩与脱壳-PE文件格式 五
2009-04-13 11:09 922Section Table(节表) 理论 : 到本课为止, ... -
压缩与脱壳-PE文件格式 四
2009-04-13 11:05 1081Optional Header 我们已经学习了关于 DOS ... -
压缩与脱壳-PE文件格式 三
2009-04-13 11:03 913File Header (文件头) ... -
压缩与脱壳-PE文件格式 二
2009-04-13 10:55 978检验PE文件的有效性 理 ... -
压缩与脱壳-PE文件格式 一
2009-04-13 10:48 1233PE 文件格式一览 PE 的 ... -
脱壳基础知识入门之认识壳
2009-04-13 10:35 10801.什么是壳 在一些计算 ...
相关推荐
### PE文件中脱壳技术的研究 #### 摘要与背景 随着信息技术的快速发展,软件保护成为了一个重要的议题。为了防止软件被非法复制或者逆向工程,开发人员常常采用一种称为“加壳”的技术来保护自己的软件产品。然而...
都会使计算机产生安全隐患或者使软件的核心技术被窃取,所以保护PE文件不被修改是一件很重要的工作,当前通常采用加壳的方法对PE文件进行保护,这其中UPX是具有代表性的压缩类外壳。 本文首先对PE文件格式进行了全面...
UPX是一个着名的压缩壳,主要功能是压缩PE文件(比如exe,dll等文件),有时候也可能被病毒用于免杀。壳upx是一种保护程序。 ........................ UPX是大家熟悉的EXE/Dll资源压缩工具,也称作可执行文件压缩...
PE文件格式是Windows操作系统中用于执行程序的标准格式,包括可执行文件(.exe)、动态链接库(.dll)等。PE脱壳工具能够识别并处理这些文件中的各种壳技术,例如UPX、ASPack、PECompact等。这些壳通常用于压缩程序...
UPX(UPX UnPacker eXtended)是一款广泛使用的可执行文件压缩工具,它能够将PE(Portable Executable,Windows平台上的可执行文件格式)文件进行压缩,从而减小程序的体积,提高分发效率。然而,这也为逆向工程和...
在计算机安全领域,PECompact是一款知名的可执行文件打包和压缩工具,它能够将Windows下的PE(Portable Executable)格式文件进行压缩,从而减小文件体积,提高分发效率。然而,这样的压缩过程也使得一些恶意软件...
总结以上内容,脱壳是逆向工程的重要组成部分,需要掌握PE文件格式、SEH机制、调试工具的使用,以及对各种加密算法和保护技术的理解。只有对这些基础知识和高级技术有了充分的认识,才能在软件安全和逆向工程的道路...
Aspack stripper就是这样的一个工具,它的主要功能是对使用ASPack 2.12版本压缩的可执行文件进行自动脱壳。这个工具的存在,使得安全分析人员能够揭示被压缩和混淆的原始代码,从而更好地理解软件的工作原理,检查...
2. **脱壳机制**:脱壳工具的核心在于正确地解压UPX壳并恢复原始的未压缩PE文件。源码可能会包含解压算法,根据UPX的压缩格式进行还原。 3. **内存操作**:由于程序可能已经在内存中运行,脱壳工具可能需要在内存中...
对于想要深入理解脱壳技术的学习者来说,熟悉PE文件格式至关重要。 **PE文件格式简介** - **定义**:PE格式由微软开发,用于定义Windows平台上可执行文件(如.EXE、.DLL等)的结构。 - **关键特性**:PE文件包含了...
- **静态与动态分析的兼容性**:构建一个可以在静态分析时重建有效PE文件,并在动态分析时执行的PE文件。 - **防止恶意软件检测**:确保脱壳过程不会被恶意软件检测到,避免分析过程中的干扰。 - **兼容性问题**:...
这个过程涉及到对PE(Portable Executable)文件格式的理解,包括节区、导入表、导出表等关键结构。脱壳机通过识别和解析ASPack的特定标志和模式来实现这一目标。 在安全社区中,ASPack常被视为一种双刃剑。一方面...
ASPACK是一款著名的软件压缩和加壳工具,由Andrey Breslav开发,它能将可执行文件进行压缩,减小其体积,同时增加反调试和反静态分析的特性,使得程序更加难以逆向工程分析。ASPACK 2.1和2.2是该工具的两个较早版本...
有些模糊处理一个Win32 PE等包装.NET程序集里面的.NET反编译器无法读取该文件。 移除大多数/所有的垃圾类添加混淆。 修复了一些的peverify错误。许多混淆器是马车和创建无法验证的代码错误。 还原类型的方法的参数和...
你可以用它来压缩或解压缩PE(Windows)、ELF(Linux)或DOS格式的可执行文件。例如,将一个.exe文件压缩,可以使用以下命令: ``` upx -q your_program.exe ``` 如果想要脱壳,只需添加`-d`参数: ``` upx -qd your...
这个工具的工作原理,它的壳特征和编译器特征保存在HackFans.txt里面,能识别出来的壳,基本上都有对应的脱壳函数,用壳特征脱壳,可以脱壳,对于一些不好特殊的壳你可以用OEP侦测来脱壳,这要依赖编译器特征,你也可以自己...
通过学习这些基础知识,初学者可以建立起对PE文件格式的理解,学会如何利用SEH技术进行调试,并且能够识别和分析不同类型的壳。这些技能是进行脱壳操作的基础,也是进一步深入学习逆向工程的关键。
UPX(Ultimate Packer for eXecutables)是一款著名的开源加壳工具,它能够对可执行文件(如Windows下的PE文件)进行压缩和加密,以减小文件大小、提高加载速度,同时也能提供一定的保护作用,防止恶意逆向分析。...