脱壳的艺术--4反分析技术

 

脱壳的艺术

Mark Vincent Yason

概述:脱壳是门艺术——脱壳既是一种心理挑战,同时也是逆向领域最为激动人心的智力游戏之一。为了甄别或解决非常难的反逆向技巧,逆向分析人员有时不得不了解操作系统的一些底层知识,聪明和耐心也是成功脱壳的关键。这个挑战既牵涉到壳的创建者,也牵涉到那些决心躲过这些保护的脱壳者。

本文主要目的是介绍壳常用的反逆向技术,同时也探讨了可以用来躲过或禁用这些保护的技术及公开可用的工具。这些信息将使研究人员特别是恶意代码分析人员在分析加壳的恶意代码时能识别出这些技术,当这些反逆向技术阻碍其成功分析时能决定下一步的动作。第二个目的,这里介绍的信息也会被那些计划在软件中添加一些保护措施用来减缓逆向分析人员分析其受保护代码的速度的研究人员用到。当然没有什么能使一个熟练的、消息灵通的、坚定的逆向分析人员止步的。 

4反分析技术                                                                 

反分析技术的目标是减缓逆向分析人员对受保护代码和(或)加壳后的程序分析和理解的速度。我们将讨论诸如加密/压缩、垃圾代码、代码变形、反-反编译等技术,这些技术的目的是为了混淆代码、考验耐心、浪费逆向分析人员的时间,解决这些问题需要逆向分析人员拥有耐心、聪慧等品质。

4.1 Encryption and Compression

加密和压缩是最基本的反分析形式。它们初步设防,防止逆向分析人员直接在反编译器内加载受保护的程序然后没有任何困难地开始分析。

加密 壳通常都既加密本身代码也加密受保护的程序。不同的壳所采用的加密算法大不相同,有非常简单的XOR循环,也有执行数次运算的非常复杂的循环。对于某些多态变形壳,为了防止查壳工具正确地识别壳,每次加壳所采用的加密算法都不同,解密代码也通过变形显得很不一样。

解密例程作为一个取数、计算、存诸操作的循环很容易辨认。下面是一个对加密过的DWORD值执行数次XOR操作的简单的解密例程。

0040A 07C       LODS DWORD PTR DS:[ESI]

0040A 07D       XOR EAX,EBX

0040A 07F       SUB EAX,12338CC3

0040A 084        ROL EAX,10

0040A 087        XOR EAX, 799F 82D0

0040A 08C       STOS DWORD PTR ES:[EDI]

0040A 08D       INC EBX

0040A 08E       LOOPD SHORT 0040A 07C ;decryption loop

这里是另一个多态变形壳的解密例程:

00476056               MOV BH,BYTE PTR DS:[EAX]

00476058               INC ESI

00476059               ADD BH,0BD

0047605C        XOR BH,CL

0047605E        INC ESI

0047605F        DEC EDX

00476060         MOV BYTE PTR DS:[EAX],BH

00476062               CLC

00476063               SHL EDI,CL

:::More garbage code

00476079       INC EDX

0047607A        DEC EDX

0047607B        DEC EAX

0047607C        JMP SHORT 0047607E

0047607E        DEC ECX

0047607F               JNZ 00476056 ;decryption loop

下面是由同一个多态壳生成的另一段解密例程:

0040C 045        MOV CH,BYTE PTR DS:[EDI]

0040C 047        ADD EDX,EBX

0040C 049        XOR CH, AL

0040C 04B       XOR CH,0D9

0040C 04E       CLC

0040C 04F        MOV BYTE PTR DS:[EDI],CH

0040C 051        XCHG AH,AH

0040C 053        BTR EDX,EDX

0040C 056        MOVSX EBX,CL

::: More garbage code

0040C 067        SAR EDX,CL

0040C 06C       NOP

0040C 06D       DEC EDI

0040C 06E       DEC EAX

0040C 06F        JMP SHORT 0040C 071

0040C 071        JNZ 0040C 045 ;decryption loop

上面两个示例中高亮的行是主要的解密指令,其余的指令都是用来迷惑逆向分析人员的垃圾代码。注意寄存器是如何交换的,还有两个示例之间解密方法是如何改变的。

Compression 压缩的主要目的是为了缩小可执行文件代码和数据的大小,但是由于原始的包含可读字符串的可执行文件变成了压缩数据,因此也有那么一些混淆的作用。看看几款壳所使用的压缩引擎:UPX使用NRVNot Really Vanished)LZMA(Lempel-Ziv-Markov chain-Algorithm)FSG使用aPLibUpack使用LZMAyoda加密壳使用LZO。这其中有些压缩引擎可以自由地使用于非商业应用,但是商业应用需要许可/注册。

对策

解密和解压缩循环很容易就能被躲过,逆向分析人员只需要知道解密和解压缩循环何时结束,然后在循环结束后面的指令上下断点。记住,有些壳会在解密循环中检测断点。

4.2 Garbage Code and Code Permutation

Garbage Code 在脱壳的例程中插入垃圾代码是另一种有效地迷惑逆向分析人员的方法。它的目的是在加密例程或者诸如调试器检测这样的反逆向例程中掩盖真正目的的代码。通过将本文描述过的调试器/断点/补丁检测技术隐藏在一大堆无关的、不起作用的、混乱的指令中,垃圾代码可以增加这些检测的效果。此外,有效的垃圾代码是那些看似合法/有用的代码。

示例

下面是一段在相关的指令中插入了垃圾代码的解密例程:

0044A 21A       JMP SHORT sample .0044A 21F

0044A 21C       XOR DWORD PTR SS:[EBP],6E4858D

0044A 223        INT 23

0044A 225        MOV ESI,DWORD PTR SS:[ESP]

0044A 228        MOV EBX, 2C 322FF0

0044A 22D        LEA EAX,DWORD PTR SS:[EBP+6EE5B321]

0044A 233        LEA ECX DWORD PTR DS:[ESI+543D583E]

0044A 239        ADD EBP, 742C 0F 15

0044A 23F       ADD DWORD PTR DS:[ESI],3CB3AA25

0044A 245        XOR EDI,7DAC77E3

0044A 24B       CMP EAX,ECX

0044A 24D       MOV EAX,5ACAC514

0044A 252        JMP SHORT sample .0044A 257

0044A 254        XOR DWORD PTR SS:[EBP],AAE47425

0044A 25B       PUSH ES

0044A 25C       ADD EBP,5BAC 5C 22

0044A 262        ADC ECX,3D 71198C

0044A 268        SUB ESI,-4

0044A 26B       ADC ECX, 3795A 210

0044A 271        DEC EDI

0044A 272        MOV EAX, 2F 57113F

0044A 277        PUSH ECX

0044A 278        POP ECX

0044A 279        LEA EAX,DWORD PTR SS:[EBP+3402713D]

0044A 27F       EDC EDI

0044A 280        XOR DWORD PTR DS:[ESI],33B568E3

0044A 286        LEA EBX,DWORD PTR DS:[EDI+57DEFEE2]

0044A 28C       DEC EDI

0044A 28D       SUB EBX,7ECDAE21

0044A 293        MOV EDI, 185C 5C 6C

0044A 298        MOV EAX,4713E635

0044A 29D       MOV EAX,4

0044A 2A 2       ADD ESI,EAX

0044A 2A 4       MOV ECX, 1010272F

0044A 2A 9       MOV ECX, 7A 49B614

0044A 2AE      CMP EAX,ECX

0044A 2B0       NOT DWORD PTR DS:[ESI]

示例中相关的解密指令是:

0044A 225        MOV ESI,DWORD PTR SS:[ESP]

0044A 23F       ADD DWORD PTR DS:[ESI],3CB3AA25

0044A 268        SUB ESI,-4

0044A 280        XOR DWORD PTR DS:[ESI],33B568E3

0044A 29D       MOV EAX,4

0044A 2A 2       ADD ESI,EAX

0044A 2B0       NOT DWORD PTR DS:[ESI]

