前段时间,老板让我 手撕 分析 一个陌生的 XDP 程序。

责任重过山头,莫敢推辞,只能硬着头皮上了。

陌生的 XDP 程序

没有源代码,且发现该 XDP 程序还没带上 debug 信息;分析的难度急剧飙升。

不过,根据 XDP 程序开发的套路,还是能够从 bpf 汇编分析一下 XDP 程序大概的处理逻辑。以下为部分 bpf 汇编:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
# bpftool p d x i 21712
   0: (bf) r3 = r1
   1: (b7) r0 = 1
   2: (79) r1 = *(u64 *)(r3 +8)
   3: (79) r4 = *(u64 *)(r3 +0)
   4: (bf) r2 = r4
   5: (07) r2 += 14
   6: (2d) if r2 > r1 goto pc+144
   7: (71) r6 = *(u8 *)(r4 +13)
   8: (67) r6 <<= 8
   9: (71) r2 = *(u8 *)(r4 +12)
  10: (4f) r6 |= r2
  11: (15) if r6 == 0xa888 goto pc+3
  12: (b7) r2 = 14
  13: (b7) r5 = 0
  14: (55) if r6 != 0x81 goto pc+7
  15: (bf) r2 = r4
  16: (07) r2 += 18
  17: (b7) r0 = 1
  18: (2d) if r2 > r1 goto pc+132
  19: (b7) r2 = 18
  20: (b7) r5 = 4
  21: (69) r6 = *(u16 *)(r4 +16)
  22: (bf) r0 = r6
  23: (57) r0 &= 65535
  24: (15) if r0 == 0x81 goto pc+2
  25: (15) if r0 == 0xa888 goto pc+1
  26: (05) goto pc+12
  27: (bf) r7 = r2
  28: (07) r7 += 4
  29: (bf) r6 = r4
  30: (0f) r6 += r7
  31: (b7) r0 = 1
  32: (2d) if r6 > r1 goto pc+118
  33: (bf) r0 = r4
  34: (07) r0 += 2
  35: (0f) r0 += r2
  36: (07) r5 += 4
  37: (69) r6 = *(u16 *)(r0 +0)
  38: (bf) r2 = r7
  39: (57) r6 &= 65535
  40: (b7) r0 = 2
  41: (15) if r6 == 0x8 goto pc+1
  42: (05) goto pc+108
  43: (bf) r7 = r4
  44: (0f) r7 += r2
  45: (bf) r6 = r7
  46: (07) r6 += 20
  47: (b7) r0 = 1
  48: (2d) if r6 > r1 goto pc+102
  49: (71) r2 = *(u8 *)(r7 +9)
  50: (b7) r0 = 2
  51: (55) if r2 != 0x6 goto pc+99
  52: (bf) r2 = r6
  53: (07) r2 += 20
  54: (b7) r0 = 1
  55: (2d) if r2 > r1 goto pc+95
  56: (69) r1 = *(u16 *)(r6 +2)
  57: (b7) r0 = 2
  58: (55) if r1 != 0xfcff goto pc+92
  59: (69) r1 = *(u16 *)(r6 +12)
  60: (57) r1 &= 512
  61: (b7) r0 = 2
  62: (15) if r1 == 0x0 goto pc+88
  63: ...
 131: (b7) r8 = 0
 132: (79) r1 = *(u64 *)(r10 -48)
 133: (05) goto pc+29
 134: (18) r1 = 0xa726f72726520
 136: (7b) *(u64 *)(r10 -8) = r1
 137: (18) r1 = 0x67616c665f706374
 139: (7b) *(u64 *)(r10 -16) = r1
 140: (18) r1 = 0x206563616c706572
 142: (7b) *(u64 *)(r10 -24) = r1
 143: (18) r1 = 0x5f6d7573635f346c
 145: (7b) *(u64 *)(r10 -32) = r1
 146: (bf) r1 = r10
 147: (07) r1 += -32
 148: (b7) r2 = 32
 149: (85) call bpf_trace_printk#-55408
 150: (b7) r0 = 1
 151: (95) exit
 152: ...
 196: (b7) r1 = 0
 197: (73) *(u8 *)(r10 -2) = r1
 198: (b7) r1 = 2674
 199: (6b) *(u16 *)(r10 -4) = r1
 200: (b7) r1 = 1869771365
 201: (63) *(u32 *)(r10 -8) = r1
 202: (18) r1 = 0x207165735f6b6361
 204: (7b) *(u64 *)(r10 -16) = r1
 205: (18) r1 = 0x206563616c706572
 207: (7b) *(u64 *)(r10 -24) = r1
 208: (18) r1 = 0x5f6d7573635f346c
 210: (7b) *(u64 *)(r10 -32) = r1
 211: (bf) r1 = r10
 212: (07) r1 += -32
 213: (b7) r2 = 31
 214: (05) goto pc-66
 215: ...

