做壳很容易,有点做病毒知识的人都可以编写出壳来。但强度就有所差别了。
什么才是凶猛的壳?
我觉的是这样的 "猛壳 = 识别 + 重定位".识别用于让壳更了解要保护的程序。重定位为了可以保证保护程序的正常运行。如果能把识别与重定位做好。那么很多新的玩意就都可以玩了。如果你能把一个壳做成一个2进制文件的2次编译器。这个编译器的输入是以编译好的程序。输出是进行重编译的程序。那么这个壳因该相当的犀利。不过事与愿违。要完美的解决以上技术问题。有相当大的难度。我们也许不能把这个程序进行这样的重编译。但是局部的代码因该还是可以的。例如"VMP"不正是这样的产物吗? 写篇文章把我最近搞的一个函数识别的小算法奉献给朋友们。

<算法流程>
0.单函数识别
从一个地址开始,在未知长度的情况下,识别出这个函数的长度。
流程算法如下:
1) 从当前位置开始进行反汇编
   2) 记录所有跳转流程指令
   3) 遇到ret或者retn指令后,回溯整个跳转流程链表
4) 如果找到jcc则判断是否跳转后的地址是否大于当前的指令地址
   5-1) 大于,则跳转到流程,进入预分析结尾流程
   5-2) 无大于的情况,此函数分析完毕,返回
预分析结尾:
   这里鉴别函数帧也可能造成不稳定,有些函数在ret之后还有一些指令在这些指令之后必定有一个JMP跳转到最后一个ret或者之前利用此来进行预分析结尾
这里出现种情况:
1) 找到上述那个最后的JMP则将此视为结尾
2) 如果找到nop视为结尾(用户可以指定此选项,默认存在)
   3) 如果找到int3视为结尾(用户可以指定此选项,默认存在)
   4) 如果找到视为结尾(用户可以指定此选项,默认存在)

1.交叉引用
从一个函数开始,检查它的对外跳转的语句以便达到另一个函数,如此循环识别出代码段中的所有被直接引用的函数体。
识别交叉引用采用三遍分析,并在分析过程中记录每天指令,并设置是否可以HOOK(用于代码乱序)
流程算法如下:
第一遍分析
   1) 依次对所有指令进行分析,设定指令是否可以交换,并进行初步HOOK分析刺探,将通过偏移的的跳转指令进行识别,并鉴别HOOK成功率,并填充这条指令对标志寄存器的影响和通用寄存器的影响
   2) 通过单函数鉴别 (上述已经阐述了其算法)函数确立入口函数的长度
   3) 再次遍历入口函数,通过分析入口函数的CALL指令,分析出其他函数
   4) 循环,2过程直到把映射中所有的函数都鉴别出来
   5) 进行链表排序(地址由低到高)
   6) 将链表与映射内存由高到低依次进行对比,找出未找到的代码块,并使用,1,2方法进行鉴别将无法分析的代码识为数据

   第二遍分析
1) 遍历函数链
   2) 在识别过程中,鉴别出一个函数开辟的局部变量所需的空间,具体监视sub esp, XXX指令
3) 在遍历过程中如果发现有指令直接对全局变量进行引用,记录其访问地址
   4) 如果遇到跳转指令,则设置它目标地址指令的跳转引用链表

   第三遍分析
   1) 遍历函数链
   2) 找到所有动态跳转指令,并对它进行反馈分析,直到找出动态跳转寄存器值的合成结果,并进行运算
这里反馈的结束算法是:
   查看当前跳转指令,是否由引用指令
如果有则遍历这个引用链
如果没有则向上分析条指令,如果没有存在可以影响寄存器的mov,lea指令,则直接视此指令为不可HOOK的指令,如果动态跳转出现使用两个寄存器做内存跳转的直接视为不可HOOK的指令。

当然这里有些步骤是可以合并的。在实际CODE中我将上面三遍合成为两遍原则。

