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;
关于几个溢出:
如果r4超过to的缓冲区长度:
BPF_MOV64_IMM(BPF_REG_4, 0x20000), //缓冲区长度只有0x2000
会在verifier报错:

如果r4仅仅超过了write的长度(sk_buff中数据的长度):
BPF_MOV64_IMM(BPF_REG_4, 0x800), //只write了0x100个字节
则会在运行时返回一个错误值:

- 完全正确时,返回值是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); |
那么我们可以在array_map_alloc函数下断点,finish之后即可返回相应的bpf_map结构体;
对于sk_buff的读取,我们可以在bpf_skb_load_bytes函数下断点,其4个参数就是我们在eBPF代码中传递的4个参数,同时可以看到我们eBPF栈的具体地址。
总结
综上我们可以先在栈中存放一个指针,然后向上抬栈,之后利用vul-reg伪造len,然后读取sk_buff中的内容到ebpf栈上,从而导致溢出,覆盖我们保存在栈上的指针,然后将这个指针从栈上取出来,就可以进行相应的读取操作,并最终提升至任意地址读写的能力。