首页
社区
课程
招聘
[原创] Frida 17.6 最新Hook注入方案分析(Zymbiote注入机制)
发表于: 3天前 2053

[原创] Frida 17.6 最新Hook注入方案分析(Zymbiote注入机制)

3天前
2053

本文主要是想要了解清楚 Frida 在新版本有什么地方不一样了。本文主要还是着重在 Zymbiote的注入机制上,其他相关板块后续再出文章分析。

然后在阅读 Frida 源码的过程也发现了很多有意思的小trick,鉴于本人水平有限如果有问题请各位大佬指出。

版本说明:

Frida 17.6

参考文章:

图片描述

Zymbiote 是 Frida 在 Android 上实现轻量级 Zygote Hook 的机制,用于在应用进程启动时注入 Frida

命名来源:Zymbiote = Zygote + Symbiote(共生体),表示与 Zygote 进程共生。

后文会出现补丁相关的代码,所以这里解释一下。其实很好理解,这里补丁就是我们patch上去的代码

补丁目前分为两种类型

这里很好理解,payload相当于我们传入的二进制代码,然后我们在 ArtMethod 改对应属性指向我们的payload,这样他们执行函数的时候就会先执行我们的payload

这个概念在后面也会出现,我们这里可以解释一下。

Boot Heap = Android ART 运行时在启动时预加载的内存区域。

我们从代码可以知道,系统判定为 boot_heap 就是判断路径是否包含boot.art,boot-framework.art,dalvik-LinearAlloc这些 Boot Image

关于 Boot Image 我们可以后续再出一个文章再详细介绍这个地方。

Android 启动时:

统一初始化入口,确保 Socket 服务器就绪,并发注入多个 Zygote 进程

这里 wait_async 再返回就是避免让程序以为是返回成功了的,我们等待其他实列完成。

创建 Unix Socket 服务器

枚举并注入所有 Zygote 进程

查找 zygote、zygote64、usap32、usap64 ( Unspecialized APP Process 也就是Android 的预 fork 进程池机制,用于加速应用启动) 这样尽可能遍历到了所有进程类型,只要有一个新的进程产生就一定会被hook,因为父进程中相关的函数已经被我们修改了。这里的实现非常优雅

对每个进程调用 do_inject_zygote_agent()

使用并发注入,等待所有完成

我们可以看见 do_inject_zygote_agent 包装了我们的 inject_zymbiote

这里算得上核心注入最核心的部分,也就是解析进程内存映射,解析符号地址,定位 ArtMethod 槽位,准备 Payload 数据。

其实就是校验我们必须需要的信息是否存在,然后再根据我们的 libc_path 与 runtime_path 找到 libc 与 runtime 的基地址。然后对应进行符号解析得到 ELF 对象,然后查找我们的 setArgV0Native 函数(_Z27android_os_Process_setArgV0P7_JNIEnvP8_jobjectP8_jstring)。

为什么需要查找我们的 setArgV0Native 函数捏?因为这是后续查找ArtMethod的关键,artMethod 的函数指针就是存储的 setArgV0Native 函数地址,所以我们可以通过解析符号表定位到 setArgV0Native 函数就可以间接定位到 ArtMethod

根据前面获取的 setArgV0Native 函数指针在 boot_heap 区域搜索就可以找到我们的 ArtMethod

这里搜索frida-zymbiote-00000000000000000000000000000000,是因为在 zymbiote 中初始化name就是这个名字

static volatile const FridaApi frida =

{

.name = "/frida-zymbiote-00000000000000000000000000000000",

};

只要找到这个位置,那么就根据这个位置后面填写我们对应的数据即可,完善这个 FridaApi 结构体

其实就是将Payload需要的libc函数地址,ArtMethod函数地址填充进去,方便后续运作

1. 前言
   1.1 版本说明
   1.2 参考文章
 
2. 历史Hook方案
   2.1 旧方案问题
   2.2 新方案
 
3. 流程图
 
4. Zymbiote注入阶段
   4.1 函数调用链
   4.2 补丁
       4.2.1 Payload补丁
       4.2.2 ArtMethod补丁
   4.3 Boot Heap
       4.3.1 Boot Image文件
   4.4 ensure_loaded (初始化)
   4.5 inject_zymbiote (核心注入逻辑)
   4.6 do_prepare_zymbiote_injection(核心准备逻辑)
       4.6.1 调用链
       4.6.2 初始化变量
       4.6.3 解析进程内存映射
       4.6.4 获取libc与runtime信息
       4.6.5 定位ArtMethod信息
       4.6.6 填充payload
1. 前言
   1.1 版本说明
   1.2 参考文章
 
2. 历史Hook方案
   2.1 旧方案问题
   2.2 新方案
 
3. 流程图
 
4. Zymbiote注入阶段
   4.1 函数调用链
   4.2 补丁
       4.2.1 Payload补丁
       4.2.2 ArtMethod补丁
   4.3 Boot Heap
       4.3.1 Boot Image文件
   4.4 ensure_loaded (初始化)
   4.5 inject_zymbiote (核心注入逻辑)
   4.6 do_prepare_zymbiote_injection(核心准备逻辑)
       4.6.1 调用链
       4.6.2 初始化变量
       4.6.3 解析进程内存映射
       4.6.4 获取libc与runtime信息
       4.6.5 定位ArtMethod信息
       4.6.6 填充payload
