首页
论坛
专栏
课程

[原创]使用Unicorn Engine绕过混淆完成算法的调用

scxc
1
2018-3-7 14:55 7347

最近在研究用Unicorn Engine调用SO时,发现网上的资料很少,并且没有一个完整的调用实例。所以我把自己做的发出来跟大家分享,共同学习进步。

下面开始: 

一、我们的目的

    
以上一串字符串中vf字段为标红部分的signature。该算法在libmcto_media_player.so+0x249BC8处。如果是Android端调用的话很简单,我们编写一个loader调用该函数传入参数获取返回值即可轻易拿到。但如果你想在Windows或linux上获取该signature就会比较麻烦。一般都是通过逆向还原代码来进行移植。但是如果遇见混淆或VM的代码,那将是痛苦的。所以这就是我为什么要介绍Unicorn Engine的原因了。我们要用Unicorn Engine来完成跨平台的调用。


二、 用NDK编写loader用做验证用。

       
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
#include <jni.h>
#include <stdlib.h>


int main(int argc,char** argv)
{

	JavaVM* vm;
	JNIEnv* env;
	jint res;
	
	JavaVMInitArgs vm_args;
	JavaVMOption options[1];
	options[0].optionString = "-Djava.class.path=.";
	vm_args.version=0x00010002;
	vm_args.options=options;
	vm_args.nOptions =1;
	vm_args.ignoreUnrecognized=JNI_TRUE;
	
	
	printf("[+] dlopen libdvm.so\n");
	void *handle = dlopen("/system/lib/libdvm.so", RTLD_LAZY);//RTLD_LAZY RTLD_NOW
	if(!handle){
	printf("[-] dlopen libdvm.so failed!!\n");
	return 0;
	}

	typedef int (*JNI_CreateJavaVM_Type)(JavaVM**, JNIEnv**, void*);
	JNI_CreateJavaVM_Type JNI_CreateJavaVM_Func = (JNI_CreateJavaVM_Type)dlsym(handle, "JNI_CreateJavaVM");
	if(!JNI_CreateJavaVM_Func){
	printf("[-] dlsym failed\n");
	return 0;
	}
	res=JNI_CreateJavaVM_Func(&vm,&env,&vm_args);
        //libmctocurl.so   libcupid.so 为libmcto_media_player.so的依赖库
	dlopen("/data/local/tmp/libmctocurl.so",RTLD_LAZY);
	dlopen("/data/local/tmp/libcupid.so",RTLD_LAZY);
	void* si=dlopen("/data/local/tmp/libmcto_media_player.so",RTLD_LAZY);
	if(si == NULL)
	{
		printf("dlopen err!\n");
		return 0;
	}

	typedef char* (*FUN1)(char* plain);
    void *addr=(void*)(*(int*)((size_t)si+0x8c)+0x249BC9);
	FUN1 func=(FUN1)addr;
	if(func==NULL)
	{
		printf("can't find  func\n");
		return 0;
	}
   
	char *plain="/vps?tvid=11949478009&vid=7b23569cbed511dd58bcd6ce9ddd7b42&v=0&qypid=11949478009_unknown&src=02022001010000000000&tm=1519712402&k_tag=1&k_uid=359125052784388&bid=1&pt=0&d=1&s=0&rs=1&dfp=1413357b5efa4a4130b327995c377ebb38fbd916698ed95a28f56939e9d8825592&k_ver=9.0.0&k_ft1=859834543&k_err_retries=0&qd_v=1";
	char* ret=func(plain);
	printf("%s\n",ret);
	return 0;
}

我之前已经将那3个so(libmctocurl.so、libcupid.so、libmcto_media_player.so) 放到/data/local/tmp下。运行结果与上面的vf字段一致。

三、 使用Unicorn Engine