参考

1
2
3
4
5
6
7
enum xdp_action {
    XDP_ABORTED = 0,
    XDP_DROP,
    XDP_PASS,
    XDP_TX,
    XDP_REDIRECT,
};

给 bpf 汇编加上一些注释:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
# bpftool p d x i 21712
   0: (bf) r3 = r1
   1: (b7) r0 = 1                       // XDP_DROP
   2: (79) r1 = *(u64 *)(r3 +8)
   3: (79) r4 = *(u64 *)(r3 +0)
   4: (bf) r2 = r4
   5: (07) r2 += 14
   6: (2d) if r2 > r1 goto pc+144
   7: (71) r6 = *(u8 *)(r4 +13)
   8: (67) r6 <<= 8
   9: (71) r2 = *(u8 *)(r4 +12)
  10: (4f) r6 |= r2
  11: (15) if r6 == 0xa888 goto pc+3    // VLAN 8021AD
  12: (b7) r2 = 14
  13: (b7) r5 = 0
  14: (55) if r6 != 0x81 goto pc+7      // VLAN 8021Q
  15: (bf) r2 = r4
  16: (07) r2 += 18
  17: (b7) r0 = 1                       // XDP_DROP
  18: (2d) if r2 > r1 goto pc+132
  19: (b7) r2 = 18
  20: (b7) r5 = 4
  21: (69) r6 = *(u16 *)(r4 +16)
  22: (bf) r0 = r6
  23: (57) r0 &= 65535
  24: (15) if r0 == 0x81 goto pc+2      // VLAN 8021Q
  25: (15) if r0 == 0xa888 goto pc+1    // VLAN 8021AD
  26: (05) goto pc+12
  27: (bf) r7 = r2
  28: (07) r7 += 4
  29: (bf) r6 = r4
  30: (0f) r6 += r7
  31: (b7) r0 = 1                       // XDP_DROP
  32: (2d) if r6 > r1 goto pc+118
  33: (bf) r0 = r4
  34: (07) r0 += 2
  35: (0f) r0 += r2
  36: (07) r5 += 4
  37: (69) r6 = *(u16 *)(r0 +0)
  38: (bf) r2 = r7
  39: (57) r6 &= 65535
  40: (b7) r0 = 2                       // XDP_PASS
  41: (15) if r6 == 0x8 goto pc+1       // ETH_P_IP
  42: (05) goto pc+108
  43: (bf) r7 = r4
  44: (0f) r7 += r2
  45: (bf) r6 = r7
  46: (07) r6 += 20                     // iphdr
  47: (b7) r0 = 1                       // XDP_DROP
  48: (2d) if r6 > r1 goto pc+102
  49: (71) r2 = *(u8 *)(r7 +9)
  50: (b7) r0 = 2                       // XDP_PASS
  51: (55) if r2 != 0x6 goto pc+99      // IPPROTO_TCP
  52: (bf) r2 = r6
  53: (07) r2 += 20                     // tcphdr
  54: (b7) r0 = 1                       // XDP_DROP
  55: (2d) if r2 > r1 goto pc+95
  56: (69) r1 = *(u16 *)(r6 +2)
  57: (b7) r0 = 2                       // XDP_PASS
  58: (55) if r1 != 0xfcff goto pc+92   // dport 65532
  59: (69) r1 = *(u16 *)(r6 +12)
  60: (57) r1 &= 512
  61: (b7) r0 = 2                       // XDP_PASS
  62: (15) if r1 == 0x0 goto pc+88      // tcp flags, not SYN
  63: ...
 131: (b7) r8 = 0
 132: (79) r1 = *(u64 *)(r10 -48)
 133: (05) goto pc+29
 134: (18) r1 = 0xa726f72726520
 136: (7b) *(u64 *)(r10 -8) = r1
 137: (18) r1 = 0x67616c665f706374
 139: (7b) *(u64 *)(r10 -16) = r1
 140: (18) r1 = 0x206563616c706572
 142: (7b) *(u64 *)(r10 -24) = r1
 143: (18) r1 = 0x5f6d7573635f346c
 145: (7b) *(u64 *)(r10 -32) = r1
 146: (bf) r1 = r10
 147: (07) r1 += -32
 148: (b7) r2 = 32
 149: (85) call bpf_trace_printk#-55408     // print "l4_csum_replace tcp_flag error\n"
 150: (b7) r0 = 1                           // XDP_DROP
 151: (95) exit
 152: ...
 196: (b7) r1 = 0
 197: (73) *(u8 *)(r10 -2) = r1
 198: (b7) r1 = 2674
 199: (6b) *(u16 *)(r10 -4) = r1
 200: (b7) r1 = 1869771365
 201: (63) *(u32 *)(r10 -8) = r1
 202: (18) r1 = 0x207165735f6b6361
 204: (7b) *(u64 *)(r10 -16) = r1
 205: (18) r1 = 0x206563616c706572
 207: (7b) *(u64 *)(r10 -24) = r1
 208: (18) r1 = 0x5f6d7573635f346c          // l4_csum_replace ack_seq erro
 210: (7b) *(u64 *)(r10 -32) = r1
 211: (bf) r1 = r10
 212: (07) r1 += -32
 213: (b7) r2 = 31
 214: (05) goto pc-66
 215: ...

