首页
社区
课程
招聘
[原创] 关于代码混淆以及垃圾代码的处理,第一篇(寄存器混淆)
2018-10-22 21:08 10006

[原创] 关于代码混淆以及垃圾代码的处理,第一篇(寄存器混淆)

2018-10-22 21:08
10006
最近一直在研究VMP,为了研究VMP的入口、handler、出口代码 不得不去看VMP混淆代码。其实VMP的混淆代码其实手工去除混淆代码其实也是很简单的,主要是量的问题。虽然去除混淆代码很简单,但是你如果不去除这些混淆代码会极大的阻碍我们的分析。
这里我们主要说两种混淆,也是VMP用的最多的混淆。

其实论坛上面也有很多说代码混淆、以及如果解决混淆的方法,但是大部分都说的模板匹配。
我们主要说一下关于寄存器、堆栈的反混淆和清除垃圾代码的通用方法。
本人比较菜,有什么说的不对的地方。狠狠的打脸。

序言:其实混淆代码就是对代码进行各种变形,插入垃圾指令来影响分析者的直观分析。插入的垃圾代码不会对结果值产生任何影响,所以可以认为如果删除这行代码或者删除这段代码不会对结果值有任何影响,那么这行(段)代码就是垃圾代码。

一、关于寄存器的混淆和反混淆


1. 寄存器混淆的简单介绍

下面这段汇编代码的 第1行和第4行对edi进行了赋值的操作,但是在两次对edi赋值之间没有把edi的值传递给其他任何对象或者把edi作为一个访问对象。那其实第1行对edi的赋值操作是没有任何意义的,所以我们认为 mov edi,0x10 不会影响到最终的运行结果, 所以这行代码是一行垃圾代码。

mov edi,0x10
mov ebx,0x30
push ebx
mov edi,0x20


下面这段代码相对于上面这串代码仅仅加了一行。
第3行 mov edx,edi 把edi的值赋值给了edx, 使用了edi的值。 所以我们就不能认为第1行的mov edi,0x10是一句垃圾代码了。

mov edi,0x10
mov ebx,0x30
mov edx,edi
push ebx
mov edi,0x20

下面这段代码 
第1-4行 对edi的值进行了一系列的运算
第5行  mov ecx,[esp + edi], edi的值当作了一个栈偏移来访问。所以就认为 第1-4行 对edi的运算操作是有效的代码。
第6-8行 对edi的值进行了一系列的运算
第9行 mov edi,0x20 直接对edx的值进行了覆盖。在此之间没有把edi的值传递给其他寄存器、写入内存或者作为运算访问。所以 6-8行的代码都是一串垃圾代码。

mov edi,0x10
mov eax,0x30
add edi,eax
shr edi,4
mov ecx,[esp + edi]
add  edi,20
shr edi,4
dec edi
mov edi,0x20

最后在举一个例子
下面这段代码的前5行和上例前5行是一样的代码,只不过我们在第6行加入了一行 mov ecx,0x40
上例说到第5行代码对edi进行了访问操作,所以1-4行对edi的操作是一个有效的操作。
第5行代码是 mov ecx,[esp + edi] 的结果值是写到ecx当中。
第6行代码又对ecx的值进行了覆盖,那么其实第5行 mov ecx,[esp + edi] 对ecx赋值操作不会对ecx的结果值产生任何影响,所以第5行的代码就变成了垃圾代码了。由于第5行的代码是垃圾代码不对结果值有影响,所以这里对edi的访问也就没任何意义,也就不能证明1-4行是有效代码了。
mov edi,0x10
mov eax,0x30
add edi,eax
shr edi,4
mov ecx,[esp + edi]
mov ecx,0x40
add  edi,20
shr edi,4
dec edi
mov edi,0x20

表达能力有限.上面这个例子说的可能有点乱,我用C语言来阐述一下把....
可以看出来var_4和var_2最终结果值是在最后两行赋值的。
前面所有对var_4和var_2的操作并不会影响最终的结果值,即使删除也没有任何关系。

