什么是“指令伪装”混淆?

逆向工程 混淆
2021-07-06 05:32:15

我有一个混淆的二进制文件,它只打印一个简单的Hello World! 并像这样退出:

Hello World!

但是,当我查看带有 的程序集时objdump,我找不到任何对printfor 的调用write,也找不到字符串Hello World!

0804840c <main>:
 804840c:       be 1e 84 04 08          mov    $0x804841e,%esi
 8048411:       89 f7                   mov    %esi,%edi
 8048413:       b9 26 00 00 00          mov    $0x26,%ecx
 8048418:       ac                      lods   %ds:(%esi),%al
 8048419:       34 aa                   xor    $0xaa,%al
 804841b:       aa                      stos   %al,%es:(%edi)
 804841c:       e2 fa                   loop   8048418 <main+0xc>
 804841e:       23 4f 29                and    0x29(%edi),%ecx
 8048421:       46                      inc    %esi
 8048422:       ae                      scas   %es:(%edi),%al
 8048423:       29 4e 5a                sub    %ecx,0x5a(%esi)
 8048426:       29 6e ae                sub    %ebp,-0x52(%esi)
 8048429:       c2 9c 2e                ret    $0x2e9c
 804842c:       ae                      scas   %es:(%edi),%al
 804842d:       a2 42 17 54 55          mov    %al,0x55541742
 8048432:       55                      push   %ebp
 8048433:       23 46 69                and    0x69(%esi),%eax
 8048436:       e2 cf                   loop   8048407 <frame_dummy+0x27>
 8048438:       c6 c6 c5                mov    $0xc5,%dh
 804843b:       8a fd                   mov    %ch,%bh
 804843d:       c5 d8 c6 ce 8b          vshufps $0x8b,%xmm6,%xmm4,%xmm1
 8048442:       a0 aa 90 90 90          mov    0x909090aa,%al
 8048447:       90                      nop
 ...
 804844f:       90                      nop

此处声称使用的混淆技术称为指令伪装(请参阅本文)。有人可以解释它是什么以及它是如何工作的吗?

2个回答

指令伪装是一种针对二进制的简单朴素静态分析的混淆技术。二进制程序由两部分组成:

  • 解码器
  • 编码的有效载荷

执行时,二进制文件首先进入解码器并解码揭示真正汇编代码的有效载荷。最后,解码器跳转到解码后的有效载荷并执行代码。

这种技术的好处是静态反汇编二进制文件不会给你关于程序真正在做什么的提示。不知何故,它迫使分析人员首先执行解码器部分(真实的或象征性的),然后查看解码的有效载荷。

在提议的示例中,解码器部分如下:

0804840c <main>:
 804840c:       be 1e 84 04 08          mov    $0x804841e,%esi
 8048411:       89 f7                   mov    %esi,%edi
 8048413:       b9 26 00 00 00          mov    $0x26,%ecx
 8048418:       ac                      lods   %ds:(%esi),%al
 8048419:       34 aa                   xor    $0xaa,%al
 804841b:       aa                      stos   %al,%es:(%edi)
 804841c:       e2 fa                   loop   8048418 <main+0xc>

您可以看到在0x8048418之间有一个循环0x804841c 它对0xaa负载中的每个字节应用异或(从0x804841e0x804841e + 0x25 = 0x8048443,循环计数器是%ecx)。

因此,了解有效载荷中做了什么的最好方法是在gdb 解码器完成任务后设置断点:

GNU gdb (GDB) 7.4.1-debian
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
(gdb) break main
Breakpoint 1 at 0x804840c
(gdb) run
Starting program: ./instruction_camouflage

Breakpoint 1, 0x0804840c in main ()

让我们检查代码是否没有改变。

