一、目标

一、目标

Unicorn 是一款非常优秀的跨平台模拟执行框架,该框架可以跨平台执行多种指令集的原生程序。本文介绍如何用Unicorn来执行Android原生的so

二、分析

  1. 先用Android NDK来编译一个so
  2. ida分析

三、代码

我们先用Android NDK 来编译一个so

main.c
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
#include <jni.h>
#include <stdlib.h>

// 用递归方法求n!
int fac(int n)
{
    int f;
    if(n<0)
        printf("n<0,zzx");
    else if(n == 0||n == 1)
        f = 1;
    else
        f = fac(n-1)*n;
    return(f);
}


int main(int argc,char** argv)
{
    int a;
    scanf("%d", &a);
    printf("%d",fac(a));
        return 0;
}

编译之后, 输入4, 返回值是 24。

编译之后的so有arm64-v8a,armeabi-v7a, x86 ,x86_64等平台,我们这里选用armeabi-v7a来执行,用ida打开生成baseso,可以看出这次生成的fac函数在偏移0x674处。

ida
1:facSub

现在我们用unicorn载入baseso,unicorn支持c++,python,go等多种语言,本文使用python

runmu.py
from __future__ import print_function

import logging
import sys

from unicorn import *
from unicorn.arm_const import *

ADDRESS = 0x674         # 开始执行的地址
BASE = 0x00400000       # 代码段地址 #0xaef52000
CODE_SIZE = 8*1024*1024
STACK_ADDR = BASE + CODE_SIZE
STACK_SIZE = 1024 * 1024
PARAM_ADDR = STACK_ADDR + STACK_SIZE
PARAM_SIZE = 1024 * 1024

# Configure logging
logging.basicConfig(
    stream=sys.stdout,
    level=logging.DEBUG,
    format="%(asctime)s %(levelname)7s %(name)34s | %(message)s"
)

logger = logging.getLogger(__name__)

# callback for tracing instructions
def hook_code(uc, address, size, user_data):
    print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size))

    pass

def main():
    print("Emulate ARM code")
    try:
        # 初始化模拟器为 ARCH_ARM 模式
        mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB)

        # 初始化数据内存段
        mu.mem_map(PARAM_ADDR, PARAM_SIZE, UC_PROT_ALL)
        # 初始化代码内存段
        mu.mem_map(BASE, CODE_SIZE, UC_PROT_ALL)
        # 初始化堆栈内存段
        mu.mem_map(STACK_ADDR,STACK_SIZE,UC_PROT_ALL)

        with open('./base.so', 'rb') as fstream:
            data = fstream.read()
            # 把代码数据写入模拟器的代码段
            mu.mem_write(BASE,data)
            # print(len(data))

            # 设置参数为 4
            mu.reg_write(UC_ARM_REG_R0, 0x4)
            # 初始化sp寄存器
            mu.reg_write(UC_ARM_REG_SP, STACK_ADDR)

            # 写入执行完函数之后的 返回地址
            mu.reg_write(UC_ARM_REG_LR, BASE + 0x6A0)

            # 执行每条指令之前调用hook
            mu.hook_add(UC_HOOK_CODE, hook_code)


            # 通过BX Rn指令进行切换THUMB和ARM模式,如下图所示,Rn最低位是1时,通过BX Rn就切换到THUMB状态,为什么可以这样操作,因为不管是THUMB状态(地址2字节对齐)或者是ARM状态(地址四字节对齐)都不允许跳转到一个非对齐的地址,作为地址时Rn的最低位是被忽略的。这样就可以通过BX和Rn的最低有效位来判断跳转时跳到ARM状态(bit0为0),还是THUMB状态(最低有效位为1)
            # 开始运行虚拟CPU,因为是Thumb模式,所以地址的最低位需要置位。
            mu.emu_start(BASE + ADDRESS | 1, BASE + 0x6A0, 0, 0)

            # 执行完之后读取返回值, 这里应该是 24
            r0 = mu.reg_read(UC_ARM_REG_R0)

            print("rc %s" % r0)

    except UcError as e:
        print("ERROR: %s" % e)

    pass

if __name__ == '__main__':
    main()

执行python runmu.py , 结果 rc = 24。

本文代码下载 http://d.91fans.com.cn/unicornone.zip

参考

https://bbs.pediy.com/thread-253868.htm Unicorn 在 Android 的应用

100

关注微信公众号,最新技术干货实时推送

100