eBPF与sk_buff


sk_buff

func#0 @0
0: R1=ctx(id=0,off=0,imm=0) R10=fp0
0: (bf) r7 = r1
1: R1=ctx(id=0,off=0,imm=0) R7_w=ctx(id=0,off=0,imm=0) R10=fp0

刚进入eBPF程序时,r1寄存器指向ctx;

skb_load调用接口如下:

BPF_RAW_INSN(BPF_JMP|BPF_CALL, 0, 0, 0, BPF_FUNC_skb_load_bytes), // args: r1 = ctx, r2 = 0, r3 = fp -8, r4 = 10 [verifier 8]

函数原型如下:

int bpf_skb_load_bytes(struct __sk_buff *skb, __u32 offset, void *to, __u32 len);

所以 r1=ctx,r2=offset,r3=to,r4=len;

关于几个溢出:

  1. 如果r4超过to的缓冲区长度:

    BPF_MOV64_IMM(BPF_REG_4, 0x20000), //缓冲区长度只有0x2000

    会在verifier报错:

  1. 如果r4仅仅超过了write的长度(sk_buff中数据的长度):

    BPF_MOV64_IMM(BPF_REG_4, 0x800), //只write了0x100个字节

    则会在运行时返回一个错误值:

  1. 完全正确时,返回值是0:

堆栈

r10寄存器是只读寄存器;

堆栈指针确实不支持SUB指令,只能用ADD指令加一个负数来实现;

在ebpf程序一开始,r10寄存器存放的栈地址的最大值,任何栈指针的增减不能大于这个地址;

fp-16存放一个指针,然后开辟32字节空间,这个时候如果load34字节的sk_buff中的数据:

则会得到如下报错:

但是如果不存放那个指针就不会出错;

但是如果在这个fp-16的位置存放一个普通的标量数字,则可以被sk_buff覆盖;栈上的指针有着相应的标记?后续可以读取并使用?

这里还需要注意堆栈的一个好处,map的空间是用户可读的,所以正常情况下是不能存入指针类型的数据的,但是堆栈却没有这个问题;

现在可以确定的是,堆栈中可以存入指针,并且这个值收到保护,之后可以取出来用于访存操作

相关调试技巧

如果我们用如下代码分配bpf_map:

int expmap_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x2000, 1);
if (expmap_fd < 0) perror("BPF_MAP_CREATE");//, err_exit("BPF_MAP_CREATE");

那么我们可以在array_map_alloc函数下断点,finish之后即可返回相应的bpf_map结构体;

对于sk_buff的读取,我们可以在bpf_skb_load_bytes函数下断点,其4个参数就是我们在eBPF代码中传递的4个参数,同时可以看到我们eBPF栈的具体地址。

总结

综上我们可以先在栈中存放一个指针,然后向上抬栈,之后利用vul-reg伪造len,然后读取sk_buff中的内容到ebpf栈上,从而导致溢出,覆盖我们保存在栈上的指针,然后将这个指针从栈上取出来,就可以进行相应的读取操作,并最终提升至任意地址读写的能力。


文章作者: q1ming
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 q1ming !
  目录