结合两个 bpf_trace_printk() 可知,这个 XDP 程序对目的端口为 65532 的 TCP SYN 包进行了网络包修改。

尽管分析的结果还不够透彻,但已满足老板的预期,就不再继续分析了。

分析过程

XDP 套路一:判断协议

在 XDP 程序里处理网络包,首先需要判断的就是当前网络包是不是预期的那个;如果不是,则 XDP_PASS

而在,判断的过程中,如果发现是畸形包,则直接 XDP_DROP

上面的 XDP 程序就判断了 ETH_P_8021QETH_P_8021AD、ETH_P_IPIPPROTO_TCP 等协议。

比如:

1
2
3
eth = (typeof(eth)) xdp->data;
if (eth->h_proto == ETH_P_8021Q || eth->h_proto == ETH_P_8021AD)
    eth = (void *) (eth + 1) + sizeof(struct vlan_hdr);

XDP 套路二:判断范围

在读取网络包内容前,必须先判断读取的内存有没有超出网络包内容的范围了。

上面的 XDP 程序里,如果超出网络包内容的范围,就 XDP_PASS。

比如:

1
2
3
eth = (typeof(eth)) xdp->data;
if ((void *) (eth + 1) > xdp->data_end)
    return XDP_PASS;

XDP 套路三:bpf\_trace\_printk() 的使用

其实,这是大部分 bpf 程序的套路了。

这是因为 bpf_trace_printk() 所使用的字符串都保存到栈上,如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 134: (18) r1 = 0xa726f72726520
 136: (7b) *(u64 *)(r10 -8) = r1
 137: (18) r1 = 0x67616c665f706374
 139: (7b) *(u64 *)(r10 -16) = r1
 140: (18) r1 = 0x206563616c706572
 142: (7b) *(u64 *)(r10 -24) = r1
 143: (18) r1 = 0x5f6d7573635f346c
 145: (7b) *(u64 *)(r10 -32) = r1
 146: (bf) r1 = r10
 147: (07) r1 += -32
 148: (b7) r2 = 32
 149: (85) call bpf_trace_printk#-55408     // print "l4_csum_replace tcp_flag error\n"

通过 r1 = 0xa726f72726520; *(u64 *)(r10 -8) = r1 的方式,将字符串保存到栈上,然后准备好 bpf_trace_printk() 的参数 R1R2,最后 call bpf_trace_printk#-55408;这里,R1 是指向字符串保存的栈空间的地址的指针,R2 是字符串大小 32 字节。

所以,可以将保存到栈上的字符串给翻译出来:

1
2
3
4
s := []uint64{0x5f6d7573635f346c, 0x206563616c706572, 0x67616c665f706374, 0xa726f72726520}
b := (*byte)(unsafe.Pointer(&s[0]))
fmt.Println(unsafe.String(b, len(s)*8))
// l4_csum_replace tcp_flag error

这里就是 bpf_printk("l4_csum_replace tcp_flag error\n") 了。

小结

只要掌握了那些 XDP(bpf)程序的开发套路,分析陌生的 XDP 程序就不在话下。