第20章 “内嵌补丁”练习
本文最后更新于:2022年5月19日 晚上
第20章“内嵌补丁”练习
对加密文件、运行时解压缩文件“打补丁”时,经常使用“内嵌补丁”(InlinePatch)技术,本章将通过示例让读者了解、学习。
20.1内嵌补丁
“内嵌补丁”是“内嵌代码补丁”(Inline Code Patch)的简称,难以直接修改指定代码时,插入并运行被称为“洞穴代码”(CodeCave)的补丁代码后,对程序打补丁。常用于对象程序经过运行时压缩(或加密处理)而难以直接修改的情况。详细说明参见图20-1。
图20-1左图描述的是典型的运行时压缩代码(或者加密代码)。EP代码先将加密的OEP代码解密,然后再跳转到OEP代码处。若要打补丁的代码存在于经过加密的OEP区域是很难打补丁的(即使知道代码所在位置也是如此),因为解码过程中可能会解出完全不同的结果。
解决上述问题的简单方法就是如图20-1中右图所示,在文件中另外设置被称为“洞穴代码” 的“补丁代码”,EP代码解密后修改JMP指令,运行洞穴代码。在洞穴代码中执行补丁代码后(由于已经解密OEP,故可以这样修改),再跳转到OEP处。也就是说,每次运行时(运行另外的补丁代码)都要对进程内存的代码打补丁,所以这种打补丁的方法被称为“内嵌代码补丁”法或“内嵌补丁”法。这也是它与一般修改代码方法的不同。表20-1中列出了普通代码补丁与内嵌补丁的不同之处。
20.2 练习: Patchme
一名叫ap0x的代码逆向分析者制作了一个patchme程序,它是完全公开的,用来帮助大家学
习代码逆向分析技术。本小节使用这个简单的示例,向各位充分展现“内嵌补丁”这一方法。
http://ap0x.jezgra.net/download/patchme_nol.rar
(出处:ap0x - Reversing Labs网页)
这是一个非常简单的小程序,总共5KB。先检查它是否含有病毒代码再运行。
运行程序,弹岀如图20-2所示的消息框,要求更改显示的字符串。单击“确定”按钮,弹出图20-3所示的对话框。
对话框中有一个字符串要求解压其本身(unpackme )。这个patchme程序比较简单,只要修改上面2处字符串即可。但问题是程序文件中2个字符串都处在加密状态,难以修改。
20.3 调试:查看代码流
首先使用OllyDbg打开程序文件,如图20-4所示。
EP代码非常简单。地址401007之后即是加密代码。为了查找图20-2与图20-3中出现的消息,选择鼠标右键菜单中的Search for All referenced text strings,如图20-5所示。
如预料的一样,所有字符串都处在加密状态,这种情形下无法查找到指定字符串。在图20-4中跟踪进入401001地址处CALL命令调用的函数(4010E9),执行一段时间后遇到图20-6所示的代码。
这段代码就是解密循环。地址4010A3处的XOR BYTE PTR DS:[EBX],44语句使用XOR命令对
特定区域(4010F5~401248)解密。跟踪进入地址4010B0处CALL命令调用的函数(4010BD),
可以看到另外2个解密循环,如图20-7所示。
地址4010C8处的XOR命令用来解密401007~401085区域,然后再使用4010DB地址处的XOR
命令对4010F5~401248区域解密。特别是该区域与图20-6中解密区域一致,由此可知该区域经过
双重加密处理。4010BD函数调用完毕后遇到4010B6地址处的CALL 401039命令,如图20-6所示。
跟踪进入被调用的函数(401039 ),看到图20-8所示的代码。
401039函数中需要注意的是位于401046地址处的校验和计算循环。首先使用401041地址处的
MOV EDX,0命令,将0代入(初始化)EDX。然后使用401046地址处的ADD命令,从特定地址区
域(4010F5~401248 )以4个字节为单位依次读入值,进行加法运算后,将累加结果存储到EDX
寄存器。
循环结束时,EDX寄存器中存储着某个特定值,这就是校验和值。由前面的讲解可知,该校验和计算区域是一个双重加密区域。可以推测岀,我们要修改的字符串就存在于此。
EDX寄存器为4个字节大小,像这样向其中不断加上4个字节的值,就会发生溢出(overflow)问题。一般的校验和计算中常常忽略该溢出问题,使用最后一个保存在EDX的值。
位于地址401062~401068处的CMP/JE命令用来将计算得到的校验和(存储在EDX寄存器的)值与31EB8DB0比较,若相同(表示代码未被改动过),则由401083地址处的JMP指令跳转到OEP(40120.)处;若不同,则输岀错误信息“CrC of this file has been modified!!!”,终止程序。
这种校验和计算方法常常用来验证特定区域的代码/数据是否被改动过。只要指定区域中的一个字节发生改变,校验和值就会改变。所以更改了指定区域中的代码/数据时,一定要修改校验和比较相关部分。 图20-9中显示的是OEP代码,用来运行对话框。执行位于40123E地址处的CALL user32.DialogBoxParamA()命令后,即弹岀对话框。下面是DialogBoxParamA() API的定义。
1 |
|
DialogBoxParamA() API的第四个参数lpDialogFunc用来指出Dialog Box Procedure的地址(在OllyDbg中显示为DlgProc)。图20-9的40122C地址处有条PUSH 4010F5命令,由此可见,函数第四个参数DlgProc的地址为4010F5。图20-10是DlgProc(4010F5)的代码,顶端粗线框部分是我们要修改的字符串(下面的方框中会使用这些字符串)。
通过以上简单的调试,我们大致把握了程序的流向,以及要修改的字符串所在的位置(40110A,401123 )(像这样,在没有源代码的条件下调试二进制文件,就像迷路时寻路或猜谜一
样,让入觉得非常有趣)。
该程序的各部分都做了加密处理,特别是要修改的字符串被加密过两次。并且在程序内部针对字符串区域计算校验和值,借以检验字符串是否发生更改,这些都大大增加了修改字符串的难度。对于这样的程序,使用常规的文件修改方法难以奏效,但使用“内嵌补丁”方法能够轻松地“打补丁”。
像示例这种加密程序其实是相当简单的,综合考虑XOR加密与校验和代码后,可以直接修改。但为了学习“内嵌补丁”这一技术,我们不会使用该方法,而是按照常见做法添加“洞穴代码”修改。
20.4 代码结构
为了方便说明,首先看一下示例代码的组织结构。若把握了代码结构,就能很容易地找岀如
何对哪些代码打补丁。
图20-11的[A]、[B]、[C]区域为加密后的代码,[EPCode]、[Decoding Code]区域存在着用于解密的代码。
大致的代码流如下所示。
1 |
|
[EP Code]只是用来调用[Decoding Code]的,实际的解密处理是由[Decoding Code]完成的。按 照[B]-[A]-[B]的顺序解码(XOR),运行解密后的[A]区代码。在[A]区代码中会求得[B]区的校验和,并据此判断[B]区代码是否发生过更改。然后对[C]区解码(XOR ),最后跳到OEP处( 40121E )。
建议各位根据代码结构与代码流亲自调试确认。“打补丁”之前掌握代码结构会使操作更加容易,且初学者在这一个过程中也会感受到许多乐趣。如果想进一步享受调试,建议各位调试时不要参考“代码结构”与“代码流”内容。自己动手挑战,成功的话将拥有无尽喜悦。
20.5 “内嵌补丁”练习
实际要打补丁的字符串全部位于[B]区,如前所见,[B]区是特别经过双重加密处理的,并且要通过求校验和来判断是否发生更改,所以直接修改字符串会有些困难。此时,一种更易使用的方法就是利用补丁代码的“内嵌补丁”法(这类补丁代码称为“洞穴代码”)。
简单说一下操作顺序。
首先向文件合适位置插入用于修改字符串的代码,然后在图20-11的[A]区域将JMP OEP命令修改为JMP补丁代码(当然修改时要充分考虑文件中的[A]区域处于加密状态)。若运行程序时遇到[A]区中的JMP补丁代码语句,(此时所有代码均处于解密状态且通过校验和验证,所以)就在补丁代码中更改字符串,通过JMP命令跳转到OEP处,这样整个内嵌补丁过程就完成了。
20.5.1补丁代码要设置在何处呢
这个问题在进行内嵌补丁的过程中非常重要。有如下3种设置方法:
①设置到文件的空白区域。
②扩展最后节区后设置。
③添加新节区后设置。
补丁代码较少时,使用方法①,其他情况使用方法②或③。首先尝试方法①,使用PEView查看示例文件的第一个节区(.text)头,如图20-12所示
第一个节区的文件形态与加载到内存中的形态如图20-13所示。
第一个节区(.text)的Size of RAW Data为400, Virtual Size为280。也就是说,第一个节区(在磁盘文件中)的尺寸为400,但是仅有280大小被加载到内存,其余区域(680~800)处于未使用状态,该区域即是要查找的空白区域(NULL-Padding )。
节区的Virtual Size为280,这并不意味着实际节区的内存大小为280,而要以Section Alignment (以上示例文件为1000 )的倍数扩展,故实际大小为1000。
使用Hex Editor打开并查看找到的空白区域,如图20-14所示。
从图20-14中可以看到,空白区域( 680~800 )全部填充着0(这种区域称为Null-padding区域)。接下来在该区域设置补丁代码(洞穴代码)。
图20-12中还有一个需要注意的是1E4的属性值中添加的IMAGE_SCN_MEM_WRITE(可写属性)。为了在程序中进行解密处理,一定要在节区头添加可写属性,获得相应内存的可写权限(当对无写权限的内存进行“写”操作时,会引发非法访问异常)。对于一个普通的PE文件,其代码节是无写权限的,但是包含上面示例在内的压缩工具、Crypter等文件的代码节都有可写权限,请各位以后分析文件时注意这一点。
20.5.2 制作补丁代码
再次使用OllyDbg调试示例程序,运行到OEP处(40121E),如图20-15所示。
前面查找到的空白区域在文件中的偏移为680~800,将其变换为进程VA后为401280~401400(参考图20-13)。从图20-15中也可以看到Null-Padding区域是从401280开始的。接下来,在401280位置处创建“补丁代码”。使用OllyDbg的Assemble(Space)命令与Edit(Ctrl+E)命令进行如下编辑。 图20-16中的汇编代码相当简单。地址40128F与4012A0处的REP MOVSB命令用于修改下面的字符串(因401123、40110A字符串处于解密状态,所以能够正常显示)。
(401123) “You must patch this NAG!!!” → (4012A8) “ReverseCore”
(40110A) “You must unpack me!!!”,,→ (4012B4) “Unpacked”
然后由图20-16中4012A2地址处的JMP命令跳转到OEP处。至此,补丁代码全部完成。每当补丁代码运行时,进程内存中解密后的字符串(401123,40110A)就会被打补丁。在OllyDbg中保存修改的内容(Copy to executable - All modifications命令)。
20.5.3 执行补丁代码
“内嵌补丁”技术的最后一步是直接修改文件以运行前面创建的补丁代码(洞穴代码)。修改哪部分好呢?观察前面介绍的代码流,可以发现地址401083处存在JMP OEP(40121E)指令,如图20-17所示。
只要把JMP OEP (40121E)命令更改为JMP洞穴代码(401280)就可以了,即在转到OEP之前先把控制交给洞穴代码,使字符串得以修改。
这里要注意的是,该区域(401083 )即是原来的加密区域。由图20-11可知,地址401083属于[A]区域,是使用XOR 7加密的区域(参考代码流)。图20-17是解密后的形式,文件中实际的加密形态如图20-18所示。
从文件偏移看,加密区域只到485,后面的0000并不是加密区域(参考图20-11)。比较图20-17 与20-18可以看到,“EE9106” 通过XOR 7加密后变为“E9 96 01”。补丁代码的地址为401280,如图20-19修改JMP命令语句的指令。
照搬指令(E9 F8 01)写入是不行的,还要考虑解密处理,应该执行完XOR 7命令后再写入。
1 |
|
使用Hex Editor修改如图20-20所示
像这样,使用内嵌补丁技术完成了对整个程序的修改工作(以Unpackme#1.aC_patched.exe文件名保存)。
20.5.4 结果确认
运行打补丁后的文件(Upackme#1.aC_patched.exe ),如图20-21所示。
比较图20-21与图20-2、图20-3,可以看到字符串已经修改成功了,即通过“内嵌补丁”技术成功修改了加密文件。最后,使用调试器查看一下被修改文件的401083地址处。原来为JMP 40121E ( OEP ),现在变为JMP 401280 (洞穴代码)(参考图20-17、图20-22 )。
如图20-23所示,执行补丁代码(洞穴代码),字符串被修改,最后跳转到OEP处(40121E)。
图20-24显示了被修改的字符串在对话框与消息框中使用的代码。“内嵌补丁”技术本身就是个非常有趣的主题,同时也是能够综合测评代码逆向分析水平(PE文件规范、调试、反汇编等)的好机会。内嵌补丁技术在后面学习API钩取技术时还会用到。
参考
《逆向工程核心原理》 第20章