由于使用了混淆。分析起来比较麻烦,所以使用Unicorn进行调用
#include "stdafx.h"
#include <inttypes.h>
#include <string.h>
#include <math.h>
#include <unicorn/unicorn.h>
#pragma comment(lib,"unicorn.lib")
//#define DEBUG
#define _DWORD uint32_t
#define LODWORD(x)  (*((_DWORD*)&(x)))
#define HIDWORD(x)  (*((_DWORD*)&(x)+1))
#define ADDRESS 0x249BC8
#define BASE  0xaef52000
#define CODE_SIZE  8*1024*1024
#define STACK_ADDR  BASE+CODE_SIZE
#define STACK_SIZE  1024 * 1024
#define PARAM_ADDR  STACK_ADDR+STACK_SIZE
#define PARAM_SIZE  1024 * 1024
uint32_t offset=0;
static uint32_t create_mem(uc_engine *uc,char* buffer,uint32_t len)
{
	uint32_t addr = PARAM_ADDR + offset;
	uc_mem_write(uc, addr, buffer, len);
	offset += len + 1;
	return addr;
}

static void print_reg(uc_engine *uc, uint32_t address)
{
#ifdef DEBUG
	uint32_t pc = 0;
	uc_reg_read(uc, UC_ARM_REG_PC, &pc);
	if (pc == address)
	{
		printf("========================\n");        printf("Break on 0x%x\n", pc);
		uint32_t values = 0;
		uc_reg_read(uc, UC_ARM_REG_R0, &values);        printf("R0 = 0x%x \n", values);
		uc_reg_read(uc, UC_ARM_REG_R1, &values);        printf("R1 = 0x%x \n", values);
		uc_reg_read(uc, UC_ARM_REG_R2, &values);        printf("R2 = 0x%x \n", values);
		uc_reg_read(uc, UC_ARM_REG_R3, &values);        printf("R3 = 0x%x \n", values);
		uc_reg_read(uc, UC_ARM_REG_R4, &values);        printf("R4 = 0x%x \n", values);
		uc_reg_read(uc, UC_ARM_REG_R5, &values);        printf("R5 = 0x%x \n", values);
		uc_reg_read(uc, UC_ARM_REG_R6, &values);        printf("R6 = 0x%x \n", values);
		uc_reg_read(uc, UC_ARM_REG_PC, &values);        printf("PC = 0x%x \n", values);
		uc_reg_read(uc, UC_ARM_REG_SP, &values);        printf("SP = 0x%x \n", values);
		printf("========================\n");
	}
#endif // DEBUG
}
static void hook_code(uc_engine *uc, uint64_t address, uint32_t size, void *user_data)
{
#ifdef DEBUG
	printf(">>> Tracing instruction at 0x%" PRIx64 ", instruction size = 0x%x\n", address, size);
#endif // DEBUG
	switch (address)
	{
		//strlen
		case BASE + 0x249BEE:
		{
			uint32_t r0 = 0;
			char buffer[4096] = "";
			uc_reg_read(uc, UC_ARM_REG_R0, &r0);
			uc_mem_read(uc, r0, buffer, 4096);
			r0 = strlen(buffer);
			uc_reg_write(uc, UC_ARM_REG_R0, &r0);
			uint32_t pc = address;
			pc += 5;
			uc_reg_write(uc, UC_ARM_REG_PC, &pc);
			break;
		}
		//malloc
		case BASE+ 0x249f3c:
		case BASE+ 0x249f06:
		case BASE + 0x249c02:
		{
			uint32_t r0 = 0;
			uc_reg_read(uc, UC_ARM_REG_R0, &r0);
			char* buffer = (char*)malloc(r0);
			r0=create_mem(uc, buffer, r0);
			free(buffer);
			uc_reg_write(uc, UC_ARM_REG_R0, &r0);
			uint32_t pc = address;
			pc += 5;
			uc_reg_write(uc, UC_ARM_REG_PC, &pc);
			break;
		}
		//memcpy 后为THUMB指令
		case BASE+0x249c68:
		case BASE+0x249c0e:
		case BASE+0x24947A:
		case BASE+0x249456:
		{
			uint32_t r0 = 0;
			uint32_t r1 = 0;
			uint32_t r2 = 0;
			uc_reg_read(uc, UC_ARM_REG_R0, &r0);
			uc_reg_read(uc, UC_ARM_REG_R1, &r1);
			uc_reg_read(uc, UC_ARM_REG_R2, &r2);
			char *buffer =(char*)malloc(r2);
			uc_mem_read(uc, r1, buffer, r2);
			uc_mem_write(uc, r0, buffer, r2);
			free(buffer);
			uint32_t pc = address;
			//memcpy 后为ARM指令
			if (address == BASE + 0x249c68)
				pc += 4;
			else
				pc += 5;
			uc_reg_write(uc, UC_ARM_REG_PC, &pc);
			break;
		}
		//特殊处理4字ARM指令
		case BASE + 0x249C6C:
		{
			uint32_t pc = address;
			pc += 5;
			uint32_t r0 = 0x2c0;
			uc_reg_write(uc, UC_ARM_REG_R0, &r0);
			uc_reg_write(uc, UC_ARM_REG_PC, &pc);
			break;
		}
		//跳过stack_guard错误的内存地址
		case BASE + 0x249BD8:
		{
			uint32_t pc = address;
			pc += 7;
			uc_reg_write(uc, UC_ARM_REG_PC, &pc);
			break;
		}
		//sin函数
		case BASE+0x249EE8:
		{
			uint32_t r0 = 0;
			uint32_t r1 = 0;
			uc_reg_read(uc, UC_ARM_REG_R0, &r0);
			uc_reg_read(uc, UC_ARM_REG_R1, &r1);
			double value = 0;
			memcpy(&value, &r0, 4);
			memcpy((char*)&value+4, &r1, 4);
			double ret=sin(value);
			r0 = LODWORD(ret);
			r1 = HIDWORD(ret);
			uc_reg_write(uc, UC_ARM_REG_R0, &r0);
			uc_reg_write(uc, UC_ARM_REG_R1, &r1);
			uint32_t pc = address;
			pc += 5;
			uc_reg_write(uc, UC_ARM_REG_PC, &pc);
			break;
		}
		//free
		case BASE+ 0x24a68c:
		case BASE+0x249f24:
		{
			uint32_t pc = address;
			pc += 5;
			uc_reg_write(uc, UC_ARM_REG_PC, &pc);
		}	
		default:
		{
			print_reg(uc, address);
			break;
		}
	}
}
static unsigned char* read_file(char* path, uint32_t* len)
{
	FILE* fp = fopen(path, "rb");
	if (fp == NULL)
		return nullptr;
	fseek(fp, 0, SEEK_END);
	*len = ftell(fp);
	fseek(fp, 0, SEEK_SET);
	unsigned char* code = (unsigned char*)malloc(*len);
	memset(code, 0, *len);
	fread(code, 1, *len, fp);
	fclose(fp);
	return code;
}
static void test_thumb(void)
{
	uc_engine *uc;
	uc_err err;
	uc_hook trace1, trace2;
	uint32_t sp = STACK_ADDR; 
	offset = 0;
	err = uc_open(UC_ARCH_ARM, UC_MODE_THUMB, &uc);
	if (err) {
		printf("Failed on uc_open() with error returned: %u (%s)\n",
			err, uc_strerror(err));
		return;
	}
	char plain[] = "/vps?tvid=11949478009&vid=7b23569cbed511dd58bcd6ce9ddd7b42&v=0&qypid=11949478009_unknown&src=02022001010000000000&tm=1519712402&k_tag=1&k_uid=359125052784388&bid=1&pt=0&d=1&s=0&rs=1&dfp=1413357b5efa4a4130b327995c377ebb38fbd916698ed95a28f56939e9d8825592&k_ver=9.0.0&k_ft1=859834543&k_err_retries=0&qd_v=1";
	uc_mem_map(uc, PARAM_ADDR, PARAM_SIZE, UC_PROT_ALL);
	uc_mem_map(uc, BASE, CODE_SIZE, UC_PROT_ALL);
	uint32_t r0 = PARAM_ADDR;
	uint32_t sp_start = sp + STACK_SIZE;
	int ret=uc_mem_map(uc, sp, sp_start - sp, UC_PROT_ALL);
	uint32_t len = 0;
	unsigned char* code = read_file("./aef52000_36e000.so", &len);
	uc_mem_write(uc, BASE, code, len);
	free(code);
	create_mem(uc, plain, strlen(plain) + 1);
	uc_reg_write(uc, UC_ARM_REG_R0, &r0);
	uc_reg_write(uc, UC_ARM_REG_SP, &sp);
	uc_hook_add(uc, &trace2, UC_HOOK_CODE, hook_code, NULL, 1, 0);
	err = uc_emu_start(uc, BASE + 0x249BC8 + 1, BASE + 0x24a692, 0, 0);
	if (err) {
		printf("Failed on uc_emu_start() with error returned: %u\n", err);
	}
	char buffer[4096] = "";
	uc_reg_read(uc, UC_ARM_REG_R0, &r0);
	uc_mem_read(uc, r0, buffer, 4096);
	printf("result:%s\n", buffer);
	uc_close(uc);
}
int main()
{
	test_thumb();
	system("pause");
    return 0;
}