int var_1, var_2, var_3, var_4;
int arr_1[30] = { 0 };
var_1 = 10;
var_2 = 30;
var_2 = var_2 + var_1;
var_2 = var_2 >> 4;
var_4 = arr_1[var_2];
var_4 = 0x40;
var_2 = 0x20;


通过上面4个简单的例子其实我们已经可以得出来一些结论:

       1. 如果一行代码对寄存器的值进行覆盖,那么之前对这个寄存器的值进行的任何运算、赋值的代码都是垃圾代码,直到遇到对这个寄存器进行访问操作、传递给其他寄存器、写入内存或者作为运算访问的代码为止(前提是这行代码不是垃圾代码)。

       2.如果一行有效代码对某个寄存器进行访问操作、传递给其他寄存器、写入内存。那么之前对这个寄存器的值进行的任何运算、赋值的代码都是有效代码,直到对这个寄存器进行值覆盖的操作。


通过以上两个结论我们就可以自己写代码去除垃圾代码.



我们这里使用的是 capstone.lib 引擎,以逆向的思路从下往上分析代码。

比如下面这段代码我们从 mov edx,0x20 开始向上分析之前对edi的操作是否有效。

不知道大家是否这样,我手工去除混淆代码的时候都是从下往上分析的。


mov edi,0x10
mov ebx,0x30
push ebx
mov edi,0x20

其实在处理寄存器优化之前还有对 CALL/JMP 这些指令的处理。这里就不贴出来了,大家有兴趣可以下载代码。虽然写的比较丑陋 !-_-


// 寄存器优化
void AntiConfuse::reg_opt()
{
	uint64_t address = 0x1000;
	std::ofstream ofs2("clear_jump.asm", std::ios::out | std::ios::trunc);
	auto count = cs_disasm(handle, process_code.data(), process_code.size(), address, 0, &insn);
	init_reg_access_info();

	for (int i = 0; i < count; ++i)
	{
		ofs2 << insn[i].mnemonic << "\t" << insn[i].op_str << std::endl;
	}

	for (int t = count - 1; t >= 0; t--) // 从下往上分析代码
	{
		process_one_code(handle, &insn[t], t); 
	}

	cs_free(insn, count);
}

init_reg_access_info 函数初始化默认的寄存器访问权限。由于我们是从下往上开始分析的默认对寄存器的写入操作和修改操作都当作有效代码,因为他们都会影响这段代码最终的结果值。

void AntiConfuse::init_reg_access_info()
{
	reg_access_info_.clear();

	add_access_flag("eax", CS_AC_WRITE); 
	add_access_flag("eax", CS_AC_WRITE | CS_AC_READ); 

	add_access_flag("ebx", CS_AC_WRITE); 
	add_access_flag("ebx", CS_AC_WRITE | CS_AC_READ); 

	add_access_flag("ecx", CS_AC_WRITE); 
	add_access_flag("ecx", CS_AC_WRITE | CS_AC_READ); 

	add_access_flag("edx", CS_AC_WRITE); 
	add_access_flag("edx", CS_AC_WRITE | CS_AC_READ); 

	add_access_flag("edi", CS_AC_WRITE); 
	add_access_flag("edi", CS_AC_WRITE | CS_AC_READ); 

	add_access_flag("esi", CS_AC_WRITE); 
	add_access_flag("esi", CS_AC_WRITE | CS_AC_READ); 

	add_access_flag("ebp", CS_AC_WRITE); 
	add_access_flag("ebp", CS_AC_WRITE | CS_AC_READ); 

	std::cout << reg_access_info_.size() << std::endl;
}

去寄存器混淆的代码都在process_one_code这个函数中,相对来说代码是比较简单的。但是很有效果!


bool AntiConfuse::process_one_code(csh handle, cs_insn *insn, uint32_t line)
{
        // 对一些特殊的指令进行特殊执行, 比如XCHG之类的...这里我偷懒还没写完,不过没遇到这个指令就不影响
	switch (insn->id) 
	{
	case X86_INS_RET: 
		line_valid_[line] = true;
		return true;
	}

	line_valid_[line] = false; // line_valid_指定对应行数的代码是否有效 默认是无效的行

	auto x86 = &(insn->detail->x86);
        
        // 或者一条指令的操作数 比如 mov eax, [esp + 0x10]
        // 就会又两个操作数 一个是 eax, 一个是[esp + 0x10]
        // 这个循环主要根据 寄存器的写入权限来判断,这个指令是否有效
	for (int i = 0; i < x86->op_count; i++) 
	{
		cs_x86_op *op = &(x86->operands[i]);

		switch (op->access) {
		case CS_AC_WRITE:
		case CS_AC_READ | CS_AC_WRITE: 

			// 如果是写入内存 我们判定为有效的 判定所有对内存写入值的代码都是有效的,关于栈的内存要写代码特殊处理一下
			if (X86_OP_MEM == (int)op->type)
			{
				line_valid_[line] = true;
				set_memory_access(handle, op);
				break;
			}

			std::string reg_name = cs_reg_name(handle, op->reg);
			reg_name = map_reg_name(reg_name);

			if (has_access_flag(reg_name, op->access)) {
                                // 如果对应的寄存器有对应的权限,那么认为这行代码是有效的代码
                                // 才可以去分析这条指令所访问的一些寄存器,否则这条指令如果是垃圾指令继续分析也没有意义
				line_valid_[line] = true; 
                                 // 如果说只有写入说明是SET操作,相当于 mov eax,0x20, pop eax这种对寄存器的值进行覆盖的操作
                                 // 根据我们的上面总结的结论就是一旦对一个值进行赋值操作
                                // 那么前面所有对这个寄存器的操作都是无效的,直到对这个寄存器进行访问操作。
                                // 所以这里我们遇到了值覆盖操作,就清除这个寄存器的所有的权限

				if (op->access == CS_AC_WRITE) { 					
                                    clear_access_flag(reg_name);
				}

			}

			break;
		}
	}
        
        // 根据上面写入操作的结果来判断是否要继续分析 这条指令所访问的寄存器
        // 如果这条指令的写入操作是无效的,那么就可以直接返回
	if (false == line_valid_[line]) {
		return false;
	}
        
        // 这里是处理这条这里访问的操作数
	for (int i = 0; i < x86->op_count; i++)
	{
		cs_x86_op *op = &(x86->operands[i]);

		if (CS_AC_READ != op->access) 
		{
			continue;
		}

		// 如果是写入内存 我们判定为有效的
		if (X86_OP_MEM == (int)op->type)
		{
			set_memory_access(handle, op);
			continue;
		} 
		else if (X86_OP_REG == (int)op->type)
		{
                        // 这里代表访问了某个寄存器
                        // 在根据文章中说的第二个结论
                        // 只要对某个寄存器进行访问操作,那么之前所有的对这个寄存器进行写入或者修改的代码都是有效的代码
                        // 所以这里我们给寄存器加上了 修改和赋值的权限
			std::string reg_name = cs_reg_name(handle, op->reg);
			reg_name = map_reg_name(reg_name);
			clear_access_flag(reg_name);
			add_access_flag(reg_name, CS_AC_WRITE);
			add_access_flag(reg_name, CS_AC_WRITE | CS_AC_READ);
			continue;
		}

	}

	return true;
}

就这几个简单的函数跑完,所有关于寄存器的垃圾代码就全部消失了。


可以运行一下程序 输入test.asm 这个文件在工程目录下。

这个文件就是汇编字节指令文件,里面的内容是VMP的虚拟机入口代码。



处理完毕后会生成 

1. org_asm.asm 原始的汇编指令

2. clear_jump.asm 清除CALL/JMP之后的汇编指令

3. clear_reg.asm 清除了 寄存器混淆和堆栈混淆的代码


下面是VMP 虚拟机入口没去混淆之前代码在org_asm.asm中


