您好,本站仅作演示所用,请勿下单付款!
商品分类

安卓内核编译和kernelsu的开发环境搭建

解决实际的报错的位置.

calleng@wd:/media/calleng/pm9a1/px6_backup202506/vmlinux-to-elf$ 
git log --since="2024-01-01" --until="2024-12-31" --oneline
da14e78 Merge pull request #64 from maringuu/master
c385e09 fix: Use raw strings for regex expressions


git show -s --format=%ci da14e78
2024-07-20 21:00:42 +0200

git show -s --format=%ci c385e09
2024-07-18 11:01:53 +0200

calleng@wd:/media/calleng/pm9a1/px6_backup202506/vmlinux-to-elf$ git checkout da14e78
注意:正在切换到 'da14e78'。

您正处于分离头指针状态。您可以查看、做试验性的修改及提交,并且您可以在切换
回一个分支时,丢弃在此状态下所做的提交而不对分支造成影响。

如果您想要通过创建分支来保留在此状态下所做的提交,您可以通过在 switch 命令
中添加参数 -c 来实现(现在或稍后)。例如:

  git switch -c <新分支名>

或者撤销此操作:

  git switch -

通过将配置变量 advice.detachedHead 设置为 false 来关闭此建议

HEAD 目前位于 da14e78 Merge pull request #64 from maringuu/master
calleng@wd:/media/calleng/pm9a1/px6_backup202506/vmlinux-to-elf$ git log -1
commit da14e789596d493f305688e221e9e34ebf63cbb8 (HEAD)
Merge: fa5c930 c385e09
Author: Marin <25437947+marin-m@users.noreply.github.com>
Date:   Sat Jul 20 21:00:42 2024 +0200

    Merge pull request #64 from maringuu/master
    
    fix: Use raw strings for regex expressions

然后进行修补 得到 elf 文件的 符号列表