2.识别代码段的数据与代码
在理论上代码与数据是不可分的,通过分析将代码与数据进行分离。这也是难度最大的地方。
这里给出一些代码。
很多朋友要问为什么要再次鉴别这些东西。从交叉引用就可以识别为什么还要继续在重新识别一遍
原因很简单:为了可以保证所有函数和无用区域都作为信息传递给壳。有些动态调用的从交叉引用很难分析得出。在引用链上没有。所以要采取这个方案重新把他找出来。还有那些函数与函数之间的区域,这些区域你没感觉到对于做壳来说很珍贵吗?发挥你想象力去利用这些空白区域。。。
下面算法没给出深度分析的算法。那个可以自己总结规则了。现在我的那个代码已经过W行了。太多了规则了。 规则越细,可以保证识别程度越准确。

代码:
/*
 * 预读分析阶段,在分区处理是时进行,数据与代码区域
 * 范围的界定,纯数据区域返回TRUE,反之为FALSE
 */
INLINE  BOOL PredictBlockEnd(LPBYTE pMem, LPBYTE pCurr, DWORD dwSize, DWORD *pOutSize, PANALYZE_CONFIGURE pAnalyzeConfigure)
{
  BOOL bBlock = FALSE;
  DWORD dwOffset = 0;
  ud_t ud_obj;
  ud_init(&ud_obj);
  ud_set_mode(&ud_obj, 32);
  ud_set_syntax(&ud_obj, UD_SYN_INTEL);
  ud_set_input_buffer(&ud_obj, pCurr, dwSize);
  while (ud_disassemble(&ud_obj) != 0)
  {
    enum ud_mnemonic_code mnemonic = ud_obj.mnemonic;
    if ((mnemonic == UD_Inop) ||
      (mnemonic == UD_Iint3) ||
      ((mnemonic == UD_Iadd) &&
      (ud_obj.inp_ctr == 2) &&
      (*(WORD *)&(ud_obj.inp_sess) == 0)))
    {
      /*
       * 到达结束条件
       * 检查是否到达了用户定义代码的最小范围,如果没到直接视为数据
       * 如果大于等于则进入深入鉴别
       */
      if (dwOffset < pAnalyzeConfigure->bCodeMixSize)
      {
        bBlock = TRUE;
      }
      else
      {
        // 进入深度分析
        bBlock = DeepAnalyzeBlock(pMem, pCurr, dwOffset, pAnalyzeConfigure);
      }
      *pOutSize = dwOffset;
      return bBlock;
    }/* end if */
    dwOffset += ud_insn_len(&ud_obj);
  }

  // 这里做深度鉴别
  bBlock = DeepAnalyzeBlock(pMem, pCurr, dwSize, pAnalyzeConfigure);
  *pOutSize = dwOffset;
  return bBlock;
}

/*
 * 进行分区处理
 * 以NOP,INT3,0,无效指令编码字符进行分区标志
 * 遇到NOP或者INT3,0,无效指令编码则将扫描过的区域
 * 作为一个区块摘出,继续查询,如果其后的数据有很长
 * 一段为连续的 NOP,INT3,0字节则将此视做绝对无效区域,
 * 在扫描过程中,如果遇到无效指令直接忽略当作这个区域
 * 的一部分如果遇到有效指令,则进入预分析阶段,尝试向前
 * 分析如果分析的指令长度小于最小代码长度则直接忽略.如果
 * 大于则将此区域继续分析,往返以上流程.直到完毕
 */
INLINE PPROCEDURE FormatBlock(LPBYTE pMem, LPBYTE pStart, DWORD dwSize, PANALYZE_CONFIGURE pAnalyzeConfigure)
{
  DWORD dwImageBase = GetNtHeader(pMem)->OptionalHeader.ImageBase;
  PPROCEDURE pBlock, *pCurrBlockPoint = NULL;
  pCurrBlockPoint = &pBlock;
  DWORD dwBlockEndSignCount = 0, dwOffset = 0;

  ud_t ud_obj;
  ud_init(&ud_obj);
  ud_set_mode(&ud_obj, 32);
  ud_set_syntax(&ud_obj, UD_SYN_INTEL);
  ud_set_input_buffer(&ud_obj, pStart, dwSize);

  while (ud_disassemble(&ud_obj) != 0)
  {
    enum ud_mnemonic_code mnemonic = ud_obj.mnemonic;
    if ((mnemonic == UD_Inop) ||
      (mnemonic == UD_Iint3) ||
      ((mnemonic == UD_Iadd) &&
      (ud_obj.inp_ctr == 2) &&
      (*(WORD *)&(ud_obj.inp_sess) == 0)))
    {
      dwBlockEndSignCount += ud_insn_len(&ud_obj);
    }
    else
    {
      /*
       * 遇到有效指令了
       * 现在该进入预读分析阶段,这里还有一个判断
       * 标志是剩余长度,如果其后的长度小于最小代码
       * 长度直接将这段代码视作为数据
       */
      DWORD dwRemainSize = dwSize - dwOffset;
      LPBYTE pCurr = pStart + dwOffset;
      if (dwRemainSize >= pAnalyzeConfigure->bCodeMixSize)
      {
        /*
         * 进入预读分析阶段
         * 在这里开始判断,如果预读处理分析的结果为单纯的数据块则
         * 与先前分析的区域进行合并
         * 否则单独为前面分析的块分配内存
         */
        DWORD dwOutSize = 0;
        if (PredictBlockEnd(pMem, pCurr, dwRemainSize, &dwOutSize, pAnalyzeConfigure) == TRUE)
        {
          // 进行合并
          dwBlockEndSignCount += dwOutSize;
        }
        else
        {
          /*
           * 判断上一组区块标记是否为0如果不为则分配空间存储它
           */
          if (dwBlockEndSignCount != 0)
          {
            (*pCurrBlockPoint) = __new__(PROCEDURE, 1);
            memset((*pCurrBlockPoint), 0, sizeof(PROCEDURE));
            (*pCurrBlockPoint)->bBlock = TRUE;//代码区域
            (*pCurrBlockPoint)->pFileStartAddress = pCurr - dwBlockEndSignCount;
            (*pCurrBlockPoint)->dwMemoryStartAddress = dwImageBase + Raw2Rva(pMem, (DWORD)((*pCurrBlockPoint)->pFileStartAddress - pMem));
            (*pCurrBlockPoint)->dwSize = dwBlockEndSignCount;
            pCurrBlockPoint = &((*pCurrBlockPoint)->pNext);
            dwBlockEndSignCount = 0;//清空标志记录计数
          }
          // 设定此段为代码区域
          (*pCurrBlockPoint) = __new__(PROCEDURE, 1);
          memset((*pCurrBlockPoint), 0, sizeof(PROCEDURE));
          (*pCurrBlockPoint)->bBlock = FALSE;//代码区域
          (*pCurrBlockPoint)->pFileStartAddress = pCurr;
          (*pCurrBlockPoint)->dwMemoryStartAddress = dwImageBase + Raw2Rva(pMem, (DWORD)(pCurr - pMem));
          (*pCurrBlockPoint)->dwSize = dwOutSize;
          pCurrBlockPoint = &((*pCurrBlockPoint)->pNext);
        }

        // 重新设定反汇编指针
        pCurr += dwOutSize;
        dwOffset += dwOutSize;
        dwRemainSize -= dwOutSize;//剩余长度
        ud_set_input_buffer(&ud_obj, pCurr, dwRemainSize);
        continue;
      }
      else
      {
        // 进入到这里就是进入到分析的末尾
        (*pCurrBlockPoint) = __new__(PROCEDURE, 1);
        memset((*pCurrBlockPoint), 0, sizeof(PROCEDURE));
        (*pCurrBlockPoint)->bBlock = TRUE;//表示有可能是数据
        dwBlockEndSignCount += dwRemainSize;
        (*pCurrBlockPoint)->dwSize = dwBlockEndSignCount;
        (*pCurrBlockPoint)->pFileStartAddress = pCurr;
        (*pCurrBlockPoint)->dwMemoryStartAddress = dwImageBase + Raw2Rva(pMem, (DWORD)(pCurr - pMem));
        pCurrBlockPoint = &((*pCurrBlockPoint)->pNext);
        dwBlockEndSignCount = 0;//清空标志记录计数
      }
    }
    dwOffset += ud_insn_len(&ud_obj);
  }

  /*
   * 处理组后一个纯区域块
   * 两种情况造成这种现象
   * 1) 整个分析的数据都是纯数据区
   * 2) 被分析的数据最后一个区域是纯数据区
   */
  if (dwBlockEndSignCount != 0)
  {
    LPBYTE pBlockZoon = NULL;
    // 第一种情况
    if (dwBlockEndSignCount == dwSize)
    {
      pBlockZoon = pStart;
    }
    else
    {
      pBlockZoon = pStart + dwSize - dwBlockEndSignCount;
    }
    (*pCurrBlockPoint) = __new__(PROCEDURE, 1);
    memset((*pCurrBlockPoint), 0, sizeof(PROCEDURE));
    (*pCurrBlockPoint)->bBlock = TRUE;//表示有可能是数据
    (*pCurrBlockPoint)->dwSize = dwBlockEndSignCount;
    (*pCurrBlockPoint)->pFileStartAddress = pBlockZoon;
    (*pCurrBlockPoint)->dwMemoryStartAddress = dwImageBase + Raw2Rva(pMem, (DWORD)(pBlockZoon - pMem));
    pCurrBlockPoint = &((*pCurrBlockPoint)->pNext);
    dwBlockEndSignCount = 0;//清空标志记录计数
  }

  return pBlock;
}

/*
 * 进行预分析,识别出数据
 * 如果分析出不是数据
 */  
INLINE PPROCEDURE AnalyzeData(LPBYTE pMem, LPBYTE pCurr, DWORD dwSize, PPROCEDURE pProcedureList, PPROGRAM pParents)
{
  PANALYZE_CONFIGURE pAnalyzeConfigure = &(pParents->AnalyzeConfigure);
  DWORD dwImageBase = GetNtHeader(pMem)->OptionalHeader.ImageBase;
  // 如果此块的长度小于用户指定长度
  if (dwSize < pAnalyzeConfigure->bCodeMixSize)
  {
    PPROCEDURE pBlock = __new__(PROCEDURE, 1);
    memset(pBlock, 0, sizeof(PROCEDURE));
    pBlock->bBlock = TRUE;
    pBlock->pFileStartAddress = pCurr;
    pBlock->dwMemoryStartAddress = dwImageBase + Raw2Rva(pMem, (DWORD)(pCurr - pMem));
    pBlock->dwSize = dwSize;
    pBlock->pNext = NULL;

    PPROCEDURE *pCurrMainPoint = &pProcedureList;
    while (*pCurrMainPoint != NULL) pCurrMainPoint = &((*pCurrMainPoint)->pNext);
    *pCurrMainPoint = pBlock;
    return pProcedureList;
  }

  /*
   * 开始对这个块进行有效的分区
   * 分区完毕后对链中的代码块进行函数帧分析
   * 初次分析
   */
  PPROCEDURE pFormatBlockList = FormatBlock(pMem, pCurr, dwSize, pAnalyzeConfigure);

  /*
   * 遍历此链表进行分析
   * 如果遇到代码块则进入并开始代码分析
   */
  PPROCEDURE pCurrBlock = pFormatBlockList;

  while (pCurrBlock != NULL)
  {
    if (pCurrBlock->bBlock == FALSE)//为代码块
    {
      // 进行分析
      PPROCEDURE pProcedure = GetProcFrame(pMem, pCurrBlock->pFileStartAddress, pCurrBlock->dwSize, pParents);
      /*
       * 调用Procedure2Procedure过后形成的函数链中存在两种函数,一种是函数存在在已有的
       * 未知区域中,一种是在做第一次扫描时分析到的函数
       */
      DWORD dwCount = Procedure2Procedure(pMem, pProcedureList, pProcedure);
    }
    else
    {
      // 直接连接到主链
      PPROCEDURE *pCurrMainPoint = &pProcedureList;
      while (*pCurrMainPoint != NULL) pCurrMainPoint = &((*pCurrMainPoint)->pNext);
      *pCurrMainPoint = __new__(PROCEDURE, 1);
      memcpy(*pCurrMainPoint, pCurrBlock, sizeof(PROCEDURE));
      (*pCurrMainPoint)->pNext = NULL;
    }
    pCurrBlock = pCurrBlock->pNext;
  }

  // 销毁分析链
  ReleaseProcedureList(&pFormatBlockList);
  return pProcedureList;
}

