0%

《逆向工程核心原理》学习笔记11

逆向工程核心原理第二十章

"内嵌补丁"练习

  • "内嵌补丁"是"内嵌代码补丁"(Inline Code Patch)的简称,难以直接修改指定代码时,插入并运行被称为"洞穴代码"(Code Cave)的补丁代码后,对程序打补丁。常用于运行时压缩(或加密处理)而难以直接修改的情况。

    image-20211206113450362

  • 上图左边描述的是典型的运行时压缩代码(或者加密代码)。EP代码先将加密的OEP代码解密,然后再跳转到OEP处。若要打补丁的代码存在于经过加密的OEP区域是很难打补丁的(即使知道代码所在位置也是如此),因为解码过程中可能会解出完全不同的结果。
  • 解决上述问题的简单方法就是,在文件中加一层中间层的补丁代码,让EP代码解密后跳转至中间层。在中间层执行补丁代码后(由于已经解密OEP,所以可以这么修改)再跳转到OEP处。

练习:Patchme

  • 《逆向工程核心原理》作者的github上可以下载本章的patchme程序,https://github.com/reversecore/book

  • 运行程序,可以看到程序非常简单,只有两个弹窗。

    image-20211206114417214

    image-20211206114436112

  • 这个patchme比较简单,只要修改上面两处字符串即可。

调试
  • 用Ollydbg打开unpackme#1.aC.exe,可以看到EP非常简单

    image-20211207161105395

  • 跟踪调试,很快可以看到第一个解密循环,如下图所示,第一个解密循环作用是将"004010F5"~"00401248"(004010F5+154-1)处的数据XOR(异或) 44

    image-20211207161254808

  • 知道第一个循环作用后,我们跳过该循环,继续往下调试,很快碰见第二个解密循环,将"00401007"~"00401085"处的数据XOR(异或)7,第二个解密循环的下面是第三个解密循环,第三个解密循环,解密的区域与第一个相同,只是XOR的值为11,说明"004010F5"~"00401249"区域经过了双重XOR加密

    image-20211207161343930

  • 跳过第二、三个解密循环后,继续调试,就能发现"00401038"处的校验函数,该函数循环从通"004010F5"~"00401248"区域读取4个字节数据累加得到校验和,再将结果与"31EB8D80"比较,判断程序是否被修改。若校验失败会弹出"CrC of this file has been modified !!!"的错误提示。0040105D处的CALL 0040108A命令是调用另一解密循环,用来解密0040124A~00401279处的数据

    image-20211207161420463

    image-20211207163811971

    EDX寄存器为4个字节大小,像这样向其中不断加上4个字节的值,就会发生溢出(overflow)问题。一般的校验和计算中常常忽略该溢出问题,使用最后一个保存在EDX的值

  • 跳过校验和部分,继续调试,很快就会跳转至OEP处,如下图所示,OEP所在地址为0040121E

    image-20211207161733025

  • 如果解密后,Ollydbg在0040121E~00401248没有显示为汇编指令,可以先选中0040121E~00401248这片区域,然后右键选择=>分析=>分析代码,然后Ollydbg就会把该处数据视为汇编代码展示出来

    image-20211207162101974

  • OEP代码用来运行对话框,执行位于0040123E地址处的CALL DialogBoxParamA()命令后即弹出对话框。下面是DialogBoxParamA() API的定义

    1
    2
    3
    4
    5
    6
    7
    INT_PTR WINAPI DialogBoxParamA(
    __in_opt HINSTANCE hInstance,
    __in LPCTSTR lpTemplateName,
    __in_opt HWND hWndParent,
    __in_opt DLGPROC lpDialogFunc,
    __in LPARAM dwInitParam
    )
  • DialogBoxParamA() API的第四个参数lpDialogFunc用来指出Dialog Box Procedure的地址。地址40122C处有条push 4010F5命令,由此可见,函数第四个参数的地址为4010F5(栈先进后出,所以第二个进栈即位倒数第二个出栈)。

    image-20211207161733025

  • 下图为004010F5处的代码,可以看到我们要修改的字符串都在里面

    image-20211207162243371

  • 根据上面的分析,可以总结出下面的,代码结构图。如图所示,[EP Code]只是用来调用[Decoding Code]的,实际的解密处理是由[Decoding Code]完成的。按照[B]-[A]-[B]的顺序解码,运行解密后的[A]区代码,在[A]区代码会求得[B]区的校验和,并据此判断[B]区是否发生过改变。然后对[C]区解码,最后跳转至OEP处(0040121E)

    image-20211207164129987

