使用Unicorn Engine繞過混淆完成算法的調用

使用Unicorn Engine繞過混淆完成算法的調用

最近在研究用Unicorn Engine調用SO時,發現網上的資料很少,並且沒有一個完整的調用實例。所以我把自己做的發出來跟大家分享,共同學習進步。

下面開始:

一、我們的目的

使用Unicorn Engine繞過混淆完成算法的調用

以上一串字符串中vf字段為標紅部分的signature。該算法在libmcto_media_player.so+0x249BC8處。如果是Android端調用的話很簡單,我們編寫一個loader調用該函數傳入參數獲取返回值即可輕易拿到。但如果你想在Windows或linux上獲取該signature就會比較麻煩。一般都是通過逆向還原代碼來進行移植。但是如果遇見混淆或VM的代碼,那將是痛苦的。所以這就是我為什麼要介紹Unicorn

Engine的原因了。我們要用Unicorn Engine來完成跨平臺的調用。

二、 用NDK編寫loader用做驗證用

#include

#include

#include

#include

#include

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&class="lazy" data-original=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;

}

使用Unicorn Engine繞過混淆完成算法的調用

我之前已經將那3個so(libmctocurl.so、libcupid.so、libmcto_media_player.so) 放到/data/local/tmp下。運行結果與上面的vf字段一致。

三、 使用Unicorn Engine

使用Unicorn Engine繞過混淆完成算法的調用

由於使用了混淆。分析起來比較麻煩,所以使用Unicorn進行調用

#include "stdafx.h"

#include

#include

#include

#include

#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&class="lazy" data-original=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;

}

使用Unicorn Engine繞過混淆完成算法的調用

代碼已經給了,就不多說了,

我沒有直接使用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 高階黑盒利用

挑戰4個任務:迅速上手Unicorn Engine

熱門閱讀

在Driver中調用I/O API的時候你考慮到了嗎?

讀取popen輸出結果時未截斷字符串導致的命令行注入

『啟發』| 3月7日這一夜,黑客耍了所有人,嗎?

編寫 PyKD 調試腳本,自動化地 Sniffer VMware 的 RPC 請求

更多幹貨等著你~


分享到:


相關文章: