郑重声明:文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,如果您不同意请关闭该页面!任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担!
前言
之前为了学习PE结构知识恶补了一下相关文章,然后使用010 Editor
这个软件也可以完整的读取出PE的结构和字段,但是这都是别人写好的,自己没有玩过一遍的东西都不叫学过,那只能叫见过所以就有了这篇文章,各位看官别急需要几天时间才能搞定,毕竟还不熟悉如果有哪里写错了,忘各位斧正!(PS:当前代码输出不够美观后期会修改)
使用环境
- Visual Studio 2019 (宇宙最强编译器
- windows 10
前置知识
恶意程序研究之PE结构梳理
获取文件映象
首先介绍下内存映射文件技术作用
- 使用内存映射文件来访问磁盘上的数据文件。这使你可以不必对文件执行I/O操作,并且可以不必对文件内容进行缓存.
- 可以使用内存映射文件,使同一台计算机上运行的多个进程能够相互之间共享数据。windows确实提供了其他一些方法,以便在进程之间进行数据通信,但是这些方法都是使用内存映射文件来实现的,诸如使用SendMessage或者PostMessage,都在内部使用了内存映射文件.这使得内存映射文件成为单个计算机上的多个进程互相进行通信的最有效的方法。
其实我们需要获取文件的内存映射的话需要完成三步
- 获取这个文件的句柄(CreateFile)
- 获取文件映射对象的句柄(CreateFileMapping)
- 在调用进程的地址空间映射一个文件视图(MapViewOfFile)
具体的代码如下,返回一个映射视图的起始地址,这个地址会贯穿全文
HANDLE OpenPeByFileName(LPTSTR FileName) { LPTSTR PortableExecutableFileName = FileName; HANDLE hFile, hMapFile, hMapAddress = NULL; DWORD dwFileSize = 0;
hFile = CreateFile(PortableExecutableFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); dwFileSize = GetFileSize(hFile, NULL); std::cout<<"文件大小: " << dwFileSize<<std::endl; hMapFile = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, dwFileSize, NULL); hMapAddress = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, dwFileSize); if (hMapAddress != NULL) return hMapAddress; } int main() { HANDLE hMapAddress = NULL; lpMapAddress = OpenPeByFileName(L"D:\\BitComet\\BitComet.exe"); return 0; }
|
获取PE指纹(DOS头)
通过把文件映象转换成PIMAGE_DOS_HEADER表,然后读取就可以
void DisplayDOSHeadInfo(HANDLE ImageBase) { PIMAGE_DOS_HEADER pDosHead = NULL; pDosHead = (PIMAGE_DOS_HEADER)ImageBase;
printf("DOS签名(MZSignature): %x\n", pDosHead->e_magic); printf("文件最后页的字节数(UsedBytesInTheLastPage): %x\n", pDosHead->e_cblp); printf("文件页数(FileSizeInPages): %x\n", pDosHead->e_cp); printf("重定义元素个数(NumberOfRelocationItems): %x\n", pDosHead->e_crlc); printf("头部尺寸(HeaderSizeInParagraphs): %x\n", pDosHead->e_cparhdr); printf("所需的最小附加段(MinimumExtraParagraphs): %x\n", pDosHead->e_minalloc); printf("所需的最大附加段(MaximumExtraParagraphs): %x\n", pDosHead->e_maxalloc); printf("初始的SS值(InitialRelativeSS): %x\n", pDosHead->e_ss); printf("初始的SP值(InitialSP): %x\n", pDosHead->e_sp); printf("校验和(Checksum): %x\n", pDosHead->e_csum); printf("初始的IP值(InitialIP): %x\n", pDosHead->e_ip); printf("初始的CS值(InitialRelativeCS): %x\n", pDosHead->e_cs); printf("重分配表文件地址(AddressOfRelocationTable): %x\n", pDosHead->e_lfarlc); printf("覆盖号(OverlayNumber): %x\n", pDosHead->e_ovno); printf("保留字[4](Reserved):\n"); printf("Reserved[0]: %x\n", pDosHead->e_res[0]); printf("Reserved[1]: %x\n", pDosHead->e_res[1]); printf("Reserved[2]: %x\n", pDosHead->e_res[2]); printf("Reserved[3]: %x\n", pDosHead->e_res[3]); printf("OEM标识符(OEMid): %x\n", pDosHead->e_oemid); printf("OEM信息(OEMinfo): %x\n", pDosHead->e_oeminfo); printf("保留字[10](Reserved2):\n"); printf("Reserved2[0]: %x\n", pDosHead->e_res2[0]); printf("Reserved2[1]: %x\n", pDosHead->e_res2[1]); printf("Reserved2[2]: %x\n", pDosHead->e_res2[2]); printf("Reserved2[3]: %x\n", pDosHead->e_res2[3]); printf("Reserved2[4]: %x\n", pDosHead->e_res2[4]); printf("Reserved2[5]: %x\n", pDosHead->e_res2[5]); printf("Reserved2[6]: %x\n", pDosHead->e_res2[6]); printf("Reserved2[7]: %x\n", pDosHead->e_res2[7]); printf("Reserved2[8]: %x\n", pDosHead->e_res2[8]); printf("Reserved2[9]: %x\n", pDosHead->e_res2[9]); printf("指示NT头的偏移(AddressOfNewExeHeader): %x\n", pDosHead->e_lfanew); }
|
可以看到我们在使用010 Editor
和VS 2019
中读取的内容一致