Code Permutation 代码变形是更高级壳使用的另一种技术。通过代码变形,简单的指令变成了复杂的指令序列。这要求壳理解原有的指令并能生成新的执行相同操作的指令序列。

一个简单的指令置换示例:

mov        eax,ebx

test           eax,eax

转换成下列等价的指令:

push        ebx

pop          eax

or            eax,eax

结合垃圾代码使用,代码变形是一种有效地减缓逆向分析人员理解受保护代码速度的技术。

示例

为了说明,下面是一个通过代码变形并在置换后的代码间插入了垃圾代码的调试器检测例程:

004018A 8        MOV ECX,A104B412

004018AD       PUSH 004018C 1

004018B2        RETN

004018B3        SHR EDX,5

004018B6        ADD ESI,EDX

004018B8        JMP SHORT 004018BA

004018BA       XOR EDX,EDX

004018BC       MOV EAX,DWORD PTR DS:[ESI]

004018BE       STC

004018BF        JB SHORT 004018DE

004018C 1        SUB ECX,EBX

004018C 3        MOV EDX, 9A 01AB 1F

004018C 8        MOV ESI,DWORD PTR FS:[ECX]

004018CB       LEA ECX DWORD PTR DS:[EDX+FFFF7FF7]

004018D1        MOV EDX,600

004018D6        TEST ECX,2B73

004018DC       JMP SHORT 004018B3

004018DE       MOV ESI,EAX

004018E0        MOV EAX,A35ABDE4

004018E5        MOV ECX,FAD 1203A

004018EA       MOV EBX,51AD5EF2

004018EF        DIV EBX

004018F 1               ADD BX, 44A 5

004018F 6               ADD ESI,EAX

004018F 8               MOVZX EDI,BYTE PTR DS:[ESI]

004018FB        OR EDI,EDI

004018FD       JNZ SHORT 00401906

其实这是一个很简单的调试器检测例程:

00401081       MOV EAX,DWORD PTR FS:[18]

00401087       MOV EAX,DWORD PTR DS:[EAX+30]

0040108A       MOVZX EAX,BYTE PTR DS:[EAX+2]

0040108E       TEST EAX,EAX

00401090       JNZ SHORT 00401099

对策

垃圾代码和代码变形是一种用来考验耐心和浪费逆向分析人员的时间的方式。因此,重要的是知道这些混淆技术背后隐藏的指令是否值得去理解(是不是仅仅执行解密、壳的初始化等动作)。

避免跟踪进入这些难懂的指令的方法之一是在壳最常用的API下断点(如:VirtualAlloc,VitualProtect,LoadLibrary,GetProcAddress等)并把这些API当作跟踪的标志。如果在这些跟踪标志之间出了错,这时候就对这一段代码进行详细的跟踪。另外,设置内存访问/写入断点也让逆向分析人员能有针对性地分析那些修改/访问受保护进程最有趣的部分的代码,而不是跟踪大量的代码最终却(很可能)发现是一个确定的例程。

最后,在VMWare中运行OllyDbg并不时地保存调试会话快照,这样一来逆向分析人员就可以回到某一个特定的跟踪状态。如果出了错,可以返回到某一特定的跟踪状态继续跟踪分析。

4.3 Anti-Disassembly

用来困惑逆向分析人员的另一种方法就是混乱反编译输出。反-反编译是使通过静态分析理解二进制代码的过程大大复杂化的有效方式。如果结合垃圾代码和代码变形一起使用将会更具效果。

-反编译技术的一个具体的例子是插入一个垃圾字节然后增加一个条件分支使执行跳转到垃圾字节(译者注:即我们常说的花指令)。但是这个分支的条件永远为FALSE。这样垃圾代码将永远不会被执行,但是反编译引擎会开始反编译垃圾字节的地址,最终导致不正确的反编译输出。

示例

这是一个加了一些反-反编译代码的简单PEB.BeingDebugged标志检查例子。高亮的行是主要指令,其余的是反-反编译代码。它用到了垃圾字节0xff并增加了用来迷惑反编译引擎的跳到垃圾字节的假的条件跳转。

