参考资料:
1、unicorn
搭配环境:
VS2019
Windows10 64位
python3.7
前言介绍:
Unicorn是一款跨平台的模拟器执行框架,可以辅助我们模拟执行x86、arm等等平台的代码。从而达到调试器的作用,它的实用场景例如ollvm还原、静态脱壳机等等。
1、搭建编译环境
1、首先VS安装该组件
2、搭配好Path环境cmd输入python
3、根据官网介绍安装pip install unicorn
4、创建python项目并且在python环境找得到unicorn则为配置环境成功
2、我们使用NDK编译一个so文件测试
我们写一个最简单不包含任何外部函数的Func1()函数,然后我们用Unicorn直接调用这个Func1函数
Execute.c
#include <stdio.h> #include <stdlib.h> #include <dlfcn.h> //函数指针 int Func1(unsigned int a1, unsigned int a2) { int result; // eax@1 signed int v3; // esi@1 int v4; // zf@2 char v5; // cl@2 unsigned char v6; // dl@2 char v7; // dl@3 char v8; // cl@3 char v9; // cl@5 unsigned char v10; // dl@5 char v11; // dl@6 char v12; // cl@6 char v13; // cl@8 unsigned char v14; // dl@8 char v15; // dl@9 char v16; // cl@9 result = a2; v3 = 0x23; *(unsigned char *)a2 = *(unsigned char *)a1; *(unsigned char *)(a2 + 1) = *(unsigned char *)(a1 + 1); *(unsigned char *)(a2 + 2) = *(unsigned char *)(a1 + 2); *(unsigned char *)(a2 + 3) = *(unsigned char *)(a1 + 3); do { v4 = *(unsigned char *)(a2 + 3) >> 7 == 0; *(unsigned char *)(a2 + 3) *= 2; v5 = *(unsigned char *)(a2 + 2); v6 = *(unsigned char *)(a2 + 2); if (v4) { v7 = v6 >> 7; v8 = 2 * v5; } else { v7 = v6 >> 7; v8 = 2 * v5 + 1; } *(unsigned char *)(a2 + 2) = v8; v4 = v7 == 0; v9 = *(unsigned char *)(a2 + 1); v10 = *(unsigned char *)(a2 + 1); if (v4) { v11 = v10 >> 7; v12 = 2 * v9; } else { v11 = v10 >> 7; v12 = 2 * v9 + 1; } *(unsigned char *)(a2 + 1) = v12; v4 = v11 == 0; v13 = *(unsigned char *)a2; v14 = *(unsigned char *)a2; if (v4) { v15 = v14 >> 7; v16 = 2 * v13; } else { v15 = v14 >> 7; v16 = 2 * v13 + 1; } *(unsigned char *)a2 = v16; if (v15) { *(unsigned char *)a2 ^= 0xBu; *(unsigned char *)(a2 + 1) ^= 0x16u; *(unsigned char *)(a2 + 2) ^= 0x21u; *(unsigned char *)(a2 + 3) ^= 0x2Cu; } --v3; } while (v3 > 0); return result; } int main(int n_argc, char** argv) { unsigned char Inta1[0x100] = { 0xdb,0x34,0xc6,0x13}; unsigned char Outa2[0x100] = { 0 }; Func1((unsigned int)Inta1,(unsigned int)Outa2); return 0; } |
Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := Execute LOCAL_SRC_FILES := Execute.c # BUILD_EXECUTABLE指明生成可执行的二进制文件 include $(BUILD_SHARED_LIBRARY) |
Application.mk
APP_ABI := armeabi-v7a x86 mips APP_PLATFORM := android-19 APP_STL := stlport_shared APP_OPTIM := debug |
然后ndk-build生成so文件
我们使用的是armeabi-v7a
3、IDA收集相关信息
获取重要数据:
起始地址:0xF38
结束地址:0x10DE
输入内容a1 = R0
输出结果a2 = R1 或则 R0
4、编写对应代码
StartAddress | 0xF38 | Func1函数起始地址 |
EndAddress | 0x10DC | Func1函数结束地址 |
image_base | 0 | 基地址 |
image_size | 0x10000 * 8 | so文件大小,图省事直接往大的写 |
Inta1 | R0 | 输入 |
Outa2 | R1 or R0 | 输出 |
代码如下:
#1、首先导入头文件 from unicorn import * from unicorn.arm_const import * import binascii import logging import sys #基础数据 StartAddress = 0xF38 #Func1函数起始地址 EndAddress = 0x10DC #Func1函数结束地址 image_base = 0 #基地址 image_size = 0x10000 * 8 #so文件大小,图省事直接往大的写 # Configure logging logging.basicConfig( stream=sys.stdout, level=logging.DEBUG, format="%(asctime)s %(levelname)7s %(name)34s | %(message)s" ) logger = logging.getLogger(__name__) def hook_code(uc, address, size, userdata): print (">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size)) try: a1 = b'\xdb\x34\xc6\x13' print("模拟so文件例子") #2、初始化模拟器为 ARCH_ARM 模式 mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB) #3、初始化数据内存段 mu.mem_map(image_base, image_size) #4、加载需要模拟的so文件 binary = open('libExecute.so','rb').read() mu.mem_write(image_base, binary) #5、创建一个堆栈,因为该函数中堆栈中使用数据,局部变量都是在堆栈中操作的 stack_base = 0xa0000 stack_size = 0x10000 * 3 stack_top = stack_base + stack_size - 0x4 mu.mem_map(stack_base, stack_size) mu.reg_write(UC_ARM_REG_SP, stack_top) #6、初始化数据段 data_base = 0xf0000 data_size = 0x10000 * 3 mu.mem_map(data_base, data_size) #6、1 给Inta1赋初始值 mu.mem_write(data_base, a1) #6、2 然后将数据传递给R0 mu.reg_write(UC_ARM_REG_R0, data_base) #7、启动 target = image_base + StartAddress target_end = image_base + EndAddress mu.emu_start(target + 1, target_end) #7、1 最终计算出的结果保存在R0 r1 = mu.reg_read(UC_ARM_REG_R0) result = mu.mem_read(r1, 4) #7、2 打印结果 print(binascii.b2a_hex(result)) except UcError as e: print(e) |
最终结果和我们C语言跑出来的是一样的
5、总结
我们这里例子中是没有任何导出函数的纯算法,非常简单的一个例子。假设我们遇到API函数memcpy、print等等则会模拟失败,因为unicorn是一个非常裸的模拟器。