首页
社区
课程
招聘
[原创]使用Unicorn Engine绕过混淆完成算法的调用
2018-3-7 14:55 26895

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

2018-3-7 14:55
26895

最近在研究用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 高阶黑盒利用

[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2018-3-8 13:27 被scxc编辑 ,原因:
上传的附件:
收藏
点赞3
打赏
分享
最新回复 (59)
雪    币: 3936
活跃值: (2013)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
Umiade 2018-3-7 15:05
2
0
这也太厉害了吧
雪    币: 20
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wawal哇 2018-3-7 15:15
3
0
很强大,学习了
雪    币: 33
活跃值: (41)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
irooky 2018-3-7 15:16
4
0
大奔。是我
雪    币: 294
活跃值: (119)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
zfdyq 1 2018-3-7 15:17
5
0
大奔神,跪舔一波
雪    币: 2124
活跃值: (6640)
能力值: ( LV11,RANK:180 )
在线值:
发帖
回帖
粉丝
爱吃菠菜 2 2018-3-7 15:29
6
0
帮顶    完全看不懂
雪    币: 5899
活跃值: (3102)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
MaYil 2018-3-7 15:29
7
0
感谢分享,  这波操作简直了,  666
雪    币: 360
活跃值: (84)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
Caln 2 2018-3-7 16:15
8
0
额  牛皮
雪    币: 337
活跃值: (133)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
地狱怪客 2 2018-3-7 16:34
9
0
大奔哥V5
雪    币: 4195
活跃值: (1821)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
cqzhou 2018-3-7 16:36
10
0
已收藏
雪    币: 35
活跃值: (821)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
繁华皆成空 2018-3-7 16:53
11
0
牛皮
雪    币: 12
活跃值: (163)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
EMSFROG 2018-3-7 17:07
12
0
牛逼啊
雪    币: 268
活跃值: (605)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
lfyyy 2018-3-7 18:29
13
0
牛皮
雪    币: 144
活跃值: (38)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zylyy 2018-3-7 19:40
14
0
666666666666
雪    币: 249
活跃值: (52)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
无所从来 2018-3-7 19:57
15
0
niubility!!!
雪    币: 2872
活跃值: (175)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
长泽雅美 2018-3-7 21:35
16
0
看了两遍,没明白意思
雪    币: 2859
活跃值: (1016)
能力值: ( LV12,RANK:215 )
在线值:
发帖
回帖
粉丝
scxc 3 2018-3-8 13:28
17
0
长泽雅美 看了两遍,没明白意思
大概意思就是不通过逆向完成跨平台调用的问题。
雪    币: 33
活跃值: (759)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
baichisi 2018-3-8 14:21
18
0
学习一下
雪    币: 120
活跃值: (1573)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
jmpews 2018-3-8 15:00
19
0
666  之前想搞个类似的,  这样缺点就是脱离不了unicorn,  之前想的是  ARM  ->  IR  ->  AnyArch(x86)
雪    币: 2859
活跃值: (1016)
能力值: ( LV12,RANK:215 )
在线值:
发帖
回帖
粉丝
scxc 3 2018-3-8 19:11
20
0
jmpews 666 之前想搞个类似的, 这样缺点就是脱离不了unicorn, 之前想的是 ARM -> IR -> AnyArch(x86)
之前不知道有unicorn我自己用capstone解析汇编写了个符号执行的。也想模拟计算出结果。工作量太大了  当时想法太天真了
雪    币: 120
活跃值: (1573)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
jmpews 2018-3-8 20:14
21
0
scxc 之前不知道有unicorn我自己用capstone解析汇编写了个符号执行的。也想模拟计算出结果。工作量太大了 当时想法太天真了
emmmmmm  这可太秀了哇  符号执行也倒是有几个框架,  后面都是  Z3  之类的  Theorem  Prover  ,  比如  barf/triton  .etc

我个人觉得即使这样,  不能直接利用哇,  有一点点鸡肋. 
雪    币: 6818
活跃值: (153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
聖blue 2018-3-8 22:33
22
0
雪    币: 105
活跃值: (379)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
雅鸦歌 2018-3-10 13:18
23
0
收藏!!!
雪    币: 484
活跃值: (872)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
天荒怨未泯 2018-3-12 10:26
24
0
牛逼!
雪    币: 2043
活跃值: (2092)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
gtict 2018-3-12 14:47
25
0
如果要实现算法,,也没有用
游客
登录 | 注册 方可回帖
返回