image B:从 0x1ED6 到
0x2454,128×32,1408 字节;它整张位于
A 的 APP13(Photoshop IRB)段内,本质是 A 的缩略图(把 A 缩到
128×32 与 B 逐像素比,平均绝对差只有
5.2/255,内容一致);它只有 1408
字节,像素量不足以读出可靠文本,读数只能以 image A 为依据。
"""Carve the two real JPEGs out of app11win.exe. Marker walk shows: * JPEG A at 0x129E: SOI, JFIF, APP1 Exif(1710, embeds a 160-byte thumbnail), APP13 Photoshop(2920, embeds the whole of JPEG B), APP1 XMP, ... SOF0 233x58, SOS, scan data, EOI at 0x4B35. * JPEG B at 0x1ED6: complete 128x32 baseline JPEG living inside A's APP13. The naive "first FF D9" carve truncates A at the Exif thumbnail's EOI, so we locate SOS explicitly and take everything up to the first real EOI after it. """ import struct
data = open("app11win.exe", "rb").read()
defcarve(soi): """Return (start, end) of a full JPEG beginning at `soi`.""" p = soi + 2 while p < len(data) - 1: while data[p] != 0xFF: p += 1 m = data[p + 1] if m == 0xDA: # SOS -> skip header, then scan for EOI ln = struct.unpack_from(">H", data, p + 2)[0] p = p + 2 + ln break if m in (0xD8, 0xD9) or0xD0 <= m <= 0xD7: p += 2 continue if m == 0xFF: p += 1 continue ln = struct.unpack_from(">H", data, p + 2)[0] p = p + 2 + ln # scan entropy-coded data for EOI (FF D9) q = p while q < len(data) - 1: if data[q] == 0xFFand data[q + 1] == 0xD9: return soi, q + 2 q += 1 return soi, len(data)
for name, off in (("image_a.jpg", 0x129E), ("image_b.jpg", 0x1ED6)): s, e = carve(off) blob = data[s:e] open("extracted/" + name, "wb").write(blob) print(f"{name}: 0x{s:X}..0x{e:X} size={len(blob)}")
1 2 3 4 5 6 7
$ cd <hts-workspace>/challenges/hts-app/app11 && uv run python carve_jpegs.py image_a.jpg: 0x129E..0x4B37 size=14489 image_b.jpg: 0x1ED6..0x2456 size=1408
"""Read the text baked into image_a.jpg glyph by glyph. We threshold the white text, crop the single text line, split it into glyph columns on fully-blank separators, and print each glyph as a #/. bitmap so the string can be read by eye. """ from PIL import Image
img = Image.open("extracted/image_a.jpg").convert("L") W, H = img.size px = img.load()
# White text on a dark red->black gradient: keep bright pixels. THRESH = 170 rows = [[1if px[x, y] > THRESH else0for x inrange(W)] for y inrange(H)]
defbbox(grid): ys = [y for y, r inenumerate(grid) ifany(r)] xs = [x for x inrange(len(grid[0])) ifany(r[x] for r in grid)] returnmin(xs), min(ys), max(xs), max(ys)
x0, y0, x1, y1 = bbox(rows) print(f"image {W}x{H}; text bbox x={x0}..{x1} y={y0}..{y1}") line = [r[x0:x1 + 1] for r in rows[y0:y1 + 1]]
# Column occupancy -> split into glyphs on blank columns. ncols = len(line[0]) col_has = [any(r[c] for r in line) for c inrange(ncols)] glyphs = [] c = 0 while c < ncols: if col_has[c]: s = c while c < ncols and col_has[c]: c += 1 glyphs.append((s, c - 1)) else: c += 1
print(f"{len(glyphs)} raw glyph runs\n") for i, (s, e) inenumerate(glyphs): sub = [r[s:e + 1] for r in line] ys = [y for y, r inenumerate(sub) ifany(r)] sub = sub[min(ys):max(ys) + 1] print(f"glyph {i:02d} x={x0+s}..{x0+e} w={e-s+1} h={len(sub)}") for r in sub: print(" " + "".join("#"if v else"."for v in r)) print()
run : 0 1 2 | 3(A+w) 4 5 6 7 | 8 9 10 | 11 12 13 14(rc) 15 | 16 | 17 18 19 20 21(ro) 22 char: T h e | A w n s e r | i s : | S e a r c h | & | D e s t r o y
即
The Awnser is: Search&Destroy(h
与 & 之间、& 与 D
之间的空隙都是 2 像素,和词内字符间距一样,所以 &
两侧没有空格)。逐字形读数与脚本输出一致;&
这种符号靠手工切图确认。