导入表注入原理:
当exe被加载时,系统会根据exe导入表信息来加载需要用到的DLL,导入表注入的原理就是修改exe导入表,将自己的DLL写入exe中
导入表注入的实现步骤:
第一步:根据目录项(第二个就是导入表)得到导入表信息
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;:指向导入表结构
DWORD Size;导入表的总大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
第二步:计算新增一个导入表需要的空间
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;
第三步:将原导入表全部Copy到空白区
第四步:在新的导入表后面,追加一个导入表
第五步:追加8个字节的INT表 8个字节的IAT表
第六步:追加一个IMAGE_IMPORT_BY_NAME 结构,前2个字节是0 后面是函数名称字符串
第七步:将IMAGE_IMPORT_BY_NAME结构的RVA赋值给INT和IAT表中的第一项
第八步:分配空间存储DLL名称字符串 并将该字符串的RVA赋值给Name属性
第九步:修正IMAGE_DATA_DIRECTORY结构的VirtualAddress和Size
实现过程中需要注意一些细节(也就是我写时候被坑的地方)
1.我们把导入表转移到别的节时,需要把该节的Misc.VirtualSize加上转移过来的数据长度,这样虚拟地址转文件偏移才不会计算错误
2.转移到的节需要加上可写属性(实际开发过程中把上面的步骤都完成想着应该ok了,结果运行的时候弹出系统错误框,瞬间无奈了,仔细想想还缺少可写属性,修改之后完美运行,非常的nice!)
3.PIMAGE_DATA_DIRECTORY 导入表结构的Size字段包括全0结构(开始的时候没去看源数据,以为长度指的是有效数据的,还是太年轻了,导致写完之后一直找不到新添加的dll)
4.记得给每个结构增加结束标记
5.要弄清楚结构中存放的地址数据是指向虚拟地址还是文件偏移,弄错一个程序就运行不起来了
6.PIMAGE_IMPORT_BY_NAME中的函数名要在注入的dll中找的到,不然运行时会弹出无法定位xx函数的提示框
7.代码是看不会的,只有不断编写,不断调试,才能体会看和写是完全不同的概念,加油加油!最后附上详细注释的源码
void TestInjectionDll(IN LPVOID pFileBuffer)
{
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER pOptionHeader = NULL;
PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
PIMAGE_IMPORT_DESCRIPTOR pImportTable = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pDosHeader + pDosHeader->e_lfanew + 4));
pOptionHeader = (PIMAGE_OPTIONAL_HEADER)(((DWORD)pDosHeader + pDosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER));
pSectionHeader = (PIMAGE_SECTION_HEADER)(((DWORD)pOptionHeader + IMAGE_SIZEOF_NT_OPTIONAL_HEADER));
//第一步:获取导入表的地址
pDataDirectory = (PIMAGE_DATA_DIRECTORY)(pOptionHeader->DataDirectory + 1);
//获取导入表的在文件中的偏移
DWORD importTableRaw = RvaToFileOffset(pDosHeader, pDataDirectory->VirtualAddress);
//获取导入表在内存中的地址
pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDosHeader + importTableRaw);
//第二步:设置需要存放注入信息的空间大小,原导入表长度加上需要追加的数据长度
DWORD codeLength = pDataDirectory->Size + 0x50;
//寻找能够存放注入信息的节,fileOffset为0表示没找到,反之保存的是新增数据的起始文件偏移地址
DWORD fileOffset = 0;
while((DWORD)pSectionHeader != 0)
{
//用节文件对齐后的大小减去节在内存中实际大小表示该节还有这么多空间没用到
if((pSectionHeader->SizeOfRawData-pSectionHeader->Misc.VirtualSize) > codeLength)
{
fileOffset = pSectionHeader->PointerToRawData + pSectionHeader->Misc.VirtualSize;
//该节的在内存中实际大小要加上codeLength,不然获取文件偏移时会出问题
pSectionHeader->Misc.VirtualSize += codeLength;
//因为系统加载dll时需要对IAT表写入函数真实地址,所以需要该节有可写权限
pSectionHeader->Characteristics |= IMAGE_SCN_MEM_WRITE;
break;
}
pSectionHeader++;
}
if(fileOffset == 0)
{
//这里可以扩大最后一个节,或者新增节,由于时间关系就没做处理了
printf("没有找到空余位置存放注入信息\n");
return;
}
//PIMAGE_IMPORT_DESCRIPTOR结构的长度
DWORD descriptorSize = 20;
//第三步:把原来导入表数据转移到新地址,因为pDataDirectory->Size长度包括结束标记(全0数据),所以只需要拷贝pDataDirectory->Size - descriptorSize长度
memcpy((LPVOID)((DWORD)pDosHeader + fileOffset), pImportTable, pDataDirectory->Size - descriptorSize);
//获取需要追加新增dll的地址,这个是在内存中的地址
PIMAGE_IMPORT_DESCRIPTOR addImport = PIMAGE_IMPORT_DESCRIPTOR((DWORD)pDosHeader + fileOffset + pDataDirectory->Size - descriptorSize);
//需要追加数据的内存中地址
DWORD address = (DWORD)addImport;
//第四步:把导入表的第一的PIMAGE_IMPORT_DESCRIPTOR结构拷贝到新增地址
memcpy((LPVOID)address, pImportTable, descriptorSize);
//向后偏移之前复制的长度
address += descriptorSize;
//添加导入表结束标志
memset((LPVOID)address, 0, descriptorSize);
address += descriptorSize;
//第五步:添加INT表,把新增的PIMAGE_IMPORT_DESCRIPTOR结构的OriginalFirstThunk指向INT的虚拟地址
memcpy((LPVOID)address, (LPVOID)&pImportTable->OriginalFirstThunk, 4);
addImport->OriginalFirstThunk = FileOffsetToRva(pDosHeader, address - (DWORD)pDosHeader);
address += 4;
//添加INT表的结束标志
memset((LPVOID)address, 0, 4);
address += 4;
//添加IAT表,把新增的PIMAGE_IMPORT_DESCRIPTOR结构的FirstThunk指向IAT的虚拟地址
memcpy((LPVOID)address, (LPVOID)&pImportTable->FirstThunk, 4);
addImport->FirstThunk = FileOffsetToRva(pDosHeader, address - (DWORD)pDosHeader);
address += 4;
//添加IAT表的结束标志
memset((LPVOID)address, 0, 4);
address += 4;
//第七步:使OriginalFirstThunk和FirstThunk同时指向PIMAGE_IMPORT_BY_NAME,这里指向的是虚拟地址
DWORD nameRva = FileOffsetToRva(pDosHeader, address - (DWORD)pDosHeader);
*(PDWORD)((DWORD)pDosHeader + RvaToFileOffset(pDosHeader, addImport->OriginalFirstThunk)) = nameRva;
*(PDWORD)((DWORD)pDosHeader + RvaToFileOffset(pDosHeader, addImport->FirstThunk)) = nameRva;
//添加PIMAGE_IMPORT_BY_NAME结构
memset((LPVOID)address, 0, 2);
address += 2;
//第八步:添加需要导入的方法名,要和注入dll提供的函数名相同,(这里用的名称导出,如果dll用的序号导出的函数需要另行处理)
LPSTR functionName = "ExportFunction";
memcpy((LPVOID)address, functionName, 15);
address += 15;
//添加需要注入的dll名字
LPSTR injectName = "InjectDll.dll";
memcpy((LPVOID)address, injectName, 14);
//更新PIMAGE_IMPORT_DESCRIPTOR结构中的name字段
addImport->Name = FileOffsetToRva(pDosHeader, address - (DWORD)pDosHeader);
//第九步:最后修复导入表的地址和长度
pDataDirectory->VirtualAddress = FileOffsetToRva(pDosHeader, fileOffset);
pDataDirectory->Size += descriptorSize;
return;
}