微信号:ikanxue

介绍:致力于移动与安全研究的开发者社区,看雪学院(kanxue.com)官方微信公众帐号.

使用Unicorn Engine绕过混淆完成算法的调用

2018-03-14 18:00 scxc


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

  • 挑战4个任务:迅速上手Unicorn Engine







本文由看雪论坛 scxc  原创

转载请注明来自看雪社区


热门阅读



点击阅读原文/read,

更多干货等着你~

 
看雪学院 更多文章 使用 Frida 逆向分析 Android 应用与 BLE 设备的通 驱动注入用户线程之跨session通知csrss之真正解决 逆向与利用嵌入式设备:软件栈(第一部分) 读取popen输出结果时未截断字符串导致的命令行注入 在Driver中调用I\/O API的时候你考虑到了吗?
猜您喜欢 去中心化已成大势所趋,手机的中枢地位岌岌可危? 从北京的雾霾说起吧… API 调用次数限制实现 不是技术牛人,如何拿到国内IT巨头的Offer “解放号”大讲堂直播第四期:高性能H5 APP开发云实践