本文围绕着 linux 系统 ls 命令,并实现 ebpf 内核命令拦截修改 ls 后最终效果等功能。
一、功能演示
视频演示:
二、内容介绍:
实现使用 ebpf 程序隐藏 Android 中进程信息的功能,不得不去了解 linux 内核中的文件系统,考虑到多数人的环境问题,这里的案例采用 ubuntu 系统下隐藏应用程序的进程号的案例实现。
-
-
我们清楚安卓底层是 linux 系统,linux 系统一切皆文件,那么就不得不去了解清楚应用程序在执行的时候我们所需要关注的 proc 文件系统,目录下所存放的主要是当前系统运行的应用程序的进程号,可以通过下面命令进行查看,如果觉得
/proc
目录下文件过多,可以自行使用其他目录。
-
# 图片中包含当前系统中运行的部分应用程序的进程,本章接下来的内容,将修改 ls 命令下指定隐藏的文件。
ls /proc
三、案例设计思路:
四、分析 ls 工具底层实现
ls 命令是linux 系统内置的命令行方法,可以通过使用 strace 工具打印 ls 命令发生了哪些系统调用。例如:
# 将输出的信息拉到倒数第 8 行左右,可以看到有和图片相似的内容输出
strace ls
下面是对截图中部分信息的解释
openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
newfstatat(3, "", {st_mode=S_IFDIR|0700, st_size=4096, ...}, AT_EMPTY_PATH) = 0
getdents64(3, 0x55c0e5b55a00 /* 20 entries */, 32768) = 632
五、eBPF关键代码实现原理分析 (sys_enter_getdents64 探测)
ebpf 程序代码实现仅分析内核态程序模块,代码块中将会对关键部分内容进行注释说明,下方程序模块主要目的是获取 getdents64 方法的 dirp 地址为后续工作准备。
-
-
11 - 16 行,用于确定发生 getdents64 系统调用的进程是我需要的
-
23 行,用于获取当前目录下所有文件对象的 dirp (可以理解为,目录下每个文件都是一个结构体,当前获取的 dirp 是所有文件的 连续的结构体首地址)
-
28 行,将 dirp 地址保存到 maps 中提供给其他程序使用
-
SEC("tp/syscalls/sys_enter_getdents64") // 我要 hook 的系统调用(hook getdents64 系统调用)
int handle_getdents_enter(struct trace_event_raw_sys_enter *ctx) // 触发的方法
{
size_t pid_tgid = bpf_get_current_pid_tgid();
....... //无关紧要代码略过
char comm[16]; //用于保存进程名
if (bpf_get_current_comm(&comm,sizeof(comm))) //取出当前系统中发生 getdents64 系统调用的进程的名字
{
return 0;
}
char target_comm[3]="ls"; //自定义指定进程名
for (int j = 0; j < sizeof(target_comm); j++) { //过滤进程名是不是我指定的
if (comm[j] != target_comm[j]) { //如果进程名字不相同, return
return 0;
}
}
int pid = pid_tgid >> 32;
unsigned int fd = ctx->args[0]; //取 fd 第一个参数
unsigned int buff_count = ctx->args[2]; // 取 linux_dirent64 总占大小 第三个参数
// 取出 linux_dirent64 首地址, 并保存到 map中
struct linux_dirent64 *dirp = (struct linux_dirent64 *)ctx->args[1]; //第二个参数
if (dirp ==NULL)
{
return 0;
}
//保存dirp 地址到 maps 结构体中用于和其他程序 进行数据传递
bpf_map_update_elem(&map_buffs, &pid_tgid, &dirp, BPF_ANY);
return 0;
}
-
getdents64 方法原型可在命令行处使用
man 2 getdents64
查看 ,我会在劫持数据章节中对他进行深度的分析。
ssize_t getdents64(int fd, void *dirp, size_t count);
六、eBPF关键代码实现原理分析(sys_exit_getdents64 探测)
我们知道在 c 语言开发程序时候是经常使用指针的,也就意味着有时候,当使用 c 语言去获取一些数据时,会提前传入一个空的地址,当数据获取成功后在往这个地址中填充数据,getdents64 方法也是如此,所以这里使用到了探测点
sys_exit_getdents64
。
-
-
以下程序模块将会在 getdents64 方法拿到当前目录下所有文件信息后进行一个处理工作,主要用于遍历出当前目录下所有的文件的 dirp 结构体地址,供后面的程序对这些结构体进行操作,达到隐藏文件的目的。
-
当前代码块主要工作 是找出我要隐藏的目标文件的地址,找到后停止继续查找,在这个过程中他会不断的对 map_to_patch 进行赋值,而 map_to_patch 结构体中保存的永远是目标文件的上一个文件,这样在发生尾调用时候,被调用的程序将会拿到目标文件的上一个文件结构体的地址,并进行算法处理,更改内存达到隐藏目标的目的。
-
SEC("tp/syscalls/sys_exit_getdents64")
int handle_getdents_exit(struct trace_event_raw_sys_exit *ctx)
{
size_t pid_tgid = bpf_get_current_pid_tgid();
//取出最终的 返回值大小
int dirp_size = ctx->ret;
//如果返回值错误说明没有读取成功
if (dirp_size <= 0) {
return 0;
}
//从 maps中取出指定pid 的值 如果地址为空,说明 这不是我们操作的进程
long unsigned int* pbuff_addr = bpf_map_lookup_elem(&map_buffs, &pid_tgid);
if (pbuff_addr == 0) {
return 0;
}
long unsigned int buff_addr = *pbuff_addr; //dirp 的真实内存地址
struct linux_dirent64 *dirp = 0; //初始化 dirp 用于接收保存每个条目(当前目录下每个文件的对象信息)
int pid = pid_tgid >> 32;
short unsigned int d_reclen = 0; //保存每个条目的大小
char filename[128]; //保存每个条目的名称
char d_type; //当前目录下条目的类型
unsigned int bpos = 0; //用于计数,上一个条目到当前条目之间的大小
....... //省略
for (int i = 0; i < 50; i ++) { //自定义的循环次数,大于当前目录下文件总数量即可
//如果计数超过了总大小,说明目录已经被遍历完了
if (bpos >= dirp_size) {
break;
}
//每次循环后,buff_addr 地址需要进行递增,用于指向当前目录中的 下一个 文件的首地址
dirp = (struct linux_dirent64 *)(buff_addr+bpos);
//从当前遍历的文件的结构体对象中取出 当前文件在内存中的大小
bpf_probe_read_user(&d_reclen, sizeof(d_reclen), &dirp->d_reclen);
//从结构体中取出 当前文件名称 长度大于当前目录下名字长度最大的文件即可
long err = bpf_probe_read_user(&filename, 128, &dirp->d_name);
//如果当前文件名字获取失败
if (err !=0)
{
//递增bpos 计数器,为获取下一个文件做准备
bpos +=d_reclen;
continue;
}
bpf_probe_read_user(&d_type, sizeof(d_type), &dirp->d_type); //拿到当前文件的类型 分为 目录/文件
int j = 0;
//当前文件名 和我传入的文件名进行比较,判断是否是我要修改的文件 text_to_hide 变量被用户态程序提前赋值过
for (j = 0; j < text_to_hide_len; j++) {
if (filename[j] != text_to_hide[j]) {
break;
}
}
// 如果字符串比较相同的数量大于等于我要修改的文件名称,直接触发尾调用,结束当前程序模块
if (j >= text_to_hide_len) {
.......// 省略
//触发尾调用,执行真正的修改数据的程序
bpf_tail_call(ctx, &map_prog_array, PROG_02);
}
//保存当前目录下刚刚比较过的条目到 maps
long err1 = bpf_map_update_elem(&map_to_patch, &pid_tgid, &dirp, BPF_ANY);
//遍历完毕后递增地址
bpos += d_reclen;
}
......//省略
return 0;
}
七、附
下一个章节,将会对隐藏文件的具体实现思路进行详细分析。
原文始发于微信公众号(None安全团队):ebpf 隐藏 Android 任意进程或任意文件实现连载 (一)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论