struct _FridaApi
{
  char name[64];
 
  void    ** art_method_slot;
  void    (* original_set_argv0) (JNIEnv * env, jobject clazz, jstring name);
 
  int     (* socket) (int domain, int type, int protocol);
  int     (* connect) (int sockfd, const struct sockaddr * addr, socklen_t addrlen);
  int *   (* __errno) (void);
  pid_t   (* getpid) (void);
  pid_t   (* getppid) (void);
  ssize_t (* sendmsg) (int sockfd, const struct msghdr * msg, int flags);
  ssize_t (* recv) (int sockfd, void * buf, size_t len, int flags);
  int     (* close) (int fd);
  int     (* raise) (int sig);
};
struct _FridaApi
{
  char name[64];
 
  void    ** art_method_slot;
  void    (* original_set_argv0) (JNIEnv * env, jobject clazz, jstring name);
 
  int     (* socket) (int domain, int type, int protocol);
  int     (* connect) (int sockfd, const struct sockaddr * addr, socklen_t addrlen);
  int *   (* __errno) (void);
  pid_t   (* getpid) (void);
  pid_t   (* getppid) (void);
  ssize_t (* sendmsg) (int sockfd, const struct msghdr * msg, int flags);
  ssize_t (* recv) (int sockfd, void * buf, size_t len, int flags);
  int     (* close) (int fd);
  int     (* raise) (int sig);
};
enable_spawn_gating() (linux-host-session.vala:261)
  └─ robo_launcher.enable_spawn_gating() (linux-host-session.vala:263)
      └─ ensure_loaded() (linux-host-session.vala:661)
          ├─ 创建 Unix Socket 服务器
          ├─ handle_zymbiote_connections.begin() (linux-host-session.vala:785)
          └─ 枚举 Zygote 进程
              └─ do_inject_zygote_agent.begin() (linux-host-session.vala:816)
                  └─ inject_zymbiote() (linux-host-session.vala:838)
                      ├─ prepare_zymbiote_injection() (linux-host-session.vala:847)
                      │   └─ do_prepare_zymbiote_injection() (linux-host-session.vala:905)
                      │       ├─ 解析 /proc/<pid>/maps
                      │       ├─ 定位 payload 基址 (libstagefright.so)
                      │       ├─ 解析 libc.so 和 libandroid_runtime.so
                      │       ├─ 定位 ArtMethod 槽位
                      │       └─ 准备 payload 数据
                      ├─ 暂停 Zygote 进程 (SIGSTOP)
                      ├─ 应用补丁 (ZymbiotePatches.apply)
                      │   ├─ 写入 payload 到可执行映射
                      │   └─ 替换 ArtMethod 槽位指针
                      └─ 恢复 Zygote 进程 (SIGCONT)

[子进程 fork 后]
  └─ frida_zymbiote_replacement_set_argv0() (zymbiote.c:44)
      ├─ 回滚 ArtMethod 槽位
      ├─ 调用原始 setArgV0Native
      ├─ 连接 Unix Socket
      ├─ 发送 Hello 消息
      ├─ 等待 ACK
      └─ 触发 SIGSTOP

[Frida Core 处理]
  └─ handle_zymbiote_connection() (linux-host-session.vala:1128)
      ├─ read_hello() (linux-host-session.vala:1130)
      ├─ 判断是否需要 spawn gating
      └─ connection.resume() (linux-host-session.vala:1150)
          ├─ 发送 ACK
          ├─ 等待进程停止
          ├─ 回滚子进程补丁
          └─ 发送 SIGCONT
// 将 payload 代码写入到可执行内存
patches.apply(payload, process_memory, payload_base);
// 替换 ArtMethod 槽位指针
patches.apply(replaced_ptr, process_memory, art_method_slot);
Boot Heap 存储:
├─ 预编译的 Android 框架类
├─ ArtMethod 对象(包括 setArgV0Native 的 ArtMethod)
├─ 预加载的类元数据
└─ 系统库的方法信息
private static bool is_boot_heap (string path) {
    return
        "boot.art" in path ||
        "boot-framework.art" in path ||
        "dalvik-LinearAlloc" in path;
}
boot.art:
├─ 预编译的核心 Android 类
├─ 系统库的预编译代码
└─ 启动时加载到内存

boot-framework.art:
├─ Android 框架的预编译类
├─ 系统服务的预编译代码
└─ 启动时加载到内存

/proc/<pid>/maps 输出:

7f1234000000-7f1235000000 rw-p 00000000 /dev/ashmem/dalvik-LinearAlloc
7f1235000000-7f1236000000 rw-p 00000000 /system/framework/boot.art
7f1236000000-7f1237000000 rw-p 00000000 /system/framework/boot-framework.art
传统方式(无USAP):
应用启动请求 → Zygote fork → 初始化 → 应用运行
              ↑ 耗时较长

USAP方式:
Zygote 预fork → USAP池 (空闲进程)
应用启动请求 → 从USAP池取进程 → 快速配置 → 应用运行
              ↑ 更快