;Anti-disassembly sequence #1

push        .jmp_real_01

stc

jnc           .jmp_fake_01

retn

.jmp_fake_01:

db            0xff

.jmp_real_01:

;--------------------------------

mov eax,dword [fs:0x18]

 

;Anti-disassembly sequence #2

push        .jmp_real_02

clc

jc             .jmp_fake_02

retn

.jmp_fake_02:

db            0xff

.jmp_real_02:

;--------------------------------

mov        eax,dword [eax+0x30]

movzx      eax,byte [eax+0x02]

test          eax,eax

jnz           .debugger_found

下面是WinDbg中的反汇编输出:

0040194A 6854194000          PUSH 0X401954

0040194F F9                         STC

00401950 7301                      JNB image00400000+0x1953(00401953)

00401952 C 3                         RET

00401953 FF 64A 118                     JMP DWORD PTR [ECX+0X18]

00401957 0000                      ADD [EAX],AL

00401959 006864                   ADD [EAX+0X64],CH

0040195C 194000                  SBB [EAX],EAX

0040195F F8                         CLC

00401960 7201                      JB image00400000+0x1963 (00401963)

00401962 C 3                         RET

00401963 FF8B40300FB6      DEC DWORD PTR [EBX+0XB 60F 3040]

00401969 40                          INC EAX

0040196A 0285C 0750731       ADD AL,[EBP+0X 310775C 0]

OllyDbg中的反汇编输出:

0040194A 6854194000          PUSH 00401954

0040194F F9                         STC

00401950 7301                      JNB SHORT 00401953

00401952 C 3                         RETN

00401953 FF 64A 118                     JMP DWORD PTR DS:[ECX+18]

00401957 0000                      ADD BYTE PTR DS:[EAX], AL

00401959 006864                   ADD BYTE PTR DS:[EAX+0X64],CH

0040195C 194000                  SBB DWORD PTR DS:[EAX],EAX

0040195F F8                         CLC

00401960 7201                      JB SHORT 00401963

00401962 C 3                         RETN

00401963 FF8B40300FB6      DEC DWORD PTR DS:[EBX+B 60F 3040]

00401969 40                          INC EAX

0040196A 0285C 0750731       ADD AL,BYTE PTR SS:[EBP+ 310775C 0]

最后IDAPro中的反汇编输出:

0040194A                       push (offset loc_401953+1)

0040194F                        stc

00401950                       jnb short loc_401953

00401952                       retn

00401953 ;------------------------------------------------------------------

00401953

00401953 loc-401953:                          ;CODE XREF: sub_401946+A

00401953                                           ;DATA XREF: sub_401946+4

00401953                       jmp dword ptr [ecx+18h]

00401953 sub_401946    endp

00401953

00401953 ;------------------------------------------------------------------

00401957                       db 0

00401958                       db 0

00401959                       db 0

0040195A                       db 68h; h

0040195B                       dd offset unk_401964

0040195F                        db 0F 8h;

00401960                       db 72h; r

00401961                       db 1

00401962                       db 0C 3h;+

00401963                       db 0FFh

00401964 unk_401964     db 8Bh; i        ;DATA XREF: text:0040195B

00401965                       db 40h; @

00401966                       db 30h; 0

00401967                       db 0Fh

00401968                       db 0B6h;|

00401969                       db 40h; @

0040196A                       db 2

0040196B                       db 85h;

0040196C                        db 0C 0h;+

0040196D                      db 75h; u

注意所有这三个反编译引擎/调试器是如何落入反-反编译陷阱的,分析这样的反汇编代码对于逆向分析人员来说是很不容易的。还有其它的几种干扰反编译引擎的手段,这只是一个例子。另外这些反-反编译代码可以编码成一个宏,这样汇编源码就清晰多了。

建议读者参考Eldad Eliam13的一本精彩的逆向书籍,里面包含了反-反编译的详细信息和其它一些逆向话题。

 

你可能感兴趣的:(脱壳的艺术--4反分析技术)