calleng@wd:/media/calleng/pm9a1/px6_backup202506/firmware/px4_factory/flame-tp1a.221005.002/output_dir$ ls ../../../../vmlinux-to-elf/
.git/  .gitignore  kallsyms-finder@  LICENSE  pics/  README.md  setup.py*  vmlinux-to-elf@  vmlinux_to_elf/
calleng@wd:/media/calleng/pm9a1/px6_backup202506/firmware/px4_factory/flame-tp1a.221005.002/output_dir$ f1613
(f1613) calleng@wd:/media/calleng/pm9a1/px6_backup202506/firmware/px4_factory/flame-tp1a.221005.002/output_dir$ ../../../../vmlinux-to-elf/vmlinux-to-elf   kernel-4.14.276-android13-pixel4   kernel-4.14.276-android13-pixel4.elf
[+] Version string: Linux version 4.14.276-gecab2e0c9918-ab8931408 (android-build@abfarm-2004-0286) (Android (7284624, based on r416183b) clang version 12.0.5 (https://android.googlesource.com/toolchain/llvm-project c935d99d7cf2016289302412d708641d52d2f7ee), LLD 12.0.5 (/buildbot/src/android/llvm-toolchain/out/llvm-project/lld c935d99d7cf2016289302412d708641d52d2f7ee)) #1 SMP PREEMPT Wed Aug 10 20:40:15 UTC 2022
[+] Guessed architecture: aarch64 successfully in 7.27 seconds
[+] Found relocations table at file offset 0x262cec0 (count=161276)
[+] Found kernel text candidate: 0xffffff8008080000
[+] Successfully applied 161276 relocations.
[+] Found kallsyms_token_table at file offset 0x01f00900
[+] Found kallsyms_token_index at file offset 0x01f00d00
[+] Found kallsyms_markers at file offset 0x01eff900
[+] Found kallsyms_names at file offset 0x01d32100
[+] Found kallsyms_num_syms at file offset 0x01d32000
[i] Negative offsets overall: 0 %
[i] Null addresses overall: 0 %
[+] Found kallsyms_offsets at file offset 0x01cb2000
[+] Successfully wrote the new ELF kernel to kernel-4.14.276-android13-pixel4.elf

如何和课程的进行对比, 从这里的一些位置可以知道,

kernel-ranchu.elf 是 6.1 的 px6的模拟器的内核文件.

./vmlinux-to-elf/vmlinux-to-elf pixel/kernel pixel/kernel_a.elf 就是 px4的 物理机的 内核.

修改补丁的具体的位置.

安卓内核编译和kernelsu的开发环境搭建

通过图上看到, kernelsu 作为内核中的一个子模块一起编译的.

在 android-kernel 目录下, 执行补丁.
echo ‘[+] GKI_ROOT: /data/android-kernel ‘

现在最重要的问题, 就是, ksu 规模庞大, 先 apatch 适应. 作为 内核hook轻量级绕过.

hook_err_t err = fp_hook_syscalln(__NR_openat, 4, before_openat,  after_openat, NULL );
if (err) {
   pr_err("panda-hide: hook openat error : %d\n", err),
} else {
hook_openat_status = 1;
pr_info("panda-hide:  hook openat success \n");
}

// fp_hook_syscalln(__NR_openat, 4, before_openat,  after_openat, NULL );
// 对于 syscall 的hook 需要我们提供调用号 
// nr , 系统调用号, 
// narg: 参数个数.
// before , 系统调用前执行的回调
// after, 执行后的回调.


void before_openat(hook_fargs4_t *args , void *udata){   多参数,  用户数据, 会直接传递到这里来. 
// hook_fargs4_t ,表示 4个 参数
// 解析参数 使用 syscall_argn(args, 0 ) 读取出来.
// 字符串 用 compat_strcpy_from_user 进一步读取.
// args->skip_origin = 1 ; 跳过原调用. ==================> 跳过原始函数的执行, 相当于,阻止它的调用.!  在 ebpf 中 是很难实现的! 
// strstr 位于< linux/string.h> 头文件(谨慎使用 c 库函数, 除非能在 kp 头文件中找到 )
// 调用内核函数_task_pid_nr_ns 获取 pid, tgid 信息.
//
//  Frida 一般不一定使用 openat 打开.
//   重定向的做法, 一般来说是非常 low 的. 
//   hook openat  一般是让他不打开文件, 

// 当他直接 打开 某些 文件时候,我们给他 block掉.  还有其他的一些检测的函数. 
//  

如果 failed , 常见的失败,怎么查找原因, 因为常见的符号 , 没有被导入进来 .
adb shell logcat | grep KP, ========> 来显示日志.

如果 没有使用到 memset , 这是编译优化引入的 builtin函数, 直接 KPM 拖到 IDA 中去看一看. 看看 memset 在哪里出现的? 明明没有使用 memset 但是, 编译器, 自动添加了, memset 这个, 特别在数组初始化的时候,

在 log 日志中, dmesg -w | grep KP , 可以阅读到,
添加 __attribute__((optimize(“o0”))) 就这样, 搞告诉编译器,不要优化 我们的这个函数了. 把 优化等级 改为 o0, 欧零 级别.!! 也可以把全局的 MakeFile 修改为 o0. 欧零.

看这个 KP 的日志, 这个目的, 就是告诉大家有没有其他的 error.

读取用户态字符串

oriole:/ # cat /proc/kallsyms | grep __arch_copy_from_user
0000000000000000 T __arch_copy_from_user
0000000000000000 r __ksymtab___arch_copy_from_user
0000000000000000 r __kstrtab___arch_copy_from_user

怎么去读取用户态的字符串的, 拷贝的到内核态里面. 如果需要读取一片固定的内存区域.

使用 , 字符串 , 使用 compat_strncpy_from_user , 这个在内核头文件里面是有导出的. 我们在用户态 时候, 需要去读取 用户态 addr_in 的结构体, 是 16个字节, 里面包含了 ip 和端口号, 这个使用,可以使用, __arch_copy_from_user
可以把 struct sockaddr_in 这个结构体, 给他读出来, 然后,我们再去解析这个端口.

调试器反检测

TracerPid字段

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");    // 输出的 第一个就是 name .  后面讲道 frida 检测的这个地方也会用到. 
	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, mm);
		task_thp_status(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);
	return 0;
}
// /media/calleng/pm9a1/p6kernel/private/gs-google/fs/proc/array.c

Name 字段是由 proc_task_name 函数生成, 这个涉及 frida 检测.
TracePid 在 task_state 生成.

往下找, 就能找到 , TracePid 生成的地方.

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)   // /media/calleng/pm9a1/p6kernel/private/gs-google/fs/proc/array.c
		tpid = task_pid_nr_ns(tracer, ns);  // 这是调试器的 PID ---> tpid 就是在这里进行赋值.


	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");    // 检测 这个字段!! /proc/pid/status State 字段
	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);   // 这里的就是 TracerPid 生成的地方.
	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, ' ');
}

