-
-
[原创]"菜壳"重建导入表代码,极垃圾,高手免进
-
发表于:
2007-4-21 23:42
8828
-
【 标题 】 发段"菜壳"重建导入表代码
【 作者 】 linxer
【 Q Q 】 3568599
【 声明 】 俺系初级选手,高手略过。失误之处敬请诸位大侠赐教!
以下代码思路来源ollydump插件源码,在此向ollydump作者表示至敬!
以下代码系本人最近虚拟脱壳后,为dump所写,在UPX,ASPACK上调试而成,今天又在PEcompact 2x上看到效果,应该对一些"菜壳"具有一定通用性......
以下代码,由于有些函数没有公示,不能执行,不过对一些和我一样菜的朋友了解,怎么搜索API调用信息和重建"菜壳"导入表,有帮助吧,当然你也可以直接查阅ollydump源码
1.一些宏定义
#define u8 unsigned char
#define s8 char
#define u16 unsigned short int
#define s16 short int
#define u32 unsigned int
#define s32 int
2.dll模块信息及其API信息登记表结构
//解壳过程中IAT处理,导入函数信息登记表结构
typedef struct tagKillShell_iat_fun_info
{
s32 nFunID; //函数ID
s32 nRVA; //本函数在IAT表中的RVA
s32 nFunSerial; //函数序号
s8 pFunName[32]; //函数名称
struct tagKillShell_iat_fun_info * next; //下一个函数地址
}KillShell_iat_fun_info, *PKillShell_iat_fun_info;
//解壳过程中IAT处理,导入到模块信息登记表结构
typedef struct tagKillShell_iat_module_info
{
s32 nModuleID; //模块ID
s32 nIAT; //其在IAT中位置
s32 nFunIDBegin; //本模块导出函数起始ID
s32 nFunIDEnd; //本模块导出函数结束ID
s8 pModuleName[32]; //模块名称
struct tagKillShell_iat_module_info * next; //下一个模块地址
PKillShell_iat_fun_info pFunInfo; //函数信息
}KillShell_iat_module_info, *PKillShell_iat_module_info;
3.与模块有关的两个辅助函数
PKillShell_iat_module_info g_pReBuildInfo = NULL; //解壳后,扫描PE文件发现的API调用信息
//查询在重建IAT时对应模块信息
PKillShell_iat_module_info search_module_info_by_nIAT(s32 ulVirAdd)
{
PKillShell_iat_module_info pModuleInfo;
for(pModuleInfo = g_peDyncmicInfo.pReBuildInfo; NULL != pModuleInfo; pModuleInfo = pModuleInfo->next)
{
if(ulVirAdd == pModuleInfo->nIAT)
{
break;
}
}
return pModuleInfo;
}
//获取模块数量
s32 get_module_count(PKillShell_iat_module_info pModuleInfo)
{
s32 nCount = 0;
for(; NULL != pModuleInfo; pModuleInfo = pModuleInfo->next)
{
nCount++;
}
return nCount;
}
4.以下代码假定g_peLoad_INH是PE文件IMAGE_NT_HEADERS指针,并假定malloc总是能申请到内存
4.1搜索API调用信息
//搜索API
//成功返回0,否则-1
s32 find_api_call_info(u8 *lpExePEBuff, u32 nExePELen)
{
PKillShell_iat_module_info pModuleInfo;
PKillShell_iat_module_info pModuleIATInfo;
PKillShell_iat_fun_info pFunInfo;
PKillShell_iat_fun_info pFunIATInfo;
s32 nSectionNum;
u32 nImageBase;
u32 nVirAdd;
s32 lAPIID;
u8 * pBeginText;
u8 * pEndText;
u8 * pText;
IMAGE_SECTION_HEADER * pISH;
IMAGE_SECTION_HEADER *pISHTemp;
u16 nInstructions[2] = {0x25ff, 0x15ff}; //用jmp [x] / call [x]搜索API调用信息
u16 nInstruction;
nSectionNum = g_peLoad_INH->FileHeader.NumberOfSections; //节数量
nImageBase = g_peLoad_INH->OptionalHeader.ImageBase; //程序映射基址
pISH = (IMAGE_SECTION_HEADER *)((s8 *)g_peLoad_INH + sizeof(IMAGE_NT_HEADERS));
//开始搜索地址
pBeginText = lpExePEBuff + pISH[0].VirtualAddress;
//结束搜索地址
pEndText = lpExePEBuff + pISH[nSectionNum - 1].VirtualAddress + pISH[nSectionNum - 1].Misc.VirtualSize;
for(pText = pBeginText; pText < pEndText; pText++)
{
nInstruction = *(u16 *)pText;
if(nInstruction == nInstructions[0] || nInstruction == nInstructions[1])
{
//取出指令操作数,它是一个虚拟地址,在这个虚拟地址中可能存放着API的ID
nVirAdd = *(u32 *)(pText + 2);
if(nVirAdd < pISH[0].VirtualAddress + nImageBase || nVirAdd > nExePELen + nImageBase - 4)
{
//虚拟地址非法
continue;
}
lAPIID = *(s32 *)(lpExePEBuff + (nVirAdd - nImageBase));
if(0 == get_iat_api_info(lAPIID, &pModuleInfo, &pFunInfo)) //get_iat_api_info函数效验API调用信息是否正常,没有展示其源码
{
pISHTemp = (IMAGE_SECTION_HEADER *)rva_to_section(nVirAdd - nImageBase); //rva_to_section这个函数,返回RVA所在节表地址
if(NULL == pISHTemp)
{
return -1;
}
//寻找这个dll在IAT中起始位置
for(; 0 != lAPIID && (nVirAdd - nImageBase) >= pISHTemp->VirtualAddress; )
{
nVirAdd -= sizeof(IMAGE_THUNK_DATA);
lAPIID = *(s32 *)(lpExePEBuff +(nVirAdd - nImageBase));
}
nVirAdd += sizeof(IMAGE_THUNK_DATA);
lAPIID = *(s32 *)(lpExePEBuff +(nVirAdd - nImageBase));
/**这里必须用其在IAT中起始项搜索,不能用dll名称,这是因为......,
比如说,upx壳,它导入kernel32.dll三次,对应有三个IAT起始地址,
用dll名称搜索,修复的程序肯定不对*/
pModuleIATInfo = search_module_info_by_nIAT(nVirAdd- nImageBase);
if(NULL != pModuleIATInfo)
{
continue; //这个dll已经处理
}
//插入这个模块信息
pModuleIATInfo = (PKillShell_iat_module_info)malloc(sizeof(KillShell_iat_module_info));
memset((void *)pModuleIATInfo, 0, sizeof(KillShell_iat_module_info));
*pModuleIATInfo = *pModuleInfo;
pModuleIATInfo->nIAT = nVirAdd - nImageBase;
pModuleIATInfo->next = NULL;
pModuleIATInfo->pFunInfo = NULL;
//将本模块信息插入到IAT模块链表中
pModuleIATInfo->next = g_pReBuildInfo;
g_pReBuildInfo = pModuleIATInfo;
//处理这个Dll中的导入函数
/**这里必须这么做,是因为用ff/2 ff/3指令搜索不一定能找到IAT表中的每项,
因此必须这么干,否则会出现IAT表中,有些项无法修复,比如Aspack中导入了Sleep函数,但是
用ff/2 ff/3却搜不着,这样导致IAT中Sleep对应项空的,程序就无法执行了,load不成功,
所以对每个dll必须先找到其在IAT中起始地址,然后从这个地方出发搞定这个dll导入的所有函数*/
for(; 0 != lAPIID; )
{
if(0 != get_iat_api_info(lAPIID, &pModuleInfo, &pFunInfo)) //这个函数说明见上
{
//没有办法,执行到这里的壳,脱后肯定不能运行,5555
continue;
}
pFunIATInfo = (PKillShell_iat_fun_info)malloc(sizeof(KillShell_iat_fun_info));
memset((void *)pFunIATInfo, 0, sizeof(KillShell_iat_fun_info));
*pFunIATInfo = *pFunInfo;
pFunIATInfo->nRVA = nVirAdd - nImageBase;
pFunIATInfo->next = NULL;
//插入到IAT模块函数链表中
pFunIATInfo->next = pModuleIATInfo->pFunInfo;
pModuleIATInfo->pFunInfo = pFunIATInfo;
nVirAdd += sizeof(IMAGE_THUNK_DATA);
lAPIID = *(s32 *)(lpExePEBuff +(nVirAdd - nImageBase));
}
}
}
}
return 0
}
4.2重建导入表
//修复导入表
//返回PE文件新添长度,即新导入表长度
s32 rebuild_imports(u8 *lpExePEBuff, u32 nExePELen)
{
s32 nOriLen; //原PE文件加载后长度
s32 nIIDLen; //IID表长
s32 nOtherLen = 0; //导入表中其它信息占用长度
s32 nSectionNum;
s32 nNewSectionFileLen; //新节磁盘中长度
s32 nNewSectionMemoryLen; //新节加到内存长度
s8 * pOtherInfo;
s8 * pOtherInfoBegin;
IMAGE_SECTION_HEADER * pISH;
IMAGE_SECTION_HEADER * pOldEndISH;
IMAGE_IMPORT_DESCRIPTOR *pIID;
PKillShell_iat_module_info pModuleInfo; //dll信息指针
PKillShell_iat_fun_info pFunInfo; //API信息指针
if-1 == find_api_call_info(lpExePEBuff, nExePELen)) //find_api_call_info执行失败或者是没有找到API
{
return 0;
}
nSectionNum = g_peLoad_INH->FileHeader.NumberOfSections;
//加一个节(导入表用)
pISH = (IMAGE_SECTION_HEADER *) ((s8 *)g_peLoad_INH + sizeof(IMAGE_NT_HEADERS));
pOldEndISH = pISH + nSectionNum - 1;
nOriLen = pOldEndISH->VirtualAddress + pOldEndISH->Misc.VirtualSize;
if(nOriLen != g_peLoad_INH->OptionalHeader.SizeOfImage)
{
return 0;
}
//计算要新建导入表的IID表长
nIIDLen = (get_module_count(g_pReBuildInfo) + 1) * sizeof(IMAGE_IMPORT_DESCRIPTOR);
//初始化IID表
memset(lpExePEBuff + nOriLen, 0, nIIDLen);
//真正的重建新节(导入表)代码
pIID = (IMAGE_IMPORT_DESCRIPTOR *)(lpExePEBuff + nOriLen);
pOtherInfo = (s8 *)pIID + nIIDLen;
pOtherInfoBegin = pOtherInfo; //保存,求导入表除IID表长用到
for(pModuleInfo = g_peDyncmicInfo.pReBuildInfo; NULL != pModuleInfo; pModuleInfo = pModuleInfo->next)
{
//FirstThunk指向地址
pIID->FirstThunk = pModuleInfo->nIAT;
//设置Dll名
strcpy(pOtherInfo, pModuleInfo->pModuleName);
pIID->Name = pOtherInfo - (s8 *)lpExePEBuff;
pOtherInfo += strlen(pModuleInfo->pModuleName) + 1;
//扫描Dll中导入函数
for(pFunInfo = pModuleInfo->pFunInfo; NULL != pFunInfo; pFunInfo = pFunInfo->next)
{
if(pFunInfo->nRVA != 0)
{
//这里要注意的是,nFunSerial和pFunName只有一个字段有效(脱壳中假定这样)
if(0 != pFunInfo->nFunSerial)
{
//序号导入
*(s32 *)(lpExePEBuff + pFunInfo->nRVA) = pFunInfo->nFunSerial | IMAGE_ORDINAL_FLAG;
}
else
{
IMAGE_IMPORT_BY_NAME * pIIBN = (IMAGE_IMPORT_BY_NAME *)pOtherInfo;
pIIBN->Hint = 0;
strcpy((s8 *)pIIBN->Name, pFunInfo->pFunName);
*(s32 *)(lpExePEBuff + pFunInfo->nRVA) = (u8 *)pIIBN - lpExePEBuff;
pOtherInfo += sizeof(pIIBN->Hint) + strlen(pFunInfo->pFunName) + 1;
}
}
}
pIID++;
}
//计算新节大小
nNewSectionFileLen = nIIDLen + (pOtherInfo - pOtherInfoBegin);
if(nNewSectionFileLen % g_peLoad_INH->OptionalHeader.FileAlignment != 0) //节对齐
{
s32 nTemp = nNewSectionFileLen;
nNewSectionFileLen = (nNewSectionFileLen / g_peLoad_INH->OptionalHeader.FileAlignment + 1) * \
g_peLoad_INH->OptionalHeader.FileAlignment;
memset(lpExePEBuff + nOriLen + nTemp, 0, nNewSectionFileLen - nTemp);
}
nNewSectionMemoryLen = nNewSectionFileLen;
if(nNewSectionMemoryLen % g_peLoad_INH->OptionalHeader.SectionAlignment != 0)
{
nNewSectionMemoryLen = (nNewSectionMemoryLen / g_peLoad_INH->OptionalHeader.SectionAlignment + 1) * \
g_peLoad_INH->OptionalHeader.SectionAlignment;
}
//在节表中新加一个结(这里假定新加节所在位置没有有用数据,5555)
pISH = pISH + nSectionNum;
pISH->Characteristics = 0xC0000040; //新节属性
memcpy(pISH->Name, ".linxer\0", 8); //新节名称
pISH->VirtualAddress = nOriLen; //新节加到内存地址
pISH->Misc.VirtualSize = nNewSectionMemoryLen; //新节加到内存中长度
pISH->PointerToRawData = nOriLen; //新节在磁盘地址
pISH->SizeOfRawData = nNewSectionFileLen; //新节在磁盘中长度
g_peLoad_INH->OptionalHeader.DataDirectory[1].VirtualAddress = nOriLen; //指向新导入表
g_peLoad_INH->OptionalHeader.DataDirectory[1].Size = nNewSectionMemoryLen; //新节大小
g_peLoad_INH->FileHeader.NumberOfSections += 1; //节数量加1
return nNewSectionFileLen;
}
5.总结
不过感觉上面代码,在虚拟脱些"菜壳",用来重建导入表,有些小题大作了,其实可以在脱壳过程中,记下一些如dll名称地址等信息,修复下就可以了,没有必要重建导入表的
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)