使用 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))
相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui