【Android安全- Frida 17.6 最新Hook注入方案分析(Zymbiote注入机制)】此文章归类为:Android安全。
本文主要是想要了解清楚 Frida 在新版本有什么地方不一样了。本文主要还是着重在 Zymbiote的注入机制上,其他相关板块后续再出文章分析。
然后在阅读 Frida 源码的过程也发现了很多有意思的小trick,鉴于本人水平有限如果有问题请各位大佬指出。
版本说明:
Frida 17.6
参考文章:
- f0eK9s2c8@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`.`.
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 |

Zymbiote 是 Frida 在 Android 上实现轻量级 Zygote Hook 的机制,用于在应用进程启动时注入 Frida
命名来源:Zymbiote = Zygote + Symbiote(共生体),表示与 Zygote 进程共生。
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
后文会出现补丁相关的代码,所以这里解释一下。其实很好理解,这里补丁就是我们patch上去的代码
补丁目前分为两种类型
这里很好理解,payload相当于我们传入的二进制代码,然后我们在 ArtMethod 改对应属性指向我们的payload,这样他们执行函数的时候就会先执行我们的payload
- 内容:编译后的 zymbiote 二进制代码(约 800-900 字节)
- 位置:libstagefright.so 的最后一页(可执行映射)(这里为什么选择这个so捏,因为这是系统库,通常已加载)
- 作用:在目标进程中放置可执行代码
// 将 payload 代码写入到可执行内存 patches.apply(payload, process_memory, payload_base);
- 内容:指针值(4 或 8 字节)
- 位置:boot heap 中的 ArtMethod 槽位
- 作用:将函数指针从原始函数改为指向 payload
// 替换 ArtMethod 槽位指针 patches.apply(replaced_ptr, process_memory, art_method_slot);
这个概念在后面也会出现,我们这里可以解释一下。
Boot Heap = Android ART 运行时在启动时预加载的内存区域。
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_heap 就是判断路径是否包含boot.art,boot-framework.art,dalvik-LinearAlloc这些 Boot Image
关于 Boot Image 我们可以后续再出一个文章再详细介绍这个地方。
Android 启动时:
- Zygote 进程启动
- 加载 boot.art → 映射到内存 (boot heap)
- 加载 boot-framework.art → 映射到内存 (boot heap)
- 预编译的类和方法信息都在这里
- ArtMethod 对象也存储在这里 ✅
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
统一初始化入口,确保 Socket 服务器就绪,并发注入多个 Zygote 进程
这里 wait_async 再返回就是避免让程序以为是返回成功了的,我们等待其他实列完成。
创建 Unix Socket 服务器
枚举并注入所有 Zygote 进程
查找 zygote、zygote64、usap32、usap64 ( Unspecialized APP Process 也就是Android 的预 fork 进程池机制,用于加速应用启动) 这样尽可能遍历到了所有进程类型,只要有一个新的进程产生就一定会被hook,因为父进程中相关的函数已经被我们修改了。这里的实现非常优雅
传统方式(无USAP):
应用启动请求 → Zygote fork → 初始化 → 应用运行
↑ 耗时较长
USAP方式:
Zygote 预fork → USAP池 (空闲进程)
应用启动请求 → 从USAP池取进程 → 快速配置 → 应用运行
↑ 更快
对每个进程调用 do_inject_zygote_agent()
使用并发注入,等待所有完成
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);
}
}
我们可以看见 do_inject_zygote_agent 包装了我们的 inject_zymbiote
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);
}
}
这里算得上核心注入最核心的部分,也就是解析进程内存映射,解析符号地址,定位 ArtMethod 槽位,准备 Payload 数据。
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),
});
}
}
其实就是校验我们必须需要的信息是否存在,然后再根据我们的 libc_path 与 runtime_path 找到 libc 与 runtime 的基地址。然后对应进行符号解析得到 ELF 对象,然后查找我们的 setArgV0Native 函数(_Z27android_os_Process_setArgV0P7_JNIEnvP8_jobjectP8_jstring)。
为什么需要查找我们的 setArgV0Native 函数捏?因为这是后续查找ArtMethod的关键,artMethod 的函数指针就是存储的 setArgV0Native 函数地址,所以我们可以通过解析符号表定位到 setArgV0Native 函数就可以间接定位到 ArtMethod
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");
根据前面获取的 setArgV0Native 函数指针在 boot_heap 区域搜索就可以找到我们的 ArtMethod
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");
这里搜索frida-zymbiote-00000000000000000000000000000000,是因为在 zymbiote 中初始化name就是这个名字
static volatile const FridaApi frida =
{
.name = "/frida-zymbiote-00000000000000000000000000000000",
};
只要找到这个位置,那么就根据这个位置后面填写我们对应的数据即可,完善这个 FridaApi 结构体
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;
}
其实就是将Payload需要的libc函数地址,ArtMethod函数地址填充进去,方便后续运作
- name[64] - Unix socket 名称
- art_method_slot - ArtMethod 槽位指针地址
- original_set_argv0 - 原始函数指针
- socket - libc socket 函数
- connect - libc connect 函数
- __errno - libc errno 函数
- getpid - libc getpid 函数
- getppid - libc getppid 函数
- sendmsg - libc sendmsg 函数
- recv - libc recv 函数
- close - libc close 函数
- raise - libc raise 函数
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);}; |
更多【Android安全- Frida 17.6 最新Hook注入方案分析(Zymbiote注入机制)】相关视频教程:www.yxfzedu.com