Android PLT hook 与 Inline hook

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

前言

Android Native 函数 Hook 技术是一种在应用运行时拦截或替换系统或自身函数行为的手段,常见实现包括 PLT Hook、Inline Hook。

PLT Hook 和 Inline Hook 是两种不同层次和机制的函数 Hook 技术,常用于逆向工程、安全分析、壳保护或热修复等场景。

PLT hook 和 Inline hook 有什么区别?

特性PLT Hook(符号级)Inline Hook(指令级)
Hook 层级链接层(.plt 或 GOT)指令层(函数入口指令)
Hook 对象动态库导出的函数任意函数(不一定导出)
修改内容修改函数地址指针修改指令流(如跳转指令)
Hook 粒度通常是库间调用可以精确到任意函数、库内调用
稳定性较高稍差(依赖指令结构、架构)
跨平台兼容性更好架构相关(ARM32/ARM64)

PLT Hook 原理(也叫 GOT Hook)

ELF 中动态链接时使用 PLT(Procedure Linkage Table) 和 GOT(Global Offset Table);

程序调用外部函数(如 malloc、fopen)时,实际上是跳到 GOT 表中的地址;

可以通过修改 GOT 表,将目标函数地址替换为你自己的函数。

✅优点:

  • 修改一次,全局生效(所有调用者都被 hook)

  • 不需要写复杂汇编指令

❌ 缺点:

  • 只能 Hook 动态链接函数(如 libc.so 中的函数)

  • 无法 Hook 静态链接或内部函数调用

Inline Hook 原理

直接修改目标函数的前几条机器指令(通常是函数入口),替换成跳转指令(如 b <new_func> 或 ldr pc, [addr]);

原函数内容被破坏,因此会备份原始指令并在新函数中支持“回调原函数”(trampoline)。

✅ 优点:

  • 可以 Hook 几乎任意函数(导出或非导出、静态或动态)

  • 精细控制,适合保护/加壳/代码注入等底层用途

❌ 缺点:

  • 对 CPU 架构高度依赖(ARM64、ARMv7)

  • 对汇编、内存保护、缓存等有要求(必须关闭写保护)

  • 稳定性较低,不当使用可能 crash

举个例子

假设目标函数是 malloc(),我们希望 Hook 它:

PLT Hook 方式:

  • 找到 .got 中 malloc 的地址

  • 改成我们自己的函数地址

void* my_malloc(size_t size) {
    printf("Intercepted malloc(%zu)\n", size);
    return real_malloc(size); // 调用原函数
}

Inline Hook 方式:

  • 找到 malloc 函数地址,直接改入口 4~8 字节为跳转指令

  • 替换后的入口可能变成:

b my_malloc  ; ARM

App 中函数 Hook 是否会影响其他应用?

是否互相影响,取决于 Hook 的层级 和 Hook 方式,以下是详细分析:

关键点:Android 的进程隔离机制

在 Android 中,每个 App 通常运行在自己独立的进程(除非使用 android:sharedUserId 和特定签名),所以:

  • Java 层 Hook(如 Xposed):是 per-process(每个进程生效),不同 App 之间互不影响

  • Native 层 Hook(如 inline/PLT):也通常在当前进程生效

  • 系统全局 Hook(如内核级 syscall hook):才可能影响所有进程,包括其他 App

示例分析

情况 1:两个 App 分别使用 inline hook hook 自己的 libc.so 的 open()

✅ 各自 Hook 的是自己进程中的 open

❌ 不会互相影响

情况 2:你 Hook 了 system_server 的某个系统服务(如 ActivityManager)

✅ 如果你有权限(如 Riru、Zygisk 模块),就能影响整个系统服务,其他 App 可能间接受影响

情况 3:你在内核或 system-wide 层 hook 了 syscall(比如使用 LKM hook read())

❗ 会影响整个系统,包括所有 App

🧨 风险大,容易被检测或引发稳定性问题

ShadowHook

ShadowHook 是一个 Android inline hook 库,它支持 thumb、arm32 和 arm64。

ShadowHook 现在被用于 TikTok,抖音,今日头条,西瓜视频,飞书中。

开源地址:https://github.com/bytedance/android-inline-hook

文档:https://github.com/bytedance/android-inline-hook/blob/main/README.zh-CN.md

