目录
分析
大致流程
一 遍历所有导入表, 如果遇到0结束遍历, 如果要加进去一个, 那么原有导入表目录最后需要至少有14h*2的空闲空间.
二 如果没有那么要先进行移动导入表的操作. 在移动导入表操作中会碰到空闲空间不够, 那么又需要开辟新节. 这是一个大致流程.
本节需要对导入表有比较透彻的了解…
最后一个非常重要的问题注入所生成的 新IAT表所在节的Characteristic必须具有可写的属性!!!
因为通过之前的学习知道调用DLL的CALL之后的地址是写死的, 地址里面在加载前是INT, 加载后就变为了函数地址, 这个过程就要求节属性是可写的!!!
具体实现
PS: 移动导入表是为了导入表注入, 而导入表又作为一个较复杂的结构体, 里面独立的单元同样是可以移动, 但是是没必要的, 也就是本文移动导入表中的第一步就完成了注入需要的操作, 下面的如果不感兴趣直接跳过. 不过在做的过程中有如下额外的收获:
1 数据目录表的size对程序运行是无影响的
2 FirstThunk中的ThunkValue值是无关紧要的即IAT可有可无
3 承接上条, 同样也是老师提到的IAT是不可移动的因为在程序中是固定地址, 一旦移动需要找到所有涉及到IAT的值 得不偿失
通常情况下最后一个导入表后面的空间只有14h, 同时移动导入表作为一个独立操作, 在演示上就直接先移动导入表再进行注入.
步骤
移动导入表
移动导入表
原始结构
最终结果
1 移动整体结构 完成导入表注入需要的操作
整个复制了之后修复数据目录表的RVA即可, size其实一点用没有, 系统加载的时候是按照遍历找到0为止的方式, 而一些PE分析软件用size作为判断导入表个数就会失效
while (pImportDescriptor->OriginalFirstThunk)
{
sizeOfImportDescriptor += SIZE_OF_IMPORT_DESCRIPTOR;
pImportDescriptor++;
}
//移动表结构并修复数据目录表
DWORD numOfImportDescriptor = sizeOfImportDescriptor / SIZE_OF_IMPORT_DESCRIPTOR;
pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pImportDescriptor - sizeOfImportDescriptor);
sizeOfImportDescriptor = sizeOfImportDescriptor + (numOfNewImportDescriptors + 1) * SIZE_OF_IMPORT_DESCRIPTOR; //为之后添加导入表打提前量 +1是因为要有0判断导入表结束 INT用的时候加即可
foaFreeSpace = GetFreeSpaceInSection(pFileBuffer, sizeOfImportDescriptor, TRUE, sizeOfFile);
if (foaFreeSpace == NEED_TO_ALLOCATE_NEW_SECTION)
return NEED_TO_ALLOCATE_NEW_SECTION;
memcpy((LPVOID)((DWORD)pFileBuffer + foaFreeSpace), pImportDescriptor, sizeOfImportDescriptor - (numOfNewImportDescriptors + 1) * SIZE_OF_IMPORT_DESCRIPTOR); //只需复制原来的即可
memset(pImportDescriptor, 0, sizeOfImportDescriptor); //原数据清0空出来 不改变section的vsize是因为如果在数据位于中间就需要整体的平移, 画蛇添足
(*(pDataDirectory + 1)).VirtualAddress = FoaDataToRvaData(pFileBuffer, foaFreeSpace);
第一步仅改变的是数据目录表中导入表的RVA 导入表本身并无任何改变
2 改变name
代码位于主循环中
名字移动到新的位置改变RVA即可
DWORD foaName = RvaDataToFoaData(pFileBuffer, pImportDescriptor->Name);
char *NameOfCurrentDll = (char *)((DWORD)pFileBuffer + foaName);
DWORD lengthOfCurrentDllName = strlen(NameOfCurrentDll) + 1;
foaFreeSpace = GetFreeSpaceInSection(pFileBuffer, lengthOfCurrentDllName, TRUE, sizeOfFile);
if (foaFreeSpace == NEED_TO_ALLOCATE_NEW_SECTION)
return NEED_TO_ALLOCATE_NEW_SECTION;
//sizeOfNames += lengthOfCurrentDllName;
memcpy((LPVOID)((DWORD)pFileBuffer + foaFreeSpace), NameOfCurrentDll, lengthOfCurrentDllName);
memset(NameOfCurrentDll, 0, lengthOfCurrentDllName);
pImportDescriptor->Name = FoaDataToRvaData(pFileBuffer, foaFreeSpace);
3 改变OriginalFirstThunk
代码位于主循环中
遍历当前导入表 统计API个数计算大小
复制INT 改变OriginalFirstThunk
while (OriginalThunkValue)
{
pImportDescriptor->OriginalFirstThunk += sizeof(DWORD);
foaOriginalFirstThunk = RvaDataToFoaData(pFileBuffer, pImportDescriptor->OriginalFirstThunk);
OriginalThunkValue = *(LPDWORD)((DWORD)pFileBuffer + foaOriginalFirstThunk);
numOfFunctions++;
}
pImportDescriptor->OriginalFirstThunk = rvaOriginalFirstThunk; //复原
sizeOfOriginalFirstThunk = (numOfFunctions + 1) * sizeof(DWORD);
foaOriginalFirstThunk = RvaDataToFoaData(pFileBuffer, rvaOriginalFirstThunk);
foaFreeSpace = GetFreeSpaceInSection(pFileBuffer, sizeOfOriginalFirstThunk, TRUE, sizeOfFile);
if (foaFreeSpace == NEED_TO_ALLOCATE_NEW_SECTION)
return NEED_TO_ALLOCATE_NEW_SECTION;
memcpy((LPVOID)((DWORD)pFileBuffer + foaFreeSpace), (LPDWORD)((DWORD)pFileBuffer + foaOriginalFirstThunk), sizeOfOriginalFirstThunk);
memset((LPDWORD)((DWORD)pFileBuffer + foaOriginalFirstThunk), 0, sizeOfOriginalFirstThunk);
//原来的PFILEBUFFER全部消失
pImportDescriptor->OriginalFirstThunk = FoaDataToRvaData(pFileBuffer, foaFreeSpace);
二三步结果如下
4 改变INT(可以选择把IAT置0或者不管 FirstThunk不改变即可)
代码位于二层循环中
判断有无函数名
移动IMAGE_THUNK_DATA32(HInt与API name)
改变INt(ThunkValue)
FirstThunkValue与Hint可置0
if ((OriginalThunkValue & 0x80000000) == 0x80000000)
DWORD ordinalOfFunction = (OriginalThunkValue & 0x0FFFFFFF);
else
{
DWORD RVA_OFT_HintAndNameOfFunction = OriginalThunkValue;
DWORD FOA_OFT_HintAndNameOfFunction = RvaDataToFoaData(pFileBuffer, RVA_OFT_HintAndNameOfFunction);
WORD hint = *(LPWORD)((DWORD)pFileBuffer + FOA_OFT_HintAndNameOfFunction);
char * NameOfFunction = (char *)((DWORD)pFileBuffer + FOA_OFT_HintAndNameOfFunction + 2);
sizeOfCurrentINT = strlen(NameOfFunction) + 1 + 2;
foaFreeSpace = GetFreeSpaceInSection(pFileBuffer, sizeOfCurrentINT, TRUE, sizeOfFile);
if (foaFreeSpace == NEED_TO_ALLOCATE_NEW_SECTION)
return NEED_TO_ALLOCATE_NEW_SECTION;
memcpy((LPVOID)((DWORD)pFileBuffer + foaFreeSpace), (LPVOID)((DWORD)pFileBuffer + FOA_OFT_HintAndNameOfFunction), sizeOfCurrentINT);
memset((LPVOID)((DWORD)pFileBuffer + FOA_OFT_HintAndNameOfFunction), 0, sizeOfCurrentINT);
LPDWORD pOriginalThunkValue = (DWORD *)((DWORD)pFileBuffer + foaOriginalFirstThunk);
*pOriginalThunkValue = FoaDataToRvaData(pFileBuffer, foaFreeSpace);
}
结果
整体代码
while (pImportDescriptor->OriginalFirstThunk)
{
sizeOfImportDescriptor += SIZE_OF_IMPORT_DESCRIPTOR;
//移动dll名字并修复NAME
DWORD foaName = RvaDataToFoaData(pFileBuffer, pImportDescriptor->Name);
char *NameOfCurrentDll = (char *)((DWORD)pFileBuffer + foaName);
DWORD lengthOfCurrentDllName = strlen(NameOfCurrentDll) + 1;
foaFreeSpace = GetFreeSpaceInSection(pFileBuffer, lengthOfCurrentDllName, TRUE, sizeOfFile);
if (foaFreeSpace == NEED_TO_ALLOCATE_NEW_SECTION)
return NEED_TO_ALLOCATE_NEW_SECTION;
//sizeOfNames += lengthOfCurrentDllName;
memcpy((LPVOID)((DWORD)pFileBuffer + foaFreeSpace), NameOfCurrentDll, lengthOfCurrentDllName);
memset(NameOfCurrentDll, 0, lengthOfCurrentDllName);
pImportDescriptor->Name = FoaDataToRvaData(pFileBuffer, foaFreeSpace);
DWORD sizeOfCurrentINT = 0;
DWORD numOfFunctions = 0;
DWORD sizeOfOriginalFirstThunk = 0;
DWORD rvaOriginalFirstThunk = pImportDescriptor->OriginalFirstThunk;
DWORD foaOriginalFirstThunk = RvaDataToFoaData(pFileBuffer, rvaOriginalFirstThunk);
DWORD OriginalThunkValue = *(DWORD *)((DWORD)pFileBuffer + foaOriginalFirstThunk);
//想要修改FirstThunkValue很简单
//DWORD rvaFirstThunk = pImportDescriptor->FirstThunk;
//DWORD foaFirstThunk = RvaDataToFoaData(pFileBuffer, rvaFirstThunk);
//LPVOID pFirstThunkValue = (LPDWORD)((DWORD)pFileBuffer + foaFirstThunk);
while (OriginalThunkValue)
{
if ((OriginalThunkValue & 0x80000000) == 0x80000000)
DWORD ordinalOfFunction = (OriginalThunkValue & 0x0FFFFFFF);
else
{
DWORD RVA_OFT_HintAndNameOfFunction = OriginalThunkValue;
DWORD FOA_OFT_HintAndNameOfFunction = RvaDataToFoaData(pFileBuffer, RVA_OFT_HintAndNameOfFunction);
WORD hint = *(LPWORD)((DWORD)pFileBuffer + FOA_OFT_HintAndNameOfFunction);
char * NameOfFunction = (char *)((DWORD)pFileBuffer + FOA_OFT_HintAndNameOfFunction + 2);
sizeOfCurrentINT = strlen(NameOfFunction) + 1 + 2;
foaFreeSpace = GetFreeSpaceInSection(pFileBuffer, sizeOfCurrentINT, TRUE, sizeOfFile);
if (foaFreeSpace == NEED_TO_ALLOCATE_NEW_SECTION)
return NEED_TO_ALLOCATE_NEW_SECTION;
memcpy((LPVOID)((DWORD)pFileBuffer + foaFreeSpace), (LPVOID)((DWORD)pFileBuffer + FOA_OFT_HintAndNameOfFunction), sizeOfCurrentINT);
memset((LPVOID)((DWORD)pFileBuffer + FOA_OFT_HintAndNameOfFunction), 0, sizeOfCurrentINT);
LPDWORD pOriginalThunkValue = (DWORD *)((DWORD)pFileBuffer + foaOriginalFirstThunk);
*pOriginalThunkValue = FoaDataToRvaData(pFileBuffer, foaFreeSpace);
}
pImportDescriptor->OriginalFirstThunk += sizeof(DWORD);
foaOriginalFirstThunk = RvaDataToFoaData(pFileBuffer, pImportDescriptor->OriginalFirstThunk);
OriginalThunkValue = *(LPDWORD)((DWORD)pFileBuffer + foaOriginalFirstThunk);
numOfFunctions++;
}
pImportDescriptor->OriginalFirstThunk = rvaOriginalFirstThunk; //复原
sizeOfOriginalFirstThunk = (numOfFunctions + 1) * sizeof(DWORD);
foaOriginalFirstThunk = RvaDataToFoaData(pFileBuffer, rvaOriginalFirstThunk);
foaFreeSpace = GetFreeSpaceInSection(pFileBuffer, sizeOfOriginalFirstThunk, TRUE, sizeOfFile);
if (foaFreeSpace == NEED_TO_ALLOCATE_NEW_SECTION)
return NEED_TO_ALLOCATE_NEW_SECTION;
memcpy((LPVOID)((DWORD)pFileBuffer + foaFreeSpace), (LPDWORD)((DWORD)pFileBuffer + foaOriginalFirstThunk), sizeOfOriginalFirstThunk);
//原来的PFILEBUFFER全部消失
pImportDescriptor->OriginalFirstThunk = FoaDataToRvaData(pFileBuffer, foaFreeSpace);
pImportDescriptor++;
}
//移动表结构并修复数据目录表
DWORD numOfImportDescriptor = sizeOfImportDescriptor / SIZE_OF_IMPORT_DESCRIPTOR;
pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pImportDescriptor - sizeOfImportDescriptor);
sizeOfImportDescriptor = sizeOfImportDescriptor + (numOfNewImportDescriptors + 1) * SIZE_OF_IMPORT_DESCRIPTOR; //为之后添加导入表打提前量 +1是因为要有0判断导入表结束 INT用的时候加即可
foaFreeSpace = GetFreeSpaceInSection(pFileBuffer, sizeOfImportDescriptor, TRUE, sizeOfFile);
if (foaFreeSpace == NEED_TO_ALLOCATE_NEW_SECTION)
return NEED_TO_ALLOCATE_NEW_SECTION;
memcpy((LPVOID)((DWORD)pFileBuffer + foaFreeSpace), pImportDescriptor, sizeOfImportDescriptor - (numOfNewImportDescriptors + 1) * SIZE_OF_IMPORT_DESCRIPTOR); //只需复制原来的即可
memset(pImportDescriptor, 0, sizeOfImportDescriptor); //原数据清0空出来
(*(pDataDirectory + 1)).VirtualAddress = FoaDataToRvaData(pFileBuffer, foaFreeSpace);
导入表注入
导入表注入
移动导入表后在原有最后一个导入表后面新加一个导入表然后再进行一一赋值即可
1改变OFT FT NAME
需要注意申请空间的时候应该多申请一个四字节因为以0判断结尾
FirstThunk可以与OriginalFirstThunk相同 不影响
while (pImportDescriptor->OriginalFirstThunk)
pImportDescriptor++;
foaFreeSpace = GetFreeSpaceInSection(pFileBuffer, (numOfFunctions + 1) * 0x4, TRUE, sizeOfFile);
if (foaFreeSpace == NEED_TO_ALLOCATE_NEW_SECTION)
return NEED_TO_ALLOCATE_NEW_SECTION;
LPDWORD pNewThunkValue = (LPDWORD)((DWORD)pFileBuffer + foaFreeSpace);
pImportDescriptor->OriginalFirstThunk = FoaDataToRvaData(pFileBuffer, foaFreeSpace);
foaFreeSpace = GetFreeSpaceInSection(pFileBuffer, (numOfFunctions + 1) * 0x4, TRUE, sizeOfFile);
if (foaFreeSpace == NEED_TO_ALLOCATE_NEW_SECTION)
return NEED_TO_ALLOCATE_NEW_SECTION;
pImportDescriptor->FirstThunk = FoaDataToRvaData(pFileBuffer, foaFreeSpace);
LPDWORD pNewFirstThunkValue = (LPDWORD)((DWORD)pFileBuffer + foaFreeSpace);
foaFreeSpace = GetFreeSpaceInSection(pFileBuffer, strlen(nameOfDll) + 1, TRUE, sizeOfFile);
if (foaFreeSpace == NEED_TO_ALLOCATE_NEW_SECTION)
return NEED_TO_ALLOCATE_NEW_SECTION;
memcpy((LPVOID)((DWORD)pFileBuffer + foaFreeSpace), nameOfDll, strlen(nameOfDll));
pImportDescriptor->Name = FoaDataToRvaData(pFileBuffer, foaFreeSpace);
2 设置INT
根据函数个数一一赋值即可
以及第二次提到了 最最重要的 注入所在区段的Characteristic需要可写!!!
for (DWORD i = 0; i < numOfFunctions; i++)
{
foaFreeSpace = GetFreeSpaceInSection(pFileBuffer, strlen(*(nameOfFunctions + i)) + 1, TRUE, sizeOfFile);
if (foaFreeSpace == NEED_TO_ALLOCATE_NEW_SECTION)
return NEED_TO_ALLOCATE_NEW_SECTION;
PIMAGE_SECTION_HEADER pCurrentSection = header.pSectionHeader + GetSectionNum(pFileBuffer, foaFreeSpace) - 1;
pCurrentSection->Characteristics |= 0x80000000;
memcpy((LPVOID)((DWORD)pFileBuffer + foaFreeSpace), *(nameOfFunctions + i), strlen(*(nameOfFunctions + i)));
*pNewThunkValue = FoaDataToRvaData(pFileBuffer, foaFreeSpace - 2); //Hint-2
*pNewFirstThunkValue = *pNewThunkValue;
pNewThunkValue++;
pNewFirstThunkValue++;
}
很多问题
1 之前移动表都是粗暴的直接加一个节然后移动过去, 这次要求"见缝插针", 又写了个获取空闲空间的函数GetFreeSpaceInSection, 本以为很简单, 但小细节没有注意比如见缝插针之后section的VirtualSize需要更改, 碰到垃圾数据时应该怎么办(区段之后的版权信息种种, 某云去掉之后516变为499kb)
2 作为PE基础的最后一节, 本来想着比较轻松, 但是有些问题就是想去弄, 感觉不太好, 在许多貌似无关紧要的事上花了大量时间, 比如第一条提到的垃圾数据. 本来老实删掉是啥事没有的, 但是由于在FileBuffer层面操作, 不可避免的遇到种种问题.
3 如果不去改变导入表中的数据那么就不会知道有些值是毫无用处的, 这个上面说了
4 花了两天时间, 对着代码看了n遍, 怀疑人生…到底哪里写错了 最后发现是插入的区段应该是可写的(GetProcByName)…
ps: 建了个学习讨论群, 欢迎新朋友加入:1094301686