push	ebp
mov	ebp, esp
sub	esp, 0xc0
push	ebx
push	esi
push	edi
lea	edi, [ebp - 0xc0]
mov	ecx, 0x30
mov	eax, 0xcccccccc
rep stosd	dword ptr es:[edi], eax
mov	esi, esp
push	0x1276b30
jmp	0x7d212
call	0xfffce0ef
mov	byte ptr [esp], 0x2e
pushfd	
jmp	0x5c9e3
mov	dword ptr [esp + 4], 0x470b69
jmp	0xfffd12c8
mov	word ptr [esp], ax
jmp	0xfffc2111
mov	dword ptr [esp], 0x574407c3
mov	dword ptr [esp], 0x5bb80365
pushfd	
lea	esp, [esp + 4]
jmp	0xfffd7765
call	0xa17
mov	dword ptr [esp], ebx
pushfd	
call	0x2473
pushfd	
mov	dword ptr [esp + 8], ebp
jmp	0x13ff
pushfd	
pop	dword ptr [esp + 4]
pushal	
mov	dword ptr [esp + 0x20], eax
call	0x674
pushal	
push	esi
pushfd	
mov	dword ptr [esp + 0x48], esi
push	0x96946d46
bswap	si
movsx	si, al
mov	dword ptr [esp + 0x48], edi
movzx	si, al
call	0xffffffbe
movsx	esi, cl
lea	esi, [eax - 0x6bdf482a]
mov	dword ptr [esp + 0x48], ecx
pushfd	
mov	dword ptr [esp + 0x48], edx
lea	esi, [eax - 0x1dabd533]
lea	esi, [ecx + 0x4a01d49e]
call	0xa8d
jmp	0x20e6
mov	dword ptr [esp + 0x48], ecx
pop	esi
mov	word ptr [esp + 4], cx
push	dword ptr [0x127cf79]
pop	dword ptr [esp + 0x40]
bswap	si
mov	dword ptr [esp + 0x3c], 0xe60000
movsx	si, al
pop	esi
push	edx
pop	esi
mov	esi, dword ptr [esp + 0x68]
pushfd	
push	0x153008d3
push	dword ptr [esp]
lea	esp, [esp + 0x44]
jmp	0x2b5a
pushal	
xchg	al, dh
pushal	
not	esi
inc	bl
jmp	0xbd
call	0x1a2d
pop	ebx
dec	esi
bswap	bp
neg	esi
sal	bp, 0xf
lea	ebp, [esp + 0x40]
rol	edi, 1
cmp	al, 0x9d
call	0x152e
sub	esp, 0x7c
cmc	
mov	edi, esp
sar	bh, cl
xadd	dl, bh
or	ebx, 0x2971f122
jmp	0xffffe91b
mov	ebx, esi
lea	edx, [ebx + 0x65526054]
setl	al
rcr	al, 1
bts	edx, edx
add	esi, dword ptr [ebp]
cmp	ah, dh
movzx	dx, al
mov	al, byte ptr [esi - 1]
dec	edx
lea	edx, [ebx + 0x65476f3c]
xor	al, bl
neg	edx
neg	al
cmc	
add	dl, cl
cmc	
add	al, 0xcb
ror	dh, cl
call	0x1390
ror	al, 6
shr	dl, cl
xor	bl, al
not	dh
movzx	dx, cl
call	0x1175
call	0x3877
movzx	eax, al
bswap	dx
mov	edx, dword ptr [eax*4 + 0x1280f10]
bt	di, 3
cmc	
ror	edx, 5
mov	word ptr [esp], 0x905
jmp	0x1173
call	0x2de
jmp	0x601
dec	esi
pushfd	
clc	
cmc	
pushal	
add	edx, 0xe60000
pushfd	
mov	dword ptr [esp + 0x34], edx
mov	byte ptr [esp + 4], bl
mov	byte ptr [esp + 0x10], 0x39
mov	word ptr [esp], ax
push	dword ptr [esp + 0x34]
ret	0x38


这里是用程序处理过寄存器混淆、堆栈混淆的代码,清除之后的指令都是有效指令。


push	ebp
mov	ebp, esp
sub	esp, 0xc0
push	ebx
push	esi
push	edi
lea	edi, [ebp - 0xc0]
mov	ecx, 0x30
mov	eax, 0xcccccccc
rep stosd	dword ptr es:[edi], eax
mov	esi, esp
push	0x1276b30
sub	esp, 4
pushfd	
mov	dword ptr [esp + 4], 0x470b69
mov	dword ptr [esp], 0x5bb80365
sub	esp, 4
mov	dword ptr [esp], ebx
pushfd	
sub	esp, 4
pushfd	
mov	dword ptr [esp + 8], ebp
pushfd	
pop	dword ptr [esp + 4]
pushal	
mov	dword ptr [esp + 0x20], eax
mov	dword ptr [esp + 0x1c], esi
mov	dword ptr [esp + 0x18], edi
mov	dword ptr [esp + 0x14], ecx
mov	dword ptr [esp + 0x10], edx
mov	dword ptr [esp + 0xc], ecx
push	dword ptr [0x127cf79]
pop	dword ptr [esp + 8]
mov	dword ptr [esp + 4], 0xe60000
mov	esi, dword ptr [esp + 0x34]
push	0x153008d3
lea	esp, [esp + 8]
pushal	
pushal	
not	esi
dec	esi
neg	esi
lea	ebp, [esp + 0x40]
sub	esp, 4
sub	esp, 0x7c
mov	edi, esp
mov	ebx, esi
add	esi, dword ptr [ebp]
mov	al, byte ptr [esi - 1]
xor	al, bl
neg	al
add	al, 0xcb
sub	esp, 4
ror	al, 6
xor	bl, al
movzx	eax, al
mov	edx, dword ptr [eax*4 + 0x1280f10]
ror	edx, 5
dec	esi
add	edx, 0xe60000
mov	dword ptr [esp], edx
push	dword ptr [esp]
ret	4

虽然还有pushfd pushad这些指令,但是这些指令也都是有效的。不过也可以继续简化指令。

后续的简化其实很简单,不过这篇可能没时间说了。本来想在这篇也把堆栈混淆也说一下,时间有限只能放在下一篇去说了。

代码中很多细节没在意...

附件密码是 bbs.pediy.com



[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2018-10-24 08:25 被菜鸟级X编辑 ,原因:
上传的附件:
收藏
点赞5
打赏
分享
最新回复 (11)
雪    币: 222
活跃值: (599)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
xxdisasm 2018-10-23 00:27
2
0
顶一下,简单又复杂的东西。看着简单,处理复杂。
雪    币: 2391
活跃值: (294)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
菜鸟级X 2 2018-10-23 08:20
3
0
是啊,去混淆是必要的一步
雪    币: 5485
活跃值: (3247)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
尐进 2018-10-23 10:24
4
0
支持一下 写的很好 持续关注
雪    币: 3451
活跃值: (2827)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
StriveMario 2018-10-23 14:17
5
0
很厉害呀.....
雪    币: 3667
活跃值: (509)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
逻辑错误 1 2018-10-23 16:40
6
0
***********
最后于 2019-2-1 10:10 被kanxue编辑 ,原因:
雪    币: 1008
活跃值: (22)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
菟菟吖 2018-10-23 20:15
7
0
楼主,第三图上面的那句话有点错误吧,前面1-4行是有效代码,怎么后面写着3-5行是垃圾代码啊
雪    币: 2391
活跃值: (294)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
菜鸟级X 2 2018-10-24 08:23
8
0
不好意思 我马上修正一下
雪    币: 2391
活跃值: (294)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
菜鸟级X 2 2018-10-24 08:25
9
0
℡陌上烟雨 楼主,第三图上面的那句话有点错误吧,前面1-4行是有效代码,怎么后面写着3-5行是垃圾代码啊
应该是6-8行是辣鸡代码
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
幽灵船长 2018-10-25 09:58
10
0
写的真好,顶一下
雪    币: 486
活跃值: (583)
能力值: ( LV12,RANK:238 )
在线值:
发帖
回帖
粉丝
scpczc 1 2020-12-23 17:02
11
0
写的真好
雪    币: 8
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_punpkihu 2022-11-16 19:21
12
0
厉害 
游客
登录 | 注册 方可回帖
返回