CFramework/verify_fonts.py

141 lines
5.6 KiB
Python

# -*- coding: utf-8 -*-
"""
Verify that ImGui font headers (fa.h, RobotoRegular.h) contain genuine
stb_compress-compressed TrueType font data and no executable payload.
Method: port of stb_decompress from imgui_draw.cpp (stb.h, public domain).
"""
import re, struct, sys, zlib
def parse_font_header(path):
"""Extract compressed byte stream from an ImGui font header."""
with open(path, 'rb') as f:
raw = f.read()
# detect BOM / encoding
if raw[:2] in (b'\xff\xfe', b'\xfe\xff'):
text = raw.decode('utf-16')
else:
text = raw.decode('latin-1')
# grab all hex u32 literals inside the array
values = re.findall(r'0x([0-9a-fA-F]{8})', text)
if not values:
raise ValueError("no array data found in " + path)
data = bytearray()
for v in values:
data += struct.pack('<I', int(v, 16))
# size sanity: header says compressed_size = N; expect N bytes
m = re.search(r'compressed_size\s*=\s*(\d+)', text)
declared = int(m.group(1)) if m else None
return bytes(data), declared
def stb_decompress(data):
"""Port of imgui_draw.cpp stb_decompress."""
def in2(x): return (data[x] << 8) + data[x+1]
def in3(x): return (data[x] << 16) + (data[x+1] << 8) + data[x+2]
def in4(x): return (data[x] << 24) + (data[x+1] << 16) + (data[x+2] << 8) + data[x+3]
if in4(0) != 0x57BC0000:
return None, "bad magic"
if in4(4) != 0:
return None, "stream >4GB"
olen = in4(8)
out = bytearray()
i = 16
n = len(data)
while i < n:
old_i = i
c = data[i]
if c >= 0x20:
if c >= 0x80:
dist = data[i+1] + 1; ln = c - 0x80 + 1; i += 2
# match: copy ln bytes from out[-dist]
for _ in range(ln): out.append(out[-dist])
elif c >= 0x40:
dist = in2(i) - 0x4000 + 1; ln = data[i+2] + 1; i += 3
for _ in range(ln): out.append(out[-dist])
else:
ln = c - 0x20 + 1
for _ in range(ln): out.append(data[i+1]); i += 1
i += 1
else:
if c >= 0x18:
dist = in3(i) - 0x180000 + 1; ln = data[i+3] + 1; i += 4
for _ in range(ln): out.append(out[-dist])
elif c >= 0x10:
dist = in3(i) - 0x100000 + 1; ln = in2(i+3) + 1; i += 5
for _ in range(ln): out.append(out[-dist])
elif c >= 0x08:
ln = in2(i) - 0x0800 + 1
for _ in range(ln): out.append(data[i+2]); i += 1
i += 2
elif c == 0x07:
ln = in2(i+1) + 1
for _ in range(ln): out.append(data[i+3]); i += 1
i += 3
elif c == 0x06:
dist = in3(i+1) + 1; ln = data[i+4] + 1; i += 5
for _ in range(ln): out.append(out[-dist])
elif c == 0x04:
dist = in3(i+1) + 1; ln = in2(i+4) + 1; i += 6
for _ in range(ln): out.append(out[-dist])
else:
# 0x00..0x03 / 0x05 are not used as token starts; 0x05 0xfa is terminator
if c == 0x05 and i+1 < n and data[i+1] == 0xfa:
break
return None, "unexpected token 0x%02x at %d" % (c, i)
if i == old_i:
return None, "stalled at %d" % i
if len(out) != olen:
return None, "len mismatch: out=%d olen=%d" % (len(out), olen)
# verify adler32: stored right after the 05 FA terminator (big-endian)
if i + 2 + 4 > n:
return None, "no adler trailer"
expected_adler = in4(i + 2)
if zlib.adler32(bytes(out)) != expected_adler:
return None, "adler mismatch: got 0x%08x expected 0x%08x" % (zlib.adler32(bytes(out)), expected_adler)
return bytes(out), None
def check_ttf(buf, name):
print("=" * 70)
print("[%s] decompressed size: %d bytes" % (name, len(buf)))
print(" head16:", buf[:16].hex())
# TTF/OTF sfnt version
sfnt = buf[:4]
if sfnt in (b'\x00\x01\x00\x00', b'OTTO', b'true', b'typ1'):
print(" sfnt version: OK (%s)" % sfnt.hex())
else:
print(" sfnt version: UNEXPECTED (%s)" % sfnt.hex())
num_tables = struct.unpack('>H', buf[4:6])[0]
print(" numTables: %d" % num_tables)
tags = []
for i in range(min(num_tables, 64)):
off = 12 + i * 16
if off + 16 > len(buf): break
tag = buf[off:off+4].decode('latin-1', 'replace')
tags.append(tag)
print(" tables:", ' '.join(tags))
# executable payload markers
mz = buf.find(b'MZ')
pe = buf.find(b'PE\x00\x00')
print(" MZ header at: %s | PE header at: %s" % (mz if mz>=0 else 'none', pe if pe>=0 else 'none'))
# any http urls / suspicious ascii
for pat in (b'http', b'powershell', b'cmd.exe', b'CreateRemoteThread', b'WinExec'):
idx = buf.find(pat)
if idx >= 0:
print(" !! found '%s' at offset %d" % (pat.decode(), idx))
for path in [r"F:\desktop\CFramework\CFramework\Framework\ImGui\Fonts\fa.h",
r"F:\desktop\CFramework\CFramework\Framework\ImGui\Fonts\RobotoRegular.h"]:
data, declared = parse_font_header(path)
print("=" * 70)
print("file:", path)
print(" raw bytes extracted: %d | declared compressed_size: %s" % (len(data), declared))
print(" magic:", data[:4].hex(), "| size field:", int.from_bytes(data[8:12], 'big'), "| adler:", hex(int.from_bytes(data[12:16], 'big')))
out, err = stb_decompress(data)
if err:
print(" DECOMPRESS FAILED:", err)
sys.exit(1)
check_ttf(out, path.split('\\')[-1])
print("=" * 70)
print("VERIFICATION COMPLETE")