最近代码能力飞速提升,顺便复习一下
一般就是
DOS头:IMAGE_DOS_HEADER
结构体
PE头:IMAGE_NT_HEADERS
结构体
Section头:IMAGE_SECTION_HEADER
结构体
OK,如果要分析PE文件的话,首先把文件读取出来
那么如何解析呢?这就是C/C++非常方便的一点:直接使用结构体转换,例如把地址值解析为数值
总体就是解释内存中的值
DOS 头
先看DOSHeader吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 typedef struct _IMAGE_DOS_HEADER { WORD e_magic; WORD e_cblp; WORD e_cp; WORD e_crlc; WORD e_cparhdr; WORD e_minalloc; WORD e_maxalloc; WORD e_ss; WORD e_sp; WORD e_csum; WORD e_ip; WORD e_cs; WORD e_lfarlc; WORD e_ovno; WORD e_res[4 ]; WORD e_oemid; WORD e_oeminfo; WORD e_res2[10 ]; LONG e_lfanew; } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
需要的是e_lfanew
NT头
文件起始地址+e_lfanew = NT头位置
得到NT头位置后
1 2 3 4 5 typedef struct _IMAGE_NT_HEADERS64 { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER64 OptionalHeader; } IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
重要的是IMAGE_OPTIONAL_HEADER
,其实都一样,可以从FileHeader
得到32位还是64位,
1 2 3 4 5 6 7 8 9 typedef struct _IMAGE_FILE_HEADER { WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics; } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
0x8664:adm64位
0x014C:intel32位
接下来以64位举例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 typedef struct _IMAGE_OPTIONAL_HEADER64 { WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; ULONGLONG ImageBase; DWORD SectionAlignment; DWORD FileAlignment; WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; DWORD SizeOfHeaders; DWORD CheckSum; WORD Subsystem; WORD DllCharacteristics; ULONGLONG SizeOfStackReserve; ULONGLONG SizeOfStackCommit; ULONGLONG SizeOfHeapReserve; ULONGLONG SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
到这里我写了部分代码,结合IDA就很好理解了
如果还要继续看的话就是DataDirectory
1 2 3 4 typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
打印虚拟地址
其实就是导入导出表之类的东西
Nt->ImageBase+这里的偏移,就是运行时的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 #define IMAGE_DIRECTORY_ENTRY_TLS 9 #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 #define IMAGE_DIRECTORY_ENTRY_IAT 12 #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14
节表相关
再回到NT头中的FileHeader
,提取得到节数量
DOS头和NT头的大小加起来后面就是第一个Section
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 BOOL FilePE::bGetSectionList () { if (this ->FileContent.length () == 0 ) { Error ("File content is zero" , DEBUG); return FALSE; } if (this ->pHeaderNT == NULL ) { Error ("NT head not initialize" , DEBUG); return FALSE; } DWORD SectionNum = this ->pHeaderNT->FileHeader.NumberOfSections; this ->SecNum = SectionNum; this ->SectionList = (PIMAGE_SECTION_HEADER*)malloc ((SectionNum + 1 ) * sizeof (PIMAGE_SECTION_HEADER)); if (this ->SectionList == NULL ) { Error ("Allcate section list memory failed\n" , DEBUG); return FALSE; } for (size_t i = 0 ; i < SectionNum; i++) { SectionList[i] = PIMAGE_SECTION_HEADER ( &this ->FileContent[0 ] + this ->pHeaderDOS->e_lfanew + sizeof (IMAGE_NT_HEADERS) + i * sizeof (IMAGE_SECTION_HEADER)); } return TRUE; }
IAT表
I. 如何找到文件的IAT表
IAT叫Import Address Table,当我们的程序需要外部库的函数时,就会从这里开始调用
IAT表的地址并不在任何头中,不过我们之前分析Directory
的时候就发现会有一个IMAGE_DIRECTORY_ENTRY_IAT
,但它不是导入表 ,真正的IAT描述:
1 pHeaderNT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
按道理来说VirtualAddress+ImageBase
就是运行起来的地址,这里我没有加上
突然想起来我们在分析节表的时候也存在类似的 基地址+偏移
对于这个结构体之前的只能用这块儿的目录
对应文件中地址为28fc-2000+1400 = 1cfc
有点意思,查阅资料发现对应的结构体:_IMAGE_IMPORT_DESCRIPTOR
,MSDN中并没有对应说明
1 2 3 4 5 6 7 8 9 10 11 typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; } DUMMYUNIONNAME; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
有个问题就是这块地址是运行时写入IAT相关信息,我们的静态分析到这一步就算断了
这里我设计了一个BOOL bGetIAT();
函数,直接去读程序的内存,为了简单,就先关闭ASLR
已经成功读取一个信息,接下来办法全部找出来,自然想到的就是Name为0就退出,但是由于总数不知道,所以需要一个动态容器,我用的vector
1 vector<PIMAGE_IMPORT_DESCRIPTOR> pIATList;
按照一样的方法全部得到后,再次尝试在运行中读取
但是很多PE分析工具静态也能看得到,利用的是RVA->RAW
的转换
2d52-2000+1400=2152
1 2 3 4 5 6 7 8 9 10 11 12 void FilePE::PrintIATStatic () { if (this ->pIATList.size () == 0 ) { Error ("IAT List empty" , DEBUG); return ; } DWORD VA = 0 ; for (size_t i = 0 ; i < this ->pIATList.size (); i++) { VA = this ->dwVAToRaw (this ->pIATList[i]->Name); cout << "DLL : " << &this ->FileContent[0 ] + VA << endl; } }
II. 找到导入函数
通过OriginalFirstThunk
,基本就是OriginalFirstThunk
进行转化找到RAW,RAW地方的地址再转为RAW就找到了,最终的位置是一个_IMAGE_IMPORT_BY_NAME
结构体
1 2 3 4 typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; CHAR Name[1 ]; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
1 2 3 4 5 6 7 8 9 void FilePE::PrintFunction (PIMAGE_IMPORT_DESCRIPTOR pIAT) { DWORD64 VA = *PDWORD64 (&this ->FileContent[0 ] + this ->dwVAToRaw (pIAT->OriginalFirstThunk)); for (size_t i = 1 ; VA != 0 ; i++) { PIMAGE_IMPORT_BY_NAME IatName = PIMAGE_IMPORT_BY_NAME (&this ->FileContent[0 ] + this ->dwVAToRaw (VA)); cout << " Function " << IatName->Name << endl; VA = *PDWORD64 (&this ->FileContent[0 ] + this ->dwVAToRaw (pIAT->OriginalFirstThunk) + i * sizeof (DWORD64)); } }
找到入口函数
很简单,在上面的nt->option_header.AddressOfEntryPoint
里面,可以直接ImageBase+AddressOfEntryPoint,二进制数据都对的上