输出内容

获取NT头
获取NT表的句柄,因为后面都需要用到,所以这边直接封装成函数就好
PIMAGE_NT_HEADERS GetNtHead(HANDLE ImageBase) { PIMAGE_DOS_HEADER pDosHead = NULL; PIMAGE_NT_HEADERS pNtHead = NULL; pDosHead = (PIMAGE_DOS_HEADER)ImageBase; pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew); return pNtHead; }
|
Signatur
这个参数和IMAGE_FILE_HEADER表一起获取即可
这个表示在NT表下面所以直接调用就好
void DisplayFileHeaderInfo(HANDLE ImageBase) { PIMAGE_NT_HEADERS pNtHead = NULL; pNtHead = GetNtHead(ImageBase); printf("签名(Signatur): %x\n", pNtHead->Signature); printf("运行平台(Machine): %x\n", pNtHead->FileHeader.Machine); printf("节区数目(NumberOfSections): %x\n", pNtHead->FileHeader.NumberOfSections); printf("创建时间(TimeDateStamp): %x\n", pNtHead->FileHeader.TimeDateStamp); printf("COFF文件符号表偏移(PointerToSymbolTable): %x\n", pNtHead->FileHeader.PointerToSymbolTable); printf("符号表的数量(NumberOfSymbols): %x\n", pNtHead->FileHeader.NumberOfSymbols); printf("可选头大小(SizeOfOptionalHeader): %x\n", pNtHead->FileHeader.SizeOfOptionalHeader); printf("文件特性(Characteristics): %x\n", pNtHead->FileHeader.Characteristics); }
|
可以看到输出内容完全一致

输出内容

直接贴代码,因为该表中IMAGE_DATA_DIRECTORY表没啥用这边就展示不写了,后面有时间补上
void DisplayOptionalHeaderInfo(HANDLE ImageBase) { PIMAGE_NT_HEADERS pNtHead = NULL; pNtHead = GetNtHead(ImageBase); printf("魔数(Magic): %x\n", pNtHead->OptionalHeader.Magic); printf("链接器的主版本号(MajorLinkerVersion): %x\n", pNtHead->OptionalHeader.MajorLinkerVersion); printf("链接器的次版本号(MinorLinkerVersion): %x\n", pNtHead->OptionalHeader.MinorLinkerVersion); printf("代码节大小(SizeOfCode): %x\n", pNtHead->OptionalHeader.SizeOfCode); printf("已初始化数大小(SizeOfInitializedData): %x\n", pNtHead->OptionalHeader.SizeOfInitializedData); printf("未初始化数大小(SizeOfUninitializedData): %x\n", pNtHead->OptionalHeader.SizeOfUninitializedData); printf("最先执行代码起始地址(AddressOfEntryPoint): %x\n", pNtHead->OptionalHeader.AddressOfEntryPoint); printf("代码基址(BaseOfCode): %x\n", pNtHead->OptionalHeader.BaseOfCode); printf("数据基址(BaseOfData): %x\n", pNtHead->OptionalHeader.BaseOfData); printf("镜像首地址(ImageBase): %x\n", pNtHead->OptionalHeader.ImageBase); printf("节段在内存中的最小单位(SectionAlignment): %x\n", pNtHead->OptionalHeader.SectionAlignment); printf("节段在磁盘文件中的最小单位(FileAlignment): %x\n", pNtHead->OptionalHeader.FileAlignment); printf("主系统的主版本号(MajorOperatingSystemVersion): %x\n", pNtHead->OptionalHeader.MajorOperatingSystemVersion); printf("主系统的次版本号(MinorOperatingSystemVersion): %x\n", pNtHead->OptionalHeader.MinorOperatingSystemVersion); printf("镜像的主版本号(MajorImageVersion): %x\n", pNtHead->OptionalHeader.MajorImageVersion); printf("镜像的次版本号(MinorImageVersion): %x\n", pNtHead->OptionalHeader.MinorImageVersion); printf("子系统的主版本号(MajorSubsystemVersion): %x\n", pNtHead->OptionalHeader.MajorSubsystemVersion); printf("子系统的次版本号(MinorSubsystemVersion): %x\n", pNtHead->OptionalHeader.MinorSubsystemVersion); printf("保留字段(Win32VersionValue): %x\n", pNtHead->OptionalHeader.Win32VersionValue); printf("镜像被加载进内存时的大小(SizeOfImage): %x\n", pNtHead->OptionalHeader.SizeOfImage); printf("所有头的总大小(SizeOfHeaders): %x\n", pNtHead->OptionalHeader.SizeOfHeaders); printf("镜像文件的校验和(CheckSum): %x\n", pNtHead->OptionalHeader.CheckSum); printf("运行此镜像所需的子系统(Subsystem): %x\n", pNtHead->OptionalHeader.Subsystem); printf("DLL标识(DllCharacteristics): %x\n", pNtHead->OptionalHeader.DllCharacteristics); printf("最大栈大小(SizeOfStackReserve): %x\n", pNtHead->OptionalHeader.SizeOfStackReserve); printf("初始提交的堆栈大小(SizeOfStackCommit): %x\n", pNtHead->OptionalHeader.SizeOfStackCommit); printf("最大堆大小(SizeOfHeapReserve): %x\n", pNtHead->OptionalHeader.SizeOfHeapReserve); printf("初始提交的局部堆空间大小(SizeOfHeapCommit): %x\n", pNtHead->OptionalHeader.SizeOfHeapCommit); printf("保留字段(LoaderFlags): %x\n", pNtHead->OptionalHeader.LoaderFlags); printf("DataDirectory的数组个数(NumberOfRvaAndSizes): %x\n", pNtHead->OptionalHeader.NumberOfRvaAndSizes); }
|
对比两者的值

输出内容

获取导入表
有些文件是没有办法获取到导入表的,但是只要是正常的PE文件的话还是可以获取的
直接贴代码
void DisplayImportTable(HANDLE ImageBase) { PIMAGE_DOS_HEADER pDosHead = NULL; PIMAGE_NT_HEADERS pNtHead = NULL; PIMAGE_IMPORT_DESCRIPTOR pInput = NULL; PIMAGE_THUNK_DATA _pThunk = NULL; DWORD dwThunk = NULL; DWORD dwOriginalFirstThunk = NULL; USHORT Hint; int iSystemBits = 0;
pDosHead = (PIMAGE_DOS_HEADER)ImageBase; pNtHead = GetNtHead(ImageBase); if (pNtHead->FileHeader.Machine== IMAGE_FILE_MACHINE_I386) { iSystemBits = 32; } if (pNtHead->FileHeader.Machine == (IMAGE_FILE_MACHINE_IA64 || IMAGE_FILE_MACHINE_AMD64)) { iSystemBits = 64; } if (pNtHead->OptionalHeader.DataDirectory[1].VirtualAddress == 0) { return; } pInput = (PIMAGE_IMPORT_DESCRIPTOR)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, pNtHead->OptionalHeader.DataDirectory[1].VirtualAddress, NULL); for (; pInput->Name != NULL;) { char* szFunctionModule = (PSTR)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, (ULONG)pInput->Name, NULL); dwThunk = pInput->FirstThunk; dwOriginalFirstThunk= pInput->OriginalFirstThunk; _pThunk = (PIMAGE_THUNK_DATA)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, (ULONG)pInput->FirstThunk, NULL); for (; _pThunk->u1.AddressOfData != NULL;) { char* szFunction = (PSTR)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, (ULONG)(((PIMAGE_IMPORT_BY_NAME)_pThunk->u1.AddressOfData)->Name), 0); if (szFunction != NULL) { memcpy(&Hint, szFunction - 2, 2); } else { Hint = -1; } if (Hint== NULL || dwThunk == NULL || dwOriginalFirstThunk == NULL || szFunctionModule == NULL || szFunction == NULL) {
} else { std::cout << "序号:" << Hint << std::endl; std::cout << "Thunk RVA:" << std::hex << dwThunk << std::endl; std::cout << "OriginalFirstThunk:" << std::hex << dwOriginalFirstThunk << std::endl; std::cout << "DLL名称:" << szFunctionModule << std::endl; std::cout << "API名称:" << szFunction << std::endl; std::cout << "--------------------------------------------------" << std::endl; }
if (iSystemBits == 32) { dwThunk += 4; } else if (iSystemBits == 64) { dwThunk += 8; } _pThunk++; } pInput++; } }
|

获取导出表
这个表一般都在DLL文件中所以,这边换了个DLL文件来获取,直接贴代码
VOID DisplayExportTable(HANDLE ImageBase) {
PIMAGE_EXPORT_DIRECTORY pExport; PIMAGE_DOS_HEADER pDosHead = NULL; PIMAGE_NT_HEADERS pNtHead = NULL; pDosHead = (PIMAGE_DOS_HEADER)ImageBase; pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew); if (pNtHead->Signature != 0x00004550) { return; }
pExport = (PIMAGE_EXPORT_DIRECTORY)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, pNtHead->OptionalHeader.DataDirectory[0].VirtualAddress, NULL); DWORD NumberOfNames = pExport->NumberOfNames; ULONGLONG** ppdwNames = (ULONGLONG**)pExport->AddressOfNames; ppdwNames = (PULONGLONG*)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, (ULONG)ppdwNames, NULL); ULONGLONG** ppdwAddr = (ULONGLONG**)pExport->AddressOfFunctions; ppdwAddr = (PULONGLONG*)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, (DWORD)ppdwAddr, NULL); ULONGLONG* ppdwOrdin = (ULONGLONG*)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, (DWORD)pExport->AddressOfNameOrdinals, NULL);
char* szFunction = (PSTR)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, (ULONG)*ppdwNames, NULL); std::cout <<"特征值:" << pExport->Characteristics << std::endl; std::cout<<"时间日期标志:" <<std::hex<< pExport->TimeDateStamp << std::endl; std::cout << "版本号:" << std::hex<<pExport->MajorVersion <<"."<<std::hex<<pExport->MinorVersion << std::endl; std::cout << "名称:"<<pExport->Name << std::endl; std::cout << "基址:"<<pExport->Base << std::endl; std::cout <<"函数个数:"<< pExport->NumberOfFunctions << std::endl; std::cout << "名称个数:"<< pExport->NumberOfNames << std::endl; std::cout << "函数地址:" << pExport->AddressOfFunctions << std::endl; std::cout << "名称地址:" << pExport->AddressOfNames << std::endl; std::cout << "名称序数地址:" << pExport->AddressOfNameOrdinals << std::endl; for (DWORD i = 0; i < NumberOfNames; i++) { std::cout << "序号:" << i << std::endl; std::cout <<"RVA"<< *ppdwAddr << std::endl; std::cout <<"名称:"<< szFunction << std::endl; std::cout <<"-----------------------------------"<< std::endl; szFunction = szFunction + strlen(szFunction) + 1; ppdwAddr++; } }
|

获取重定位表
这个好像用处不大,到时候有时间写,没时间就算了
文章用到的源码
https://github.com/Ascotbe/virus/tree/master/PortableExecutableParser
|
参考文章
http://www.cppblog.com/API/archive/2011/03/09/141423.html https://www.cnblogs.com/LyShark/p/11748296.html
|