使用 AndroidNativeEmu 调用 JNI 函数

版权归作者所有,如有转发,请注明文章出处: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 的示例代码

example.py

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))
相关推荐
2501_916008894 小时前
Web 前端开发常用工具推荐与团队实践分享
android·前端·ios·小程序·uni-app·iphone·webview
我科绝伦(Huanhuan Zhou)4 小时前
MySQL一键升级脚本(5.7-8.0)
android·mysql·adb
怪兽20145 小时前
Android View, SurfaceView, GLSurfaceView 的区别
android·面试
龚礼鹏6 小时前
android 图像显示框架二——流程分析
android
消失的旧时光-19436 小时前
kmp需要技能
android·设计模式·kotlin
帅得不敢出门7 小时前
Linux服务器编译android报no space left on device导致失败的定位解决
android·linux·服务器
雨白8 小时前
协程间的通信管道 —— Kotlin Channel 详解
android·kotlin
TimeFine9 小时前
kotlin协程 容易被忽视的CompletableDeferred
android
czhc114007566311 小时前
Linux1023 mysql 修改密码等
android·mysql·adb
GOATLong12 小时前
MySQL内置函数
android·数据库·c++·vscode·mysql