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

使用Unicorn Engine绕过混淆完成算法的调用_第1张图片

最近在研究用Unicorn Engine调用SO时,发现网上的资料很少,并且没有一个完整的调用实例。所以我把自己做的发出来跟大家分享,共同学习进步。

下面开始:


一、我们的目的

使用Unicorn Engine绕过混淆完成算法的调用_第2张图片

以上一串字符串中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&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张图片

我之前已经将那3个so(libmctocurl.so、libcupid.so、libmcto_media_player.so) 放到/data/local/tmp下。运行结果与上面的vf字段一致。


三、 使用Unicorn Engine


使用Unicorn Engine绕过混淆完成算法的调用_第4张图片

由于使用了混淆。分析起来比较麻烦,所以使用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&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绕过混淆完成算法的调用_第5张图片

代码已经给了,就不多说了,

我没有直接使用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

更多干货等着你~

你可能感兴趣的:(使用Unicorn Engine绕过混淆完成算法的调用)