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

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

一、内容介绍:

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

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

# 图片中包含当前系统中运行的部分应用程序的进程,本章接下来的内容,将修改 ls 命令下指定隐藏的文件。ls /proc
  • 我们清楚安卓底层是 linux 系统,linux 系统一切皆文件,那么就不得不去了解清楚应用程序在执行的时候我们所需要关注的 proc 文件系统,目录下所存放的主要是当前系统运行的应用程序的进程号,可以通过下面命令进行查看,如果觉得 /proc 目录下文件过多,可以自行使用其他目录。


二、案例设计思路:

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

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

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


三、分析 ls 工具底层实现

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

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

下面是对截图中部分信息的解释# openat : 打开当前目录这个文件,这个文件比较特殊它的名字是个隐藏的 . (点) ,文件描述符返回 3

openat(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;}
  • 11 - 16 行,用于确定发生 getdents64 系统调用的进程是我需要的

  • 23 行,用于获取当前目录下所有文件对象的 dirp (可以理解为,目录下每个文件都是一个结构体,当前获取的 dirp 是所有文件的 连续的结构体首地址)

  • 28 行,将 dirp 地址保存到 maps 中提供给其他程序使用

  • 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;}
  • 以下程序模块将会在 getdents64 方法拿到当前目录下所有文件信息后进行一个处理工作,主要用于遍历出当前目录下所有的文件的 dirp 结构体地址,供后面的程序对这些结构体进行操作,达到隐藏文件的目的。

  • 当前代码块主要工作 是找出我要隐藏的目标文件的地址,找到后停止继续查找,在这个过程中他会不断的对 map_to_patch 进行赋值,而 map_to_patch 结构体中保存的永远是目标文件的上一个文件,这样在发生尾调用时候,被调用的程序将会拿到目标文件的上一个文件结构体的地址,并进行算法处理,更改内存达到隐藏目标的目的。

转载自:None安全团队公众号 

链接:ebpf 隐藏 Android 任意进程或任意文件实现连载 (一) (qq.com)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值