代码已经给了,就不多说了,

我没有直接使用libmcto_media_player.so因为data段需要重定位。所以我写了一个dump工具。

将SO从内存中dump出来。直接调用这段已经重定位过的内存。

修复内存报错的位置。实现该算法中涉及的几个API 包括 strlen memcpy malloc free  sin 函数。
主要就是注意BLX调用完API的时候下一条指令是THUMB模式还是ARM模式就好。
最后运行,运行结果也与vf字段一致。
dump通过命令
shell@hammerhead:/data/local/tmp $./dump ./libmcto_media_player.so ./libmctocurl.so ./libcupid.so
[+] dlopen ./libmctocurl.so
[+] dlopen ./libcupid.so
[+] dlopen libdvm.so
[+] save 0xaf009000_0x377000.so

四、总结

       这只是一个简单的算法函数,涉及的API并不多,如果是复杂的算法涉及API数量庞大这种自己实现API的方式就并不可取。所以接下来有时间会继续研究SO的完整的调用。让他像loader一样方便。

五、参考

Android SO 高阶黑盒利用


[防守篇]2018看雪.TSRC CTF 挑战赛(团队赛)11月1日征题开启!

最后于 2018-3-8 13:27 被scxc编辑 ,原因:
上传的附件:
最新回复 (47)
Umiade 2018-3-7 15:05
2