"内嵌补丁"练习
  • 操作顺序:首先向文件合适位置插入用于修改字符串的代码,然后在上图的[A]区域将JMP OEP命令修改为JMP补丁代码(修改时要充分考虑文件中的[A]区域处于加密状态)。在补丁代码中更改字符串,然后再通过JMP命令跳转至OEP处,这样整个内嵌补丁过程就完成了。
补丁代码要设置在何处
  • 这个问题在进行内嵌补丁的过程中非常重要。有如下3种设置方法:

    1. 设置到文件的空白区域
    2. 扩展最后节区后设置
    3. 添加新节区后设置
  • 补丁代码较少时,使用方法1,其他情况使用方法2或方法3。首先尝试方法1,使用PE View查看示例文件的第一个节区(.text)头,如下图所示

    image-20211207165423151

  • 接着查看Section Alignment的值

    image-20211207165827158

  • 可以看到第一个节区Virtual Size为280,但是由于Section Alignment为1000,所以第一个节区后面RVA 1280之后存在大量NULL填充的空白区域,我们可以在该区域添加补丁代码。

  • 用Ollydbg查看00401280地址,如下图所示,可以看到,该处之后确实存在大量空白区域

    image-20211207172959638

  • 我们开始添加补丁代码,首先添加我们需要修改的字符串。鼠标选中00401280地址处,右键选择二进制编辑

    image-20211207173214791

  • 取消勾选保持大小,然后在ASCII中输入所需字符串,这里为"blog.iz4.cc",然后点确定即可

    image-20211207173250338

  • 同样的在0040128C处添加字符串"Unpacked!"

    image-20211207173547014

  • 如果添加字符串后,该部分数据被Ollydbg识别为汇编指令的话,可以鼠标选中该区域,右键选择 分析=>分析代码,然后OD就能将该部分识别为ASCII字符串了。

    image-20211207173624240

    image-20211207173746325

  • 添加好字符串后,接着我们添加补丁代码的汇编指令,在00401296处按空格即可编辑汇编指令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    mov ecx, 0xC		; 0xC为要拷贝的字符串的字符长度(包括'\0')==> blog.iz4.cc\0
    mov esi, 00401280 ; 让ESI指向第一个需要拷贝的字符串所在地址
    mov edi, 00401123 ; 修改的目标字符串所在地址
    rep movsb ; <==> rep movs byte ptr es:[edi],byte ptr ds:[esi]
    ;这个指令意思就是将ESI指向的地址的值以1字节方式拷贝到EDI指向的地址中
    ;重复执行ECX次,每次执行后ESI+1,EDI+1,ECX-1

    mov ecx, 0xA ; 0xA为要拷贝的字符串的字符串长度(包括'\0')==> Unpacked\0
    mov esi, 0040128C ; 让ESI指向第二个需要拷贝的字符串所在地址
    mov edi, 0040110A ; 修改的目标字符串所在地址
    rep movsb ; <==> rep movs byte ptr es:[edi],byte ptr ds:[esi] ; 作用同上

    jmp 0040121E ; 跳转至OEP

    image-20211207175428169

  • 添加完,指令后,我们先修改地址00401083处的jmp命令跳转至我们的补丁代码位置,测试效果

    image-20211207175604997

  • 如下图所示,可以看到,两个字符串成功被修改,接下来就是保存修改到可执行文件

    image-20211207180207186

    image-20211207180221420

  • 在左下角的数据窗口中,选中修改的00401280~004012BC区域,然后右键选择复制到可执行文件,再保存即可

    image-20211207182713918

  • 这里不直接保存00401083处的jmp命令修改,因为该区域是加密的,所以文件中修改该处的jmp指令也应该替换为加密后的数据

  • 由00401083 => RVA 1083 => RAW: 483,用HEdit查看该处偏移。从文件偏移看加密只到485,后面的0000并不是加密区域。

    image-20211207184027437

  • 如下图所示,OD中jmp 00401296对应的机器码为E90E020000,从上面可知,后面两个00不用加密。因为是XOR(异或)加密,所以加解密都是对同一个key进行XOR运算即可。所以应该将"E90E02"加密后为"EE0905",所以文件483偏移处的"EE9106"应该修改为"EE0905"

    image-20211207184447970

    1
    2
    3
    E9 xor 07 = EE
    0E xor 07 = 09
    02 xor 07 = 05
  • 最后使用HEdit将文件483偏移处的"EE9106"应该修改为"EE0905"保存即可,运行可以发现程序成功跳转至补丁代码。

    image-20211207180207186

    image-20211207180221420