【读书笔记】-PE导入表读取遇到的问题
1、 关于PE加载器加载PE文件与内存映射文件的区别;
Pe加载器加载pe文件到内存,如用户点击一个应用程序时,pe加载器就开始进行例行工作,加载pe文件及所需的相关资源引用到内存;
内存映射是将磁盘文件一模一样的映射到虚拟地址空间中;
所以两者有个很重要的区别:就是对齐单位的不同;pe加载器加载pe文件到内存,使用的内存对齐4k大小;而内存映射文件则还是文件对齐的单位200k【当然是指默认的情况下】。
因为上述情况,解释了我的一个疑惑,为什么很多读取导入表、导出表的程序中,会有一个很频繁的调用 _RvaToFileOffset 就是讲内存RVA转换为文件偏移;
就是因为我们通常读取pe文件的一些信息都是通过内存映射的方式,这种方式就是把硬盘的文件格式放到内存中而已;但是我们读取到的pe文件格式字段中有大量的rva值得字段,所以涉及到这些字段,都必须转换为文件偏移值,才能读取到正确的地址。
2、 编写程序过程中需要trace一些寄存器或变量的方法;
Masm32中有很多已经定义好了的宏,我们只需包含以下头文件就可以利用这些宏来trace一些数据,以验证程序的正确性;
PrintString 变量 ;输出变量的值
PrintStringByAddr 变量/寄存器 ;输出变量或寄存器所保存地址的字符串
PrintDec 寄存器 ;输出寄存器的十进制数值
PrintHex 寄存器 ;输出寄存器的十六进制数值
PrintLine ;输出一条线
这就是几个常用的用来trace的宏,当然也可以用MessageBox函数
记住:要引用相关的头文件
include comdlg32.inc includelib comdlg32.lib include debug.inc includelib debug.lib include masm32.inc includelib masm32.lib
3、 对于导入表的相关结构加深印象与理解;
IMAGE_IMPORT_DESCRIPTOR 这个结构 对应着程序里面引用的DLL个数;程序每引用一个DLL动态链接库,就会有一个这样的结构;这么一系列结构 以一个所有字段为0的IMAGE_IMPORT_DESCRIPTOR 结束;
其中需要关注的字段是OriginalFirstThunk与FirstThunk 这两个字段 可以推演出程序的导入函数信息;只不过前者是可以推出INT表,后者可推出IAT表,
IMAGE_IMPORT_DESCRIPTOR.Name1 这个字段 是一个rva值,指向dll名称字符串。
IMAGE_THUNK_DATA 一个指针结构,4个字节RVA;以全0的IMAGE_THUNK_DATA表示结束。这个RVA值,指向IMAGE_IMPORT_BY_NAME 【导入函数信息】
IMAGE_IMPORT_BY_NAME结构 就2个字段,一个是Hint 意义不大;另外一个是IMAGE_IMPORT_BY_NAME.Name1 这个是导入函数的ascii 字符串函数名称【这是内存映射模式下,pe加载器模式下还没有研究】。
4、 附源码:
;=============================================== ;读取pe文件的导入表信息 ;james.Moriarty ;2012/04/24 ;=============================================== .386 .model flat,stdcall option casemap:none include windows.inc include kernel32.inc includelib kernel32.lib include user32.inc includelib user32.lib ;下面的头文件主要是为了输出调试信息而用 include comdlg32.inc includelib comdlg32.lib include debug.inc includelib debug.lib include masm32.inc includelib masm32.lib .data szFileName db 'D:\Source\macro\macro.exe',0 szErrorMsg db '文件操作错误!',0 hFile dd ? hMapFile dd ? lpMemory dd ? .code ;----------------------------------------------- ;将内存rva转换为文件偏移foa ;----------------------------------------------- _RvaToFileOffset proc _lpFile,_dwRva LOCAL @dwRet pushad ;esi -> pe头 mov esi, _lpFile assume esi :ptr IMAGE_DOS_HEADER add esi, [esi].e_lfanew assume esi :ptr IMAGE_NT_HEADERS mov edi, _dwRva ;edx -> 节表 ;ecx <= 节的个数 mov edx, esi add edx, sizeof IMAGE_NT_HEADERS assume edx :ptr IMAGE_SECTION_HEADER movzx ecx, [esi].FileHeader.NumberOfSections ;循环每个节表 以求得参数_dwRva这个rva值是在哪个节内? .repeat mov eax, [edx].VirtualAddress ;VirtualSize的值不一定准确,用SizeOfRawData取节区的起止范围也可以 ;当然也可以取下一个节区的起始值作为本节的结束值 ;(VirtualAddress+SizeOfRawData) --- 下个节区开始 这个范围都是 0 填充的 add eax, [edx].SizeOfRawData ;_dwRva在 本节区范围内 .if (edi>=[edx].VirtualAddress) && (edi<eax) ;计算节内偏移 mov eax, [edx].VirtualAddress sub edi, eax ;该节 起始位置的文件偏移 mov eax, [edx].PointerToRawData ;加上节内偏移 就等于 rva的 文件偏移 add eax, edi jmp @f .endif add edx, sizeof IMAGE_SECTION_HEADER .untilcxz assume edi :nothing assume esi :nothing mov eax, -1 @@: mov @dwRet, eax popad mov eax, @dwRet ret _RvaToFileOffset endp start: ;打开指定文件,并将文件内存映射 invoke CreateFile,offset szFileName,FILE_SHARE_READ or FILE_SHARE_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL .if eax == INVALID_HANDLE_VALUE jmp error .endif mov hFile, eax invoke CreateFileMapping,hFile,NULL,PAGE_READONLY,0,0,NULL .if !eax jmp error .endif mov hMapFile, eax invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0 .if !eax jmp error .endif mov lpMemory, eax ;esi指向dos头 mov esi, eax assume esi :ptr IMAGE_DOS_HEADER ;edi指向pe头 mov edi, [esi].e_lfanew add edi, esi assume edi :ptr IMAGE_NT_HEADERS ;edx 指向DataDirectory的第二项 即导入表 mov edx, [edi].OptionalHeader.DataDirectory[8].VirtualAddress invoke _RvaToFileOffset,esi,edx mov edx, eax add edx, esi assume edx :ptr IMAGE_IMPORT_DESCRIPTOR ;循环遍历每个IMAGE_IMPORT_DESCRIPTOR结构(即每个引用的dll) .while [edx].OriginalFirstThunk || [edx].TimeDateStamp || [edx].ForwarderChain || [edx].Name1 || [edx].FirstThunk invoke _RvaToFileOffset,esi,[edx].Name1 add eax, esi PrintLine PrintStringByAddr eax .if [edx].OriginalFirstThunk mov ebx, [edx].OriginalFirstThunk .elseif mov ebx, [edx].FirstThunk .endif ;PrintHex ebx invoke _RvaToFileOffset,esi,ebx add eax, esi mov ebx, eax ;循环遍历每个dll里面的每个函数 .while dword ptr [ebx] .if dword ptr [ebx] & IMAGE_ORDINAL_FLAG32 mov eax, dword ptr [ebx] and eax, 0ffffh PrintHex eax .else invoke _RvaToFileOffset,esi,dword ptr [ebx] add eax, esi assume eax :ptr IMAGE_IMPORT_BY_NAME movzx ecx, [eax].Hint PrintDec ecx ;IMAGE_IMPORT_BY_NAME.Name1是一个ascii字符串 变长数组 ;所以取地址 lea eax, [eax].Name1 PrintStringByAddr eax .endif ; add ebx, 4 .endw add edx, sizeof IMAGE_IMPORT_DESCRIPTOR .endw ;释放资源 invoke UnmapViewOfFile,lpMemory invoke CloseHandle,hMapFile invoke CloseHandle,hFile jmp return error: invoke MessageBox,NULL,offset szErrorMsg,NULL,MB_OK return: invoke ExitProcess,NULL end start
posted on 2012-04-24 17:35 james_moriarty 阅读(2010) 评论(0) 编辑 收藏 举报