ShadowHook 手册:https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.zh-CN.md

运行 ShadowHook

首先把 ShadowHook 源码 clone 到本地并导入到 Android Studio。

点击运行,提示找不到 CMake ‘3.30.5’

[CXX1300] CMake '3.30.5' was not found in SDK, PATH, or by cmake.dir property.
Affected Modules: app, shadowhook, systest

打开 Android SDK 配置

  • 点击右下角 Show Package Details。

  • 找到并勾选 CMake 3.30.5。

  • 点击 Apply → 自动下载并安装。

word/media/image1.png

也可以用命令行工具安装:

cd D:\App\android\sdk\cmdline-tools\latest\bin

./sdkmanager "cmake;3.30.5"

Warning: Observed package id 'system-images;android-34;lineage;arm64-v8a' in inconsistent location 'D:\App\android\sdk\system-images\android-34\google_apis_playstore\arm64-v8a' (Expected 'D:\App\android\sdk\system-images\android-34\lineage\arm64-v8a')
Warning: Observed package id 'system-images;android-34;lineage;x86_64' in inconsistent location 'D:\App\android\sdk\system-images\android-34\google_apis_playstore\x86_64' (Expected 'D:\App\android\sdk\system-images\android-34\lineage\x86_64')
Warning: Observed package id 'system-images;android-34;lineage;arm64-v8a' in inconsistent location 'D:\App\android\sdk\system-images\android-34\google_apis_playstore\arm64-v8a' (Expected 'D:\App\android\sdk\system-images\android-34\lineage\arm64-v8a')
Warning: Observed package id 'system-images;android-34;lineage;x86_64' in inconsistent location 'D:\App\android\sdk\system-images\android-34\google_apis_playstore\x86_64' (Expected 'D:\App\android\sdk\system-images\android-34\lineage\x86_64')
[=======================================] 100% Unzipping... share/vim/vimfiles/s

需要下载对应版本的 ndk

word/media/image2.png

word/media/image3.png

再次运行,提示如下错误

Execution failed for task ':shadowhook:compileDebugJavaWithJavac'.
> Java compiler version 21 has removed support for compiling with source/target version 7.
  Try one of the following options:
      1. [Recommended] Use Java toolchain with a lower language version
      2. Set a higher source/target version
      3. Use a lower version of the JDK running the build (if you're not using Java toolchain)
  For more details on how to configure these settings, see https://developer.android.com/build/jdks.

Set Java Toolchain to 11
Change Java language level and jvmTarget to 11 in all modules if using a lower level.
Pick a different compatibility level...
Pick a different JDK to run Gradle...
More information...

这个错误的意思是你使用的是 JDK 21,而你的项目设置了 sourceCompatibility = 1.7(也就是 Java 7),但 JDK 21 已经移除了对 Java 7 的编译支持。

设置 Java Toolchain 为 Java 11

word/media/image4.png

编译运行成功!

word/media/image5.png

集成 ShadowHook

启用 Android 的  Prefab  功能,这是用于支持 C/C++ 依赖包管理 的功能。

android {
    buildFeatures {
        prefab = true
    }
}

在 gradle/libs.versions.toml 增加 shadowhook

[versions]
shadowhook = "x.y.z"

[libraries]
shadowhook = { group = "com.bytedance.android", name = "shadowhook", version.ref = "shadowhook" }

x.y.z 请替换成版本号,建议使用最新的 release 版本。

在 build.gradle.kts 中增加依赖

dependencies {
    implementation(libs.shadowhook)
}

参考:ShadowHook 手册

初始化 ShadowHook

// 初始化 ShadowHook
ShadowHook.init(
    ConfigBuilder()
        // 设置 hook 模式:
        // UNIQUE 模式:同一个 hook 点只能被 hook 一次(unhook 后可以再次 hook)。
        // SHARED 模式:可对同一个 hook 点并发执行多个 hook 和 unhook,彼此互不干扰。
        .setMode(ShadowHook.Mode.UNIQUE)

        // 启用调试日志,方便开发阶段查看 hook 的行为
        .setDebuggable(true)

        // 启用 hook 记录功能,可以记录每一次 hook 的详细信息,开发调试有用(建议发布时设为 false)
        .setRecordable(true)

        // 构建配置对象
        .build()
)

CMakeLists.txt 配置

add_library( # 设置库的名称
        cyrus_studio_hook

        # 设置库的类型
        SHARED

        # 设置源文件路径
        cyrus_studio_hook.cpp
)

target_link_libraries(
        cyrus_studio_hook
        # 链接 log 库
        ${log-lib}
        # 链接 shadowhook
        shadowhook::shadowhook
)

hook execve 函数,禁用 dex2oat

通过 hook dex2oat 调用来禁用 dex2oat,防止 OAT 文件生成(避免反编译、加壳保护)

目标函数:execve(当执行 /system/bin/dex2oat)

wayne:/ # cd /system/bin/
wayne:/system/bin # ls -hl | grep dex2oat
lrwxr-xr-x 1 root   shell      37 2009-01-01 08:00 dex2oat -> /apex/com.android.runtime/bin/dex2oat

execve 函数源码:

#include <unistd.h>
#include "syscall.h"

int execve(const char *path, char *const argv[], char *const envp[])
{
/* do we need to use environ if envp is null? */
return syscall(SYS_execve, path, argv, envp);
}

https://cs.android.com/android/platform/superproject/+/android10-release:bionic/libc/include/unistd.h;l=95

https://cs.android.com/android/platform/superproject/main/+/main:external/musl/src/process/execve.c

dex2oat 是 Android ART 在安装 APK 或运行时优化 dex 时调用的工具,通常通过 execve 执行。我们可以 Hook 掉这个 execve,当它的第一个参数包含 dex2oat 时直接返回一个错误码(如 -1),从而实现 跳过 dex2oat 执行。

cyrus_studio_hook.cpp

#include <unistd.h>
#include <android/log.h>
#include <jni.h>
#include <string>
#include <stddef.h>
#include "shadowhook.h"

#define LOG_TAG "cyrus_studio_hook"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)


// 原始 execve 函数指针
int (*orig_execve)(const char *__file, char *const *__argv, char *const *__envp);

// 替代 execve 实现
int my_execve(const char *__file, char *const *__argv, char *const *__envp) {

    LOGI("execve called: %s", __file);

    if (__file && strstr(__file, "dex2oat")) {
        LOGW("Blocked dex2oat execution: %s", __file);
        // 返回失败,模拟 dex2oat 调用失败
        return -1;
    }

    // 调用原始 execve
    return orig_execve(__file, __argv, __envp);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_cyrus_example_hook_CyrusStudioHook_hookExecve(JNIEnv *, jclass) {
    void *handle = shadowhook_hook_sym_name(
            "libc.so",  // 函数所在模块
            "execve", // 要 hook 的符号名
            reinterpret_cast<void *>(my_execve),
            reinterpret_cast<void **>(&orig_execve)
    );

    if (handle != nullptr) {
        LOGI("Successfully hooked execve");
    } else {
        LOGW("Failed to hook execve");
    }
}

kotlin 代码如下:

package com.cyrus.example.hook

import com.bytedance.shadowhook.ShadowHook
import com.bytedance.shadowhook.ShadowHook.ConfigBuilder

object CyrusStudioHook {

    init {
        System.loadLibrary("cyrus_studio_hook") // 加载 native 实现
    }

    fun init(){
        // 初始化 ShadowHook
        ShadowHook.init(
            ConfigBuilder()
                // 设置 hook 模式:
                // UNIQUE 模式:同一个 hook 点只能被 hook 一次(unhook 后可以再次 hook)。
                // SHARED 模式:可对同一个 hook 点并发执行多个 hook 和 unhook,彼此互不干扰。
                .setMode(ShadowHook.Mode.UNIQUE)

                // 启用调试日志,方便开发阶段查看 hook 的行为
                .setDebuggable(true)

                // 启用 hook 记录功能,可以记录每一次 hook 的详细信息,开发调试有用(建议发布时设为 false)
                .setRecordable(true)

                // 构建配置对象
                .build()
        )
    }

    @JvmStatic
    external fun hookExecve()
}

执行 hookExecve() 方法,日志输出如下:

2025-05-07 00:14:51.790 21783-21783 shadowhook_tag          com.cyrus.example                    I  shadowhook: hook_sym_name(libc.so, execve, 0x7a178a9874) ...
2025-05-07 00:14:51.790 21783-21783 shadowhook_tag          com.cyrus.example                    I  exit: alloc out library, exit 7aaa27b020, pc 7aabaa5f70, distance 182af50, range [-8000000, 7fffffc]
2025-05-07 00:14:51.790 21783-21783 shadowhook_tag          com.cyrus.example                    I  a64 rewrite: type 0, inst d2801ba8
2025-05-07 00:14:51.790 21783-21783 shadowhook_tag          com.cyrus.example                    I  a64: hook (WITH EXIT) OK. target 7aabaa5f70 -> exit 7aaa27b020 -> new 7a178a9874 -> enter 7aaa2e7200 -> remaining 7aabaa5f74
2025-05-07 00:14:51.790 21783-21783 shadowhook_tag          com.cyrus.example                    I  switch: hook in UNIQUE mode OK: target_addr 7aabaa5f70, new_addr 7a178a9874
2025-05-07 00:14:51.791 21783-21783 shadowhook_tag          com.cyrus.example                    I  shadowhook: hook_sym_name(libc.so, execve, 0x7a178a9874) OK. return: 0x7aacaa2a80. 0 - OK
2025-05-07 00:14:51.791 21783-21783 cyrus_studio_hook       com.cyrus.example                    I  Successfully hooked execve

Successfully hooked execve

ClassLinker::LoadMethod

ClassLinker::LoadMethod 是 ART 中负责将 dex 文件中的方法信息解析并填充到 ArtMethod 结构体中的关键函数,用于方法加载过程中的元数据初始化。

找到导出符号

ART 使用 Clang/LLVM 编译,符号导出受 C++ 名字修饰(name mangling)影响:

  • 不同版本可能使用不同的 Clang 版本;

  • 编译参数变化(如 -fvisibility=hidden)会改变导出符号行为。

C 函数没有 Name Mangler 的概念,所以 execve 函数的符号就是函数名字;但 ClassLinker::LoadMethod 是 C++ 函数,如果要实现更好的 Android 版本兼容性,需要自己处理函数符号名的差异。

1、找到 libart.so

wayne:/ # find /system -iname 'libart.so'
/system/apex/com.android.runtime.release/lib64/libart.so

2、把 libart.so pull 下来

 adb pull /system/apex/com.android.runtime.release/lib64/libart.so

3、使用 nm 确认符号名

cyrus@cyrus:/mnt/d$ nm -D libart.so | grep LoadMethod
000000000016b168 T _ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_13ClassAccessor6MethodENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE

得到 LoadMethod 方法的符号如下:

_ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_13ClassAccessor6MethodENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE

hook ClassLinker::LoadMethod

ClassLinker::LoadMethod 函数原型如下:

void ClassLinker::LoadMethod(const DexFile& dex_file,
                             const ClassAccessor::Method& method,
                             Handle<mirror::Class> klass,
                             ArtMethod* dst) 

https://cs.android.com/android/platform/superproject/+/android10-release:art/runtime/class_linker.cc;l=3732

声明一个函数指针变量,存放原 ClassLinker::LoadMethod 函数,由于访问不了系统中的类,用 void * (通用指针)代替。

void *(*orig_LoadMethod)(void *, void *, void *, void *, void *);

注意:实际上有 5 个参数,第一个是 ClassLinker 对象。

自定义替换的函数

void *my_LoadMethod(void *linker, void *dex_file, void *method, void *klass_handle, void *dst) {

    ...
    
    // 调用原始函数
    void *result = orig_LoadMethod(linker, dex_file, method, klass_handle, dst);

    ...

    return result;
}

根据导出符号 hook ClassLinker::LoadMethod 函数

void *handle = shadowhook_hook_sym_name(
        "libart.so",
        "_ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_13ClassAccessor6MethodENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE", // 要 hook 的符号名
        reinterpret_cast<void *>(my_LoadMethod),
        reinterpret_cast<void **>(&orig_LoadMethod)
);

if (handle != nullptr) {
    LOGI("Successfully hooked LoadMethod");
} else {
    LOGW("Failed to hook LoadMethod");
}

访问 DexFile

如果你想访问 DexFile 和 ArtMethod 的内容(比如打印 base、size 或 method_index),你需要将这些 void* 转换回真实的类型指针。

我们可以根据 android 源码 在工程中增加 DexFile 的结构定义,通过 namespace 区分不同版本的 DexFile

https://cs.android.com/android/platform/superproject/+/android-9.0.0_r60:art/libdexfile/dex/dex_file.h;l=1042

https://cs.android.com/android/platform/superproject/+/android-15.0.0_r9:art/libdexfile/dex/dex_file.h;l=920

#ifndef CYURS_DEX_FILE_H
#define CYURS_DEX_FILE_H

#include <stdint.h>
#include <string>

namespace cyurs {
    namespace dex {
        struct Header {
        public:
            uint8_t magic_[8];
            uint32_t checksum_;  // See also location_checksum_
            uint8_t signature_[20];
            uint32_t file_size_;  // size of entire file
            uint32_t header_size_;  // offset to start of next section
            uint32_t endian_tag_;
            uint32_t link_size_;  // unused
            uint32_t link_off_;  // unused
            uint32_t map_off_;  // unused
            uint32_t string_ids_size_;  // number of StringIds
            uint32_t string_ids_off_;  // file offset of StringIds array
            uint32_t type_ids_size_;  // number of TypeIds, we don't support more than 65535
            uint32_t type_ids_off_;  // file offset of TypeIds array
            uint32_t proto_ids_size_;  // number of ProtoIds, we don't support more than 65535
            uint32_t proto_ids_off_;  // file offset of ProtoIds array
            uint32_t field_ids_size_;  // number of FieldIds
            uint32_t field_ids_off_;  // file offset of FieldIds array
            uint32_t method_ids_size_;  // number of MethodIds
            uint32_t method_ids_off_;  // file offset of MethodIds array
            uint32_t class_defs_size_;  // number of ClassDefs
            uint32_t class_defs_off_;  // file offset of ClassDef array
            uint32_t data_size_;  // unused
            uint32_t data_off_;  // unused
        };

        struct MapItem {
            uint16_t type_;
            uint16_t unused_;
            uint32_t size_;
            uint32_t offset_;
        };

        struct MapList {
            uint32_t size_;
            MapItem list_[1];
        };

        // Raw string_id_item.
        struct StringId {
            uint32_t string_data_off_;  // offset in bytes from the base address
        };
        // Raw type_id_item.
        struct TypeId {
            uint32_t descriptor_idx_;  // index into string_ids
        };
        // Raw field_id_item.
        struct FieldId {
            uint16_t class_idx_;  // index into type_ids_ array for defining class
            uint16_t type_idx_;  // index into type_ids_ array for field type
            uint32_t name_idx_;  // index into string_ids_ array for field name
        };
        // Raw method_id_item.
        struct MethodId {
            uint16_t class_idx_;  // index into type_ids_ array for defining class
            uint16_t proto_idx_;  // index into proto_ids_ array for method prototype
            uint32_t name_idx_;  // index into string_ids_ array for method name

        };
        // Raw proto_id_item.
        struct ProtoId {
            uint32_t shorty_idx_;  // index into string_ids array for shorty descriptor
            uint16_t return_type_idx_;  // index into type_ids array for return type
            uint16_t pad_;             // padding = 0
            uint32_t parameters_off_;  // file offset to type_list for parameter types
        };
        // Raw class_def_item.
        struct ClassDef {
        public:
            uint32_t class_idx_;  // index into type_ids_ array for this class
            uint32_t access_flags_;
            uint32_t superclass_idx_;  // index into type_ids_ array for superclass
            uint32_t interfaces_off_;  // file offset to TypeList
            uint32_t source_file_idx_;  // index into string_ids_ for source file name
            uint32_t annotations_off_;  // file offset to annotations_directory_item
            uint32_t class_data_off_;  // file offset to class_data_item
            uint32_t static_values_off_;  // file offset to EncodedArray
        };
        // Raw code_item.
        struct CodeItem {
        public:
            uint16_t registers_size_;            // the number of registers used by this code
            //   (locals + parameters)
            uint16_t ins_size_;                  // the number of words of incoming arguments to the method
            //   that this code is for
            uint16_t outs_size_;                 // the number of words of outgoing argument space required
            //   by this code for method invocation
            uint16_t tries_size_;                // the number of try_items for this instance. If non-zero,
            //   then these appear as the tries array just after the
            //   insns in this instance.
            uint32_t debug_info_off_;            // file offset to debug info stream
            uint32_t insns_size_in_code_units_;  // size of the insns array, in 2 byte code units
            uint16_t insns_[1];                  // actual array of bytecode.
        };
        // Raw try_item.
        struct TryItem {
            uint32_t start_addr_;
            uint16_t insn_count_;
            uint16_t handler_off_;
        };

        struct ClassDataHeader {
            uint32_t static_fields_size_;  // the number of static fields
            uint32_t instance_fields_size_;  // the number of instance fields
            uint32_t direct_methods_size_;  // the number of direct methods
            uint32_t virtual_methods_size_;  // the number of virtual methods
        };

        struct ClassDataField {
        public:
            uint32_t field_idx_delta_;  // delta of index into the field_ids array for FieldId
            uint32_t access_flags_;  // access flags for the field

            ClassDataField(uint32_t field_idx_delta_, uint32_t access_flags_) :
                    field_idx_delta_(field_idx_delta_), access_flags_(access_flags_) {
            }

            ClassDataField() : field_idx_delta_(0), access_flags_(0) {
            }
        };

        // A decoded version of the method of a class_data_item
        struct ClassDataMethod {
        public:
            uint32_t method_idx_delta_;  // delta of index into the method_ids array for MethodId
            uint32_t access_flags_;
            uint32_t code_off_;

            ClassDataMethod(uint32_t method_idx_delta_, uint32_t access_flags_, uint32_t code_off_)
                    :
                    method_idx_delta_(method_idx_delta_), access_flags_(access_flags_),
                    code_off_(code_off_) {
            }

            ClassDataMethod() :
                    method_idx_delta_(0), access_flags_(0), code_off_(0) {
            }
        };
    };

    namespace V21 {
        class DexFile {
        public:
            //vtable pointer
            void *_;

            // The base address of the memory mapping.
            const uint8_t *const begin_;

            // The size of the underlying memory allocation in bytes.
            const size_t size_;

            // Typically the dex file name when available, alternatively some identifying string.
            //
            // The ClassLinker will use this to match DexFiles the boot class
            // path to DexCache::GetLocation when loading from an image.
            const std::string location_;

            const uint32_t location_checksum_;

            // Manages the underlying memory allocation.
            std::unique_ptr<void *> mem_map_;

            // Points to the header section.
            const dex::Header *const header_;

            // Points to the base of the string identifier list.
            const dex::StringId *const string_ids_;

            // Points to the base of the type identifier list.
            const dex::TypeId *const type_ids_;

            // Points to the base of the field identifier list.
            const dex::FieldId *const field_ids_;

            // Points to the base of the method identifier list.
            const dex::MethodId *const method_ids_;

            // Points to the base of the prototype identifier list.
            const dex::ProtoId *const proto_ids_;

            // Points to the base of the class definition list.
            const dex::ClassDef *const class_defs_;

        };
    } //namespace V21

    namespace V28 {
        class DexFile {
        public:
            //vtable pointer
            void *_;

            // The base address of the memory mapping.
            const uint8_t *const begin_;

            // The size of the underlying memory allocation in bytes.
            const size_t size_;

            // The base address of the data section (same as Begin() for standard dex).
            const uint8_t *const data_begin_;

            // The size of the data section.
            const size_t data_size_;

            // Typically the dex file name when available, alternatively some identifying string.
            //
            // The ClassLinker will use this to match DexFiles the boot class
            // path to DexCache::GetLocation when loading from an image.
            const std::string location_;

            const uint32_t location_checksum_;

            // Points to the header section.
            const dex::Header *const header_;

            // Points to the base of the string identifier list.
            const dex::StringId *const string_ids_;

            // Points to the base of the type identifier list.
            const dex::TypeId *const type_ids_;

            // Points to the base of the field identifier list.
            const dex::FieldId *const field_ids_;

            // Points to the base of the method identifier list.
            const dex::MethodId *const method_ids_;

            // Points to the base of the prototype identifier list.
            const dex::ProtoId *const proto_ids_;

            // Points to the base of the class definition list.
            const dex::ClassDef *const class_defs_;

        };
    } //namespace V28

    namespace V35 {
        template <typename T>
        class ArrayRef {
        private:
            T* array_;
            size_t size_;
        };
        class DexFile {
        public:
            void *_;

            // The base address of the memory mapping.
            const uint8_t* begin_;

            size_t unused_size_ = 0;  // Preserve layout for DRM (b/305203031).

            // Data memory range: Most dex offsets are relative to this memory range.
            // Standard dex: same as (begin_, size_).
            // Dex container: all dex files (starting from the first header).
            // Compact: shared data which is located after all non-shared data.
            //
            // This is different to the "data section" in the standard dex header.
            ArrayRef<const uint8_t> const data_;

            // The full absolute path to the dex file, if it was loaded from disk.
            //
            // Can also be a path to a multidex container (typically apk), followed by
            // DexFileLoader.kMultiDexSeparator (i.e. '!') and the file inside the
            // container.
            //
            // On host this may not be an absolute path.
            //
            // On device libnativeloader uses this to determine the location of the java
            // package or shared library, which decides where to load native libraries
            // from.
            //
            // The ClassLinker will use this to match DexFiles the boot class
            // path to DexCache::GetLocation when loading from an image.
            const std::string location_;

            uint32_t location_checksum_;

            // Points to the header section.
            const dex::Header*  header_;

            // Points to the base of the string identifier list.
            const dex::StringId*  string_ids_;

            // Points to the base of the type identifier list.
            const dex::TypeId* type_ids_;

            // Points to the base of the field identifier list.
            const dex::FieldId* field_ids_;

            // Points to the base of the method identifier list.
            const dex::MethodId* method_ids_;

            // Points to the base of the prototype identifier list.
            const dex::ProtoId* proto_ids_;

            // Points to the base of the class definition list.
            const dex::ClassDef* class_defs_;
        };
    } //namespace V35
};//namespace cyrus

#endif //CYURS_DEX_FILE_H

参考:https://github.com/luoyesiqiu/dpt-shell/tree/main/shell/src/main/cpp/dex

在 so 初始化的时候获取当前系统的 api level

int g_sdkLevel = 0;

__attribute__ ((constructor)) void init() {
    // api level
    g_sdkLevel = android_get_device_api_level();
}

根据 api level 把 void* 类型的 dex_file 参数强转成对应版本的 DexFile,这样就可以访问 DexFile 的成员变量了

// DexFile
std::string location;
uint8_t *begin = nullptr;
uint64_t dexSize = 0;
if (g_sdkLevel >= 35) {
    auto *dexFileV35 = (V35::DexFile *) dex_file;
    location = dexFileV35->location_;
    begin = (uint8_t *) dexFileV35->begin_;
    dexSize = dexFileV35->header_->file_size_;
} else if (g_sdkLevel >= __ANDROID_API_P__) {
    auto *dexFileV28 = (V28::DexFile *) dex_file;
    location = dexFileV28->location_;
    begin = (uint8_t *) dexFileV28->begin_;
    dexSize = dexFileV28->size_ == 0 ? dexFileV28->header_->file_size_ : dexFileV28->size_;
} else {
    auto *dexFileV21 = (V21::DexFile *) dex_file;
    location = dexFileV21->location_;
    begin = (uint8_t *) dexFileV21->begin_;
    dexSize = dexFileV21->size_ == 0 ? dexFileV21->header_->file_size_ : dexFileV21->size_;
}

// 打印 DexFile 信息
LOGI("[pid=%d][API=%d] enter my_LoadMethod:\n  DexFile Base    = %p\n  DexFile Size    = %zu bytes\n  DexFile Location= %s",
     getpid(), g_sdkLevel, begin, dexSize, location.c_str());

访问 ArtMethod

ArtMethod 源码:

word/media/image6.png
https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/art_method.h;l=744

其中,GcRoot<mirror::Class> declaring_class_; 是 ART(Android Runtime)内部定义的一个模板类,用于在 native 层持有对 Java 对象的 GC 安全引用。它是 ART 中替代裸指针的“托管指针”。

本质上它只是封装了一个指针,所以 arm64 中实际占用 8 字节,这里用 uint8_t 代替。

class ArtMethod {
public:
    // Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
    // The class we are a part of.
    uint8_t declaring_class_;

    // Access flags; low 16 bits are defined by spec.
    // Getting and setting this flag needs to be atomic when concurrency is
    // possible, e.g. after this method's class is linked. Such as when setting
    // verifier flags and single-implementation flag.
    uint32_t access_flags_;

    /* Dex file fields. The defining dex file is available via declaring_class_->dex_cache_ */

    // Offset to the CodeItem.
    uint32_t dex_code_item_offset_;

    // Index into method_ids of the dex file associated with this method.
    uint32_t dex_method_index_;

    /* End of dex file fields. */

    // Entry within a dispatch table for this method. For static/direct methods the index is into
    // the declaringClass.directMethods, for virtual methods the vtable and for interface methods the
    // ifTable.
    uint16_t method_index_;

    union {
        // Non-abstract methods: The hotness we measure for this method. Not atomic,
        // as we allow missing increments: if the method is hot, we will see it eventually.
        uint16_t hotness_count_;
        // Abstract methods: IMT index (bitwise negated) or zero if it was not cached.
        // The negation is needed to distinguish zero index and missing cached entry.
        uint16_t imt_index_;
    };
};

my_LoadMethod 函数实现

在 my_LoadMethod 函数中:

  1. 根据 g_sdkLevel 解析 DexFile 并打印其 begin、size 和 location;

  2. 调用原始 LoadMethod 以保证 ArtMethod 已初始化;

  3. 在不同 Android 版本下提取 ArtMethod 的 dex_method_index_ 和 dex_code_item_offset_;

void *my_LoadMethod(void *linker, void *dex_file, void *method, void *klass_handle, void *dst) {

    // DexFile
    std::string location;
    uint8_t *begin = nullptr;
    uint64_t dexSize = 0;
    if (g_sdkLevel >= 35) {
        auto *dexFileV35 = (V35::DexFile *) dex_file;
        location = dexFileV35->location_;
        begin = (uint8_t *) dexFileV35->begin_;
        dexSize = dexFileV35->header_->file_size_;
    } else if (g_sdkLevel >= __ANDROID_API_P__) {
        auto *dexFileV28 = (V28::DexFile *) dex_file;
        location = dexFileV28->location_;
        begin = (uint8_t *) dexFileV28->begin_;
        dexSize = dexFileV28->size_ == 0 ? dexFileV28->header_->file_size_ : dexFileV28->size_;
    } else {
        auto *dexFileV21 = (V21::DexFile *) dex_file;
        location = dexFileV21->location_;
        begin = (uint8_t *) dexFileV21->begin_;
        dexSize = dexFileV21->size_ == 0 ? dexFileV21->header_->file_size_ : dexFileV21->size_;
    }

    // 打印 DexFile 信息
    LOGI("[pid=%d][API=%d] enter my_LoadMethod:\n  DexFile Base    = %p\n  DexFile Size    = %zu bytes\n  DexFile Location= %s",
         getpid(), g_sdkLevel, begin, dexSize, location.c_str());

    // 调用原始函数,使 ArtMethod 数据填充完成
    void *result = orig_LoadMethod(linker, dex_file, method, klass_handle, dst);

    // ArtMethod
    uint32_t dex_code_item_offset_ = -1;
    uint32_t dex_method_index_;
    if (g_sdkLevel >= 31) {
        auto *dstV31 = (V31::ArtMethod *) dst;
        auto classAccessor_method = reinterpret_cast<Method &>(method);
        dex_code_item_offset_ = classAccessor_method.code_off_;
        dex_method_index_ = dstV31->dex_method_index_;
    } else {
        auto *dstV28 = (V28::ArtMethod *) dst;
        dex_code_item_offset_ = dstV28->dex_code_item_offset_;
        dex_method_index_ = dstV28->dex_method_index_;
    }

    // 打印 Method 信息
    LOGI("[pid=%d][API=%d] enter my_LoadMethod:\n"
         "  ArtMethod.dex_code_item_offset_ = 0x%x\n"
         "  ArtMethod.dex_method_index_     = %d",
         getpid(), g_sdkLevel, dex_code_item_offset_, dex_method_index_);

    return result;
}

测试

运行后输出如下,可以看到正常打印了 DexFile 和 ArtMethod 中的信息。

word/media/image7.png

完整源码

开源地址:https://github.com/CYRUS-STUDIO/AndroidExample

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值