使用 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 小时前
手把手带你玩转Android gRPC:一篇搞定原理、配置与客户端开发
android·前端·grpc
ktl14 小时前
Android 编译加速/优化 80%:一个文件搞定,零侵入零配置
android
alexhilton1 天前
使用FunctionGemma进行设备端函数调用
android·kotlin·android jetpack
冬奇Lab1 天前
InputManagerService:输入事件分发与ANR机制
android·源码阅读
张小潇1 天前
AOSP15 Input专题InputManager源码分析
android·操作系统
RdoZam1 天前
Android-封装基类Activity\Fragment,从0到1记录
android·kotlin
奥陌陌2 天前
android 打印函数调用堆栈
android
用户985120035832 天前
Compose Navigation 3 深度解析(二):基础用法
android·android jetpack
恋猫de小郭2 天前
Android 官方正式官宣 AI 支持 AppFunctions ,Android 官方 MCP 和系统级 OpenClaw 雏形
android·前端·flutter
黄林晴2 天前
Android 17 Beta 2,隐私这把锁又拧紧了
android