/*
 * 找寻所有不在链上的内存区域,并归结到链上
 */
INLINE PPROCEDURE AnalyzeProcedurePass1DiscoveryUnknowZoon(LPBYTE pMem, PPROCEDURE pProcedureList, PPROGRAM pParents)
{
  PPROCEDURE pCurrProcedure = pProcedureList;
  LPBYTE pStart = pMem + GetEntryPointSection(pMem)->PointerToRawData, pCurr = pStart;
  DWORD dwCodeSize = GetEntryPointSection(pMem)->Misc.VirtualSize;
  PPROCEDURE pUnknowProcedureList = NULL;
  LPBYTE pNow = NULL;
  DWORD dwCurrSize = 0, dwBlockSize = 0, dwRemainSize = 0;

  g_pAnalyzeDataDispatcher = MakeAnalyzeDataFromInstructionDispatcher();

_new_analyze:
  while (pCurrProcedure != NULL)
  {
    pNow = pCurrProcedure->pFileStartAddress;
    dwCurrSize = pCurrProcedure->dwSize;

    if (pCurr == pNow)//如果相当则直接略过
    {
      pCurr += dwCurrSize;
      dwRemainSize -= dwCurrSize;
    }
    else if (pCurr < pNow)//分析这个代码块
    {
      dwBlockSize = (DWORD)(pNow - pCurr);
      dwRemainSize -= dwBlockSize;
      goto _handler;
    }
    pCurrProcedure = pCurrProcedure->pNext;
  }

  // 判断是否到达末尾
  if (dwRemainSize != 0)
  {
    dwBlockSize = dwRemainSize;
    goto _handler;
  }

  // 释放派遣表
  DestroyDispatcherList(&g_pAnalyzeDataDispatcher);
  return pProcedureList;

// 这里为处理函数所在
_handler:
  /*
   * 这里的处理和前一次处理有个区别就是不知道当前这个区域是
   * 数据还是代码,所有首先要做数据鉴别
   */
  pUnknowProcedureList = AnalyzeData(pMem, pCurr, dwBlockSize, pProcedureList, pParents);
  pProcedureList = SortProcedureList(pProcedureList);//排序
  // 重新设定所有初始值,然后重新来过
  pCurr = pStart;
  dwRemainSize = dwCodeSize;//重新设定长度
  pUnknowProcedureList = NULL;
  pCurrProcedure = pProcedureList;
  __PrintProcedureListCount__(pProcedureList);
  goto _new_analyze;//重新进入新的一次分析
}
3.YY。。。
目前这种算法做规则很稳定。但是也很麻烦。不仅代码量大 而且要不断的更新规则。很麻烦。目前我正在通过人工神经网络去模糊识别代码和数据。已经有一个比较不错的方案。准备作为毕业的论文来搞。如果论文发表成功,我再翻译成中文奉献给论坛的朋友们。

三种流程如图: