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