使用Unicorn Engine繞過混淆完成演算法的調用
最近在研究用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
"); void *handle = dlopen("/system/lib/libdvm.so", RTLD_LAZY);//RTLD_LAZY RTLD_NOW if(!handle){ printf("[-] dlopen libdvm.so failed!!
"); 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
"); 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!
"); return 0; } typedef char* (*FUN1)(char* plain); void *addr=(void*)(*(int*)((size_t)si+0x8c)+0x249BC9); FUN1 func=(FUN1)addr; if(func==NULL) { printf("cant find func
"); 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
",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 * 1024uint32_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("========================
"); printf("Break on 0x%x
", pc); uint32_t values = 0; uc_reg_read(uc, UC_ARM_REG_R0, &values); printf("R0 = 0x%x
", values); uc_reg_read(uc, UC_ARM_REG_R1, &values); printf("R1 = 0x%x
", values); uc_reg_read(uc, UC_ARM_REG_R2, &values); printf("R2 = 0x%x
", values); uc_reg_read(uc, UC_ARM_REG_R3, &values); printf("R3 = 0x%x
", values); uc_reg_read(uc, UC_ARM_REG_R4, &values); printf("R4 = 0x%x
", values); uc_reg_read(uc, UC_ARM_REG_R5, &values); printf("R5 = 0x%x
", values); uc_reg_read(uc, UC_ARM_REG_R6, &values); printf("R6 = 0x%x
", values); uc_reg_read(uc, UC_ARM_REG_PC, &values); printf("PC = 0x%x
", values); uc_reg_read(uc, UC_ARM_REG_SP, &values); printf("SP = 0x%x
", values); printf("========================
"); }#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
", 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)
", 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
", err); } char buffer[4096] = ""; uc_reg_read(uc, UC_ARM_REG_R0, &r0); uc_mem_read(uc, r0, buffer, 4096); printf("result:%s
", 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 高階黑盒利用
- 挑戰4個任務:迅速上手Unicorn Engine
本文由看雪論壇 scxc 原創 轉載請註明來自看雪社區
熱門閱讀
- 在Driver中調用I/O API的時候你考慮到了嗎?
- 讀取popen輸出結果時未截斷字元串導致的命令行注入
- 『啟發』| 3月7日這一夜,黑客耍了所有人,嗎?
- 編寫 PyKD 調試腳本,自動化地 Sniffer VMware 的 RPC 請求
關注看雪學院公眾號:ikanxue
更多乾貨等著你~
http://weixin.qq.com/r/M3W7oxbE-0uArVLA9yAh (二維碼自動識別)
推薦閱讀:
※從零開始手敲次世代遊戲引擎(四十五)
※包含min函數的棧
※偽·從零開始學演算法 - 1.2 演算法的歷史
※二叉搜索樹的後序遍歷序列
※RSA演算法詳解