private async void ensure_loaded (Cancellable? cancellable) throws Error, IOError {
    while (ensure_request != null) {
        try {
            
            yield ensure_request.future.wait_async (cancellable);
            return;
        } catch (Error e) {
            throw e;
        } catch (IOError e) {
            cancellable.set_error_if_cancelled ();
        }
    }
    ensure_request = new Promise<bool> ();

    if (server_name == null) {
        string name = "/frida-zymbiote-" + Uuid.string_random ().replace ("-", "");
        var address = new UnixSocketAddress.with_type (name, -1, UnixSocketAddressType.ABSTRACT);

        try {
            var socket = new Socket (SocketFamily.UNIX, SocketType.STREAM, SocketProtocol.DEFAULT);
            socket.bind (address, true);
            socket.listen ();

            server_name = name;
            server_address = address;

            handle_zymbiote_connections.begin (socket);
        } catch (GLib.Error raw_err) {
            var err = new Error.TRANSPORT ("%s", raw_err.message);
            ensure_request.reject (err);
            throw err;
        }
    }

    uint pending = 1;
    GLib.Error? first_error = null;

    CompletionNotify on_complete = error => {
        pending--;
        if (error != null && first_error == null)
            first_error = error;

        if (pending == 0) {
            var source = new IdleSource ();
            source.set_callback (ensure_loaded.callback);
            source.attach (MainContext.get_thread_default ());
        }
    };

    foreach (HostProcessInfo info in System.enumerate_processes (new ProcessQueryOptions ())) {
        var name = info.name;
        if (name == "zygote" || name == "zygote64" || name == "usap32" || name == "usap64") {
            uint pid = info.pid;
            if (zymbiote_patches.has_key (pid))
                continue;

            pending++;
            do_inject_zygote_agent.begin (pid, name, cancellable, on_complete);
        }
    }

    on_complete (null);

    yield;

    on_complete = null;

    if (first_error == null) {
        ensure_request.resolve (true);
    } else {
        ensure_request.reject (first_error);
        ensure_request = null;

        throw_api_error (first_error);
    }
}
private async void do_inject_zygote_agent (uint pid, string name, Cancellable? cancellable, CompletionNotify on_complete) {
    try {
        yield inject_zymbiote (pid, cancellable);

        on_complete (null);
    } catch (GLib.Error e) {
        on_complete (e);
    }
}
private async void inject_zymbiote (uint pid, Cancellable? cancellable) throws Error, IOError {
    // 调用 prepare_zymbiote_injection() 获取 ZymbiotePrepResult
    var prep = yield prepare_zymbiote_injection (pid, cancellable);
    // 发送 SIGSTOP 信号,暂停进程
    Posix.kill ((Posix.pid_t) pid, Posix.Signal.STOP);
    yield wait_until_stopped (pid, cancellable);
    try {
        // 创建 ZymbiotePatches 管理补丁
        var patches = new ZymbiotePatches ();
        // 获取 payload 数据
        unowned uint8[] payload = prep.payload.get_data ();
        // 如果已经补丁,则打开 payload 文件,读取原始数据
        if (prep.already_patched) {
            var handle = Posix.open (prep.payload_path, Posix.O_RDONLY);
            if (handle == -1) {
                throw new Error.PERMISSION_DENIED ("Unable to open payload backing file: %s",
                    strerror (errno));
            }
            var backing_file = new FileDescriptor (handle);
            var original = new uint8[payload.length];
            backing_file.pread_all (original, prep.payload_file_offset);
            //写入 payload 到可执行映射(payload_base)
            patches.apply (payload, prep.process_memory, prep.payload_base, new Bytes.take ((owned) original));
        } else {
            patches.apply (payload, prep.process_memory, prep.payload_base);
        }

        if (prep.already_patched) {
            // 替换 ArtMethod 槽位指针为 payload 地址
            patches.apply (prep.replaced_ptr, prep.process_memory, prep.art_method_slot,
                new Bytes (prep.original_ptr));
        } else {
            patches.apply (prep.replaced_ptr, prep.process_memory, prep.art_method_slot);
        }
        // 保存补丁记录到 zymbiote_patches[pid]
        zymbiote_patches[pid] = patches;
    } finally {
        Posix.kill ((Posix.pid_t) pid, Posix.Signal.CONT);
    }
}
private async ZymbiotePrepResult prepare_zymbiote_injection (uint pid, Cancellable? cancellable) throws Error, IOError {
    var task = new Task (this, cancellable, (obj, res) => {
        prepare_zymbiote_injection.callback ();
    });
    task.set_task_data ((void *) pid, null);
    task.run_in_thread ((t, source_object, task_data, c) => {
        unowned RoboLauncher launcher = (RoboLauncher) t.get_unowned_source_object ();
        uint pid_to_prep = (uint) task_data;
        try {
            var r = do_prepare_zymbiote_injection (pid_to_prep, launcher.server_name);
            t.return_pointer ((owned) r, Object.unref);
        } catch (GLib.Error e) {
            t.return_error ((owned) e);
        }
    });
    yield;

    try {
        return (ZymbiotePrepResult) (owned) task.propagate_pointer ();
    } catch (GLib.Error e) {
        throw_api_error (e);
    }
}
private static ZymbiotePrepResult do_prepare_zymbiote_injection (uint pid, string server_name) throws Error, IOError {
    // 进行一些变量的初始化
    uint64 payload_base = 0;
    string? payload_path = null;
    uint64 payload_file_offset = 0;
    string? libc_path = null;
    string? runtime_path = null;
    Gee.List<Gum.MemoryRange?> heap_candidates = new Gee.ArrayList<Gum.MemoryRange?> ();

    var iter = ProcMapsIter.for_pid (pid);
    while (iter.next ()) {
        string path = iter.path;
        string flags = iter.flags;
        if (path.has_suffix ("/libstagefright.so") && "x" in flags) {
            if (payload_base == 0) {
                payload_base = iter.end_address - Gum.query_page_size ();
                payload_path = path;
                payload_file_offset = iter.file_offset;
            }
        } else if (path.has_suffix ("/libc.so")) {
            if (libc_path == null)
                libc_path = path;
        } else if (path.has_suffix ("/libandroid_runtime.so")) {
            if (runtime_path == null)
                runtime_path = path;
        } else if (flags == "rw-p" && is_boot_heap (path)) {
            uint64 start = iter.start_address;
            uint64 end = iter.end_address;
            heap_candidates.add (Gum.MemoryRange () {
                base_address = start,
                size = (size_t) (end - start),
            });
        }
    }

    if (payload_base == 0)
        throw new Error.NOT_SUPPORTED ("Unable to pick a payload base");
    if (libc_path == null)
        throw new Error.NOT_SUPPORTED ("Unable to detect libc.so path");
    if (runtime_path == null)
        throw new Error.NOT_SUPPORTED ("Unable to detect libandroid_runtime.so path");
    if (heap_candidates.is_empty)
        throw new Error.NOT_SUPPORTED ("Unable to detect any VM heap candidates");

    var libc_entry = ProcMapsSoEntry.find_by_path (pid, libc_path);
    if (libc_entry == null)
        throw new Error.NOT_SUPPORTED ("Unable to detect libc.so entry");

    var runtime_entry = ProcMapsSoEntry.find_by_path (pid, runtime_path);
    if (runtime_entry == null)
        throw new Error.NOT_SUPPORTED ("Unable to detect libandroid_runtime.so entry");

    Gum.ElfModule libc;
    try {
        libc = new Gum.ElfModule.from_file (libc_path);
    } catch (Gum.Error e) {
        throw new Error.NOT_SUPPORTED ("Unable to parse libc.so: %s", e.message);
    }

    Gum.ElfModule runtime;
    try {
        runtime = new Gum.ElfModule.from_file (runtime_path);
    } catch (Gum.Error e) {
        throw new Error.NOT_SUPPORTED ("Unable to parse libandroid_runtime.so: %s", e.message);
    }

    uint64 set_argv0_address = 0;
    runtime.enumerate_exports (e => {
        if (e.name == "_Z27android_os_Process_setArgV0P7_JNIEnvP8_jobjectP8_jstring") {
            set_argv0_address = runtime_entry.base_address + e.address;
            return false;
        }
        return true;
    });
    if (set_argv0_address == 0)
        throw new Error.NOT_SUPPORTED ("Unable to locate android.os.Process.setArgV0(); please file a bug");

    uint pointer_size = ("/lib64/" in libc_path) ? 8 : 4;

    var original_ptr = new uint8[pointer_size];
    var replaced_ptr = new uint8[pointer_size];

    (new Buffer (new Bytes.static (original_ptr), ByteOrder.HOST, pointer_size)).write_pointer (0, set_argv0_address);
    (new Buffer (new Bytes.static (replaced_ptr), ByteOrder.HOST, pointer_size)).write_pointer (0, payload_base);

    var fd = open_process_memory (pid);

    uint64 art_method_slot = 0;
    bool already_patched = false;
    foreach (var candidate in heap_candidates) {
        var heap = new uint8[candidate.size];
        var n = fd.pread (heap, candidate.base_address);
        if (n != heap.length)
            throw new Error.NOT_SUPPORTED ("Short read");

        void * p = memmem (heap, original_ptr);
        if (p == null) {
            p = memmem (heap, replaced_ptr);
            already_patched = p != null;
        }

        if (p != null) {
            art_method_slot = candidate.base_address + ((uint8 *) p - (uint8 *) heap);
            break;
        }
    }
    if (art_method_slot == 0)
        throw new Error.NOT_SUPPORTED ("Unable to locate method slot; please file a bug");

    var blob = (pointer_size == 8)
#if ARM || ARM64
        ? Frida.Data.Android.get_zymbiote_arm64_bin_blob ()
        : Frida.Data.Android.get_zymbiote_arm_bin_blob ();
#else
        ? Frida.Data.Android.get_zymbiote_x86_64_bin_blob ()
        : Frida.Data.Android.get_zymbiote_x86_bin_blob ();
#endif

    unowned uint8[] payload_template = blob.data;
    void * p = memmem (payload_template, "/frida-zymbiote-00000000000000000000000000000000".data);
    assert (p != null);
    size_t data_offset = (uint8 *) p - (uint8 *) payload_template;

    var payload = new Buffer (new Bytes (payload_template), ByteOrder.HOST, pointer_size);

    size_t cursor = data_offset;
    payload.write_string (cursor, server_name);
    cursor += 64;

    payload.write_pointer (cursor, art_method_slot);
    cursor += pointer_size;

    payload.write_pointer (cursor, set_argv0_address);
    cursor += pointer_size;

    string[] wanted = {
        "socket",
        "connect",
        "__errno",
        "getpid",
        "getppid",
        "sendmsg",
        "recv",
        "close",
        "raise",
    };

    var index_of = new Gee.HashMap<string, int> ();
    for (int i = 0; i != wanted.length; i++)
        index_of[wanted[i]] = i;

    var addrs = new uint64[wanted.length];
    uint pending = wanted.length;
    libc.enumerate_exports (e => {
        if (index_of.has_key (e.name)) {
            int idx = index_of[e.name];
            addrs[idx] = libc_entry.base_address + e.address;
            pending--;
        }
        return pending != 0;
    });

    for (int i = 0; i != addrs.length; i++) {
        assert (addrs[i] != 0);
        payload.write_pointer (cursor, addrs[i]);
        cursor += pointer_size;
    }

    return new ZymbiotePrepResult () {
        process_memory = fd,
        already_patched = already_patched,

        art_method_slot = art_method_slot,
        original_ptr = original_ptr,
        replaced_ptr = replaced_ptr,

        payload = payload.bytes,
        payload_base = payload_base,
        payload_path = payload_path,
        payload_file_offset = payload_file_offset,
    };
}
do_prepare_zymbiote_injection() (linux-host-session.vala:925)
          ├─ ProcMapsIter.for_pid() - 创建内存映射迭代器
          ├─ ProcMapsIter.next() - 遍历内存映射
          ├─ ProcMapsSoEntry.find_by_path() - 查找 SO 映射条目
          ├─ Gum.ElfModule.from_file() - 解析 ELF 文件
          ├─ Gum.ElfModule.enumerate_exports() - 枚举导出符号
          ├─ open_process_memory() - 打开进程内存 (linux-host-session.vala:1692)
          │   └─ Posix.open("/proc/<pid>/mem") - 打开 /proc/<pid>/mem
          ├─ FileDescriptor.pread() - 读取进程内存
          ├─ memmem() - 内存搜索 (linux-host-session.vala:1110)
          ├─ is_boot_heap() - 判断是否为 boot heap (linux-host-session.vala:1107)
          └─ Buffer.write_pointer() / write_string() - 填充 payload
            uint64 payload_base = 0;
            string? payload_path = null;
            uint64 payload_file_offset = 0;
            string? libc_path = null;
            string? runtime_path = null;
            Gee.List<Gum.MemoryRange?> heap_candidates = new Gee.ArrayList<Gum.MemoryRange?> ();
