Unicorn学习笔记之一

本文介绍了如何利用Unicorn框架在Windows环境下模拟执行Android的armeabi-v7a平台上的本地库函数。通过编写C代码并用NDK编译生成SO文件,然后在IDA中获取函数关键信息,最后使用Unicorn进行模拟运行,验证了模拟结果与原始C程序执行相同。
摘要由CSDN通过智能技术生成

参考资料:

1、unicorn

2、Unicorn 在 Android 的应用

搭配环境:

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、编写对应代码

StartAddress0xF38Func1函数起始地址
EndAddress0x10DCFunc1函数结束地址
image_base0基地址
image_size0x10000 * 8so文件大小,图省事直接往大的写
Inta1R0输入
Outa2R1 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是一个非常裸的模拟器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值