使用 AndroidNativeEmu 调用 JNI 函数

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

AndroidNativeEmu

AndroidNativeEmu 专为 Android 原生代码调试和模拟设计,特别关注 JNI 调用和 Android 环境。相比之下,Unicorn 是通用的多平台 CPU 模拟器,适用于更广泛的原生代码分析。

项目地址:https://github.com/AeonLucid/AndroidNativeEmu

支持的功能:

  • 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,我们想在模拟器中模拟它:

复制代码
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 环境中:

复制代码
pip install /path/to/AndroidNativeEmu

在 examples 目录下是 AndroidNativeEmu 的示例代码

example.py

复制代码
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))
相关推荐
驰骋工作流13 小时前
流程时效考核的表结构:从待办时限到 WF_CH 考核结果驰骋BPM驰骋低代码低代码工作流引擎
android·低代码·rxjava
传奇开心果编程14 小时前
【Flutter踩坑排雷】第7课:巨型 build 函数坑|全部 UI 写在同一个 build,维护难、隐性异常
android·flutter·ios·移动开发·跨平台·dart·声明式ui
方白羽18 小时前
Android 双开环境底层剖析与风控检测落地
android·安全·app
weixin_5633714718 小时前
Android Studio 2024 版 Device Explorer 打开方法
android·ide·android studio
AIsoft_868921 小时前
职场音频文件怎么整理?录音APP有哪些用法
android·音视频
booboosui21 小时前
第一批个人 Agent 用户正在卸载,问题不在模型,在它住在哪儿
android
洞见前行21 小时前
点击图标到内容出现,你的 App 在这几秒里做了什么:Android 冷启动全链路优化实战
android·性能优化·android studio
凯基迪科技21 小时前
芯片UID绑定修改 加密与逆向修改思路分析
android
Liben1 天前
Weex开发踩坑记录
android·前端·ios
小向是个Der1 天前
嵌入式进阶——编译与链接
汇编·单片机·学习·编译原理·交叉编译