var iter = ProcMapsIter.for_pid (pid);
while (iter.next ()) {
    string path = iter.path;
    string flags = iter.flags;
    if (path.has_suffix ("/libstagefright.so") && "x" in flags) {
            if (payload_base == 0) {
                    payload_base = iter.end_address - Gum.query_page_size ();
                    payload_path = path;
                    payload_file_offset = iter.file_offset;
            }
    } else if (path.has_suffix ("/libc.so")) {
            if (libc_path == null)
                    libc_path = path;
    } else if (path.has_suffix ("/libandroid_runtime.so")) {
            if (runtime_path == null)
                    runtime_path = path;
    } else if (flags == "rw-p" && is_boot_heap (path)) {
            uint64 start = iter.start_address;
            uint64 end = iter.end_address;
            heap_candidates.add (Gum.MemoryRange () {
                    base_address = start,
                    size = (size_t) (end - start),
            });
    }
}
if (payload_base == 0)
        throw new Error.NOT_SUPPORTED ("Unable to pick a payload base");
if (libc_path == null)
        throw new Error.NOT_SUPPORTED ("Unable to detect libc.so path");
if (runtime_path == null)
        throw new Error.NOT_SUPPORTED ("Unable to detect libandroid_runtime.so path");
if (heap_candidates.is_empty)
        throw new Error.NOT_SUPPORTED ("Unable to detect any VM heap candidates");

var libc_entry = ProcMapsSoEntry.find_by_path (pid, libc_path);
if (libc_entry == null)
        throw new Error.NOT_SUPPORTED ("Unable to detect libc.so entry");

var runtime_entry = ProcMapsSoEntry.find_by_path (pid, runtime_path);
if (runtime_entry == null)
        throw new Error.NOT_SUPPORTED ("Unable to detect libandroid_runtime.so entry");
        
Gum.ElfModule libc;
try {
        libc = new Gum.ElfModule.from_file (libc_path);
} catch (Gum.Error e) {
        throw new Error.NOT_SUPPORTED ("Unable to parse libc.so: %s", e.message);
}

Gum.ElfModule runtime;
try {
        runtime = new Gum.ElfModule.from_file (runtime_path);
} catch (Gum.Error e) {
        throw new Error.NOT_SUPPORTED ("Unable to parse libandroid_runtime.so: %s", e.message);
}

uint64 set_argv0_address = 0;
runtime.enumerate_exports (e => {
        if (e.name == "_Z27android_os_Process_setArgV0P7_JNIEnvP8_jobjectP8_jstring") {
                set_argv0_address = runtime_entry.base_address + e.address;
                return false;
        }
        return true;
});
if (set_argv0_address == 0)
        throw new Error.NOT_SUPPORTED ("Unable to locate android.os.Process.setArgV0(); please file a bug");
