Challenge
Application Challenge 11 (Windows) — Find the password. (medium) 目标:从这个 Windows GUI 程序里找出 password。
包内只有一个 app11win.exe(40 KB,PE32 GUI)。程序是个 Visual Basic 6 写的窗口,界面上有一个 Text1 输入框和一个标题为 Check 的按钮:典型的输入密码点确认形态。真正的东西不在代码里,而在程序随身带着的一张图片里:password 被画成文字印在内嵌的 JPEG 上。
Solution
file app11win.exe→PE32 executable for MS Windows 4.00 (GUI), Intel i386, 3 sections;导入表只有MSVBVM60.DLL→ VB6 编译的程序。strings里能看到控件名和工程信息:Form1、Command1、Check、Text1、App Challenge、challenge5Project1;.text段偏移0x54BC处有 VB 工程头魔数VB5!。入口点把&DAT_004054bc(就是这个工程头)交给 VB 运行时初始化,是标准的 VB6 启动流程。- 二进制里 搜不到任何明文答案:
Search/Destroy/Awnser/Answer/password在 ASCII 和 UTF-16 两种编码下命中数都是 0。密码是一组像素。 binwalk app11win.exe直接报出 JPEG:
$ binwalk app11win.exeDECIMAL HEXADECIMAL DESCRIPTION------------------------------------------------------------------------0 0x0 Windows PE binary, machine type: Intel x864766 0x129E JPEG image, total size: 4536 bytesStep 1: 提取内嵌图片
binwalk 只给出了一个 blob,但那实际上是两个叠加在一起的 JPEG。手工遍历一遍 JPEG marker 才能看清:
--- walk from 0x129E --- 0x129E: FFD8 0x12A0: FFE0 len=16 JFIF 0x12B2: FFE1 len=1710 Exif <- 内含一张 160 字节的 Exif 缩略图 0x1962: FFED len=2920 Photoshop <- 2300 字节的 APP13,里面塞了一整张独立 JPEG 0x24CC: FFE1 len=4680 XMP <- APP1 XMP 元数据 0x3716: FFEE len=14 APP14 <- Adobe 标记 0x3726: FFDB len=132 DQT <- 量化表 0x37AC: FFC0 len=17 b'\x08\x00:\x00\xe9\x03...' <- SOF0: 高 0x3A=58, 宽 0xE9=233 0x3969: FFDA len=12 SOS ...scan data then EOI at 0x4B35
--- walk from 0x1ED6 --- 0x1ED6: FFD8 0x1ED8: FFE0 len=16 JFIF 0x1F8E: FFC0 len=17 b'\x08\x00 \x00\x80\x03...' <- SOF0: 高 0x20=32, 宽 0x80=128 0x20E8: FFDA len=12 SOS ...scan data then EOI at 0x2454结论:
- image A:从
0x129E到0x4B35,233×58,14489 字节。它不能按第一个FF D9截断:A 的 APP1 Exif 段里那张缩略图的 EOI(0x1960)会先命中,截出来的图打不开(identify直接报错),脚本因此先定位SOS再找它后面第一个真正的 EOI。 - 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 welocate SOS explicitly and take everything up to the first real EOI after it."""import struct
data = open("app11win.exe", "rb").read()
def carve(soi): """Return (start, end) of a full JPEG beginning at `soi`.""" p = soi + 2 while p < len(data) - 1:21 collapsed lines
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) or 0xD0 <= 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] == 0xFF and 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)}")$ cd <hts-workspace>/challenges/hts-app/app11 && uv run python carve_jpegs.pyimage_a.jpg: 0x129E..0x4B37 size=14489image_b.jpg: 0x1ED6..0x2456 size=1408
$ file extracted/image_a.jpg extracted/image_b.jpgextracted/image_a.jpg: JPEG image data, JFIF ... baseline, precision 8, 233x58, components 3extracted/image_b.jpg: JPEG image data, JFIF ... baseline, precision 8, 128x32, components 3直接读,得到 The Awnser is: Search&Destroy