0%

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

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

运行时压缩

  • 运行时压缩器(Run-TimePacker)是软件逆向分析学的常见主题。为了理解好它,需要掌握有关PE文件格式、操作系统的基本知识(进程、内存、DLLL等),同时也要了解有关压缩/解压缩算法的基本内容。

数据压缩

  • 计算机中文件(数据)都是由二进制(0或1)组成的,只要使用合适的压缩算法,就能缩减其大小。经过压缩的文件若能100%恢复,则称该压缩为"无损压缩"(Lossless Data Compression);若不能恢复原状,则称该压缩为"有损压缩"(Loss Data Compression)。
无损压缩
  • 最具代表性的无损压缩算法有Run-Length、Lempel-Ziv、Huffman等,此外还有许多其他算法,它们都是在上面3种压缩算法等基础上改造而成的。只要准确理解了上面3种,就能轻松掌握其他各种压缩算法。
有损压缩
  • 有损压缩允许压缩文件(数据)时损失一定信息,以换取高压缩率。压缩多媒体文件(jpg、mp3、mp4)时,大部分都使用这种有损压缩方式。从压缩特性来看,有损压缩的数据解压后不能完全恢复原始数据。人的肉眼与听觉几乎无法察觉到这些多媒体文件在压缩中损失的数据。
运行时压缩器
  • 运行时压缩器是针对PE(Protable Executable)文件而言的,可执行文件内部含有解压缩代码,文件在运行瞬间于内存中解压缩后执行。

  • 运行时压缩文件也是PE文件,内部含有原PE文件与解码程序。在程序的EP代码中执行解码程序,同时在内存中解压缩后执行。下表为运行时压缩与普通zip压缩的不同点。

    项 目 普通压缩 运行时压缩
    对象文件 所有文件 PE文件(exe、dll、sys)
    压缩结果 压缩文件(.zip、.rar等) PE文件(exe、dll、sys)
    解压缩方式 使用专门解压缩程序 内部含有解码程序
    文件是否可执行 本身不可执行 本身可执行
    优点 可以对所有文件高压缩率压缩 无须专门解压程序便可直接运行
    缺点 若无专门解压缩软件则无法使用压缩文件 每次运行均需调用解码程序导致运行时间过长
  • 把普通PE文件创建成运行时压缩文件的使用程序称为"压缩器"(Packer),经过反逆向(Anti-Reversing)技术特别处理的压缩器称为保护器(Protector)。

压缩器
  • 使用目的:缩减PE文件大小、隐藏PE文件内部代码与资源
  • 压缩器种类:大致分为两类:一类是单纯用于压缩普通PE文件的压缩器,常见有:UPX、ASPack等;另一类是对源文件进行较大变形、严重破坏PE头、意图不纯的压缩器(专门用于恶意程序),常见有UPack、PESpin、NSAnti等。
保护器
  • 使用目的:防止破解,没人愿意自己编写的程序被非法破解使用,此时使用保护器可有效保护PE文件;保护代码与资源,保护器不仅可以保护PE文件本身,还可以在文件运行时,保护进程内存,防止打开Dump窗口。因此,使用保护器可以比较安全地保护程序自身的代码与资源。
  • 保护器种类:有商业程序和公用程序,商业保护器:ASProtect、Themida、SVKP等;公用保护器:UltraProtect、Morphine等。

运行时压缩测试

  • 下面以前面用到的abexcm1.exe为例进行运行时压缩测试。

  • 测试使用的压缩器为UPX:https://github.com/upx/upx,下载后在cmd中运行upx.exe -o abexcm1_upx.exe abexcm1.exe即可

    image-20211201172402003

  • 从运行结果的输出可以看到,文件大小从8192变为了6656,压缩率为81.25%。

