使用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\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一樣方便。
五、參考
挑戰4個任務:迅速上手Unicorn Engine
本文由看雪論壇 scxc 原創 轉載請註明來自看雪社群
相關文章
- powershell程式碼混淆繞過2020-06-21
- Windows下cmd/powershell命令混淆繞過2022-02-05Windows
- 使用HTTP頭進行403繞過 速率繞過 Rate Limit Bypass2024-10-27HTTPMIT
- js繞過-前端加密繞過2021-08-12JS前端加密
- waf 繞過的技巧2020-08-19
- 以"2021強網杯unicorn_like_a_pro"入門unicorn2021-06-21
- 使用sqlmap中tamper指令碼繞過waf2020-08-19SQL指令碼
- html5中呼叫攝像頭拍照並上傳(附繞過https的想法)2019-04-17HTMLHTTP
- WAF繞過之道2018-07-30
- 繞過Snoopy的記錄功能2018-05-07OOP
- 利用基於 NTP 的 TOTP 演算法缺陷繞過 WordPress 登陸驗證2020-08-19演算法
- node中使用C++模組呼叫呼叫speex完成語音檔案壓縮2019-04-17C++
- 在小程式後端中轉獲取介面資料,繞過前端呼叫限制2021-09-09後端前端
- unidbg過混淆過的arm64程式初體驗2021-07-07
- 使用DLL注入繞過“受控制的資料夾訪問”功能2018-10-17
- 使用CBC位元反轉攻擊繞過加密的會話令牌2020-08-19加密會話
- md5繞過2024-04-27
- 併發,繞不過的彎兒2020-04-07
- Windows10 bypassUAC繞過使用者賬戶控制2018-12-27Windows
- android 解碼混淆過的堆疊資訊2019-03-01Android
- sqlmap常用繞過指令碼2020-10-20SQL指令碼
- Sql注入之WAF繞過2024-06-21SQL
- AMSI 淺析及繞過2021-10-27
- 登陸框select繞過2021-05-25
- frida反除錯繞過2023-04-06除錯
- Webscan360的防禦與繞過2020-08-19Web
- 使用Tor繞過防火牆進行遠端匿名訪問2020-08-19防火牆
- Java使用OkHttp庫完成圖形採集的全過程2023-10-19JavaHTTP
- 使用proguard混淆springboot程式碼2019-03-04Spring Boot
- 教你一種繞過谷歌禁止反射的方法2019-04-08谷歌反射
- 關於Ghostscript SAFER沙箱繞過漏洞的分析2018-08-31
- 五種繞過Linux命令別名的方法2018-03-13Linux
- 利用白名單繞過限制的更多測試2020-08-19
- 雙因素認證繞過的釣魚工具2019-06-18
- flowable 繞過idm自帶的身份驗證2022-04-27
- 第五章-WAF 繞過2024-04-06
- 小程式繞過 sign 簽名2024-03-20
- 繞過PowerShell執行策略方法2020-06-20