首页
社区
课程
招聘
[原创]"菜壳"重建导入表代码,极垃圾,高手免进
2007-4-21 23:42 8328

[原创]"菜壳"重建导入表代码,极垃圾,高手免进

2007-4-21 23:42
8328
【  标题  】 发段"菜壳"重建导入表代码
【  作者  】 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名称地址等信息,修复下就可以了,没有必要重建导入表的

[培训]《安卓高级研修班(网课)》月薪三万计划,掌 握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞7
打赏
分享
最新回复 (4)
雪    币: 217
活跃值: (28)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wmjm 2007-4-23 11:16
2
0
学习一下,谢谢。
雪    币: 405
活跃值: (10)
能力值: ( LV9,RANK:1130 )
在线值:
发帖
回帖
粉丝
binbinbin 28 2007-4-24 10:15
3
0
脱壳机的编写~~
真是非常感谢分享
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
兔子军团 2007-4-24 13:32
4
0
不错,可以在加一些东西进去
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
点点 2007-4-24 17:12
5
0
呵呵,用来当学习PE的资料更合适
游客
登录 | 注册 方可回帖
返回