比较abexcm1.exe和abexcm1_upx.exe
  • 下图是从PE文件视觉比较两个文件的示意图,很好地反映了UPX压缩器的特点

    image-20211201181531561

  • abexcm1.exe与abexcm1_upx.exe比较项目

    • PE头大小一样(0~400h)
    • 节区名称和数量改变
    • abexcm1_upx.exe第一个节区的RawDataSize=0(文件中的大小为0)
    • abexcm1_upx.exe的EP位于第二个节区(原文件的EP在第一个节区)
    • 资源节区(.rsrc)大小几乎无变化
  • 需要注意的是abexcm1_upx.exe第一个节区(UPX0)的RawDataSize为0,即第一个节区在磁盘文件中是不存在的,用PEView查看该节区头,可以看到VirtualSize为"00006000",这是为abexcm1_upx.exe运行时解压预留的空间。也就是说,程序运行时将(文件中的)压缩的代码解压到(内存中的)第一个节区,更详细点来说,解压缩代码与被压缩的代码都在第二个节区。

    image-20211201181743885


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

调试UPX压缩的abexcm1程序

abexcm1.exe的EP代码
  • 先用Ollydbg查看原abexcm1.exe的EP代码,如下图所示,可以看到代码量很少,因为这是汇编编写的程序

    image-20211202154618660

abexcm1_upx.exe的EP代码
  • 再用Ollydbg打开abexcm1_upx.exe,查看相应的EP代码,如下图所示,可以看到代码发生了明显变化

    image-20211202155000431

  • EP地址为"004071B0",该处即为第二个节区"UPX1"的末端部分。实际压缩的abexcm1.exe代码存在于EP地址"004071B0"上方。

  • 下面查看代码开始的部分("004071B0")

    1
    2
    3
    004071B0 > $  60            pushad
    004071B1 . BE 00704000 mov esi,abexcm1_.00407000
    004071B6 . 8DBE 00A0FFFF lea edi,dword ptr ds:[esi-0x6000]
  • 首先使用PUSHAD命令将寄存器EAX~EDI的值保存到栈,然后分别把第二个节区的起始地址"00407000"与第一个节区地址"00401000"设置到ESI和EDI寄存器。UPX文件的第一个节区仅存在于内存,该处即是解压缩后保存源文件代码的地方。

  • 调试时,遇到这样同时设置ESI与EDI,就能预见从ESI所指缓冲区到EDI所指缓冲区到内存发生了复制。此时从Source(ESI)读取数据,解压缩后保存到Destination(EDI)。我们的目标是跟踪全部UPX EP代码,并最终找到原abexcm1的EP代码。

跟踪UPX文件
  • 下面开始跟踪代码,跟踪数量庞大的代码时,请遵循如下法则:"遇到循环时,先了解作用再跳出"

  • Ollydbg的跟踪命令:跟踪数量庞大的代码时,通常不会使用Step Into(F7)命令,而使用Ollydbg中另外提供的跟踪调试命令,如下表所示:

    命 令 快捷键 说 明
    自动步入 Ctrl+F7 自动执行StepInto命令(画面显示)
    自动步过 Ctrl+F8 自动执行StepOver命令(画面显示)
    跟踪步入 Ctrl+F11 自动执行StepInto命令(画面不显示)
    跟踪步过 Ctrl+F12 自动执行StepOver命令(画面不显示)
  • 除了画面显示之外,自动命令与跟踪命令是类似的,由于自动命令要把跟踪过程显示在画面中,所以执行速度略微慢一些。两者最大差别在于,跟踪命令会自动在事先设置的跟踪条件处停下来,并生成日志文件。

循环#1
  • 开始跟踪代码不久后,会遇到一个短循环。暂停跟踪,仔细查看相应循环如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    004071BD   . /EB 0B         jmp short abexcm1_.004071CA
    004071BF |90 nop
    004071C0 > |8A06 mov al,byte ptr ds:[esi]
    004071C2 . |46 inc esi ; abexcm1_.00407004
    004071C3 . |8807 mov byte ptr ds:[edi],al
    004071C5 . |47 inc edi ; abexcm1_.00401000
    004071C6 > |01DB add ebx,ebx
    004071C8 . |75 07 jnz short abexcm1_.004071D1
    004071CA > \8B1E mov ebx,dword ptr ds:[esi]
    004071CC . 83EE FC sub esi,-0x4
    004071CF 11DB adc ebx,ebx
    004071D1 ^ 72 ED jb short abexcm1_.004071C0
  • 循环开始前先从读取ESI"00407000"地址处的值并复制给EBX寄存器,然后将ESI寄存器的值减去-0x4(加0x4)得到新值"00407004",再执行adc ebx ebx=>"操作对象1 = 操作对象1 + 操作对象2 + CF",jb short abexcm1_.004071C0然后判断CF是否等于"1",是则跳转。然后进入"004071C0"开始循环,循环内容为从ESI("00407004")中读取一个字节写入EDI("00401000")中,然后分别增加ESI和EDI的值,在让EBX加上自己,直到执行add ebx, ebx指令无溢出时即CF=0循环终止。

  • 调试遇到这样的循环应该跳出来,在"004071D3"地址处下断点,F9跳出循环

循环#2
  • 在断点处再次使用自动步过(Ctrl+F8)命令继续跟踪代码,不久后遇到下图所示的循环,(比之前的循环要大,包含了第一个循环的代码),这个循环是正式解码循环(解压缩循环),不断从ESI所指的第二个节区(UPX1)地址中依次读取值,经过适当的运算解压后,将值写入EDI所指的第一个节区(UPX0)。

    image-20211202195945064

  • 在"0040727A"处设置断点可跳过第二个循环,运行到"0040727A"时,左下角数据窗口Ctrl+G跳转至"00401000"地址,可以看到相应的代码已经解压第一个节区(UPX0)了,原来都是NULL填充的

    image-20211202202338099

循环#3
  • 重新跟踪代码,稍后会遇到如下图所示的第三个循环

    image-20211202201356737

  • 这部分代码用于恢复源代码的CALL/JMP指令(操作码:E8/E9)的目标地址。在"004072AE处"下断点运行后可以跳出该循环。到此接近尾声了,只要再设置好IAT,UPX解压缩代码就结束了。

循环#4
  • 重新跟踪代码,再稍微进行一段,遇到第四个循环,该循环即为设置IAT的循环。在地址"004072AE"处设置EDI=00406000,它指向第二个节区(UPX1)区域,该区域保存着原abexcm1.exe调用的API函数名称的字符串。

    image-20211202202130380
    image-20211202202541268

  • UPX压缩原abexcm1.exe文件时,它会分析其IAT,提取出程序中调用的API名称列表,形成API名称字符串。利用这些API名称字符串调用"004072DF"处的GetProcAddress()函数,获取API的起始地址,然后把API地址输入EBX寄存器所指的原abexcm1.exe的IAT区域。这个过程会反复执行,直到恢复所有原abexcm1.exe的IAT。

  • abexcm1.exe全部解压缩完成后,应该将程序的控制返回到OEP处,下图显示的就是跳转到OEP的代码。另外"00407325"处的POPAD命令与UPX代码的第一条PUSHAD命令对应,用来把当前寄存器恢复原状。

    image-20211202203827648

快速查找UPX OEP的方法
  • 在POPAD指令后的JMP指令处设置断点:UPX压缩器的特征之一是,其EP代码被包含在PUSHAD/POPAD之间。并且,跳转到OEP代码的JMP指令紧接着出现在POPAD指令之后。只要在JMP指令处设置好断点,运行就能直接找到OEP了。

    PUSHAD:将8个通用寄存器(EAX~EDX)的值保存到栈

    POPAD:把PUSHAD命令储存在栈的值再次恢复到各个寄存器

在栈中设置硬件断点
  • 这个方法同样利用UPX的PUSHAD/POPAD指令的特点。在执行"004071B0"地址处的PUSHAD命令后,查看栈

    image-20211202205233698

  • EAX到EDX寄存器的值依次被储存到栈。从Ollydbg的Dump窗口进入栈地址"0019FF54",将光标定位到19FF54地址,右键按下图所示设置硬件断点

    image-20211202205604651

  • 硬件断点是CPU支持的断点,最多可以设置4个。与普通断点不同的是,硬件断点的指令执行完成后才暂停调试。在这种状态下,程序会边解压缩边执行代码,在执行到POPAD的瞬间访问设置有硬件断点的0019FF54地址,然后暂停调试。其下方即是跳转至OEP的JMP指令。