(gdb) disas
Dump of assembler code for function main:
=> 0x0804840c <+0>: mov    $0x804841e,%esi
   0x08048411 <+5>: mov    %esi,%edi
   0x08048413 <+7>: mov    $0x26,%ecx
   0x08048418 <+12>:    lods   %ds:(%esi),%al
   0x08048419 <+13>:    xor    $0xaa,%al
   0x0804841b <+15>:    stos   %al,%es:(%edi)
   0x0804841c <+16>:    loop   0x8048418 <main+12>
   0x0804841e <+18>:    and    0x29(%edi),%ecx
   0x08048421 <+21>:    inc    %esi
   0x08048422 <+22>:    scas   %es:(%edi),%al
   0x08048423 <+23>:    sub    %ecx,0x5a(%esi)
   0x08048426 <+26>:    sub    %ebp,-0x52(%esi)
   0x08048429 <+29>:    ret    $0x2e9c
   0x0804842c <+32>:    scas   %es:(%edi),%al
   0x0804842d <+33>:    mov    %al,0x55541742
   0x08048432 <+38>:    push   %ebp
   0x08048433 <+39>:    and    0x69(%esi),%eax
   0x08048436 <+42>:    loop   0x8048407 <frame_dummy+39>
   0x08048438 <+44>:    mov    $0xc5,%dh
   0x0804843b <+47>:    mov    %ch,%bh
   0x0804843d <+49>:    vshufps $0x8b,%xmm6,%xmm4,%xmm1
   0x08048442 <+54>:    mov    0x909090aa,%al
   0x08048447 <+59>:    nop
...
   0x0804844f <+67>:    nop
End of assembler dump.

让我们在循环之后放置一个断点并继续直到它到达。

(gdb) break *0x0804841e
Breakpoint 2 at 0x804841e
(gdb) continue
Continuing.

Breakpoint 2, 0x0804841e in main ()

现在,我们应该能够访问将要执行的代码。

(gdb) disas
Dump of assembler code for function main:
   0x0804840c <+0>: mov    $0x804841e,%esi
   0x08048411 <+5>: mov    %esi,%edi
   0x08048413 <+7>: mov    $0x26,%ecx
   0x08048418 <+12>:    lods   %ds:(%esi),%al
   0x08048419 <+13>:    xor    $0xaa,%al
   0x0804841b <+15>:    stos   %al,%es:(%edi)
   0x0804841c <+16>:    loop   0x8048418 <main+12>
=> 0x0804841e <+18>:    and    %ebp,%esp
   0x08048420 <+20>:    sub    $0x4,%esp
   0x08048423 <+23>:    and    $0xfffffff0,%esp
   0x08048426 <+26>:    add    $0x4,%esp
   0x08048429 <+29>:    push   $0x8048436
   0x0804842e <+34>:    call   0x80482f0 <puts@plt>
   0x08048433 <+39>:    mov    %ebp,%esp
   0x08048435 <+41>:    ret    
   0x08048436 <+42>:    dec    %eax
   0x08048437 <+43>:    gs
   0x08048438 <+44>:    insb   (%dx),%es:(%edi)
   0x08048439 <+45>:    insb   (%dx),%es:(%edi)
   0x0804843a <+46>:    outsl  %ds:(%esi),(%dx)
   0x0804843b <+47>:    and    %dl,0x6f(%edi)
   0x0804843e <+50>:    jb     0x80484ac <__libc_csu_init+76>
   0x08048440 <+52>:    and    %ecx,%fs:(%edx)
   0x08048443 <+55>:    add    %dl,-0x6f6f6f70(%eax)
   0x08048449 <+61>:    nop
   ...
   0x0804844f <+67>:    nop
End of assembler dump.

而且,在 之后仍然有这些奇怪的指令ret,让我们将它想象成一个字符串。

(gdb) x /s 0x08048436
0x8048436 <main+42>:     "Hello World!\n"

所以,我们找到了程序的所有部分以及它是如何工作的。

这也通常称为加密包装器我敢肯定,行业中还有其他几个类似的名称。

实际代码不如概念重要。明文代码由负责解码代码主体的解密存根(按执行顺序)预先准备。通过这种方式,主要代码体(在恶意软件的情况下为有效载荷)被加密,因此没有固定字节。在这个例子中,解码器存根本身保持不变,尽管多态是后来的进化,它重新生成编码器和解码器,因此它们也不包含常量字节。通过减少代码副本之间的常量字节数,减少了检测签名的暴露。

解码器存根也可以提供解压缩。

这种机制在自我复制 PC 病毒的早期得到了大量使用。这些被标记为自加密的特性时至今日,一些颠覆性软件仍在使用它。

重要的是,本机代码并不是唯一可以以这种方式“包装”的代码。