struct seq_file 缓冲器,
类似java里面的 一个 StringBuilder 的字符串 缓冲区, 不断的 构造数据, 然后,从缓冲区把数据取出来.
* buf 指向数据地址
count 指向数据尾部.

struct seq_operations;

// /media/calleng/pm9a1/p6kernel/private/gs-google/include/linux/seq_file.h

struct seq_file {   // linux 中非常常见的缓冲区,  结构体是这样.
	char *buf;   // 用到的字段 1  ---> * buf 指向数据地址
	size_t size;  // 用到的字段 2  -->   count 指向数据尾部.
	size_t from;   // 用到的字段 3
	size_t count;     // 用到的字段 4
	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;
};

struct seq_operations {
	void * (*start) (struct seq_file *m, loff_t *pos);
	void (*stop) (struct seq_file *m, void *v);
	void * (*next) (struct seq_file *m, void *v, loff_t *pos);
	int (*show) (struct seq_file *m, void *v);
};

等下定义只要, 这个4个连续的字段, 因为在最前面. 因为它是连续的.
很多的虚拟的文件, 比如, maps 的内存映射, status 的文件 映射都使用 seq_file 缓冲区生成.
/proc/pid/status tracerpid 字段
/proc/pid/status State 字段
/proc/pid/wchan 文件
/proc/pid/stat 文件. 这个标志 ps -A 也可以查看.

比如这些缓冲区有一些的操作函数.比如.,
seq_puts
seq_put_decimal_ull // pull 一个 十进制的数
seq_putc //
或者还有格式字符串的操作都有.

通过 cat /proc/self/staus 可以看到自己的结构体定义.

130|oriole:/ # cat /proc/self/status
Name:	cat      // 这里第一个就是 Name
Umask:	0022
State:	R (running)
Tgid:	13637
Ngid:	0
Pid:	13637
PPid:	7442
TracerPid:	2237
Uid:	0	0	0	0
Gid:	0	0	0	0
FDSize:	64
Groups:	 
VmPeak:	10829656 kB
VmSize:	10829504 kB
VmLck:	       0 kB
VmPin:	       0 kB
VmHWM:	    3496 kB
VmRSS:	    3496 kB
RssAnon:	     668 kB
RssFile:	    2592 kB
RssShmem:	     236 kB
VmData:	    8112 kB
VmStk:	     136 kB
VmExe:	     304 kB
VmLib:	    3732 kB
VmPTE:	     112 kB
VmSwap:	       0 kB
CoreDumping:	0
THP_enabled:	1
Threads:	1
SigQ:	0/27701
SigPnd:	0000000000000000
ShdPnd:	0000000000000000
SigBlk:	0000000080000000
SigIgn:	0000002000000000
SigCgt:	0000004c400084f8
CapInh:	000001ffffffffff
CapPrm:	000001ffffffffff
CapEff:	000001ffffffffff
CapBnd:	000001ffffffffff
CapAmb:	000001ffffffffff
NoNewPrivs:	0
Seccomp:	0
Seccomp_filters:	0
Speculation_Store_Bypass:	thread vulnerable
Cpus_allowed:	ff
Cpus_allowed_list:	0-7
Mems_allowed:	1
Mems_allowed_list:	0
voluntary_ctxt_switches:	0
nonvoluntary_ctxt_switches:	5

这个结构体我们会在后面,多次接触到.

我们 HOOK的时候, 只会使用4个字段

struct seq_file {
    char *buf;  // 指向了一片内存, 是存放数据的地方. 
    size_t size;  // size 这片内存区域最大是多大? 
    size_t from;
    size_t count;  // 指向了数据的尾部. 有多少个字节.比如原来 buffer 是空的, put seq =c ,
                    // 我放一个字符进去, count就变成1 , 以此类推. count 表示, 我们buffer里面 有多少有效的数据.
                    // 我想要从 buffer 中 删除一些数据时候, 就可以从 cunt 来进行删除.
};
// 为了方便使用, 只需要前 4个字段.  将这个连续4个字段,定义出来,就可以用了. 
// 只要不使用4个 之外字段, 都是可以的. 

当我们想要修改一些数据的时候, 只需要,修改一下它的 count 来进行删除.

在 int proc_pid_status ( ) 下面可以找到, task_state 调用.

这里面会发现, 会放置一个 seq_put_decimal_ull () 十进制 无符号数进去.

	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);   // 这里的就是 TracerPid 生成的地方. \t 就是制表符, 把 tpid 给放进去.

这个数的名字 叫做 TracerPid.

目的: TracerPid 的 值永远改为 0.

如果hook, task_state() , 他里面生成了很多数据.
所以我们 hook, seq_put_decimal_ull( ) 这个函数.

同时,这是一个导出函数, 同时, 它是有个导出符号的,就是导出函数. 那么我们可以,定位它的运行时地址. 并 hook 上去.

oriole:/ # cat /proc/kallsyms | grep seq_put_decimal_ull
0000000000000000 T seq_put_decimal_ull_width
0000000000000000 T seq_put_decimal_ull
oriole:/ # 

这里可以 hook 上 导出函数, 确定地址.

我们看到了源码之后, 设备上的内核, 是否也是这个情况?
确定一下,设备上的内核的, 导出到 IDA 中进行观看. proc_pid_status 这个导出函数,可以找到.< === 可读性很高,是 6.1的内核, 在 5.10的生产分支里面, 符号丢失 严重.

通过ida 反汇编 px6 的 boot_a 的槽位的 导出的 kernel 的 elf. 文件的符号. 确实是这样的.

    __break(0x5512u);
  seq_puts(a1, task_state_array[v22]);
  seq_put_decimal_ull(a1, aTgid_1, v12);
  seq_put_decimal_ull(a1, aNgid, 0LL);
  v23 = pid_nr_ns(a3, a2);
  seq_put_decimal_ull(a1, aPid_0, v23);
  seq_put_decimal_ull(a1, aPpid, v10);
  seq_put_decimal_ull(a1, aTracerpid, v11); < ========== 这个地方. hook ,虽然被 inline 了. 

但是, seq_put_decimail_ull 这个函数已经被 inline,了.

__int64 __fastcall seq_put_decimal_ull(__int64 a1, __int64 a2, __int64 a3)
{
  return seq_put_decimal_ull_width(a1, a2, a3, 0LL);
}

它的这里 定义的 是 3个参数.

hook思路, 直接读取 这个 3个参数的 seq_put_decimal_ull() 函数的的” 字符串[在第二参数位置]”, 看看是不是 包含 TracerPid的 这个字符串,如果是,那么 , 直接 第三个 参数修改为 0.

我们想要 这段代码执行的时候,

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

tpid 传递 进去的是 0.
在下面实现的,时候, 首先函数地址找到. 通过, kallsyms_lookup_name 来找到相对应的地址 .

找到后, 我们去hook _wrap3() –> 3 是参数的意思.

我们只需要, before hook 就可以了, 因为我们需要改它的参数. 改 参数时候, 我们就要在 before的时候去修改.

在 before 回调里面, 我们判断第一个参数,
void before_seq_put_decimal_ull(hook_fargs3_t *args, void *udata)
hook_fargs3_t 这个表示的是 3个参数. 读取,第1个字符串的指针,然后, 逐个, strcmp 函数, 进行比较, 如果是 TracerPid:\t” 这个东西 == 0 , 等于0 表示 是的.
&& args->arg2 != 0 , 并且传递进来的 tpid 不为 0 .

直接 hook 是一个内核函数, 我们syscall的字符串是 用户态传递进来的, 我们在内核态hook 不能直接读取用户态的 字符串, 我们, 直接使用, 一个内核态的函数. 可以直接. 去使用 字符串的地址.

strcmp 有个 定义, 如果 相等的话, 返回是 0, 不等 ,返回 1.

通过一些实践能力环节.===> 撒花, .—->

get_task_state的检测源码

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)];  // 被它处理.
}

然后就是调试器的等待状态.

/*
 * The task state array is a strange "bitmap" of
 * reasons to sleep. Thus "running" is zero, and
 * you can test for combinations of others with
 * simple bit tests.
 */
static const char * const task_state_array[] = {

	/* states in TASK_REPORT: */
	"R (running)",		/* 0x00 */
	"S (sleeping)",		/* 0x01 */  // 可以修改为这个 sleep! 
	"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 */
};

大家搞逆向的, 就要多读源码!!

很多时候, 源码都读不懂, 还逆什么呢? 对把.

所以多多看一下源码,提升 代码 审计能力.

看了源码, 到 IDA 中, 反汇编, 实际 核对一下, 看看是否存在. 看看是否有这样的结构体存在.

看看这里.要么 改为 running, seleep 这种.

  v17 = raw_spin_unlock(a4 + 2336);
  _rcu_read_unlock(v17);
  if ( (v15 & 0x80000000) == 0 )
    seq_printf(a1, "Umask:\t%#04o\n", v15);
  seq_puts(a1, aState);                        // State 在这里.使用了, seq_puts() ,  本身作为参数,传递进去.
  v18 = *(_DWORD *)(a4 + 48);
  v19 = ((unsigned __int8)*(_DWORD *)(a4 + 1476) | (unsigned __int8)v18) & 0x7F;
  if ( v18 == 1026 )
    v19 = 128;
  if ( v18 == 4096 )
    v20 = 2;
  else
    v20 = v19;
  v21 = 32 - __clz(v20);
  if ( v20 )
    v22 = v21;
  else
    v22 = 0;
  if ( v22 >= 9 )
    __break(0x5512u);
  seq_puts(a1, task_state_array[v22]);   // 这里也有一个   task_state_array , 同样调用 seq_puts! 
  seq_put_decimal_ull(a1, aTgid_1, v12);
  seq_put_decimal_ull(a1, aNgid, 0LL);
  v23 = pid_nr_ns(a3, a2);
  seq_put_decimal_ull(a1, aPid_0, v23);
  seq_put_decimal_ull(a1, aPpid, v10);
  seq_put_decimal_ull(a1, aTracerpid, v11);

直接, hook seq_puts , 检测它的第二个参数,

这里反编译后的伪代码, 显示 都是 a1, 所以, seq_puts(a1, aState); 和 seq_puts(a1, task_state_array[v22]);

所以,

简化版本的源代码对比

static inline void task_state(struct seq_file *m, struct pid_namespace *ns,
				struct pid *pid, struct task_struct *p)
{
  .................. 省略 很多

	if (umask >= 0)
		seq_printf(m, "Umask:\t%#04o\n", umask);
	seq_puts(m, "State:\t");    // 检测 这个字段!! /proc/pid/status State 字段
	seq_puts(m, get_task_state(p)); // 这里的, get_task_state 从 task_state_array[] 获取静态字符串返回
                                       // 调用 seq_puts 写入! 


..................... 省略很多.

}

他们 都是在一起.

我们hook , seq_puts( ) 直接 before_hook 就可以了.

如果它要 puts 这样的一个字符串, 被我们检测到. 我们就把他们改为 runing 状态的字符串.

比如

void before_seq_puts(hook_fargs2_t *args, void *udata) {
    if ( strcmp((const char *) args->arg1, "t (tracing stop)") == 0) {
        pr_info("panda-debugger-hide: seq_puts:t\n");
        args->arg1 = (uint64_t)"R (running)";   // 如果确实达到目标,直接在这里进行修改!
    }
}

有个小问题, 这里引用, 执行完毕, 并没有被释放 , 可能导致 crash. 当我们字符串,卸载之后, 引用并没有消失.

这样就容易出现一些内核的crash. 不稳定的一些情况.

根据自己的内核实际考察一下.

测试环节, —-> 调试器 IDA 直接挂载, 直接返回, sleeping 的状态.

找一个ie幸运的进程,直接挂上一个调试器,

# ps -A |grep 5412 
# 通过 IDA pro 挂载一个  进程,   5539. com.example.checkfrida. [choose process to attach to ]

# 它的进程的名字 是 5539. 
# cat /proc/5539/?????????????? status  缺失什么? 5539.?      status
# cat /proc/self/status 

# 我们看到 ,   State: S(sleeping )
Name:	mple.checkfirda      // 这里第一个就是 Name
Umask:	0077
State:  S (sleeping)    <----------- 原来的, t (tracing stop)",变成了sleeping
Tgid:	13637
Ngid:	0
Pid:	13637
PPid:	7442

# 通过 dmesg -w | grep panda-hide 的输出.我们得到了,  seq_puts: tracing stop 的输出 ! 

kpatch 修改很快,只要找到了修改点.

wchan 位置

wchan 文件内容是 内核中,进程休眠位置 对应的符号名称 ,比如 等待调试器就是 ptrace_stop, 正常情况下是 0.

#ifdef CONFIG_KALLSYMS
/*
 * Provides a wchan file via kallsyms in a proper one-value-per-file format.
 * Returns the resolved symbol.  If that fails, simply return the address.
 */
static int proc_pid_wchan(struct seq_file *m, struct pid_namespace *ns,   // 从定义看, 并没有找到合适的 hook 点. 
			  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');    // 如果没有找到任何的函数,符号, 那么它返回的是 0
                              // . 是字符 0.  字符串,的最大长度,就是 1.   如果是正常的,他是不是只有 1个字符. 
                          // 整个缓冲区里面, 只有这么一个字符? 
	return 0;
}
#endif /* CONFIG_KALLSYMS */ 

# 就是在 ptrace 的 stop 里面,  休息, 睡觉. 
// 他里面其实也是 ,  ,struct seq_file , 


通过附加一个 phone上去, 就出来了.

oriole:/ # ps -ef  | grep phone                                                                                                                                                                                                                        
radio         2317   854 0 06:58:04 ?     00:00:04 com.android.phone
root         15708 14753 23 16:02:12 pts/0 00:00:00 grep phone
oriole:/ # cat /proc/2317/wchan                                                                                                                                                                                                                        
ptrace_stop  # 这里并没有换行符. 
oriole:/ # 

这就是 被 oriole forward tcp:23946 tcp:23946 附加了的调试器. ida dbgsrv,

所显现的状态.

struct seq_file {
    char *buf;  // 指向了一片内存, 是存放数据的地方. <===== 缓冲区它只有一个字符!  
    size_t size;
    size_t from;
    size_t count;  // 指向了数据的尾部. 有多少个字节.
};
// buffer 也还是指向缓冲区的, count 也是等于 1 . 
// hook 也是 after hook , 函数修改, 就是修改执行后的. 
// 我们也是从 sq_file的 角度去修改. 

我们只改, seq_file 的输出结果, 如果 , 改的话, 我们直接将他的缓冲区的数据改了.

所以我们 hook 的目标,就是 after hook了.

方法1 , before hook: 向 seq_file 写入 ‘0’, 字符, count +1

方法2, after hook : 覆盖 seq_file 数据, 写入 ‘0’, 把 count 改成 1.

为了代码的稳定性判断一下, if(m && m->buf) {,

m 不为0 , buffer , 也不为 0. 再继续往后,做, 否则,容易崩溃的.

我们把不想要的全部都改成0 ,了, 要不就是 1 把. ==========> 这里不可以,否则, 这个功能就废掉了.
把 ptrace_stop 等于这个值的 情况下, if() 在这个条件下, 我们把就 第[0]个字符,换为 0. 把 第[1]位的字符换位 \0 结尾. 然后 count 改为 1.

最后 pr_onfo 输出一下. 就是 after hook.
make 后, push

加载 模块, success 后,

oriole:/ # ps -ef  | grep phone                                                                                                                                                                                                                        
radio         2317   854 0 06:58:04 ?     00:00:04 com.android.phone
root         15708 14753 23 16:02:12 pts/0 00:00:00 grep phone
oriole:/ # cat /proc/2317/wchan                                                                                                                                                                                                                        
0  # 这里并没有换行符. 
oriole:/ # 被hook的 phone 的值, 直接变成 0 了. 
# 进程之前是 ptrace_stop . 

do_task_stat

最后一个,就是 do_task_stat 文件.

seq_putc 放入 1 字节

不能 hook seq_putc

不能特异性区分.

// /media/calleng/p6kernel/out/mixed/device-kernel/staging/lib/modules/5.10.198-android13-4-dirty/source/fs/proc/array.c

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_file 内容
// after hook do_task_stat 检测有括号 + 空格后 的 1 字节是否 为 t.

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)];
}
oriole:/ # ps -ef  | grep phone                                                                                                                                                                                                                        
radio         2317   854 0 06:58:04 ?     00:00:04 com.android.phone
root         15708 14753 23 16:02:12 pts/0 00:00:00 grep phone

# 2317 就是电话进程.
oriole:/ # cat /proc/2317/stat                                                                                                                                                                                                                         
2317 (m.android.phone) t 854 854 0 0 -1 1077936448 18564 0 173 0 311 148 0 0 20 0 51 0 897 17719033856 39022 18446744073709551615 432752218112 432752222224 548713562224 0 0 0 4612 1 1073775864 0 0 0 17 0 0 0 5 0 0 432752231592 432752231616 433092550656 548713565899 548713565998 548713565998 548713570270 0
oriole:/ # 
# phone) t 这里的 t 代表的,就是 进程正在被调试.

正常就是 r , running , 就是进程正在被调试.
使用 ps -A 命令也可以看到这一行.

oriole:/ # ps -A | grep phone                                                                                                                                                                                                                          
radio         2317   854   17303744 156088 ptrace_stop         0 t com.android.phone
oriole:/ # 

也是可以看到这一行被调试!

Frida 检测

哪些 检测手段

maps 文件特征

内存特征

线程名特征

D-Bus 端口特征

maps 文件和内存

/proc/pid/maps 文件内容描述了进程的内存布局信息

frida 会注入一个 frida-agent 模块, 因此, 在 maps 里面能够找到对应的内存映射信息.
这里面能够找到一个 maps 文件.
能够找到内存的映射信息.

内核定位 : show_map_vma
show_map_vma 输出 maps 文件中 一段内存区域(一行)

static void
show_map_vma(struct seq_file *m, struct vm_area_struct *vma)
{
        // ****************
	start = vma->vm_start;
	end = vma->vm_end;
        // 输出内存区间范围,标志等 
	show_vma_header_prefix(m, start, end, flags, pgoff, dev, ino);
         // 输出
	/*
	 * Print the dentry name for named mappings, and a
	 * special [heap] marker for the heap:
	 */
	if (file) {
		seq_pad(m, ' ');
		seq_file_path(m, file, "\n");   // 输出路径相关
		goto done;
	}

	if (vma->vm_ops && vma->vm_ops->name) {
		name = vma->vm_ops->name(vma);
		if (name)
			goto done;
	}
       
	name = arch_vma_name(vma);    // 获取内存区间 name
	if (!name) {
		struct anon_vma_name *anon_name;

		if (!mm) {
			name = "[vdso]";
			goto done;
		}

		if (vma->vm_start <= mm->brk &&
		    vma->vm_end >= mm->start_brk) {
			name = "[heap]";
			goto done;
		}

		if (is_stack(vma)) {
			name = "[stack]";
			goto done;
		}

		anon_name = anon_vma_name(vma);
		if (anon_name) {
			seq_pad(m, ' ');
			seq_printf(m, "[anon:%s]", anon_name->name);
		}
	}

done:
	if (name) {
		seq_pad(m, ' ');
		seq_puts(m, name);
	}
	seq_putc(m, '\n');
}

show_map_vma 是导出符号.
after hook 从 seq_file 找 关键字, frida .

问题: 视频演示, 应该6.1 的内核, 但是 5.10 源代码, 并没有 , get_vma_name() 这个函数.

要隐藏 Frida 的话, 它的 name 和 trace , 不能确定是哪一个, 但是它的name 和 trace, 肯定包含, Frida Agent 输出的字符串.

内存特征, 比如 frida:rpc, FridaScriptEngine, 以及一些不可见的 byte 特侦, 都在 frida-agent 段里面.

oriole:/ # cat /proc/6522/maps | grep frida
7391e2a000-7392822000 r--p 00000000 00:01 6423                           /memfd:frida-agent-64.so (deleted)
7392823000-7393542000 r-xp 009f8000 00:01 6423                           /memfd:frida-agent-64.so (deleted)
7393542000-7393612000 r--p 01716000 00:01 6423                           /memfd:frida-agent-64.so (deleted)
7393613000-739362f000 rw-p 017e6000 00:01 6423                           /memfd:frida-agent-64.so (deleted)
oriole:/ #    
# 常规的 f1613.的版本 , 在 oriole  版本下的输出 的so.

内核层面修改隐藏 maps 文件, 隐藏 frida 相关 的内存, 通过是绕过 maps 检测 和 内存特征检测.

字符串, 大致在, struct seq_file *m 这一块 输出的.

我们不需要这一行, 在 show_map_vma ( ) 这一行, 进入 之前的 这一行数据, 整个输出,我们不要了.
做法, 在 after hook 从 seq_file 找到 关键字, frida , 找到了, 我们就不要了.

如果找到了 , 这个关键的, frida-agent 的这个 show_map_vma (() 的调用 , 它作用就是输出 frida 所占用的内存.

我们不想输出, 但是 after hook , 它的已经被输出到 seq_file 里面了.

用到前面的方法, 把 seq_file 的 count, 改了, 改到, 进入到 show_map_vma( ) 这个进入的时候, 有多大?

退出的时候, 也给他改成多大 !

效果: 从 show_map_vma( ) 函数, 进入到 结束, 输出, 都被删除掉了.
头疼的地方, -> after hook 中 如何从 seq_file 删除 当前 show_map_vma( ) 输出的全部数据, 而不影响其他调用数据?

巧妙的地方 -> , before hook 记录 seq_file 中的 count输出多少字节 , 在 after hook 中 设置 count 为记录值, 效果上等价于 删除.

for taskdir in /proc/22495/task/*; do
    tid=$(basename "$taskdir")
    name=$(grep 'Name:' "$taskdir/status" 2>/dev/null | awk '{print $2}')
    [ -n "$name" ] && echo "TID: $tid, Name: $name"
done

# 进阶版本:输出 TID 与线程名

TID: 22495, Name: yimian.envcheck
TID: 22504, Name: Signal
TID: 22505, Name: perfetto_hprof_
TID: 22506, Name: Jit
TID: 22507, Name: HeapTaskDaemon
TID: 22508, Name: ReferenceQueueD
TID: 22509, Name: FinalizerDaemon
TID: 22510, Name: FinalizerWatchd
TID: 22517, Name: binder:22495_1
TID: 22518, Name: binder:22495_2
TID: 22520, Name: Profile
TID: 22522, Name: RenderThread
TID: 22524, Name: mali-mem-purge
TID: 22525, Name: mali-utility-wo
TID: 22526, Name: mali-utility-wo
TID: 22527, Name: mali-utility-wo
TID: 22528, Name: mali-utility-wo
TID: 22529, Name: mali-utility-wo
TID: 22530, Name: mali-utility-wo
TID: 22531, Name: mali-utility-wo
TID: 22532, Name: mali-utility-wo
TID: 22533, Name: mali-cmar-backe
TID: 22538, Name: hwuiTask0
TID: 22539, Name: hwuiTask1
TID: 22540, Name: SurfaceSyncGrou
TID: 22542, Name: binder:22495_3
TID: 22544, Name: binder:22495_3
TID: 22635, Name: binder:22495_4
TID: 22656, Name: yimian.envcheck
TID: 22657, Name: gmain
TID: 22659, Name: gdbus
TID: 22660, Name: Thread-2
oriole:/ # 

今天回头看, 昨天的 带着 加上一个 width ,
才想起, 给 制表符加上一个宽度…..

结合
int proc_pid_status(struct seq_file *m, struct pid_namespace *ns,
struct pid *pid, struct task_struct *task)
这个函数就是 /proc//status(和 /proc//task//status) 的生成者。

就知道了, 为什么要加上一个循环. 所有 线程下的 Name .
PID 的, TracerPid, TID 下的, TracerPid, 原来都有. 内核层的hook 这些拦截不让出现.

线程名特征

_get_task_comm ,
获取进程/线程名.
导出符号.

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们

联系我们

888-888-8888

在线咨询:点击这里给我发消息

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信
关注微信
分享本页
返回顶部