var fd = open_process_memory (pid);

uint64 art_method_slot = 0;
bool already_patched = false;
foreach (var candidate in heap_candidates) {
        var heap = new uint8[candidate.size];
        var n = fd.pread (heap, candidate.base_address);
        if (n != heap.length)
                throw new Error.NOT_SUPPORTED ("Short read");

        void * p = memmem (heap, original_ptr);
        if (p == null) {
                p = memmem (heap, replaced_ptr);
                already_patched = p != null;
        }

        if (p != null) {
                art_method_slot = candidate.base_address + ((uint8 *) p - (uint8 *) heap);
                break;
        }
}
if (art_method_slot == 0)
        throw new Error.NOT_SUPPORTED ("Unable to locate method slot; please file a bug");
var blob = (pointer_size == 8)
#if ARM || ARM64
        ? Frida.Data.Android.get_zymbiote_arm64_bin_blob ()
        : Frida.Data.Android.get_zymbiote_arm_bin_blob ();
#else
        ? Frida.Data.Android.get_zymbiote_x86_64_bin_blob ()
        : Frida.Data.Android.get_zymbiote_x86_bin_blob ();
#endif

unowned uint8[] payload_template = blob.data;
void * p = memmem (payload_template, "/frida-zymbiote-00000000000000000000000000000000".data);
assert (p != null);
size_t data_offset = (uint8 *) p - (uint8 *) payload_template;

var payload = new Buffer (new Bytes (payload_template), ByteOrder.HOST, pointer_size);

size_t cursor = data_offset;
payload.write_string (cursor, server_name);
cursor += 64;

payload.write_pointer (cursor, art_method_slot);
cursor += pointer_size;

payload.write_pointer (cursor, set_argv0_address);
cursor += pointer_size;

string[] wanted = {
        "socket",
        "connect",
        "__errno",
        "getpid",
        "getppid",
        "sendmsg",
        "recv",
        "close",
        "raise",
};

var index_of = new Gee.HashMap<string, int> ();
for (int i = 0; i != wanted.length; i++)
        index_of[wanted[i]] = i;

var addrs = new uint64[wanted.length];
uint pending = wanted.length;
libc.enumerate_exports (e => {
        if (index_of.has_key (e.name)) {
                int idx = index_of[e.name];
                addrs[idx] = libc_entry.base_address + e.address;
                pending--;
        }
        return pending != 0;
});

for (int i = 0; i != addrs.length; i++) {
        assert (addrs[i] != 0);
        payload.write_pointer (cursor, addrs[i]);
        cursor += pointer_size;
}

本文主要是想要了解清楚 Frida 在新版本有什么地方不一样了。本文主要还是着重在 Zymbiote的注入机制上,其他相关板块后续再出文章分析。

然后在阅读 Frida 源码的过程也发现了很多有意思的小trick,鉴于本人水平有限如果有问题请各位大佬指出。

版本说明:

Frida 17.6

参考文章:

  • 707K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6E0M7q4)9J5k6i4N6W2K9i4S2A6L8W2)9J5k6i4q4I4i4K6u0W2j5$3!0E0i4K6u0r3M7#2)9K6c8W2)9#2k6W2)9#2k6X3u0A6P5W2)9K6c8p5#2*7g2e0y4y4g2q4V1#2e0i4A6c8P5p5#2m8i4K6y4p5i4K6y4p5i4K6t1$3j5h3#2H3i4K6y4n7L8h3W2V1i4K6y4p5x3U0t1@1y4K6b7^5y4e0l9H3x3W2)9J5y4X3q4E0M7q4)9K6b7X3W2V1P5q4)9K6c8o6q4Q4x3U0k6S2L8i4m8Q4x3@1u0K6L8W2)9K6c8o6q4X3x3r3f1%4x3o6M7%4k6U0p5&6x3U0M7H3x3$3c8T1j5K6l9%4z5e0x3#2j5e0j5K6x3e0p5K6z5e0R3H3i4K6t1$3j5h3#2H3i4K6y4n7j5$3S2C8M7$3#2Q4x3@1c8X3k6o6y4X3y4o6f1%4x3h3p5$3x3K6j5K6k6e0k6X3z5e0x3$3z5r3q4S2x3e0R3H3z5o6M7$3j5K6j5H3x3U0N6U0j5e0t1#2k6U0l9@1z5h3t1#2x3U0l9K6x3o6f1K6z5h3q4V1j5h3f1%4k6o6b7&6x3U0c8T1z5e0R3%4y4o6V1H3y4h3x3%4z5o6p5$3y4r3b7I4i4K6t1$3j5h3#2H3i4K6y4n7L8i4m8K6K9r3q4J5k6g2)9K6c8o6q4Q4x3U0k6S2L8i4m8Q4x3@1u0K6j5$3g2F1k6g2)9K6c8o6t1K6i4K6t1$3j5h3#2H3i4K6y4n7M7%4u0U0K9h3c8Q4x3@1b7H3x3e0t1I4d9q4N6V1e0Y4f1K6d9@1y4W2b7Y4y4X3e0#2t1%4k6s2y4%4k6U0k6Q4x3U0k6S2L8i4m8Q4x3@1u0K6K9r3q4J5k6i4u0Q4y4h3k6K6K9r3q4J5k6h3W2F1k6X3!0Q4x3@1b7H3z5e0f1%4z5h3x3J5y4K6l9#2x3$3t1%4j5e0m8S2j5e0u0W2k6r3j5@1z5o6V1H3y4U0f1@1x3o6t1^5x3q4)9J5y4X3q4E0M7q4)9K6b7Y4y4Z5j5i4u0W2M7W2)9#2k6Y4y4Z5j5i4u0W2K9h3&6X3L8#2)9#2k6X3k6A6M7Y4y4@1i4K6y4p5j5$3p5&6z5e0m8X3k6r3b7I4z5r3t1I4z5h3q4S2k6o6q4T1j5e0W2U0z5o6M7K6z5o6p5@1z5h3u0X3k6r3u0Q4x3U0y4J5k6l9`.`.

