Apatch内核模块搭建到隐藏Frida注入
zsk Lv4

APatch是什么

  • 一种基于内核修补(Kernel Patching)的 Android Root 方案,核心是 KernelPatch
  • 提供内核模块机制,支持动态装载、动态卸载、符号管理等
  • 提供 syscall hook、inline hook 接口,方便定制修改内核
  • 与 Magisk 的区别
    APatch 直接修改内核实现功能,而非通过 Zygote 注入用户空间。更适合深度内核级定制(如隐藏 Frida)。

内核分析与提取

系统调用(System Call)

  • 作用:用户态与内核态交互的安全接口
  • Hook 意义:拦截系统调用是隐藏痕迹的核心手段
    (如监控文件访问openat、进程信息读取read
  • 定位关键:需通过内核符号表查找系统调用处理函数
    (如__arm64_sys_openat

arm64系统调用符号表: https://blog.xhyeax.com/2022/04/28/arm64-syscall-table/

内核符号(Kernel Symbols)

  • 查看符号导出配置:zcat /proc/config.gz | grep CONFIG_KALLSYMS
  • 获取所有导出符号: cat /proc/kallsyms

内核源码:https://cs.android.com/

如何提取kernel内核?(物理设备)

提取 boot.img -> dd 提取kernel -> magiskboot 重建符号表 -> vmlinux-to-elf IDA载入

  1. 查看boot挂载 ls /dev/block/bootdevice/by-name -l |grep boot
    1
    2
    3
    blueline:/ $ ls /dev/block/bootdevice/by-name -l |grep boot
    lrwxrwxrwx 1 root root 16 1970-04-30 12:52 boot_a -> /dev/block/sda11
    lrwxrwxrwx 1 root root 16 1970-04-30 12:52 boot_b -> /dev/block/sda12
  2. dd 命令提取 dd if=/dev/block/sda11 of=/sdcard/boot_a.img (一般默认是第一个)

    root权限下执行:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    blueline:/ $ dd if=/dev/block/sda11 of=/sdcard/boot_a.img
    dd: /dev/block/sda11: Permission denied
    0+0 records in
    0+0 records out
    0 bytes (0 B) copied, 0.000146 s, 0 B/s
    1|blueline:/ $ su
    blueline:/ # dd if=/dev/block/sda11 of=/sdcard/boot_a.img
    131072+0 records in
    131072+0 records out
    67108864 bytes (64 M) copied, 13.041891 s, 4.9 M/s
  3. 搜索 magiskboot 程序
    1
    2
    3
    blueline:/ # find / -name magiskboot 2>/dev/null
    /data/adb/magisk/magiskboot
    /data/adb/ap/bin/magiskboot
  4. 提取kernel: magiskboot unpack /sdcard/boot_a.img
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    blueline:/data/adb/magisk # ./magiskboot unpack /sdcard/boot_a.img
    Parsing boot image: [/sdcard/boot_a.img]
    HEADER_VER [1]
    KERNEL_SZ [20902609]
    RAMDISK_SZ [9380449]
    SECOND_SZ [0]
    RECOV_DTBO_SZ [0]
    OS_VERSION [9.0.0]
    OS_PATCH_LEVEL [2019-07]
    PAGESIZE [4096]
    NAME []
    CMDLINE [console=ttyMSM0,115200n8 androidboot.console=ttyMSM0 printk.devkmsg=on msm_rtb.filter=0x237 ehci-hcd.park=3 service_locator.enable=1 firmware_class.path=/vendor/firmware cgroup.memory=nokmem lpm_levels.sleep_disabled=1 usbcore.autosuspend=7 buildvariant=user]
    CHECKSUM [9183e0d7f1dd8c679304844924b78f226ae61664000000000000000000000000]
    KERNEL_DTB_SZ [862480]
    KERNEL_FMT [lz4]
    RAMDISK_FMT [gzip]
    unexpected ASN.1 DER tag: expected SEQUENCE, got APPLICATION [1] (primitive)
    VBMETA
  5. 使用vmlinux-to-elf把内核转换成可分析的kernel.elf
    将 kernel push到pc端使用vmlinux-to-elf工具, https://github.com/marin-m/vmlinux-to-elf.git
    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
    ➜  vmlinux-to-elf git:(master) ✗ ./vmlinux-to-elf kernel kernel.elf
    /Users/zskkk/Downloads/vmlinux-to-elf/vmlinux_to_elf/elf_symbolizer.py:101: SyntaxWarning: invalid escape sequence '\('
    """
    [+] Version string: Linux version 4.9.148-g02e4d6af1113-ab5555671 (android-build@abfarm032) (Android clang version 5.0.1 (https://us3-mirror-android.googlesource.com/toolchain/clang 00e4a5a67eb7d626653c23780ff02367ead74955) (https://us3-mirror-android.googlesource.com/toolchain/llvm ef376ecb7d9c1460216126d102bb32fc5f73800d) (based on LLVM 5.0.1svn)) #0 SMP PREEMPT Fri May 10 20:41:11 UTC 2019
    [+] Guessed architecture: aarch64 successfully in 0.58 seconds
    [+] Found relocations table at file offset 0x26f19c0 (count=218991)
    [+] Found kernel text candidate: 0xffffff8008000000
    WARNING! bad rela offset ffffff800b061810
    [+] Found kallsyms_token_table at file offset 0x01e97900
    [+] Found kallsyms_token_index at file offset 0x01e97d00
    [+] Found kallsyms_markers at file offset 0x01e96300
    [+] Found kallsyms_names at file offset 0x01c34a00
    [+] Found kallsyms_num_syms at file offset 0x01c34900
    [!] WARNING: Less than half (0%) of offsets are negative
    You may want to re-run this utility, overriding the relative base
    [!] WARNING: More than half (100%) of offsets look like absolute addresses
    [!] You may want to re-run this utility, overriding the relative base
    [i] Note: sometimes there is junk at the beginning of the kernel and the load address is not the guessed
    base address provided. You may need to play around with different load addresses to get everything
    to line up. There may be some decent tables in the kernel with known patterns to line things up
    heuristically, but I have not explored this yet.
    [i] Negative offsets overall: 0 %
    [i] Null addresses overall: 0 %
    [+] Found kallsyms_offsets at file offset 0x01b84ffc
    [+] Base address fallback, using first_symbol_virtual_address (37e1000)
    [+] Successfully wrote the new ELF kernel to kernel.elf

如何提取kernel内核?(AARCH64模拟器)

  1. 直接在宿主文件系统找到压缩镜像文件 /Users/xxxx/Library/Android/sdk/system-images/android-34/google_apis/arm64-v8a/kernel-ranchu
  2. 添加 .gz,然后用 gunzip kernel-ranchu.gz 解压,得到 kernel-ranhu
    1
    2
    3
    4
    cp kernel-ranchu kernel-ranchu_copy.gz
    gunzip kernel-ranchu.gz
    或者一行:
    gunzip -c kernel-ranchu > kernel
  3. kernel-ranchu 就是解压后的 kernel 文件,用 vmlinux-to-elf 处理即可得到带符号的内核elf

如何安装APatch(物理设备)

参考官方文档:https://apatch.dev/zh_CN/install.html
image

如何安装APatch(模拟器设备)

创建设备,进到对应的镜像目录下,如:

/Users/zskkk/Library/Android/sdk/system-images/android-34/google_apis/arm64-v8a

由于虚拟机没有boot.img,使用mkbootimg工具对 kernel-ranchu 和 ramdisk.img 合并成 boot.img

https://android.googlesource.com/platform/system/tools/mkbootimg

命令:

1
python /Users/zskkk/Downloads/mkbootimg/mkbootimg.py  --kernel kernel-ranchu --ramdisk ramdisk.img -o boot.img

将 boot.img push到设备上进行修补后,使用 unpack_bootimg.py 对boot.img进行解包,如果使用下面的需要进到根目录执行 make

https://github.com/osm0sis/mkbootimg.git

1
./unpackbootimg -i /Users/zskkk/Downloads/apatch_patched_11039_0.11.2_qfyb.img
1
2
3
4
5
6
7
8
9
➜  mkbootimg git:(master) ✗ ls
apatch_patched_11039_0.11.2_qfyb.img-base apatch_patched_11039_0.11.2_qfyb.img-ramdisk mincrypt
apatch_patched_11039_0.11.2_qfyb.img-board apatch_patched_11039_0.11.2_qfyb.img-ramdisk_offset mkbootimg
apatch_patched_11039_0.11.2_qfyb.img-cmdline apatch_patched_11039_0.11.2_qfyb.img-second_offset mkbootimg.c
apatch_patched_11039_0.11.2_qfyb.img-hashtype apatch_patched_11039_0.11.2_qfyb.img-tags_offset mkbootimg.o
apatch_patched_11039_0.11.2_qfyb.img-header_version bootimg.h NOTICE
apatch_patched_11039_0.11.2_qfyb.img-kernel libmincrypt unpackbootimg
apatch_patched_11039_0.11.2_qfyb.img-kernel_offset libmincrypt.a unpackbootimg.c
apatch_patched_11039_0.11.2_qfyb.img-pagesize Makefile unpackbootimg.o
1
2
3
4
5
mv apatch_patched_11039_0.11.2_qfyb.img-kernel kernel-ranchu
mv apatch_patched_11039_0.11.2_qfyb.img-ramdisk ramdisk.img

cp ramdisk.img /Users/zskkk/Library/Android/sdk/system-images/android-34/google_apis/arm64-v8a/
cp kernel-ranchu /Users/zskkk/Library/Android/sdk/system-images/android-34/google_apis/arm64-v8a/

然后启动设备使用禁止快照

1
2
3
➜  emulator pwd
/Users/zskkk/Library/Android/sdk/emulator
➜ emulator ./emulator -avd Pixel_6_API_34 -no-snapshot

Kernel Patch Module开发

环境:
AArch64 交叉工具链
下载地址:https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads

  • host: Macos / Linux
  • target: bare-metal target(aarch64-none-elf)

下载后解压到某个位置,设置环境变量

export TARGET_COMPILE=/Users/xxx/arm-gnu-toolchain/bin/aarch64-none-elf-

IDE配置(VSCode + Clangd):

  • 安装bear:生成clangd所需的辅助文件 compile_commands.json
  • bear 从 make 命令运行生成

kpm hello world模块

目录结构

kpm模块组成

模块描述,导入 #include <kpmodule.h> 后定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
///< The name of the module, each KPM must has a unique name.
KPM_NAME("kpm-inject-hide");

///< The version of the module.
KPM_VERSION("1.0.0");

///< The license type.
KPM_LICENSE("GPL v2");

///< The author.
KPM_AUTHOR("Zskkk");

///< The description.
KPM_DESCRIPTION("inject-hide: init test");

装载回调 / 卸载回调 / 控制回调

  • 回调函数使用如下宏静态注册
    1
    2
    3
    4
    KPM_INIT(inject_hide_init); // 装载回调
    KPM_CTL0(inject_hide_control0); // 控制0回调
    KPM_CTL1(inject_hide_control1); // 控制1回调
    KPM_EXIT(inject_hide_exit); // 卸载回调

装载回调,执行模块初始化代码,比如安装 Hook

1
2
3
4
5
6
7
8
9
10
static long inject_hide_init(const char *args, const char *event, void *__user reserved)
{
pr_info("inject-hide init, event: %s, args: %s\n", event, args);
pr_info("kernelpatch version: %x\n", kpver);

frida_hide_install();

pr_info("inject-hide install\n");
return 0;
}

卸载回调,执行模块卸载代码,比如卸载 Hook

1
2
3
4
5
6
static long inject_hide_exit(void *__user reserved)
{
frida_hide_uninstall();
pr_info("inject-hide exit\n");
return 0;
}

控制回调,用户态向kpm模块传递控制参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static long inject_hide_control0(const char *args, char *__user out_msg, int outlen)
{
pr_info("inject-hide control0, args: %s\n", args);
char echo[64] = "echo: ";
strncat(echo, args, 48);
compat_copy_to_user(out_msg, echo, sizeof(echo));
return 0;
}

static long inject_hide_control1(void *a1, void *a2, void *a3)
{
pr_info("inject-hide control1, a1: %llx, a2: %llx, a3: %llx\n", a1, a2, a3);
return 0;
}

完整main.c

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <compiler.h>
#include <kpmodule.h>
#include <linux/printk.h>
#include <common.h>
#include <kputils.h>
#include <linux/string.h>

#include "example/frida_hide.h"

///< The name of the module, each KPM must has a unique name.
KPM_NAME("kpm-inject-hide");

///< The version of the module.
KPM_VERSION("1.0.0");

///< The license type.
KPM_LICENSE("GPL v2");

///< The author.
KPM_AUTHOR("Zskkk");

///< The description.
KPM_DESCRIPTION("inject-hide: init test");


/**
* @brief initialization
* @details
*
* @param args
* @param reserved
* @return int
*/
static long inject_hide_init(const char *args, const char *event, void *__user reserved)
{
pr_info("inject-hide init, event: %s, args: %s\n", event, args);
pr_info("kernelpatch version: %x\n", kpver);

frida_hide_install();

pr_info("inject-hide install\n");
return 0;
}

static long inject_hide_control0(const char *args, char *__user out_msg, int outlen)
{
pr_info("inject-hide control0, args: %s\n", args);
char echo[64] = "echo: ";
strncat(echo, args, 48);
compat_copy_to_user(out_msg, echo, sizeof(echo));
return 0;
}

static long inject_hide_control1(void *a1, void *a2, void *a3)
{
pr_info("inject-hide control1, a1: %llx, a2: %llx, a3: %llx\n", a1, a2, a3);
return 0;
}

static long inject_hide_exit(void *__user reserved)
{
frida_hide_uninstall();
pr_info("inject-hide exit\n");
return 0;
}

KPM_INIT(inject_hide_init); // 装载回调
KPM_CTL0(inject_hide_control0); // 控制0回调
KPM_CTL1(inject_hide_control1); // 控制1回调
KPM_EXIT(inject_hide_exit); // 卸载回调

kpm模块编译

  • 首次编译可以用 bear – make -B 生成clangd补全文件

会生成一个空的 compile_commands.json 文件,因为先使用了交叉编译工具的gcc,它识别不了,先用gcc来编译, 先将 Makefile 前三行注释掉

1
2
3
# ifndef TARGET_COMPILE
# $(error TARGET_COMPILE not set)
# endif

将环境变量删除,这样就能使用系统的gcc,就会被bear识别到,生成正确的compile_commands.json,只需要生成一次,有报错不影响生成文件

1
2
3
export TARGET_COMPILE=         
make
bear -- make -B

再把生成好的compile_commands.json里面 “/usr/bin/gcc” 改为交叉工具的gcc “/Users/zskkk/arm-gnu-toolchain/bin/aarch64-none-elf-gcc”
把注释的还原,把环境变量还原

  • 编译模块
    1
    2
    export TARGET_COMPILE=/Users/xxx/arm-gnu-toolchain/bin/aarch64-none-elf-
    make
  • 编译成功后得到 inject-hide.kpm 模块
  • make push 命令自动编译 + 推送到 /sdcard/inject-hide.kpm

kpm模块加载

  • KPModele(内核模块) 界面,右下角,加载
    image

kpm模块日志查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
➜  ~ adb logcat | grep inject-hide
07-13 03:27:42.006 4247 4247 D PickerActionHandler: onFinished() [content://com.android.externalstorage.documents/document/primary%3Ainject-hide.kpm]
07-13 03:27:42.047 1629 1681 I MediaProvider: Open with lower FS for /storage/emulated/0/inject-hide.kpm. Uid: 10093
07-13 03:27:42.054 4197 4197 W : [+] KP D name: kpm-inject-hide
07-13 03:27:42.054 4197 4197 W : [+] KP D description: inject-hide: init test
07-13 03:27:42.054 4197 4197 I : inject-hide init, event: load-file, args: (null)
07-13 03:27:42.054 4197 4197 I inject-hide: svc_hook_example_install
07-13 03:27:42.054 4197 4197 I inject-hide: hook openat success
07-13 03:27:42.054 4197 4197 I : inject-hide install
07-13 03:27:42.054 4197 4197 W : [+] KP I load_module: [kpm-inject-hide] succeed with [(null)]
^C
➜ ~ adb logcat | grep KP
07-13 03:23:43.457 0 0 W : KP _ __ _ ____ _ _
07-13 03:23:43.457 0 0 W : KP Kernel pa: 40200000
07-13 03:23:43.457 0 0 W : KP Kernel va: ffffffc008000000
07-13 03:23:43.457 0 0 W : KP Kernel Version: 60117
07-13 03:23:43.457 0 0 W : KP KernelPatch Version: b02
07-13 03:23:43.457 0 0 W : KP KernelPatch Config: 2
07-13 03:23:43.457 0 0 W : KP KernelPatch Compile Time: 01:05:21 Feb 12 2025
07-13 03:23:43.457 0 0 W : KP KernelPatch link base: d000, runtime base: ffffffc0876cc000
07-13 03:23:43.457 0 0 W : KP Kernel stext prot: 50000040210783

Hide debugger

框架搭起来了,在隐藏Frida前先来隐藏debugger练手

有哪些 debugger check 方法?

  • /proc/pid/status 文件 TracerPid 字段
  • /proc/pid/status 文件 State 字段
  • /proc/pid/wchan 文件
  • /proc/pid/stat 文件

/proc/pid/status 文件 TracerPid 字段

内核源码定位:proc_pid_status

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
27
int proc_pid_status(struct seq_file *m, struct pid_namespace *ns,
struct pid *pid, struct task_struct *task)
{
struct mm_struct *mm = get_task_mm(task);

seq_puts(m, "Name:\t");
proc_task_name(m, task, true);
seq_putc(m, '\n');

task_state(m, ns, pid, task);

if (mm) {
task_mem(m, mm);
task_core_dumping(m, task);
task_thp_status(m, mm);
task_untag_mask(m, mm);
mmput(mm);
}
task_sig(m, task);
task_cap(m, task);
task_seccomp(m, task);
task_cpus_allowed(m, task);
cpuset_task_status_allowed(m, task);
task_context_switch_counts(m, task);
arch_proc_pid_thread_features(m, task);
return 0;
}

Name 字段有 proc_task_name 函数生成,这个涉及 frida 检测
TracerPid 在 task_state 生成,task_state

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
static inline void task_state(struct seq_file *m, struct pid_namespace *ns,
struct pid *pid, struct task_struct *p)
{
struct user_namespace *user_ns = seq_user_ns(m);
struct group_info *group_info;
int g, umask = -1;
struct task_struct *tracer;
const struct cred *cred;
pid_t ppid, tpid = 0, tgid, ngid;
unsigned int max_fds = 0;

rcu_read_lock();
ppid = pid_alive(p) ?
task_tgid_nr_ns(rcu_dereference(p->real_parent), ns) : 0;

tracer = ptrace_parent(p);
if (tracer)
tpid = task_pid_nr_ns(tracer, ns);

tgid = task_tgid_nr_ns(p, ns);
ngid = task_numa_group_id(p);
cred = get_task_cred(p);

task_lock(p);
if (p->fs)
umask = p->fs->umask;
if (p->files)
max_fds = files_fdtable(p->files)->max_fds;
task_unlock(p);
rcu_read_unlock();

if (umask >= 0)
seq_printf(m, "Umask:\t%#04o\n", umask);
seq_puts(m, "State:\t");
seq_puts(m, get_task_state(p));

seq_put_decimal_ull(m, "\nTgid:\t", tgid);
seq_put_decimal_ull(m, "\nNgid:\t", ngid);
seq_put_decimal_ull(m, "\nPid:\t", pid_nr_ns(pid, ns));
seq_put_decimal_ull(m, "\nPPid:\t", ppid);
seq_put_decimal_ull(m, "\nTracerPid:\t", tpid);
seq_put_decimal_ull(m, "\nUid:\t", from_kuid_munged(user_ns, cred->uid));
seq_put_decimal_ull(m, "\t", from_kuid_munged(user_ns, cred->euid));
seq_put_decimal_ull(m, "\t", from_kuid_munged(user_ns, cred->suid));
seq_put_decimal_ull(m, "\t", from_kuid_munged(user_ns, cred->fsuid));
seq_put_decimal_ull(m, "\nGid:\t", from_kgid_munged(user_ns, cred->gid));
seq_put_decimal_ull(m, "\t", from_kgid_munged(user_ns, cred->egid));
seq_put_decimal_ull(m, "\t", from_kgid_munged(user_ns, cred->sgid));
seq_put_decimal_ull(m, "\t", from_kgid_munged(user_ns, cred->fsgid));
seq_put_decimal_ull(m, "\nFDSize:\t", max_fds);

seq_puts(m, "\nGroups:\t");
group_info = cred->group_info;
for (g = 0; g < group_info->ngroups; g++)
seq_put_decimal_ull(m, g ? " " : "",
from_kgid_munged(user_ns, group_info->gid[g]));
put_cred(cred);
/* Trailing space shouldn't have been added in the first place. */
seq_putc(m, ' ');

#ifdef CONFIG_PID_NS
seq_puts(m, "\nNStgid:");
for (g = ns->level; g <= pid->level; g++)
seq_put_decimal_ull(m, "\t", task_tgid_nr_ns(p, pid->numbers[g].ns));
seq_puts(m, "\nNSpid:");
for (g = ns->level; g <= pid->level; g++)
seq_put_decimal_ull(m, "\t", task_pid_nr_ns(p, pid->numbers[g].ns));
seq_puts(m, "\nNSpgid:");
for (g = ns->level; g <= pid->level; g++)
seq_put_decimal_ull(m, "\t", task_pgrp_nr_ns(p, pid->numbers[g].ns));
seq_puts(m, "\nNSsid:");
for (g = ns->level; g <= pid->level; g++)
seq_put_decimal_ull(m, "\t", task_session_nr_ns(p, pid->numbers[g].ns));
#endif
seq_putc(m, '\n');

seq_printf(m, "Kthread:\t%c\n", p->flags & PF_KTHREAD ? '1' : '0');
}

参数一是 struct seq_file 的结构体,seq_file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct seq_file {
char *buf;
size_t size;
size_t from;
size_t count;
size_t pad_until;
loff_t index;
loff_t read_pos;
struct mutex lock;
const struct seq_operations *op;
int poll_event;
const struct file *file;
void *private;
};
  • 类似一个 StringBuilder 的字符串缓冲区
  • buf 指向数据地址
  • count 指向数据尾部,表示当前 buf 的长度,buf 空时表示0

该缓冲区的操作函数以 seq_ 开头

  • seq_puts
  • seq_put_decimal_ull
  • seq_putc

proc_pid_status 第一个参数就是该缓冲区

maps / status 文件都使用 seq_file 缓冲区生成

为了方便使用,只需要前4个字段,4个字段的顺序是固定的

seq_put_decimal_ull(m, “\nTracerPid:\t”, tpid);

task_state 调用 seq_put_decimal_ull 把 TracerPid 放到缓冲区

seq_put_decimal_ull 是导出函数

1
2
3
emu64a:/data/local/tmp # cat /proc/kallsyms|grep seq_put_decimal_ull
0000000000000000 T seq_put_decimal_ull_width
0000000000000000 T seq_put_decimal_ull

导出函数意味着可以找到运行时地址,并hook,然后判断第二个参数是“TracerPid:”时修改第三个参数的值为0

seq_put_decimal_ull hook

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include "kallsyms.h"
#include "linux/printk.h"
#include "kpmodule.h"
#include "hook.h"
#include "stdint.h"
#include "syscall.h"
#include "compiler.h"
#include "kputils.h"
#include "linux/string.h"
#include <asm/current.h>

void * seq_put_decimal_ull = 0;

void before_seq_put_decimal_ull(hook_fargs3_t *args, void *udata)
{
if (strcmp((const char *)args->arg1, "\nTracerPid:\t") == 0 && args->arg2 != 0)
{
pr_info("inject-hide: seq_put_decimal_ull called, TracerPid: %d\t\n", args->arg2);
args->arg2 = 0; // 将 TracerPid 设置为 0,隐藏调试器
}
}

void debugger_hide_install(void)
{
seq_put_decimal_ull = (void *) kallsyms_lookup_name("seq_put_decimal_ull");
if (!seq_put_decimal_ull) {
pr_warn("inject-hide: debugger_hide_install: seq_put_decimal_ull not found\n");
} else {
hook_err_t err = hook_wrap3(seq_put_decimal_ull, before_seq_put_decimal_ull, NULL, NULL);
if (err) {
pr_err("inject-hide: hook_wrap3 failed %d\n", err);
seq_put_decimal_ull = 0;
}
}
}

void debugger_hide_uninstall(void)
{
if (seq_put_decimal_ull) {
unhook(seq_put_decimal_ull);
}
}
  • before hook,注意是3个参数
  • 判断确实是添加了 TracerPid,并将参数3点值强制设置为0

使用IDA对随便一个app进行调试,查看它的 status TracerPid 是0,adb logcat 查看日志hook修改成功

1
07-01 01:25:45.426  5530  5530 I inject-hide: seq_put_decimal_ull called, TracerPid: 4913

/proc/pid/status 文件 State 字段

1
2
3
4
5
emu64a:/ # cat /proc/5710/status
Name: me.bmax.apatch
Umask: 0077
State: t (tracing stop)
...

内核源码定位:task_state

1
2
seq_puts(m, "State:\t");
seq_puts(m, get_task_state(p));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static const char * const task_state_array[] = {

/* states in TASK_REPORT: */
"R (running)", /* 0x00 */
"S (sleeping)", /* 0x01 */
"D (disk sleep)", /* 0x02 */
"T (stopped)", /* 0x04 */
"t (tracing stop)", /* 0x08 */
"X (dead)", /* 0x10 */
"Z (zombie)", /* 0x20 */
"P (parked)", /* 0x40 */

/* states beyond TASK_REPORT: */
"I (idle)", /* 0x80 */
};

static inline const char *get_task_state(struct task_struct *tsk)
{
BUILD_BUG_ON(1 + ilog2(TASK_REPORT_MAX) != ARRAY_SIZE(task_state_array));
return task_state_array[task_state_index(tsk)];
}
  • get_task_state 从 task_state_array 获取静态字符串返回
  • 调用 seq_puts 写入

前8个是 TASK_REPORT 的状态(可用于用户态工具报告):

位标志(bitmask) 状态字符 含义
0x00 R Running(运行中)
0x01 S Sleeping(可中断的睡眠)
0x02 D Disk sleep(不可中断睡眠)
0x04 T Stopped(停止,如被 SIGSTOP)
0x08 t Tracing stop(被调试器跟踪时停止)
0x10 X Dead(任务终止,尚未清理)
0x20 Z Zombie(僵尸进程)
0x40 P Parked(暂时不参与调度)

后面是非 TASK_REPORT 的特殊状态:

位标志(bitmask) 状态字符 含义
0x80 I Idle(空闲任务)

seq_puts hook

1
2
3
4
5
6
7
8
9
10
void * seq_puts = 0;

void before_seq_puts(hook_fargs2_t *args, void *udata)
{
if (strcmp((const char *)args->arg1, "S (sleeping)") == 0)
{
pr_info("inject-hide: seq_puts called, str: %s\n", (const char *)args->arg1);
args->arg1 = (uint64_t)"R (running)";
}
}

效果

1
2
3
4
5
emu64a:/ # cat /proc/5710/status
Name: me.bmax.apatch
Umask: 0077
State: R (running)
...

/proc/pid/wchan 文件

内核源码定位:proc_pid_wchan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static int proc_pid_wchan(struct seq_file *m, struct pid_namespace *ns,
struct pid *pid, struct task_struct *task)
{
unsigned long wchan;
char symname[KSYM_NAME_LEN];

if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS))
goto print0;

wchan = get_wchan(task);
if (wchan && !lookup_symbol_name(wchan, symname)) {
seq_puts(m, symname);
return 0;
}

print0:
seq_putc(m, '0');
return 0;
}
  • wchan 文件内容是内核中进程休眠位置对应的符号名称,比如等待调试器就是 ptrace_stop,正常情况是0

没被调试

1
2
emu64a:/ # cat /proc/5710/wchan
do_epoll_wait

被调试

1
2
emu64a:/ # cat /proc/5710/wchan
ptrace_stope

方法1:before hook,向 seq_file 写入0字符,count+1
方法2:after hook,覆盖 seq_file 数据,写入 0,把 count 改成1

proc_pid_wchan hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct seq_file{
char *buf;
size_t size;
size_t from;
size_t count;
};

void * proc_pid_wchan = 0;

void aftet_proc_pid_wchan(hook_fargs4_t *args, void *udata)
{
struct seq_file *m = (struct seq_file *)args->arg0;
if (m && m->buf) {
if (strcmp((const char *)m->buf, "ptrace_stop") == 0) {
pr_info("inject-hide: proc_pid_wchan called, wchan: %s\n", m->buf);
m->buf[0] = '0';
m->buf[1] = '\0';
m->count = 1;
}
}
}

/proc/pid/stat 文件

非调试情况

1
2
3
4
emu64a:/ # ps -A|grep me.bmax.apatch
u0_a192 4828 383 15474964 149904 do_epoll_wait 0 S me.bmax.apatch
emu64a:/ # cat /proc/4828/stat
4828 (me.bmax.apatch) S 383 383 0 0 -1 4194624 38678 0 8 0 54 50 0 0 10 -10 27 0 56247 15846363136 37476 18446744073709551615 392129871872 392129875760 549365500736 0 0 0 4612 1 1073775864 0 0 0 17 2 0 0 0 0 0 392129937408 392129938680 392380674048 549365501849 549365501948 549365501948 549365506014 0

调试情况

1
2
3
4
emu64a:/ # ps -A|grep me.bmax.apatch
u0_a192 4828 383 15474964 150336 0 0 t me.bmax.apatch
emu64a:/ # cat /proc/4828/stat
4828 (me.bmax.apatch) t 383 383 0 0 -1 4194624 38678 0 8 0 54 50 0 0 10 -10 27 0 56247 15846363136 37584 18446744073709551615 392129871872 392129875760 549365500736 0 0 0 4612 1 1073775864 0 0 0 17 2 0 0 0 0 0 392129937408 392129938680 392380674048 549365501849 549365501948 549365501948 549365506014 0

或者 ps -A |grep me.bmax.apatch
内核源码定位:do_task_stat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int do_task_stat(struct seq_file *m, struct pid_namespace *ns,
struct pid *pid, struct task_struct *task, int whole)
{
......
state = *get_task_state(task);
......
seq_put_decimal_ull(m, "", pid_nr_ns(pid, ns));
seq_puts(m, " (");
proc_task_name(m, task, false);
seq_puts(m, ") ");
seq_putc(m, state);
......
return 0;
}
  • seq_putc 放入1个字节
  • 不能 hook seq_putc
  • 不能特异性区分

怎么办?

  • 检测 seq_file 内容
  • after hook do_task_stat 检测右括号+空格后的1字节是否为 t

do_task_stat hook
/proc/[pid]/stat 是由 do_task_stat()struct seq_file 写入格式化字符串,我们必须在它完成写入后,修改其输出缓冲区(m->buf)内容

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
void after_do_task_stat(hook_fargs5_t *args, void *udata)
{
struct seq_file *m = (struct seq_file *)args->arg0;
if (!m || !m->buf || m->count == 0)
return;

char *buf = m->buf;
size_t len = m->count;

// 例子:4828 (me.bmax.apatch) t
// 找 ')' 后第一个非空字符,判断是否是 't'
char *end_paren = strchr(buf, ')');
if (!end_paren || end_paren >= buf + len)
return;

char *state_ptr = end_paren + 1;
while (*state_ptr == ' ' && state_ptr < buf + len)
state_ptr++;

// 替换 t 为 S
if (*state_ptr == 't') {
*state_ptr = 'S';
pr_info("inject-hide: modified state from 't' to 'S' in /proc/stat\n");
}
}

Hide Frida

有哪些 frida check 方法?

  • maps 文件特征
  • 内存特征
  • 线程名特征
  • D-BUS 端口特征
    image

maps 文件特征与内存特征

使用frida对app注入后,/proc/pid/maps 文件内容描述了进程的内存布局信息,frida 会注入一个 frida-agent 模块,因此在 maps 里面能找到对应的内存映射信息。

1
2
3
4
5
emu64a:/ # cat /proc/11727/maps |grep frida
7736365000-7736d7c000 r--p 00000000 00:01 2898 /memfd:frida-agent-64.so (deleted)
7736d7d000-7737aaa000 r-xp 00a17000 00:01 2898 /memfd:frida-agent-64.so (deleted)
7737aaa000-7737b7a000 r--p 01743000 00:01 2898 /memfd:frida-agent-64.so (deleted)
7737b7b000-7737b97000 rw-p 01813000 00:01 2898 /memfd:frida-agent-64.so (deleted)

内存特征,可以扫描 maps 里面的内存,寻找 frida 特征,比如:frida-rpc、FridaScriptEngine、以及一些不可见的 bytes 特征,都在 frida-agent 段里面。所以可以直接在内核层面修改maps文化,隐藏frida相关的内存,同时绕过maps检测和内存特征检测

负责写入 /proc/self/maps 文件的内核函数为 show_map_vma,内核源码定位:show_map_vma

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
27
28
29
30
31
32
33
34
35
36
37
static void
show_map_vma(struct seq_file *m, struct vm_area_struct *vma)
{
const struct path *path;
const char *name_fmt, *name;
vm_flags_t flags = vma->vm_flags;
unsigned long ino = 0;
unsigned long long pgoff = 0;
unsigned long start, end;
dev_t dev = 0;

if (vma->vm_file) {
const struct inode *inode = file_user_inode(vma->vm_file);

dev = inode->i_sb->s_dev;
ino = inode->i_ino;
pgoff = ((loff_t)vma->vm_pgoff) << PAGE_SHIFT;
}

start = vma->vm_start;
end = vma->vm_end;
// 输出内存区间范围,标志等
show_vma_header_prefix(m, start, end, flags, pgoff, dev, ino);
get_vma_name(vma, &path, &name, &name_fmt); // 获取内存区间 name
// 输出路径相关
if (path) {
seq_pad(m, ' ');
seq_path(m, path, "\n");
} else if (name_fmt) {
seq_pad(m, ' ');
seq_printf(m, name_fmt, name);
} else if (name) {
seq_pad(m, ' ');
seq_puts(m, name);
}
seq_putc(m, '\n');
}

show_map_vma 函数输出 maps 文件中的一段(一行)内存区域

使用哪种hook?

  • after hook 中如何从 seq_file 中删除当前 show_map_vma 输出的全部数据,而不影响其他调用数据?
  • before hook 记录 seq_file 中的count,在 after hook 中设置 count 为记录值,效果上等价删除

show_map_vma hook

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 内核环境下的 memmem 实现
static void *memmem_local(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen)
{
if (!haystack || !needle || haystacklen < needlelen || needlelen == 0)
return NULL;
for (size_t i = 0; i <= haystacklen - needlelen; ++i) {
if (memcmp((const char *)haystack + i, needle, needlelen) == 0)
return (void *)((const char *)haystack + i);
}
return NULL;
}

// 检查 seq_file 缓冲区中是否包含敏感关键词
static int is_hiden_module(struct seq_file *m)
{
if (!m || !m->buf || m->count == 0) return false;
// 需要隐藏的关键词列表
static const char *keywords[] = {
"frida-agent",
"frida",
"gum-js-loop",
"GumJS",
"gmain",
NULL
};

for (int i = 0; keywords[i] != NULL; ++i) {
if (memmem_local(m->buf, m->count, keywords[i], strlen(keywords[i])))
return 1;
}
return 0;
}

void before_show_map_vma(hook_fargs2_t *args, void *udata)
{
struct seq_file *m = (struct seq_file *)args->arg0;
args->local.data0 = 0;
if (m && m->buf) {
// 记录 seq_file 中的count,在 after hook 中设置 count 为记录值
args->local.data0 = m->count;
}
}

void after_show_map_vma(hook_fargs2_t *args, void *udata)
{
struct seq_file *m = (struct seq_file *)args->arg0;
if (m && m->buf) {
if (args->local.data0 && is_hiden_module(m)) { // is_hiden_module 查找 frida-agent 等字符串
pr_info("inject-hide: maps hide -> frida-agent \n");
m->count = args->local.data0; // 恢复原来的 count 值
}
}
}

void frida_hide_install(void)
{
show_map_vma = (void *) kallsyms_lookup_name("show_map_vma");
if (show_map_vma) {
hook_err_t err = hook_wrap2(show_map_vma, before_show_map_vma, after_show_map_vma, NULL);
}
}

void frida_hide_uninstall(void)
{
if (show_map_vma) {
unhook(show_map_vma);
show_map_vma = 0;
}
}

线程名特征

1
adb shell "for taskdir in /proc/self/task/*; do cat "\$taskdir/status" |grep 'Name:'; done"
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
➜  ~ adb shell "for taskdir in /proc/4348/task/*; do cat "\$taskdir/status" |grep 'Name:'; done"
Name: yimian.envcheck
Name: Signal Catcher
Name: perfetto_hprof_
Name: Jit thread pool
Name: HeapTaskDaemon
Name: ReferenceQueueD
Name: FinalizerDaemon
Name: FinalizerWatchd
Name: binder:4348_1
Name: binder:4348_2
Name: gmain
Name: gdbus
Name: Thread-2
Name: ackageinstaller
Name: binder:4348_3
Name: Profile Saver
Name: RenderThread
Name: SurfaceSyncGrou
Name: binder:4348_4
Name: hwuiTask0
Name: hwuiTask1
Name: binder:4348_4
Name: queued-work-loo
Name: binder:4348_5

status 文件的 Name 字段是 frida 注入后启动的特有线程,线程名集合:gmain、gum-js-loop、ggdbus、pool-frida、linjector

内核源码定位:proc_pid_status

image

注意:官方显示的内核版本是新的,proc_task_name 源码跟老版本的会有出入,可以根据内核版本号在下面网站查看
https://elixir.bootlin.com/linux/v6.1.23/source/fs/proc/array.c#L98

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void proc_task_name(struct seq_file *m, struct task_struct *p, bool escape)
{
char tcomm[64];

/*
* Test before PF_KTHREAD because all workqueue worker threads are
* kernel threads.
*/
if (p->flags & PF_WQ_WORKER)
wq_worker_comm(tcomm, sizeof(tcomm), p);
else if (p->flags & PF_KTHREAD)
get_kthread_comm(tcomm, sizeof(tcomm), p);
else
__get_task_comm(tcomm, sizeof(tcomm), p);

if (escape)
seq_escape_str(m, tcomm, ESCAPE_SPACE | ESCAPE_SPECIAL, "\n\\");
else
seq_printf(m, "%.64s", tcomm);
}
1
2
3
4
5
6
7
8
char *__get_task_comm(char *buf, size_t buf_size, struct task_struct *tsk)
{
task_lock(tsk);
/* Always NUL terminated and zero-padded */
strscpy_pad(buf, tsk->comm, buf_size);
task_unlock(tsk);
return buf;
}

proc_task_name 会调用 __get_task_comm 获取进程/线程名

函数返回的是 buf,可以使用 after hook 检测 buf 缓冲区,检测到关键词就替换成空格

__get_task_comm hook

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
char *(*__get_task_comm)(char *buf, size_t buf_size, struct task_struct *tsk) = 0;  // 为了后续能够调用,定义成函数指针变量
int __get_task_comm_hook_status = 0;

int is_hiden_comm(const char *comm)
{
// 需要隐藏的线程名关键词列表
static const char *keywords[] = {
"gmain",
"gum-js-loop",
"gdbus",
"pool-frida",
"linjector",
};

for (int i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
if (strstr(comm, keywords[i])) {
return 1;
}
}
return 0;
}

void __attribute__((optimize("O0"))) after_get_task_comm(hook_fargs3_t *args, void *udata)
{
char *comm = (char *)args->arg0;
size_t comm_buf_len = (size_t)args->arg1;
if (comm && comm_buf_len) {
if (is_hiden_comm(comm)){
pr_info("inject-hide: get_task_comm hide -> %s\n", comm);
size_t hide_len = strlen(comm);
for(size_t i = 0; i < hide_len; i++) {
comm[i] = ' ';
}
}
}
}

void frida_hide_install(void)
{
show_map_vma = (void *) kallsyms_lookup_name("show_map_vma");
if (show_map_vma) {
hook_err_t err = hook_wrap2(show_map_vma, before_show_map_vma, after_show_map_vma, NULL);
}

__get_task_comm = (void *) kallsyms_lookup_name("__get_task_comm");
if (__get_task_comm) {
hook_err_t err = hook_wrap3(__get_task_comm, 0, after_get_task_comm, 0);
__get_task_comm_hook_status = err ? 0 : 1;
}
}

效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
➜  ~ adb shell "for taskdir in /proc/4348/task/*; do cat "\$taskdir/status" |grep 'Name:'; done"
Name: yimian.envcheck
Name: Signal Catcher
Name: perfetto_hprof_
Name: Jit thread pool
Name: HeapTaskDaemon
Name: ReferenceQueueD
Name: FinalizerDaemon
Name: FinalizerWatchd
Name: binder:4348_1
Name:
Name:
Name: Thread-2
Name: binder:4348_2
Name: gearhead:shared
Name: binder:4348_3
Name: Profile Saver
Name: RenderThread
Name: SurfaceSyncGrou
Name: hwuiTask0
Name: hwuiTask1
Name: binder:4348_4
Name: binder:4348_4
Name: queued-work-loo

frida 端口特征

  • frida 注入后在目标进程监听 27042 端口,等待外部工具(如 frida-server 或 frida-client)连接
  • 只有名为 adbd 的进程会主动连接这个 27042 端口。

adbd 是 Android 设备上的调试守护进程,只有它会通过 adb 端口转发机制连接 frida-agent 监听的 27042 端口,其他普通应用或进程不会主动连接 27042 端口。

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
27
28
29
30
31
32
struct sockaddr_in {
short sin_family;
unsigned short sin_port;
unsigned int sin_addr;
char sin_zero[8];
};
unsigned long (*__arch_copy_from_user)(void *to, const void __user *from, unsigned long n) = 0;
int connect_hook_status = 0;

u16 ntohs(u16 port) {
return port >> 8 | port << 8;
}
void before_connect(hook_fargs3_t *args, void *udata) {
struct sockaddr_in addr_kernel;
const char __user *addr = (typeof(addr))syscall_argn(args, 1);
if (!addr) return;

__arch_copy_from_user(&addr_kernel, addr, sizeof(struct sockaddr_in));

u16 port = ntohs(addr_kernel.sin_port);
if (port == 27042) {
char comm[16];
__get_task_comm(comm, sizeof(comm), current);

pr_warn("inject-hide: connect to frida-agent, comm: %s, port: %d\n", comm, port);
if (!strstr(comm, "adbd")) { // 只允许 adbd 连接 frida
pr_warn("inject-hide: connect to frida-agent blocked, comm: %s, port: %d\n", comm, port);
args->skip_origin = 1; // 跳过原始的 connect 函数
args->ret = -1; // 返回 -1 表示拒绝连接
}
}
}

最终效果
image

完整代码

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#include "kallsyms.h"
#include "linux/printk.h"
#include "kpmodule.h"
#include "hook.h"
#include "stdbool.h"
#include "stdint.h"
#include "syscall.h"
#include "compiler.h"
#include "kputils.h"
#include "linux/kernel.h"
#include "linux/string.h"
#include <asm/current.h>


struct seq_file{
char *buf;
size_t size;
size_t from;
size_t count;
};
struct sockaddr_in {
short sin_family;
unsigned short sin_port;
unsigned int sin_addr;
char sin_zero[8];
};

void *show_map_vma = 0;
char *(*__get_task_comm)(char *buf, size_t buf_size, struct task_struct *tsk) = 0; // 为了后续能够调用,定义成函数指针变量
unsigned long (*__arch_copy_from_user)(void *to, const void __user *from, unsigned long n) = 0;

int __get_task_comm_hook_status = 0;
int connect_hook_status = 0;

// 内核环境下的 memmem 实现
static void *memmem_local(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen)
{
if (!haystack || !needle || haystacklen < needlelen || needlelen == 0)
return NULL;
for (size_t i = 0; i <= haystacklen - needlelen; ++i) {
if (memcmp((const char *)haystack + i, needle, needlelen) == 0)
return (void *)((const char *)haystack + i);
}
return NULL;
}

// 检查 seq_file 缓冲区中是否包含敏感关键词
static int is_hiden_module(struct seq_file *m)
{
if (!m || !m->buf || m->count == 0) return false;
// 需要隐藏的关键词列表
static const char *keywords[] = {
"frida-agent",
"frida",
"gum-js-loop",
"GumJS",
"gmain",
NULL
};

for (int i = 0; keywords[i] != NULL; ++i) {
if (memmem_local(m->buf, m->count, keywords[i], strlen(keywords[i])))
return 1;
}
return 0;
}

int is_hiden_comm(const char *comm)
{
// 需要隐藏的线程名关键词列表
static const char *keywords[] = {
"gmain",
"gum-js-loop",
"gdbus",
"pool-frida",
"linjector",
};

for (int i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
if (strstr(comm, keywords[i])) {
return 1;
}
}
return 0;
}

void before_show_map_vma(hook_fargs2_t *args, void *udata)
{
struct seq_file *m = (struct seq_file *)args->arg0;
args->local.data0 = 0;
if (m && m->buf) {
// 记录 seq_file 中的count,在 after hook 中设置 count 为记录值
args->local.data0 = m->count;
}
}

void after_show_map_vma(hook_fargs2_t *args, void *udata)
{
struct seq_file *m = (struct seq_file *)args->arg0;
if (m && m->buf) {
if (args->local.data0 && is_hiden_module(m)) { // is_hiden_module 查找 frida-agent 等字符串
pr_info("inject-hide: maps hide -> frida-agent \n");
m->count = args->local.data0; // 恢复原来的 count 值
}
}
}

void __attribute__((optimize("O0"))) after_get_task_comm(hook_fargs3_t *args, void *udata)
{
char *comm = (char *)args->arg0;
size_t comm_buf_len = (size_t)args->arg1;
if (comm && comm_buf_len) {
if (is_hiden_comm(comm)){
pr_info("inject-hide: get_task_comm hide -> %s\n", comm);
size_t hide_len = strlen(comm);
for(size_t i = 0; i < hide_len; i++) {
comm[i] = ' ';
}
}
}
}

// 网络协议中的端口号(大端)转换为主机字节序(小端)
u16 ntohs(u16 port) {
return port >> 8 | port << 8;
}
void before_connect(hook_fargs3_t *args, void *udata) {
struct sockaddr_in addr_kernel;
const char __user *addr = (typeof(addr))syscall_argn(args, 1);
if (!addr) return;

__arch_copy_from_user(&addr_kernel, addr, sizeof(struct sockaddr_in));

u16 port = ntohs(addr_kernel.sin_port);
if (port == 27042) {
char comm[16];
__get_task_comm(comm, sizeof(comm), current);

pr_warn("inject-hide: connect to frida-agent, comm: %s, port: %d\n", comm, port);
if (!strstr(comm, "adbd")) { // 只允许 adbd 连接 frida
pr_warn("inject-hide: connect to frida-agent blocked, comm: %s, port: %d\n", comm, port);
args->skip_origin = 1; // 跳过原始的 connect 函数
args->ret = -1; // 返回 -1 表示拒绝连接
}
}
}

void frida_hide_install(void)
{
show_map_vma = (void *) kallsyms_lookup_name("show_map_vma");
if (show_map_vma) {
hook_err_t err = hook_wrap2(show_map_vma, before_show_map_vma, after_show_map_vma, NULL);
}

__get_task_comm = (void *) kallsyms_lookup_name("__get_task_comm");
if (__get_task_comm) {
hook_err_t err = hook_wrap3(__get_task_comm, 0, after_get_task_comm, 0);
__get_task_comm_hook_status = err ? 0 : 1;
}

__arch_copy_from_user = (void *)kallsyms_lookup_name("__arch_copy_from_user");
if(__arch_copy_from_user && __get_task_comm) {
hook_err_t err = fp_hook_syscalln(__NR_connect, 3, before_connect, 0, NULL);
connect_hook_status = err ? 0 : 1;
}
}

void frida_hide_uninstall(void)
{
if (show_map_vma) {
unhook(show_map_vma);
show_map_vma = 0;
}

if (__get_task_comm) {
unhook(__get_task_comm);
__get_task_comm = 0;
__get_task_comm_hook_status = 0;
}

if(connect_hook_status) {
fp_unhook_syscalln(__NR_connect, before_connect, 0);
connect_hook_status = 0;
}
}
 评论