PE 移动导入表/注入
前言:
到目前的话,自己已经把滴水的PE要写的全写完了,供大家参考!
项目地址:https://github.com/adezz/MyPe
导入表注入原理:
当exe文件被加载时,系统会根据exe导入表信息来加载需要用到的DLL,导入表注入的原理就是修改exe导入表,将自己的DLL添加到exe的导入表中,这样exe运行时可以将自己的DLL加载到exe的进程空间
导入表移动的步骤:
第一步:新增节
第二步:复制原来的导入表到新的节数据中
第三步:追加一个新的导入表
第四步:新的导入表后面继续追加8个字节的INT表 8个字节的IAT表,为什么是两个表都是八个字节呢?因为还有4个字节是作为结束标识符的
第五步:继续在后面追加一个IMAGE_IMPORT_BY_NAME 结构,前2个字节是0 后面是函数名称字符串
第六步:因为RVA和FOA还有区别,所以需要在导入表中的三个属性 INT和IAT和导入表相关的DLL名称中记录的地址都需要转换为RVA再存储进去
第七步:修正IMAGE_DATA_DIRECTORY结构的VirtualAddress和Size
根据目录项(第二个就是导入表)得到导入表信息:
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
实现代码:
void MoveAndInjectImportTable(PVOID pFileBuffer,PDWORD OldBufferSize,PVOID* pNewBuffer){ PIMAGE_DOS_HEADER pImageDosHeader = NULL; PIMAGE_FILE_HEADER pImageFileHeader = NULL; PIMAGE_OPTIONAL_HEADER32 pImageOptionalHeader = NULL; PIMAGE_SECTION_HEADER pImageSectionHeaderGroup = NULL; PIMAGE_SECTION_HEADER NewSec = NULL; PIMAGE_IMPORT_DESCRIPTOR pIMPORT_DESCRIPTOR = NULL; PIMAGE_IMPORT_DESCRIPTOR pIMPORT_DESCRIPTOR_Temp = NULL; PIMAGE_IMPORT_BY_NAME IMPORT_BY_NAME = NULL; DWORD RVA = 0; DWORD FOA = 0; DWORD isOk; DWORD NewLength=0; PVOID LastSection = NULL; PVOID CodeSection = NULL; PVOID SectionOfNew= NULL; PVOID SectionOfNewTemp = NULL; pImageDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer; pImageFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pImageDosHeader + pImageDosHeader->e_lfanew + 4); pImageOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pImageFileHeader + sizeof(IMAGE_FILE_HEADER)); pImageSectionHeaderGroup = (PIMAGE_SECTION_HEADER)((DWORD)pImageOptionalHeader + pImageFileHeader->SizeOfOptionalHeader); //判断是否可以容纳相应的节表 isOk = (DWORD)pImageOptionalHeader->SizeOfHeaders - ((DWORD)pImageDosHeader->e_lfanew + IMAGE_SIZEOF_FILE_HEADER + pImageFileHeader->SizeOfOptionalHeader + 40*pImageFileHeader->NumberOfSections); if(isOk < 80){ printf("空间太小 无法进行添加!"); return; } //生成对应的内存大小的空间 NewLength += *OldBufferSize + 0x1000; *pNewBuffer = (PVOID)malloc(NewLength); ZeroMemory(*pNewBuffer,NewLength); //拷贝之前内存空间 到 当前新生成的内存空间 memcpy(*pNewBuffer,pFileBuffer,*OldBufferSize); //获取新的结构体 pImageDosHeader = (PIMAGE_DOS_HEADER)(*pNewBuffer); pImageFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pImageDosHeader + pImageDosHeader->e_lfanew + 4); pImageOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pImageFileHeader + sizeof(IMAGE_FILE_HEADER)); pImageSectionHeaderGroup = (PIMAGE_SECTION_HEADER)((DWORD)pImageOptionalHeader + pImageFileHeader->SizeOfOptionalHeader); // pImageFileHeader->NumberOfSections修改 pImageFileHeader->NumberOfSections = pImageFileHeader->NumberOfSections + 1; // pImageOptionalHeader->SizeOfImage修改 pImageOptionalHeader->SizeOfImage = (DWORD)pImageOptionalHeader->SizeOfImage + 0x1000; //复制代码段的节数据到 当前最后一个节数据后面 CodeSection = (PVOID)(&pImageSectionHeaderGroup[0]); //新增节的位置 LastSection = (PVOID)(DWORD)(&pImageSectionHeaderGroup[pImageFileHeader->NumberOfSections-1]); memcpy(LastSection,CodeSection,40); //修正相关属性 NewSec = (PIMAGE_SECTION_HEADER)LastSection; strcpy(NewSec,".NewSec"); NewSec->Misc.VirtualSize = 0x1000; NewSec->SizeOfRawData = 0x1000; NewSec->Characteristics = 0xC0000040; NewSec->VirtualAddress = pImageSectionHeaderGroup[pImageFileHeader->NumberOfSections-2].VirtualAddress + pImageSectionHeaderGroup[pImageFileHeader->NumberOfSections-2].SizeOfRawData; NewSec->PointerToRawData = pImageSectionHeaderGroup[pImageFileHeader->NumberOfSections-2].PointerToRawData + pImageSectionHeaderGroup[pImageFileHeader->NumberOfSections-2].SizeOfRawData; //修改大小长度 *OldBufferSize = NewLength; //这里得到新节位置的指针 SectionOfNew = (PVOID)((DWORD)*pNewBuffer + (DWORD)NewSec->PointerToRawData); //先获取导入表的地址 RVA_TO_FOA(*pNewBuffer,pImageOptionalHeader->DataDirectory[1].VirtualAddress,&FOA); pIMPORT_DESCRIPTOR = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)*pNewBuffer + (DWORD)FOA); //printf("start:%x\n", pIMPORT_DESCRIPTOR); /* 第三步: 将原导入表全部Copy到空白区 */ SectionOfNewTemp = SectionOfNew; while (pIMPORT_DESCRIPTOR->OriginalFirstThunk && pIMPORT_DESCRIPTOR->FirstThunk) { //printf("%x\n", (DWORD)SectionOfNewTemp - (DWORD)*pNewBuffer); memcpy(SectionOfNewTemp,pIMPORT_DESCRIPTOR,20); pIMPORT_DESCRIPTOR++; SectionOfNewTemp = (PVOID)((DWORD)SectionOfNewTemp + 20); } //保存复制完导入表之后的地址 pIMPORT_DESCRIPTOR_Temp = SectionOfNewTemp; printf("开始添加自己的导入表的地址:%x\n",(DWORD)SectionOfNewTemp-(DWORD)*pNewBuffer); /* 第四步: 在新的导入表后面,追加一个导入表. typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; }; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR; */ pIMPORT_DESCRIPTOR_Temp->TimeDateStamp = 0; pIMPORT_DESCRIPTOR_Temp->ForwarderChain = 0; FOA_TO_RVA(*pNewBuffer,(DWORD)pIMPORT_DESCRIPTOR_Temp + 40 - (DWORD)*pNewBuffer,&RVA); // INT表占8个字节 pIMPORT_DESCRIPTOR_Temp->OriginalFirstThunk = RVA; //这个是指向导入表相关INT表 存的是RVA,所以前面还需要转换下 FOA_TO_RVA(*pNewBuffer,(DWORD)pIMPORT_DESCRIPTOR_Temp + 40 + 8 - (DWORD)*pNewBuffer,&RVA); // IAT表占8个字节 pIMPORT_DESCRIPTOR_Temp->FirstThunk = RVA;// 这个是指向导入表相关的IAT 存的是RVA,所以前面还需要转换下 FOA_TO_RVA(*pNewBuffer,(DWORD)pIMPORT_DESCRIPTOR_Temp + 40 + 16 - (DWORD)*pNewBuffer,&RVA); // dll函数名占8个字节,这里自己就模拟 dll名称为abc.dll 长度为7个字节 最后一个字节为\0 pIMPORT_DESCRIPTOR_Temp->Name = RVA; // 这个是指向导入表相关的DLL名称 存的是RVA 所以前面还需要转换下 strcpy((PVOID)((DWORD)pIMPORT_DESCRIPTOR_Temp + 40 + 16),"abc.dll"); /* 第五步: 追加 一个_IMAGE_THUNK_DATA32结构是4个字节 但是还需要4个字节来作为结束的标识符 所以这里IAT's IMAGE_THUNK_DATA32,INT's IMAGE_THUNK_DATA32总共是占16个字节 typedef struct _IMAGE_THUNK_DATA32 { union { PBYTE ForwarderString; PDWORD Function; DWORD Ordinal; //序号 PIMAGE_IMPORT_BY_NAME AddressOfData; //指定IMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA32; */ // INT FOA_TO_RVA(*pNewBuffer, ((DWORD)pIMPORT_DESCRIPTOR_Temp + 40 + 24 - (DWORD)*pNewBuffer),&RVA); *(PDWORD)((DWORD)pIMPORT_DESCRIPTOR_Temp + 40) = RVA; //_IMAGE_THUNK_DATA32结构中的属性指向PIMAGE_IMPORT_BY_NAME 存的是RVA 所以前面需要转换下 //IAT FOA_TO_RVA(*pNewBuffer, ((DWORD)pIMPORT_DESCRIPTOR_Temp + 40 + 24 - (DWORD)*pNewBuffer),&RVA); *(PDWORD)((DWORD)pIMPORT_DESCRIPTOR_Temp + 40 + 8) = RVA; //_IMAGE_THUNK_DATA32结构中的属性指向PIMAGE_IMPORT_BY_NAME 存的是RVA 所以前面需要转换下 // *(PDWORD)((DWORD)pIMPORT_DESCRIPTOR_Temp + 40 + 8) = RVA; //指向PIMAGE_IMPORT_BY_NAME 存的是RVA 所以前面需要转换下 /* 第六步: 追加一个IMAGE_IMPORT_BY_NAME 结构,前2个字节是0 后面是函数名称字符串 */ //IMPORT_BY_NAME = (PIMAGE_IMPORT_BY_NAME)((DWORD)pIMPORT_DESCRIPTOR_Temp + 40 + 26); //IMPORT_BY_NAME->Hint = 0; //IMPORT_BY_NAME->Name = "myFun"; //strcpy(&IMPORT_BY_NAME->Name,"myFun"); *(PWORD)((DWORD)pIMPORT_DESCRIPTOR_Temp + 40 + 24 + 2) = 0; strcpy((PVOID)((DWORD)pIMPORT_DESCRIPTOR_Temp + 40 + 24 + 2),"myFun");//这里写死了,函数的名称为myFun /* 第七步: 修正IMAGE_DATA_DIRECTORY结构的VirtualAddress和Size */ FOA_TO_RVA(*pNewBuffer,(DWORD)NewSec->PointerToRawData,&RVA); pImageOptionalHeader->DataDirectory[1].VirtualAddress = RVA; //pImageOptionalHeader->DataDirectory[1].Size = (DWORD)pImageOptionalHeader->DataDirectory[1].Size + 20; //最后进行存盘操作 MyWriteFile(*pNewBuffer, NewLength); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY