版权归作者所有,如有转发,请注明文章出处:cyrus-studio.github.io/blog/
AndroidNativeEmu
AndroidNativeEmu 专为 Android 原生代码调试和模拟设计,特别关注 JNI 调用和 Android 环境。相比之下,Unicorn 是通用的多平台 CPU 模拟器,适用于更广泛的原生代码分析。
项目地址:github.com/AeonLucid/A...
支持的功能:
-
elf 文件解析及 so 加载
-
栈支持
-
内存管理
-
文件系统
-
JNI 支持
-
常见 syacall 模拟支持
-
...
但是,暂时不支持 arm64。
系统调用
AndroidNativeEmu 系统调用的模拟实现都在 cpu/intertupt_handler.py,根据不同的 intno 调用具体的 handler
目前已经实现的系统调用可以在 syscall_hooks.py 看到
JNIEnv
对应 JNI 接口的模拟实现在 src/androidemu/java/jni_env.py,通过 write_function_table 方法模拟实现 JNI 函数表
jni 函数代码是通过 keystone 来生成的,具体代码在:src/androidemu/hooker.py
so 加载过程中,对 so 中导入符号的 Hook,具体代码在:src/androidemu/native/hooks.py
AndroidNativeEmu 默认已经实现的外部函数
native_method 装饰器
native_method 装饰器用于在 AndroidNativeEmu 中将 Python 函数标记为模拟的 JNI 原生方法,允许函数在模拟器中执行并与 Android 的原生方法交互。
假设我们有一个 C++ 函数 strlen,我们想在模拟器中模拟它:
python
from androidemu.emulator import Emulator
from androidemu.java.helpers.native_method import native_method
# 初始化模拟器
emulator = Emulator()
# 使用 native_method 装饰器标记 Python 函数为模拟的本地方法
@native_method
def strlen(mu, address):
content = memory_helpers.read_utf8(mu, address)
length = len(content)
return length
# 在 strlen 符号上安装钩子,执行自定义逻辑
emulator.modules.add_symbol_hook('strlen',emulator.hooker.write_function(strlen) + 1)
当使用 native_method 装饰器标记 Python 方法时,框架会自动处理方法参数和返回值的类型转换。
androidemu 维护了一个类型映射表,将常见的 JNI 类型与 Python 数据类型进行对应。具体实现在 native_translate_arg 方法中。
在方法调用返回时,会自动判断返回值是否 JNI 类型并进行查表和类型转换返回真实的值,否则直接返回寄存器中的值。
examples
通过以下命令将项目安装到当前 python 环境中:
bash
pip install /path/to/AndroidNativeEmu
在 examples 目录下是 AndroidNativeEmu 的示例代码
ini
import logging
import sys
from unicorn import UC_HOOK_CODE
from unicorn.arm_const import *
from androidemu.emulator import Emulator
# 配置日志记录
logging.basicConfig(
stream=sys.stdout, # 输出到标准输出
level=logging.DEBUG, # 日志级别为调试(DEBUG)
format="%(asctime)s %(levelname)7s %(name)34s | %(message)s" # 日志格式
)
# 创建日志记录器
logger = logging.getLogger(__name__)
# 初始化模拟器,启用浮点指令集
emulator = Emulator(vfp_inst_set=True)
# 加载 libc.so,禁用初始化(do_init=False)
emulator.load_library("example_binaries/32/libc.so", do_init=False)
# 加载 libnative-lib.so
lib_module = emulator.load_library("example_binaries/32/libnative-lib.so", do_init=False)
# 显示加载的模块信息
logger.info("Loaded modules:")
# 遍历所有已加载的模块并打印其基地址和文件名
for module in emulator.modules:
logger.info("[0x%x] %s" % (module.base, module.filename))
# 定义调试钩子函数,跟踪代码执行
def hook_code(uc, address, size, user_data):
# 读取当前地址的指令
instruction = uc.mem_read(address, size)
# 将指令转换为十六进制格式的字符串
instruction_str = ''.join('{:02x} '.format(x) for x in instruction)
# 打印出当前的指令地址、大小和指令内容
print('# Tracing instruction at 0x%x, instruction size = 0x%x, instruction = %s' % (address, size, instruction_str))
# 将调试钩子添加到模拟器
emulator.uc.hook_add(UC_HOOK_CODE, hook_code)
# 调用 "libnative-lib.so" 中的 "_Z4testv" 方法
emulator.call_symbol(lib_module, '_Z4testv')
# 或者通过地址调用
emulator.call_native(lib_module.base+0x000007C4+1)
# 输出返回的字符串长度("strlen" 返回值存在于 R0 寄存器中)
print("String length is: %i" % emulator.uc.reg_read(UC_ARM_REG_R0))