使用Unicorn Engine繞過混淆完成演算法的呼叫

Editor發表於2018-03-14

最近在研究用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 <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;

}

使用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 <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;

}


使用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


本文由看雪論壇 scxc 原創 轉載請註明來自看雪社群

相關文章