0

这也太厉害了吧
wawal哇 2018-3-7 15:15
3

0

很强大,学习了
irooky 2018-3-7 15:16
4

0

大奔。是我
zfdyq 1 2018-3-7 15:17
5

0

大奔神,跪舔一波
爱吃菠菜 1 2018-3-7 15:29
6

0

帮顶    完全看不懂
MaYil 2018-3-7 15:29
7

0

感谢分享,  这波操作简直了,  666
Caln 2 2018-3-7 16:15
8

0

额  牛皮
地狱怪客 2 2018-3-7 16:34
9

0

大奔哥V5
cqzhou 2018-3-7 16:36
10

0

已收藏
繁华皆成空 2018-3-7 16:53
11

0

牛皮
EMSFROG 2018-3-7 17:07
12

0

牛逼啊
lfyyy 2018-3-7 18:29
13

0

牛皮
zylyy 2018-3-7 19:40
14

0

666666666666
无所从来 2018-3-7 19:57
15

0

niubility!!!
长泽雅美 2018-3-7 21:35
16

0

看了两遍,没明白意思
scxc 1 2018-3-8 13:28
17

0

长泽雅美 看了两遍,没明白意思
大概意思就是不通过逆向完成跨平台调用的问题。
baichisi 2018-3-8 14:21
18

0

学习一下
jmpews 2018-3-8 15:00
19

0

666  之前想搞个类似的,  这样缺点就是脱离不了unicorn,  之前想的是  ARM  ->  IR  ->  AnyArch(x86)
scxc 1 2018-3-8 19:11
20

0

jmpews 666 之前想搞个类似的, 这样缺点就是脱离不了unicorn, 之前想的是 ARM -> IR -> AnyArch(x86)
之前不知道有unicorn我自己用capstone解析汇编写了个符号执行的。也想模拟计算出结果。工作量太大了  当时想法太天真了
jmpews 2018-3-8 20:14
21

0

scxc 之前不知道有unicorn我自己用capstone解析汇编写了个符号执行的。也想模拟计算出结果。工作量太大了 当时想法太天真了
emmmmmm  这可太秀了哇  符号执行也倒是有几个框架,  后面都是  Z3  之类的  Theorem  Prover  ,  比如  barf/triton  .etc

我个人觉得即使这样,  不能直接利用哇,  有一点点鸡肋. 
聖blue 2018-3-8 22:33
22

0

雅鸦歌 2018-3-10 13:18
23

0

收藏!!!
天荒怨未泯 2018-3-12 10:26
24

0

牛逼!
gtict 2018-3-12 14:47
25

0

如果要实现算法,,也没有用
奔跑的阿狸 1 2018-3-12 17:43
26

0

无名侠 8 2018-3-14 19:05
27

0

楼主写得很详细了!学习一下。
saloyun 2018-3-14 20:33
28

0

牛逼,mark一下。
十年寒窗 2018-3-14 21:29
29

0

赞,mark。
貌似Android里边还有包名md5验证,有一个参与vf md5运算的串混淆的比较厉害(有点像OLLVM),后来发现是固定的,不知道现在有没有更新,有兴趣多交流 
最后于 2018-3-15 10:07 被十年寒窗编辑 ,原因:
Youngs 2018-3-15 10:36
30

0

社会我大奔哥!!膜拜一波!
scxc 1 2018-3-15 18:18
31

0

十年寒窗 赞,mark。貌似Android里边还有包名md5验证,有一个参与vf md5运算的串混淆的比较厉害(有点像OLLVM),后来发现是固定的,不知道现在有没有更新,有兴趣多交流&nbsp;
那个MD5是对输入的串的MD5  后面的都混淆了
空白即是正义 2018-3-15 18:36
32

0

厉害了  mark一下
naville 1 2018-3-17 11:20
33

0

scxc 之前不知道有unicorn我自己用capstone解析汇编写了个符号执行的。也想模拟计算出结果。工作量太大了 当时想法太天真了
我记得在哪看到篇paper说这个东西实质等同Halting  Problem,不关注这块。
另外LLVM  IR也不是真正意义上的跨平台所以基于此还得再做一层转义。

不搞这块不太清楚,op这个思路我也只是实现过指令级的模拟而已
lofrank 2018-3-17 19:24
34

0

竟然还有  Unicorn  Engine  这么好的东西,感谢楼主
yaneng 2018-3-22 23:00
35

0

牛掰,逆向人员的福音,安全防护的地狱!
smartdon 1 2018-3-28 21:46
36

0

学习了
超级嗯哼 2018-4-4 11:26
37

0

只能膜拜了
kakasasa 2018-5-26 16:47
38

0

mark,好厉害
追风燕子 1 2018-5-26 16:52
39

0

这个厉害了
zaimongli 2018-5-26 18:06
40

0


Unicorn  Engine可是神器啊

JoyFei 2 2018-7-2 19:52
41

0

Unicorn Engine,最近正在看,感谢分享
wuaiwu 3 2018-7-17 10:47
42

0

不错,跑过ios的算法代码,问题还是很多
Lemonr 2018-9-14 14:12
43

0

Unicorn Engine 学习了
MsScotch 2018-9-14 16:43
44

0

mark
chmlqw 2018-10-10 20:30
45

0

试了一下,有个问题请教下LZ。
  err = uc_emu_start(uc, BASE + 0x249BC8 + 1, BASE + 0x24a692, 0, 0);
这里函数结束的偏移位置是0024A6A8                 POP             {R4-R7,PC}, 但是为什么提前0x24a692就结束了(当然结果都一样)
如果运行到0024A6A8就会提示内存出错
tigerwood 2018-10-25 13:54
46

0

大牛,art模式下的loader怎么写,应该参考源码的那一部分,谢谢,我现在使用android8.1.0
scxc 1 2018-10-25 15:16
47

0

tigerwood 大牛,art模式下的loader怎么写,应该参考源码的那一部分,谢谢,我现在使用android8.1.0
你参考init进程启动过程如何创建JavaVM就行了
tigerwood 2018-10-29 09:25
48

0

收到,谢谢大牛
返回