Zymbiote 是 Frida 在 Android 上实现轻量级 Zygote Hook 的机制,用于在应用进程启动时注入 Frida

命名来源:Zymbiote = Zygote + Symbiote(共生体),表示与 Zygote 进程共生。

后文会出现补丁相关的代码,所以这里解释一下。其实很好理解,这里补丁就是我们patch上去的代码

补丁目前分为两种类型

这里很好理解,payload相当于我们传入的二进制代码,然后我们在 ArtMethod 改对应属性指向我们的payload,这样他们执行函数的时候就会先执行我们的payload

  • 内容:编译后的 zymbiote 二进制代码(约 800-900 字节)
  • 位置:libstagefright.so 的最后一页(可执行映射)(这里为什么选择这个so捏,因为这是系统库,通常已加载)
  • 作用:在目标进程中放置可执行代码
  • 内容:指针值(4 或 8 字节)
  • 位置:boot heap 中的 ArtMethod 槽位
  • 作用:将函数指针从原始函数改为指向 payload

这个概念在后面也会出现,我们这里可以解释一下。

Boot Heap = Android ART 运行时在启动时预加载的内存区域。

关于 Boot Image 我们可以后续再出一个文章再详细介绍这个地方。

Android 启动时:

  1. Zygote 进程启动
  2. 加载 boot.art → 映射到内存 (boot heap)
  3. 加载 boot-framework.art → 映射到内存 (boot heap)
  4. 预编译的类和方法信息都在这里
  5. ArtMethod 对象也存储在这里 ✅

统一初始化入口,确保 Socket 服务器就绪,并发注入多个 Zygote 进程

我们可以看见 do_inject_zygote_agent 包装了我们的 inject_zymbiote

这里算得上核心注入最核心的部分,也就是解析进程内存映射,解析符号地址,定位 ArtMethod 槽位,准备 Payload 数据。

其实就是校验我们必须需要的信息是否存在,然后再根据我们的 libc_path 与 runtime_path 找到 libc 与 runtime 的基地址。然后对应进行符号解析得到 ELF 对象,然后查找我们的 setArgV0Native 函数(_Z27android_os_Process_setArgV0P7_JNIEnvP8_jobjectP8_jstring)。

为什么需要查找我们的 setArgV0Native 函数捏?因为这是后续查找ArtMethod的关键,artMethod 的函数指针就是存储的 setArgV0Native 函数地址,所以我们可以通过解析符号表定位到 setArgV0Native 函数就可以间接定位到 ArtMethod

根据前面获取的 setArgV0Native 函数指针在 boot_heap 区域搜索就可以找到我们的 ArtMethod

这里搜索frida-zymbiote-00000000000000000000000000000000,是因为在 zymbiote 中初始化name就是这个名字

static volatile const FridaApi frida =

{

.name = "/frida-zymbiote-00000000000000000000000000000000",

};

只要找到这个位置,那么就根据这个位置后面填写我们对应的数据即可,完善这个 FridaApi 结构体

其实就是将Payload需要的libc函数地址,ArtMethod函数地址填充进去,方便后续运作

  1. name[64] - Unix socket 名称
  2. art_method_slot - ArtMethod 槽位指针地址
  3. original_set_argv0 - 原始函数指针
  4. socket - libc socket 函数
  5. connect - libc connect 函数
  6. __errno - libc errno 函数
  7. getpid - libc getpid 函数
  8. getppid - libc getppid 函数
  9. sendmsg - libc sendmsg 函数
  10. recv - libc recv 函数
  11. close - libc close 函数
  12. raise - libc raise 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1. 前言
   1.1 版本说明
   1.2 参考文章
 
2. 历史Hook方案
   2.1 旧方案问题
   2.2 新方案
 
3. 流程图
 
