PE文件的全称是Portable Executable,意为可移植的可执行的文件,常见的EXE、DLL、OCX、SYS、COM都是PE文件,我们比较熟悉的DLL和exe文件都是PE文件。了解PE文件格式有助于加深对操作系统的理解,掌握可执行文件的数据结构机器运行机制,对于逆向破解,加壳等安全方面方面的同学极其重要。
PE在二进制文件中就是一堆0和1,但是它是按一定的格式来存储的,下面我先从宏观上来给大家介绍。
PE分dos头、NT头(包含标准PE头和可选PE头(包含数据目录))、节表。
下面我上一张图给大家看一下:
dos头(方块1第一块),NT头(方块1第二块),标准PE头(方块2),可选PE头(方块3),数据目录(方块5)、节表(方块4)。
下面我按着这个编号来介绍。
首先我讲一下dos头,如图
微软定义如下:
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header WORD e_magic; +0 // Magic number DOS签名 4D5A "MZ" WORD e_cblp; +2 // Bytes on last page of file WORD e_cp; +4 // Pages in file WORD e_crlc; +6 // Relocations WORD e_cparhdr; +8 // Size of header in paragraphs WORD e_minalloc; +10 // Minimum extra paragraphs needed WORD e_maxalloc; +12 // Maximum extra paragraphs needed WORD e_ss; +14 // Initial (relative) SS value WORD e_sp; +16 // Initial SP value WORD e_csum; +18 // Checksum WORD e_ip; +20 // Initial IP value WORD e_cs; +22 // Initial (relative) CS value WORD e_lfarlc; +24 // File address of relocation table WORD e_ovno; +26 // Overlay number WORD e_res[4]; +28 +32 +34 +36 // Reserved words WORD e_oemid; +38 // OEM identifier (for e_oeminfo) WORD e_oeminfo; +40 // OEM information; e_oemid specific WORD e_res2[10]; // Reserved words LONG e_lfanew; +60 // File address of new exe header 指向PE头 3Ch } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
虽然字段比较多,但是我们初学只要知道2个字段就ok了—— WORD e_magic和LONG e_lfanew; e_magic字段是标记,用途就是标记这个文件是什么类型的文件,比如:exe,dll,sys文件都是4D5A开头的。
MZ是一个大佬名字的简称,不过他貌似已经挂了???xxxx 反正知道mz标记就是代表可执行文件就ok了。
lfanew字段就是标记NT头在文件里的偏移,文件头+lfanew就是NT头的位置(中间有一段没用的数据叫 dos sub 下文增加节会讲)。
0x50 0x45是PE的标志 ,因为“PE”的ascll码就是0x50 0x45。
NT头包含标准PE头和可选PE头,文件的很多信息都在这里面。
Signature字段里存的就是pe标记,注意宽度是4,然后就是标准PE头。
标准PE头一共是20个字节,下面我讲一下字段的用处。
typedef struct _IMAGE_FILE_HEADER { * WORD [60]+4 Machine; 运行平台 * WORD NumberOfSections; [60]+6 文件的节数目 * DWORD TimeDateStamp; [60]+8 文件创建日期和时间 DWORD PointerToSymbolTable; [60]+12 指向符号表(用于调试) DWORD NumberOfSymbols; [60]+16 符号表中的符号数量(用于调试) * WORD SizeOfOptionalHeader;[60]+20 可选头的长度 * WORD Characteristics; [60]+22 文件属性 } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER
1.Machine字段,表示目标CPU 的类型。
几个常见的及其标识如下:
机器 标识
Intel I386 14ch
MIPS R3000 162h
Alpha AXP 184h
Power PC 1F0h
MIPS R4000 184h
2.NumberOfSection,标识节的数目,关于节后面会详细讲。
3.TimeDateStamp
PE文件创建的时间,这个时间是指从1970年1月1日到创建该文件的所有的秒数。
4)PointerToSymbolTable。略
5)NumberOfSymbol。略
6)SizeOfOptionalHeader:紧跟着标准PE头后面的数据大小,这也是一个数据结构,它叫做可选PE头,其大小依赖于是64位还是32位文件。32位文件值通常是00E0h,对于64位值通常为00F0h。
7)Characteristics:文件属性,普通EXE文件这个字段值为010fh,DLL文件这个字段一般是0210h。
(标准pe头)
可选PE头,其大小依赖于是64位还是32位文件。32位文件值通常是00E0h,对于64位值通常为00F0h。
可选PE头字段多,但是重要的不多(加*的都很重要,不要慌,*是我加的)。
typedef struct _IMAGE_OPTIONAL_HEADER { * WORD Magic; 10B 32位PE 20B 64位PE 107 ROM映像[60]+24 BYTE MajorLinkerVersion; 链接器版本号 [60]+26 BYTE MinorLinkerVersion; 链接器副版本号 [60]+27 * DWORD SizeOfCode; 所有代码节的总和 该大小是基于文件对齐后的大小[60]+28 * DWORD SizeOfInitializedData; 所有含已初始化数据的节的总大小 [60]+32 * DWORD SizeOfUninitializedData; 所有含未初始化数据的节的大小 [60]+36 * DWORD AddressOfEntryPoint; 程序执行入口RVA [60]+40 * DWORD BaseOfCode; 代码节的起始RVA [60]+44 * DWORD BaseOfData; 数据节的起始RVA [60]+48 * DWORD ImageBase; 程序的优先装载地址 [60]+52 * DWORD SectionAlignment; 内存中节的对齐粒度 [60]+56 * DWORD FileAlignment; 文件中节的对齐粒度 [60]+60 WORD MajorOperatingSystemVersion; 操作系统主版本号 [60]+64 WORD MinorOperatingSystemVersion; 操作系统副版本号 [60]+66 WORD MajorImageVersion; PE文件映像的版本号 [60]+68 WORD MinorImageVersion; [60]+70 WORD MajorSubsystemVersion; 子系统的版本号 [60]+72 WORD MinorSubsystemVersion; [60]+74 DWORD Win32VersionValue; 未用 必须设置0 [60]+76 DWORD SizeOfImage; 内存中整个PE文件的映像尺寸 [60]+80 DWORD SizeOfHeaders; 所有节表按照文件对齐粒度后的大小 [60]+84 DWORD CheckSum; 校验和 [60]+88 WORD Subsystem; 指定使用界面的子系统 [60]+92 WORD DllCharacteristics; DALL文件属性 [60]+94 * DWORD SizeOfStackReserve; 初始化时保留的栈的大小 [60]+96 * DWORD SizeOfStackCommit; 初始化时实际提交的栈的大小 [60]+100 * DWORD SizeOfHeapReserve; 初始化时保留的堆的大小 [60]+104 * DWORD SizeOfHeapCommit; 初始化时实际提交的堆的大小 [60]+108 DWORD LoaderFlags; 加载标志 未用 [60]+112 DWORD NumberOfRvaAndSizes; 下面的数据目录结构的数量 +116 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];+120 } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
额,重要的也蛮多。。。
详细字段的用处我会在下面代码里会详细说明,数据目录。。。。好吧,现在讲。
在PE结构中最重要的就是节表和数据目录表,数据目录一共指向16张表,分别是:导出表、导入表、资源表、异常信息表、安全证书表、重定位表、调试信息表、版权所以表、全局指针表
TLS表、加载配置表、绑定导入表、IAT表、延迟导入表、COM信息表 最后一个保留未使用。
这么多表中我们只要注重导出表、导入表、重定位表、IAT表就可以了。他们直接影响程序运行。 注意:数据目录只是各种表的地址(rva)。需要寻址才能找到他们。
(数据目录,太长了没有截完)
每张记录宽度8字节 然后*16=128字节
经过解析就变成
代码实现思路就是按照字段宽度偏移来遍历dos头、NT头。(这里rva和foa都一样)
__int64 flen = 0; //int arr_dos[30]; char* word_dos[] = { "e_magic", "e_cblp", "e_cp", "e_crlc", "e_cparhdr", "e_minalloc", "e_maxalloc", "e_ss", "e_sp", "e_csum", "e_ip", "e_cs", "e_lfarlc","e_ovno", "e_res[4]", "e_res[4]", "e_res[4]", "e_res[4]", "e_oemid", "e_oeminfo", "e_res2[10]", "e_res2[10]", "e_res2[10]", "e_res2[10]", "e_res2[10]", "e_res2[10]", "e_res2[10]", "e_res2[10]", "e_res2[10]", "e_res2[10]", "e_lfanew" }; /////////////////////////////////////////////////////////////////char* NT_word[] int arr_pe[8] = { 4,2,2,4,4,4,2,2 }; //标准pe每个字段的宽度 int pe_s = 0;//没用 凑数的 int arr_o_pe[] = { 2,1,1,4,4,4,4,4,4,4,4,4,2,2,2,2,2,2,4 ,4,4,4,2,2,4 ,4,4,4 ,4,4 };//取字段字节数 int arr_sch[10] = { 8,4,4,4,4,4,4,2,2,4 }; //char*// file_ser(char*, unsigned __int64); char* file_open(char* fstr)//打开文件并保存文件流 { FILE* pf = fopen(fstr, "rb"); if (pf == NULL) { perror("The following error occurred"); return 0; } fseek(pf, 0, 2); unsigned __int64 fsize = ftell(pf); flen = fsize; rewind(pf); char* fbuff = (char*)malloc(fsize); if (fbuff == NULL) { perror("The following error occurred"); return 0; } memset(fbuff, 0, fsize); unsigned __int64 r = fread(fbuff, 1, fsize, pf); if (r == 0) { perror("The following error occurred"); return 0; } //fclose(pf); //free(pf); //pf = 0; //print_pe(fbuff); //ftoi_32(fbuff); return fbuff; } 先打开文件,然后调用打印函数。 void print_pe(char* ppe)//打印pe字段 { char* flag = ppe;//flag是游标 for (int j = 0; j <= 58; j += 2)//dos头除了最后一个字段都是2个字节 { printf("%s %X\n", word_dos[j / 2], *(short*)flag); flag += 2; } printf("%s %X\n", word_dos[30], *(int*)flag);//e_lfanew字段 int pe_offsets = *(int*)flag; flag = (char*)((int)ppe + pe_offsets); //游标跳NT头 printf("*************pe******************\n"); for (int i = 0; i < 8; i++)//处理标准pe { if (arr_pe[i] == 4) { printf("%X\n", *(int*)flag); flag += 4; } else if (arr_pe[i] == 2) { printf("%X\n", *(short*)flag); flag += 2; } } printf("*************option pe******************\n"); for (int i1 = 0; i1 < sizeof(arr_o_pe) / sizeof(arr_o_pe[0]); i1++)//处理可选pe { if (arr_o_pe[i1] == 4) { printf("%X\n", *(int*)flag); flag += 4; } else if (arr_o_pe[i1] == 2) { printf("%X\n", *(short*)flag); flag += 2; } else if (arr_o_pe[i1] == 1) { printf("%X\n", *(char*)flag); flag += 1; } } return; }
PE文件中所有节的属性都被定义在节表中,节表由一系列的IMAGE_SECTION_HEADER结构排列而成,每个结构用来描述一个节,结构的排列顺序和它们描述的节在文件中的排列顺序是一致的。全部有效结构的最后以一个空的IMAGE_SECTION_HEADER结构作为结束,所以节表中总的IMAGE_SECTION_HEADER结构数量等于节的数量加一。节表总是被存放在紧接在PE文件头的地方。
另外,节表中 IMAGE_SECTION_HEADER 结构的总数总是由PE文件头 IMAGE_NT_HEADERS 结构中的 FileHeader.NumberOfSections 字段来指定的。
一个节表的宽度是40个字节,最后一个节表后面要跟上40个字节的0,也就是结束标志。(也可以没有结束标志,不过可能会炸。。。)
(节表)
一个文件至少由1个节表组成,每个节表记录了节的地址(rva和foa),大小,权限等,每节表占40个字节。
下面来说说每一个字段和作用。
typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; +0 union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; +8 双字 是该节在没有对齐前的真实尺寸,该值可以不准确。 DWORD VirtualAddress; +12 节区在内存中的偏移地址。加上ImageBase才是在内存中的真正地址址. DWORD SizeOfRawData; +16 节在文件中对齐后的尺寸. DWORD PointerToRawData; +20 节区在文件中的偏移. raw地址+偏移=下一节的地址 DWORD PointerToRelocations; +24 在obj文件中使用 对exe无意义 DWORD PointerToLinenumbers; +28 行号表的位置 调试的时候使用 WORD NumberOfRelocations; +32 在obj文件中使用 对exe无意义 WORD NumberOfLinenumbers; +34 行号表中行号的数量 调试的时候使用 DWORD Characteristics; +36 节的属性 } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
联合体就当成一个变量来用就可以了,宽度是最大的那一个变量。
1、Name 8个字节 一般情况下是以"\0"结尾的ASCII吗字符串来标识的名称,内容可以自定义.
注意:该名称并不遵守必须以"\0"结尾的规律,如果不是以"\0"结尾,系统会截取8个字节的长度进行处理.
2、Misc 双字 是该节在没有对齐前的真实尺寸,该值可以不准确。
3、VirtualAddress 节区在内存中的偏移地址。加上ImageBase才是在内存中的真正地址.
4、SizeOfRawData 节在文件中对齐后的尺寸.
5、PointerToRawData 节区在文件中的偏移.
6、PointerToRelocations 在obj文件中使用 对exe无意义
7、PointerToLinenumbers 行号表的位置 调试的时候使用
8、NumberOfRelocations 在obj文件中使用 对exe无意义
9、NumberOfLinenumbers 行号表中行号的数量 调试的时候使用
10、Characteristics 节的属性
节的属性:
[值:00000020h] [IMAGE_SCN_CNT_CODE // Section contains code.(包含可执行代码)]
[值:00000040h] [IMAGE_SCN_CNT_INITIALIZED_DATA // Section contains initialized data.(该块包含已初始化的数据)]
[值:00000080h] [IMAGE_SCN_CNT_UNINITIALIZED_DATA // Section contains uninitialized data.(该块包含未初始化的数据)]
[值:00000200h] [IMAGE_SCN_LNK_INFO // Section contains comments or some other type of information.]
[值:00000800h] [IMAGE_SCN_LNK_REMOVE // Section contents will not become part of image.]
[值:00001000h] [IMAGE_SCN_LNK_COMDAT // Section contents comdat.]
[值:00004000h] [IMAGE_SCN_NO_DEFER_SPEC_EXC // Reset speculative exceptions handling bits in the TLB entries for this section.]
[值:00008000h] [IMAGE_SCN_GPREL // Section content can be accessed relative to GP.]
[值:00500000h] [IMAGE_SCN_ALIGN_16BYTES // Default alignment if no others are specified.]
[值:01000000h] [IMAGE_SCN_LNK_NRELOC_OVFL // Section contains extended relocations.]
[值:02000000h] [IMAGE_SCN_MEM_DISCARDABLE // Section can be discarded.]
[值:04000000h] [IMAGE_SCN_MEM_NOT_CACHED // Section is not cachable.]
[值:08000000h] [IMAGE_SCN_MEM_NOT_PAGED // Section is not pageable.]
[值:10000000h] [IMAGE_SCN_MEM_SHARED // Section is shareable(该块为共享块).]
[值:20000000h] [IMAGE_SCN_MEM_EXECUTE // Section is executable.(该块可执行)]
[值:40000000h] [IMAGE_SCN_MEM_READ // Section is readable.(该块可读)]
[值:80000000h] [IMAGE_SCN_MEM_WRITE // Section is writeable.(该块可写)]
可选PE头下面就是节表,所以我们可以用dos头+lfanew+NT头的宽度来定位,注意可选pe头宽度不确定,所以只能一步一步偏移过来,下面代码我会讲。
这个文件一共有6张表,而且在标准pe头里已经记录了
当你打开一个exe时,操作系统首先会读取PE的数据,然后再把节给加载到内存里,
但是加载的时候有可能会拉升节,因为内存对齐是0x1000也就是一个页。但是老的编译器生成的PE文件的文件对齐是0x200,所以这种文件会被拉升。
PE加载的过程:
1、根据SizeOfImage的大小,开辟一块缓冲区(ImageBuffer).
2、根据SizeOfHeader的大小,将头信息从FileBuffer拷贝到ImageBuffer
3、根据节表中的信息循环讲FileBuffer中的节拷贝到ImageBuffer中.
void print_section(char* ppe) { int arr_sch[10] = { 8,4,4,4,4,4,4,2,2,4 }; char* sch = (char*)((int)ppe + (*(int*)((int)ppe + 60))/*dos+*/ + 24);/*pe头*/ //printf("%x..\n", *(short*)sch); sch += *(short*)((int)ppe + (*(int*)((int)ppe + 60))/*dos+*/ + 20); //printf("%x...", *(short*)((int)ppe + (*(int*)((int)ppe + 60))/*dos+*/ + 20)); //printf("%x..\n", *(short*)sch); char str[9] = { 0 }; printf("**********section*************\n"); for (int i = 0; i < *(short*)((int)ppe + (*(int*)((int)ppe + 60))/*dos+*/ + 6/*节表个数*/); i++) { for (int j = 0; j < 10; j++)//处理 { if (arr_sch[j] == 4) { printf("%X\n", *(int*)sch); sch += 4; } else if (arr_sch[j] == 2) { printf("%X\n", *(short*)sch); sch += 2; } else if (arr_sch[j] == 8) { //sch += 8; memset(str, 0, 9); for (int k = 0; k < 8; k++) { str[k] = *(sch++); } printf("%s \n", str); } } } }
char* ftoi_32(char* fp) { if (*(short*)fp != 0x5a4d) { cout << "错误 不是mz"; return 0; } char* pfbuff = fp; char* buff = pfbuff; char* soi = (char*)((int)fp + (*(int*)((int)fp + 60))/*dos+*/ + 80);/*sizeofimage*/ /*if (*(int*)(soi-24) == 0x1000) { return fp; }*/ printf("%x..", *(unsigned int*)soi); char* imbuff = (char*)malloc(*(unsigned int*)soi); char* imagebuff = imbuff; memset(imagebuff, 0, *(unsigned int*)soi); printf("%x soh ", *((int*)(soi + 4))); //if (*((int*)(soi + 4))<= 0x1000)// 拷贝头+拉升 //{ int i = 0; while (i++ <= *(unsigned int*)(soi + 4)) { *(char*)imagebuff++ = *(char*)pfbuff++; } int sct_sum = *(short*)((int)fp + (*(int*)((int)fp + 60))/*dos+*/ + 6);//numberofsection char* sord = (char*)((int)fp + (*(int*)((int)fp + 60))/*dos+*/ + 24);/*pe头*/ sord += *(short*)((int)fp + (*(int*)((int)fp + 60))/*dos+*/ + 20); //pe头末尾+可选pe头的宽度=节表 sord += 16;//sizeofrawdata imagebuff = (imbuff + *(int*)(sord - 4)); pfbuff = (fp + *(int*)(sord + 4)); printf("%x ", *(int*)sord); printf("\n%x ", *(int*)(sord - 4)); for (int s = 0; s < sct_sum; s++) { //pfbuff = (fp + *(int*)(sord -4)); printf("节大小%x ", *(int*)sord); //cout << sct_sum << endl; for (int s1 = 0; s1 < *(int*)sord; s1++) { *(char*)imagebuff++ = *(char*)pfbuff++; } sord += 40;//下一个节表的sizeofrawdata imagebuff = imbuff + (*(int*)(sord - 4)); pfbuff = (fp + *(int*)(sord + 4)); } FILE* fp1 = NULL; fp1 = fopen("copyi3.exe", "wb"); fwrite((char*)imbuff, 1, *(int*)(soi), fp1); //cout << u; fclose(fp1); fp = NULL; //free(imagebuff); //free(pfbuff); // free(imbuff); //itof_32(imbuff); return imbuff; }
char* itof_32(char* imbuff,char* name)//filebuffer to imagebuffer { if (*(short*)imbuff != 0x5a4d) { cout << "错误 不是mz"; return 0; } char* imgb = imbuff; unsigned int fbuff_size = 0; int sct_cnt = *(short*)((int)imbuff + (*(int*)((int)imbuff + 60))/*dos+*/ + 6/*节表个数*/); char* sord = (char*)((int)imbuff + (*(int*)((int)imbuff + 60))/*dos+*/ + 24);/*pe头*/ sord += *(short*)((int)imbuff + (*(int*)((int)imbuff + 60))/*dos+*/ + 20); //pe头末尾+可选pe头的宽度=节表 sord += 16;//sizeofrawdata printf(" %x ", *((int*)sord)); int si = 0; while (si++ < sct_cnt) { fbuff_size += (*(int*)sord); sord += 40; } fbuff_size += *(int*)((int)imbuff + (*(int*)((int)imbuff + 60))/*dos+*/ + 84);/*sizeofheaders*/ ////**************************************缩短头 char* fbuff = (char*)malloc(flen); memset(fbuff, 0, flen); if (!fbuff) { perror("The following error occurred"); getchar(); return 0; } char* buff = fbuff; memset(buff, 0, flen); char* soh = (char*)((int)imbuff + (*(int*)((int)imbuff + 60))/*dos+*/ + 84);/*sizeofheaders*/ //int fa = *(int*)(soh - 24);//FileAlignment; //if (*(int*)soh < fa)// 拷贝头+缩短 //{ int i = 0; while (i++ <= *(int*)(soh)) { *(char*)fbuff++ = *(char*)imbuff++; } //************************************缩短节 sord -= 40 * sct_cnt;//第一个节表的SizeOfRawData; printf(" %x ", *(int*)(sord - 4)); imbuff = (imgb + *(int*)(sord - 4)); fbuff = (buff + *(int*)(sord + 4)); int s = 0; while (s++ < sct_cnt)//节缩短 { // cout << sct_cnt << endl; int s1 = 0; while (s1++ < *(int*)sord) { *(char*)fbuff++ = *(char*)imbuff++; } sord = (char*)(((int)sord) + 40);//下一个节表的sizeofrawdata fbuff = (buff + *(int*)(sord + 4)); imbuff = imgb + *(int*)(sord - 4); } //file_ser(buff, fbuff_size); FILE* fp1 = fopen(name, "wb"); fwrite((char*)buff, 1, flen, fp1); fclose(fp1); fp1 = NULL; return buff; }
通常每个节都会遗留一些0,这和对齐有关系,比如说文件对齐是200h,存储的数据只有10h,那么这个节里还会有190h的空间给我们随便搞。如果想在别人程序里加一段自己的程序,就可以在代码节或其他节的空隙里添加硬编码,然后修改OEP让程序一开始先执行你的程序,然后再跳到原来的OEP上。
放代码前,先来了解2个硬编码,E8,E9。
E8对应的汇编语句就是call,E9就是jmp。
E8后面跟地址(4个字节)E9也是。不过跳转的地址不能写死,有一个公式就是:
真正要跳转的地址 = E8这条指令的下一行地址 + X
X = 真正要跳转的地址 - E8这条指令的下一行地址
写代码的时候只要计算一下x,然后把x填到E8/E9后面就可以了。
int rva_to_foa(char* i,int n) { Int rva = 0; Int rva_offset = 0; rva_offset = n; int sct_cnt = *(short*)((int)i + (*(int*)((int)i + 60))/*dos+*/ + 6/*节表个数*/); char* sord = (char*)((int)i + (*(int*)((int)i + 60))/*dos+*/ + 24);/*pe头*/ sord += *(short*)( (int)i + (*(short*)((int)i + 60))/*可选头大小*/ + 20); sord += 12;//VirtualAddress; sord += 40 * (sct_cnt - 1);//最后一个节表的virtusladdress int s = sct_cnt; while (s > 0) { if (rva_offset >= *(int*)sord) { break; } sord -= 40; s--; } if (s == 0)//偏移在头部 { //printf("foa%x\n", rva_offset); return rva_offset; } rva_offset -= *(int*)(sord);//减fva+foa rva_offset += *(int*)(sord + 8); //printf("foa%x\n", rva_offset); return rva_offset; }
int foa_to_rva(char* i, int n) { Int rva = 0; Int rva_offset = 0; rva_offset = n; int sct_cnt = *(short*)((int)i + (*(int*)((int)i + 60))/*dos+*/ + 6/*节表个数*/); char* sord = (char*)((int)i + (*(int*)((int)i + 60))/*dos+*/ + 24);/*pe头*/ sord += *(short*)((int)i + (*(short*)((int)i + 60))/*可选头大小*/ + 20); sord += 20;//foa; sord += 40 * (sct_cnt - 1);//最后一个节表的virtusladdress int s = sct_cnt; while (s > 0) { if (rva_offset >= *(int*)sord) { break; } sord -= 40; s--; } if (s == 0)//偏移在头部 { //printf("foa%x\n", rva_offset); return rva_offset; } rva_offset -= *(int*)(sord);//减foa+rva rva_offset += *(int*)(sord-8 ); //printf("rva%x\n", rva_offset); return rva_offset; }
//先调用文件打开函数-》文件buffer转映像buff-》添加代码函数(也就是这个)-》映像buff
//转文件buff函数存盘
char* inseart_code(char* imbuff) { char* buff = imbuff; char* sod = (imbuff + (*(int*)(imbuff + 60) + 24));//pe头尾 sod += *(short*)(imbuff + (*(int*)(imbuff + 60) + 20)); while (strcmp(sod, ".text") != 0) { sod += 40; } buff = (imbuff + (*(int*)(sod + 12))) + (*(int*)(sod + 8));//节的空白区 char* oep = (imbuff + (*(int*)(imbuff + 60) + 40)); // oep += *(short*)(imbuff + (*(int*)(imbuff + 60) + 20)); int roep = *(int*)oep; *(int*)oep = buff - imbuff;//代码入口 char a[] = { 0x6A,0,0x6A,0,0x6A,0,0x6A,0,0xE8,0,0,0,0,0xE9,0,0,0,0 }; int dest_addr = 0x75fc0047;//messageBox 的地址 这个地址会改变 int x = dest_addr - (((int)(buff - imbuff) + 13) + 0x400000); int y = 0x400000 + roep - (((int)(buff - imbuff) + 18) + 0x400000); *(int*)(a + 9) = x; *(int*)(a + 14) = y; int s = 0; //75FC0026 8BEC mov ebp, esp while (s <= (sizeof(a) / sizeof(a[0]))) { (*(char*)buff++) = a[s]; s++; } return imbuff; }
这个比较简单,多余文字我不写了。直接上代码。。
char* inseart_jb(char* imbuff,int size) { char* buff = imbuff; char* sh = (char*)((imbuff + (*(int*)(imbuff + 60))) + 24)/*pe*/; int sh_cnt = *(short*)(sh - 18);//节表个数 int old_size = *(int*)(sh + 56); *(int*)(sh + 56) += size;//sizeofimg扩大 flen += size; unsigned int nl = *(int*)(sh + 56); (*(short*)(sh - 18))++;//节表数+1 sh += *(short*)(sh - 4);//节表 int s = 0; while (s++ < sh_cnt) { sh += 40; } char code[] = { 0x2E, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x40, 0xE9, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xF0, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0xf0 };//节表 strcpy(code, "tttt"); unsigned int va = 0; int vs_rs = size;//尺寸 if (*(int*)(sh - 32) >= *(int*)(sh - 24)) { va = (*(int*)(sh - 32)) + (*(int*)(sh - 28)); } else { va = (*(int*)(sh - 24)) + (*(int*)(sh - 28)); } unsigned int ra = (*(int*)(sh - 20)) + (*(int*)(sh - 24)); *(int*)(code + 8) = vs_rs; *(int*)(code + 12) = va;//p偏移 *(int*)(code + 16) = vs_rs; *(int*)(code + 20) = ra; s = 0; while (s<40) { *sh = code[s]; sh++; s++; } //****************************新增节 char* buff1 = (char*)realloc(imbuff,nl); memset((char*)(buff1 + old_size), 0, size); imbuff=buff1 ; return buff1; }
由于有的时候节表剩余空间不够80个字节,所以就不能增加节(当然可以也可以把dos sub给覆盖掉)但可以用扩大最后节/合并节
扩大最后节:
1、拉伸到内存
2、分配一块新的空间:SizeOfImage + Ex
3、将最后一个节的SizeOfRawData和VirtualSize改成N
SizeOfRawData = VirtualSize = N
N = (SizeOfRawData或者VirtualSize 内存对齐后的值) + Ex
4、修改SizeOfImage大小
SizeOfImage = SizeOfImage + Ex
合并节
(字有点丑。。。) 最后把节的属性改到最大就ok。
char* expand_lastjb(char* imgbuff) { char* si = (imgbuff + (*(unsigned int*)(imgbuff + 60)) + 80); (*(int*)si) += 0x1000;//扩大映像尺寸 unsigned sl = (*(unsigned int*)si); char* buff = (char*)realloc(imgbuff,sl); if (buff==0) { printf("内存分配失败--expand——lastjb"); getchar(); return 0; } memset(buff, 0, sl); imgbuff = buff; char* sh = (imgbuff + (*(unsigned int*)(imgbuff + 60)));//pe int sh_cnt = *(short*)(sh + 6); sh += ((*(short*)(sh + 20)) +24);//节表头 int s = 0; while (s++ < (sh_cnt-1)) { sh += 40; } *(int*)(sh + 36) |= 0x60000020; //加上代码节的权限 *(int*)(sh + 8) += 0x1000; *(int*)(sh + 16) += 0x1000; return imgbuff; }
char* merge_jb(char* imbuff) { char* buff = imbuff; int all_jb_char = 0;//保存所有节表的属性; char* pe = (imbuff + (*(int*)(imbuff + 60)));//pe头 char* sh = (pe + 24 + (*(short*)(pe + 20)) );//节表 int code_size = 0; int s = 0; while (s <(*(short*)(pe+6))) //得到节表的大小和权限 { if(s == (*(short*)(pe + 6))-1) if (*(int*)(sh + 8 + 40 * s) >= *(int*)(sh + 16 + 40 * s))//virtualsize和sizeofrawdata做比较 { code_size += *(int*)(sh + 8 + 40 * s) + *(int*)(sh + 12 + 40 * s) - (*(int*)(pe + 84)); } else { code_size += *(int*)(sh + 16 + 40 * s)+ (*(int*)(sh + 12 + 40 * s) - (*(int*)(pe + 84))); } all_jb_char |= *(int*)(sh + 36 + 40 * s); for (int i = 0; i < 40 && s!=0; i++)//除第一个节表外都置位0 { *(sh+i+40*s) = 0; } s++; } *(int*)(sh + 8) = code_size; *(int*)(sh + 16) = code_size; *(int*)(sh + 36) = all_jb_char; *(short*)(pe + 6) = 1;//numberofsection置位1; return imbuff; }
1、如何定位导出表:
数据目录项的第一个结构,就是导出表.
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
VirtualAddress 导出表的RVA
Size 导出表大小
字段说明
1.按名称导出
2.按序号导出
int GetFunctionAddrByOrdinals(char* fbuff, int ord) { char* pe = (fbuff + (*(int*)(fbuff + 60)));//pe头 char* data_dir = pe + 120;//数据目录 char* exportt = fbuff + rva_to_foa(fbuff, *(int*)data_dir);//导出表头 if (*(int*)data_dir == 0) { printf("此文件没有导出表"); getchar(); return 0; } int* aof = (int*)(fbuff + rva_to_foa(fbuff, *(int*)(exportt + 28)));//地址表头 int nof = *(int*)(exportt + 20);// 所有导出函数的个数 int* arr_fun = (int*)malloc(sizeof(int)*nof); int base = *(int*)(exportt + 16); int s = 0; while (s<nof) { arr_fun[s] = *(int*)(aof + s);//函数表 //printf("rav:%x ", arr_fun[s]); if (ord - base == s) { printf("rav:%x ", arr_fun[s]); return arr_fun[s]; } s++; } printf("无此函数"); getchar(); return 0; }
int GetFunctionAddrByName(char* fbuff, char* name)//导出表:按名字导出+显示导出函数信息 { char* pe = (fbuff + (*(int*)(fbuff + 60)));//pe头 char* data_dir = pe + 120;//数据目录 char* export1 = fbuff+ rva_to_foa(fbuff, *(int*)data_dir);//回来发现export是关键字 不能拖内存 所以又定义了一个 char* export=export1;//导出表头 if (*(int*)data_dir == 0) { printf("此文件没有导出表"); getchar(); return 0; } int NumberOfNames= *(int*)(export+24); int* aon = (int*)(fbuff + rva_to_foa(fbuff, *(int*)(export+32)));//名称地址表头 //aon = (int*)(fbuff + rva_to_foa(fbuff, *(int*)(aon))); int* arr_name = (int*)malloc(sizeof(int)*NumberOfNames); int base = *(int*)(export+16); int flag = -1;//索引 int s = 0; while (s<NumberOfNames) { arr_name[s]=(int)(fbuff + rva_to_foa(fbuff, *(int*)(aon+s)));//函数名地址 //printf("%s\n", arr_name[s]); if (strcmp((char*)arr_name[s],name) == 0) { flag = s; } s++; } if (flag == -1)//判断有没有这个函数 { printf("没有此函数"); getchar(); return 0; } short* aono = (short*)(fbuff + rva_to_foa(fbuff, *(int*)(export + 36)));//序号表 short* arr_name_ord = (short*)malloc(sizeof(short)*NumberOfNames); s = 0; while (s<NumberOfNames) { arr_name_ord[s] = *(short*)(aono+s)+base;//函数序号表 //printf("%d\n", arr_name_ord[s]); s++; } int* aof = (int*)(fbuff + rva_to_foa(fbuff, *(int*)(export + 28)));//地址表头 int nof = *(int*)(export + 20);// 所有导出函数的个数 int* arr_fun= (int*)malloc(sizeof(int)*nof); s = 0; while (s<nof) { arr_fun[s] = *(int*)(aof + s);//函数表 printf("rav:%x ", arr_fun[s]); printf("%x ", arr_name_ord[s]); printf("%s\n",arr_name[s]); s++; } printf("%x", arr_fun[arr_name_ord[flag]-base]); return arr_fun[arr_name_ord[flag]];//返回地址 }
//此文件已经新增节处理过了。
char* move_export(char* fbuff) { char* pe = (fbuff + (*(int*)(fbuff + 60)));//pe头 char* data_dir = pe + 120;//数据目录 char* exportt = fbuff + rva_to_foa(fbuff, *(int*)data_dir);//导出表头 if (*(int*)data_dir == 0) { printf("此文件没有导出表"); getchar(); return 0; } char* nexport= fbuff + rva_to_foa(fbuff, getlastjb_rva_add(fbuff));//定位到最后一个节的开始地址 char* nexporth = nexport; *(int*)data_dir = foa_to_rva(fbuff,nexport-fbuff); int s = 0; while (s<40) { *(char*)(nexport + s) = *(char*)(exportt + s); s++; } int nf = *(int*)(exportt + 20);//numberoffunction int nn = *(int*)(exportt + 24);//numberofnames char* name = fbuff + rva_to_foa(fbuff, *(int*)(exportt+12));//dll名字的地址 char* paddr = fbuff + rva_to_foa(fbuff, *(int*)(exportt + 28)); char* pnaddr = nexport + 40; s = 0; *(int*)(nexporth + 28) = foa_to_rva(fbuff,pnaddr-fbuff);//复原AddressOfFunctions; while (s<nf)//拷贝函数表 { *(int*)(pnaddr)= *(int*)(paddr+4*s) ; /* pnaddr= (char*)((int*)pnaddr+1); paddr = (char*)((int*)paddr + 1); */ //这个表达式比较麻烦所以用下面的+4 pnaddr += 4; //函数地址! s++; } paddr = fbuff + rva_to_foa(fbuff, *(int*)(exportt + 36)); s = 0; *(int*)(nexporth + 36) = foa_to_rva(fbuff, pnaddr - fbuff);// AddressOfNameOrdinals while (s<nn)//拷贝函数序号表 { *(short*)(pnaddr) = *(short*)(paddr+2*s); pnaddr += 2; s++; } paddr = fbuff +rva_to_foa(fbuff, *(int*)(exportt + 32)); s = 0; int strl = strlen(name);//两个地址转换偏移 char* straddr =name;//存储字符串的地方; char* nstradd = pnaddr + 4 * nn; //地址表后 *(int*)(nexporth + 32) = foa_to_rva(fbuff, pnaddr - fbuff);//AddressOfNames; while (s<nn+1)//拷贝函名称数表 { if (s!=0)//s=0时复制dll的NAME { *(int*)(pnaddr) =(foa_to_rva(fbuff,nstradd-fbuff)); *(int*)(nexporth + 12) = (int)foa_to_rva(fbuff,pnaddr-fbuff);//定位name pnaddr += 4;//表指针后移 paddr += 4; } for (int i = 0; i < strl; i++) { *nstradd++= *straddr++; } *nstradd=0;// 少0 nstradd++; strl = strlen(fbuff + rva_to_foa(fbuff, *(int*)paddr)); straddr = fbuff + rva_to_foa(fbuff, *(int*)(paddr)); s++; } return fbuff; }
重定位就是你本来这个程序理论上要占据这个地址,但是由于某种原因,这个地址现在不能让你占用,你必须转移到别的地址,这就需要基址重定位。你可能会问,不是说过每个进程都有自己独立的虚拟地址空间吗?既然都是自己的,怎么会被占据呢?对于EXE应用程序来说,是这样的。但是动态链接库就不一样了,我们说过动态链接库都是寄居在别的应用程序的空间的,所以出现要载入的基地址被应用程序占据了或者被其它的DLL占据了,也是很正常的,这时它就不得不进行重定位了。
1.重定位表定位
2.重定位表解析
void print_relocation(char* fbuff) { char* pe = (fbuff + (*(int*)(fbuff + 60)));//pe头 char* data_dir = pe + 120;//数据目录 if (*(int*)(data_dir+8*5) == 0) { printf("文件没有重定位表"); getchar(); return; } char* relocation = fbuff + rva_to_foa(fbuff, *(int*)(data_dir + 8 * 5));//重定位表头 char* flag = relocation; int s = 0; int s1 = 0; int rva = 0;//记录偏移 int b = 0; printf("%x\n", 12 & 0x3); while (*(int*)flag != 0 && *(int*)(flag+4) != 0) { rva = *(int*)flag; printf("*************%x\n", rva); flag += 8;// DWORD SizeOfBlock s = 0; s1 = (*(int*)(flag - 4)-8) / 2; while (s++ < s1)//当前页有多少个地址 { b = *(short*)flag; //if (b & 0x3000 == 0x3000) printf("%x\n",rva+(b & 0xfff)); flag += 2; } } }
char* move_relocation(char* fbuff,int offset)//移动重定位表到新增节 偏移可以为0 也可以修改重定位数据 { char* pe = (fbuff + (*(int*)(fbuff + 60)));//pe头 char* data_dir = pe + 120;//数据目录 if (*(int*)(data_dir + 8 * 5) == 0) { printf("文件没有重定位表"); getchar(); return 0; } char* relocation = fbuff + rva_to_foa(fbuff, *(int*)(data_dir + 8 * 5));//重定位表头 char* flag = relocation; int s = 0; int s1 = 0; int rva = 0;//记录偏移 short b = 0; char* nrelocation = fbuff + rva_to_foa(fbuff, (getlastjb_rva_add(fbuff)));//移动的目的地foa *(int*)(data_dir + 8 * 5) = foa_to_rva(fbuff, nrelocation - fbuff);//改变数据目录的偏移; while (*(int*)flag != 0 && *(int*)(flag + 4) != 0) { rva = *(int*)flag; *(int*)nrelocation = (*(int*)flag); nrelocation += 4; *(int*)nrelocation = *(int*)(flag+4); nrelocation += 4; //这里可以用别的库函数代替 printf("*************%x\n", rva); flag += 8;// DWORD SizeOfBlock s = 0; s1 = (*(int*)(flag - 4) - 8) / 2; while (s++ < s1)//当前页有多少个地址 { b = *(short*)flag; //if (b & 0x3000 == 0x3000) *(int*)(fbuff + rva_to_foa(fbuff,rva + (b & 0x0fff))) += offset; // else *(short*)nrelocation = (*(short*)flag); //*(short*)nrelocation = (*(short*)flag); nrelocation += 2; printf("%x\n", rva + (b & 0xfff)); flag += 2; } } s = 0; while (s++<2)//为了避免意外情况,后面补8个0 表示结束 { *(int*)nrelocation=0; nrelocation += 4; } return fbuff; }
0x8导入表
在编程中常常用到“导入函数”(Import functions),导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL中,在调用者程序中只保留一些函数信息,包括函数名及其驻留的DLL名等。
于磁盘上的PE 文件来说,它无法得知这些输入函数在内存中的地址,只有当PE 文件被装入内存后,Windows 加载器才将相关DLL 装入,并将调用输入函数的指令和函数实际所处的地址联系起来。这就是“动态链接”的概念。动态链接是通过PE 文件中定义的“导入表”来完成的,导入表中保存的正是函数名和其驻留的DLL 名等。
导入表结构:
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; //RVA 指向IMAGE_THUNK_DATA结构数组 }; DWORD TimeDateStamp; //时间戳 DWORD ForwarderChain; DWORD Name; //RVA,指向dll名字,该名字已0结尾 DWORD FirstThunk; //RVA,指向IMAGE_THUNK_DATA结构数组 } IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
OriginalFirstThunk字段就是指向INT表,FirstThunk字段指向IAT表。通常情况下程序加载前这个两个表的内容是一样的,如果TimeDateStamp;字段为-1的话,IAT表存的就是地址。这是绑定导入表的内容,本文不做阐述。
程序加载前和加载后IAT表的值可能不一样。在时间戳为-1、dll时间戳没有变化、dll没有发生重定位的情况下,IAT表的值都是函数的绝对地址。一般情况下程序加载前IAT表的值和INT表的值是一样的,程序加载后IAT表的内容变成了函数的地址,INT则不变。
typedef struct _IMAGE_THUNK_DATA32 { union { PBYTE ForwarderString; PDWORD Function; DWORD Ordinal; PIMAGE_IMPORT_BY_NAME AddressOfData; } u1; } IMAGE_THUNK_DATA32; typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
这个就是INT表的结构,当成一个4字节的容器就ok。。它通常指向留一个表,也就是函数名结构数组。
也就是这个东西。。
typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; //可能为空,编译器决定 如果不为空 是函数在导出表中的索引 BYTE Name[1]; //函数名称,以0结尾 } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
void print_import(char* fbuff) { char* pe = (fbuff + (*(int*)(fbuff + 60)));//pe头 char* data_dir = pe + 120;//数据目录 if (*(int*)(data_dir + 8) == 0) { printf("文件没有导入表"); getchar(); return ; } char* importt = fbuff + rva_to_foa(fbuff, *(int*)(data_dir + 8));//导入表第一张表的表头 char* pimportt = importt; char* poft=0; char* pft = 0; char* str=0; // int b = 0xffffffff >> 31; //printf("%d",b ); while (*(int*)pimportt != 0 && *(int*)(pimportt + 16) != 0) { char* name =fbuff + rva_to_foa(fbuff, *(int*)(pimportt + 12)); printf("*******************%s\n",name); poft = fbuff+rva_to_foa(fbuff,*(int*)(pimportt));//poft是 FirstThunk; 指向的地址; pft = fbuff + rva_to_foa(fbuff, *(int*)(pimportt + 16));//pft是 OriginalFirstThunk指向的地址; while (*(int*)(poft) != 0) { if (*(unsigned int*)(poft) >> 31 == 1)//算术右移和逻辑右移不一样 { printf("int:%0x ", (*(int*)(poft) << 1) / 2);//位运算把第一位去掉 再/2 =取低31位 poft += 4; if (*(int*)(pimportt + 4) == -1)//时间戳 { printf("iat: %x\n", *(int*)(pft)); } else printf("iat:%0x \n", (*(int*)(pft) << 1) / 2); //位运算把第一位去掉 再/2 =取低31位 pft += 4; continue; } str = fbuff + 2 + rva_to_foa(fbuff, *(int*)(poft)); printf("int:%s ", str); poft += 4; if (*(int*)(pimportt + 4) == -1)//时间戳 { printf("iat: %x\n", *(int*)(pft)); } else { str = fbuff + 2 + rva_to_foa(fbuff, *(int*)(pft)); printf("iat: %s\n", str); } pft += 4; } pimportt += 20; } }
当Exe被加载时,系统会根据Exe导入表信息来加载需要用到的DLL,导入表注入的原理就是修改exe导入表,将自己的DLL添加到exe的导入表中,这样exe运行时可以将自己的DLL加载到exe的进程空间.
注意事项:移动导入表时,最好不要移动IAT表,因为它里面存的有可能是函数的绝对地址,移动的话可能会需要重定位,否则程序会崩溃。我当时移动导入表然后注入的时候,程序不能运行,找bug找了好久,最后打开od调试时发现IAT表出问题了,它载入内存时没有变成函数地址(有可能改一下时间戳就ok,但最后我放弃移动IAT表了。。。)
左图是我手动移动导入表后的IAT表,右图是用工具注入dll后的IAT表。
下面来说说我导入表注入的思路(因为我比较懒,所以直接选择把导入表移动到新增节里了。。。):
1. 找到原表,2.找到新增节的rva,3. 先把自己构造的导入表放进去,然后一边遍历原表一边复制(我把新增节分成4块,导入表块,INT表块,dll名称块,函数名结构数组块)IAT表不动。
效果:
原文件
注入后的文件
void inject_dll(char* fbuff, char* fun_name,char* dll_name) { char* pe = (fbuff + (*(int*)(fbuff + 60)));//pe头 char* data_dir = pe + 120;//数据目录 if (*(int*)(data_dir + 8) == 0) { printf("文件没有导入表"); getchar(); return; } char* importt = fbuff + rva_to_foa(fbuff, *(int*)(data_dir + 8));//导入表第一张表的表头 char* pimportt = importt; char* poft = 0; char* pft = 0; char* str = 0; char* pdest = fbuff + rva_to_foa(fbuff, getlastjb_rva_add(fbuff));//新导出表的位置 char* niat = pdest + 0x500;//新iat表位置 char* nint = pdest + 0x800;//新int表位置 char* ndllname = pdest + 0x1100;//新dll名 char* nstr = pdest + 0x1800;//新dll名数组 //构建导入表 注入的dll *(int*)pdest = foa_to_rva(fbuff,nint - fbuff); *(int*)(pdest + 4) = 0; *(int*)(pdest + 8) = 0; *(int*)(pdest + 12) = foa_to_rva(fbuff, ndllname - fbuff); *(int*)(pdest + 16) = foa_to_rva(fbuff, niat - fbuff); pdest += 20; strcpy(ndllname, dll_name); //*(short*)ndllname = 0; ndllname += strlen(dll_name)+1; *(int*)nint = foa_to_rva(fbuff,nstr-fbuff); *(int*)niat = *(int*)nint; *(int*)(nint+4) = 0; *(int*)(niat +4) = 0; nint += 8; niat += 8; *(short*)nstr = 0;//函数名结构数组 nstr += 2; strcpy(nstr, fun_name); nstr += strlen(fun_name) + 1; while (*(int*)pimportt != 0 && *(int*)(pimportt + 16) != 0) {//iat表没有复制 char* name = fbuff + rva_to_foa(fbuff, *(int*)(pimportt + 12)); printf("*******************%s\n", name); *(int*)pdest = 0;//foa_to_rva(fbuff, nina - fbuff); *(int*)(pdest + 4) = -0; *(int*)(pdest + 8) = 0; *(int*)(pdest + 12) = foa_to_rva(fbuff, ndllname - fbuff); *(int*)(pdest + 16) = *(int*)(pimportt + 16);//foa_to_rva(fbuff, nita - fbuff); pdest += 20; /*for (int i = 0; i < 20; i++) { *(pdest + i) = *(pimportt + i); }*/ strcpy(ndllname,name);//拷贝dll名字 //*(short*)ndllname = 0; ndllname += strlen(name) + 1; poft = fbuff + rva_to_foa(fbuff, *(int*)(pimportt));//poft是 OriginalFirstThunk 指向的地址; pft = fbuff + rva_to_foa(fbuff, *(int*)(pimportt + 16));//pft是 FirstThunk指向的地址; while (*(int*)(poft) != 0) { /*if (*(unsigned int*)(poft) >> 31 == 1)//当ina里存的是序号 ,算术右移和逻辑右移不一样 { printf("int:%0x ", (*(int*)(poft) << 1) / 2);//位运算把第一位去掉 再/2 =取低31位 *(int*)nina = 0;//*(int*)(poft);//复制ina if (*(int*)(pimportt + 4) == -1)//时间戳 当ita是函数地址 { printf("iat: %x\n", *(int*)(pft)); *(int*)nita = *(int*)(pft);//复制ina } else { printf("iat:%x \n", (*(int*)(pft) << 1) / 2);//位运算把第一位去掉 再/2 =取低31位 *(int*)nita = *(int*)(pft);//复制ina } nina += 4; nita += 4; pft += 4; poft += 4; continue; }*/ str = fbuff + 2 + rva_to_foa(fbuff, *(int*)(poft)); printf("int:%s ", str); *(int*)niat = foa_to_rva(fbuff,nstr-fbuff);//复制ina 指向字符串 *(short*)nstr = *(short*)(str-2);//函数名结构数组 nstr += 2; strcpy(nstr, str); nstr += strlen(str) + 1; //nina += 4; /* if (*(int*)(pimportt + 4) == -1)//时间戳 当ita是函数地址 { printf("iat: %x\n", *(int*)(pft)); *(int*)nita = *(int*)(pft);//复制ina //nita += 4; } else { str = fbuff + 2 + rva_to_foa(fbuff, *(int*)(pft)); printf("iat: %s\n", str); *(int*)nita = *(int*)nina;//指向同一个地方 //nita += 4; }*/ //*(int*)nina = 0; //nita += 4; nint += 4; poft += 4; pft += 4; } for (int i = 0; i < 2; i++)//补0 { *(int*)nint = 0; nint += 4; //*(int*)nitt = 0; //nita += 4; } pimportt += 20; } for (int i = 0; i < 4; i++)//补0 { *(int*)pdest = 0; pdest += 4; } *(int*)(data_dir + 8) = getlastjb_rva_add(fbuff);//修改数据目录 }
Ok,我讲完了,完整代码我一会发到下面。我要去肝win32了,下次见,886+。
哦哦,我这篇文章主要发的代码比较多,理论少了点。所以我放几个参考资源来了解、掌握PE。
书籍:《PE权威指南》
视频:https://ke.qq.com/course/179553?taid=1062952866332001&tuin=437f9e13
帖子:
1. https://www.cnblogs.com/You0/p/4229529.html
2. https://www.cnblogs.com/You0/p/4238829.html
3. https://www.cnblogs.com/You0/p/4238954.html
4. https://www.cnblogs.com/You0/p/4239024.html