4. Zymbiote注入阶段
   4.1 函数调用链
   4.2 补丁
       4.2.1 Payload补丁
       4.2.2 ArtMethod补丁
   4.3 Boot Heap
       4.3.1 Boot Image文件
   4.4 ensure_loaded (初始化)
   4.5 inject_zymbiote (核心注入逻辑)
   4.6 do_prepare_zymbiote_injection(核心准备逻辑)
       4.6.1 调用链
       4.6.2 初始化变量
       4.6.3 解析进程内存映射
       4.6.4 获取libc与runtime信息
       4.6.5 定位ArtMethod信息
       4.6.6 填充payload
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct _FridaApi
{
  char name[64];
 
  void    ** art_method_slot;
  void    (* original_set_argv0) (JNIEnv * env, jobject clazz, jstring name);
 
  int     (* socket) (int domain, int type, int protocol);
  int     (* connect) (int sockfd, const struct sockaddr * addr, socklen_t addrlen);
  int *   (* __errno) (void);
  pid_t   (* getpid) (void);
  pid_t   (* getppid) (void);
  ssize_t (* sendmsg) (int sockfd, const struct msghdr * msg, int flags);
  ssize_t (* recv) (int sockfd, void * buf, size_t len, int flags);
  int     (* close) (int fd);
  int     (* raise) (int sig);
};
  • 14eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6E0M7q4)9J5k6i4N6W2K9i4S2A6L8W2)9J5k6i4q4I4i4K6u0W2j5$3!0E0i4K6u0r3M7#2)9K6c8W2)9#2k6W2)9#2k6X3u0A6P5W2)9K6c8p5#2*7g2e0y4y4g2q4V1#2e0i4A6c8P5p5#2m8i4K6y4p5i4K6y4p5i4K6t1$3j5h3#2H3i4K6y4n7L8h3W2V1i4K6y4p5x3U0t1@1y4K6b7^5y4e0l9H3x3W2)9J5y4X3q4E0M7q4)9K6b7X3W2V1P5q4)9K6c8o6q4Q4x3U0k6S2L8i4m8Q4x3@1u0K6L8W2)9K6c8o6q4X3x3r3f1%4x3o6M7%4k6U0p5&6x3U0M7H3x3$3c8T1j5K6l9%4z5e0x3#2j5e0j5K6x3e0p5K6z5e0R3H3i4K6t1$3j5h3#2H3i4K6y4n7j5$3S2C8M7$3#2Q4x3@1c8X3k6o6y4X3y4o6f1%4x3h3p5$3x3K6j5K6k6e0k6X3z5e0x3$3z5r3q4S2x3e0R3H3z5o6M7$3j5K6j5H3x3U0N6U0j5e0t1#2k6U0l9@1z5h3t1#2x3U0l9K6x3o6f1K6z5h3q4V1j5h3f1%4k6o6b7&6x3U0c8T1z5e0R3%4y4o6V1H3y4h3x3%4z5o6p5$3y4r3b7I4i4K6t1$3j5h3#2H3i4K6y4n7L8i4m8K6K9r3q4J5k6g2)9K6c8o6q4Q4x3U0k6S2L8i4m8Q4x3@1u0K6j5$3g2F1k6g2)9K6c8o6t1K6i4K6t1$3j5h3#2H3i4K6y4n7M7%4u0U0K9h3c8Q4x3@1b7H3x3e0t1I4d9q4N6V1e0Y4f1K6d9@1y4W2b7Y4y4X3e0#2t1%4k6s2y4%4k6U0k6Q4x3U0k6S2L8i4m8Q4x3@1u0K6K9r3q4J5k6i4u0Q4y4h3k6K6K9r3q4J5k6h3W2F1k6X3!0Q4x3@1b7H3z5e0f1%4z5h3x3J5y4K6l9#2x3$3t1%4j5e0m8S2j5e0u0W2k6r3j5@1z5o6V1H3y4U0f1@1x3o6t1^5x3q4)9J5y4X3q4E0M7q4)9K6b7Y4y4Z5j5i4u0W2M7W2)9#2k6Y4y4Z5j5i4u0W2K9h3&6X3L8#2)9#2k6X3k6A6M7Y4y4@1i4K6y4p5j5$3p5&6z5e0m8X3k6r3b7I4z5r3t1I4z5h3q4S2k6o6q4T1j5e0W2U0z5o6M7K6z5o6p5@1z5h3u0X3k6r3u0Q4x3U0y4J5k6l9`.`.
  • 需要 Zygote 内注入 agent
    • 需要 ptrace 暂停/恢复线程
    • 在所有子进程中留下痕迹
    • 需要隐藏 FD,否则 Zygote 会 abort
  • 依赖于 syscall tracing
    • Ptrace 的 syscall tracing 逻辑复杂且脆弱
  • Teardown 路径不安全
    • 难以保证在各种时机正确回收
    • 容易在 Zygote 和 system_server 中造成不稳定
  • 需要 ptrace 暂停/恢复线程
  • 在所有子进程中留下痕迹
  • 需要隐藏 FD,否则 Zygote 会 abort
  • Ptrace 的 syscall tracing 逻辑复杂且脆弱
  • 难以保证在各种时机正确回收
  • 容易在 Zygote 和 system_server 中造成不稳定

传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 8
支持
分享
最新回复 (3)
雪    币: 104
活跃值: (7531)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
tql
2天前
0
雪    币: 3656
活跃值: (3386)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
tql
1天前
0
雪    币: 9
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
牛逼
1天前
0
游客
登录 | 注册 方可回帖
返回