HackThisSite - Forensic Mission 3

Challenge

Forensic 3 — PapaSmurphey's Pizza

The local police just arrested a suspect believed to be the notorious PapaSmurphey, an online pizza trader. A flash drive was recovered from the scene and was copied; poorly. As best as you can, examine the contents for any incriminating evidence on the suspect. This challenge has several steps. The completion password will look like this: HTS{the_answer}

Solution

Step 1: 素材清单

附件 forensic_3.rar 解开后是 flashdrive/ 下 9 个文件。关键文件是体积异常的 shh.jpg:它只有 500×500 灰度图像,却占用约 10 MB;siggies.txt 则提供了文件签名 (magic bytes)参考。

1
2
3
4
5
6
7
8
49ZfDSNg.jpg                     90658   680x992 JPEG(其实是一张三格漫画)
bitcoin.pdf 184292 PDF 1.4,9 页
DYNAMITE - Winamp 5.0RC8crk.mp3 2230928 MPEG layer III 48kHz
Hack This Site!.url 55 Windows Internet Shortcut(内容就是 hackthissite.org)
legrandelibrary.xlsx 12871 xlsx,作者 Sam
shh.jpg 10376722 500x500 **灰度** JPEG,尺寸与体积严重不符
siggies.txt 54743 十六进制文件签名对照表
UXo9C84.jpg 57822 475x960 progressive JPEG

siggies.txt 是一张文件签名/魔数对照表(TGA、MOV、MOF……), 题面用它给出提示:类型按魔数判断,不以扩展名为准。

一张 500×500 的灰度 JPEG 不可能有 10 MB。binwalk 直接给出结构:

1
binwalk extracted/flashdrive/shh.jpg
1
2
0          0x0       JPEG image, total size: 32346 bytes
10371574 0x9E41F6 JPEG image, total size: 5148 bytes

也就是说:第一张 JPEG 在偏移 32346 处结束,文件从这个偏移开始的内容不是普通 JPEG 尾部,而是连续嵌入的数据。继续扫描 JPEG 起始标记 FF D8 FF,在偏移 10371574 处找到第二张 JPEG(205×80);因此中间区间 [32346, 10371574) 正好是隐藏归档。

这条边界很重要:如果只用 binwalk 或普通文件识别,可能只能看到两张 JPEG,或者把 RAR 当成未知数据;应按文件签名和 JPEG 的 FF D9/FF D8 FF 边界手工确认。

Step 2: Carve 隐藏文件并修复 RAR 签名

shh.jpg 的结构可以概括为:

1
2
[JPEG #1][Zar!\x1a\x07\x00 ... encrypted RAR ...][JPEG #2]
^ RAR 的第一个签名字节被改成了 Z

下面的 carving 脚本从实际 JPEG 标记计算第一张图片的结束位置和第二张图片的起始位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/env python3
"""Carve and repair the embedded RAR stream from shh.jpg."""

from pathlib import Path

RAR4_SIGNATURE = b"Rar!\x1a\x07\x00"
BROKEN_SIGNATURE = b"Zar!\x1a\x07\x00"


def carve(source, rar_output, tail_jpeg_output):
data = Path(source).read_bytes()
first_eoi = data.find(b"\xff\xd9")
rar_start = first_eoi + 2
tail_start = data.find(b"\xff\xd8\xff", rar_start)
if first_eoi < 0 or tail_start < 0 or not rar_start < tail_start:
raise SystemExit("could not locate JPEG/RAR/JPEG boundaries")

carved = bytearray(data[rar_start:tail_start])
if bytes(carved[: len(BROKEN_SIGNATURE)]) != BROKEN_SIGNATURE:
raise SystemExit(
"unexpected embedded signature: %r" % bytes(carved[:8])
)
carved[: len(RAR4_SIGNATURE)] = RAR4_SIGNATURE
Path(rar_output).write_bytes(carved)
Path(tail_jpeg_output).write_bytes(data[tail_start:])
print("first JPEG end:", rar_start)
print("RAR start:", rar_start)
print("second JPEG start:", tail_start)
print("RAR bytes:", len(carved))
print("tail JPEG bytes:", len(data) - tail_start)


def main():
carve("shh.jpg", "fixed.rar", "tail_embedded.jpg")
print("RAR signature:", Path("fixed.rar").read_bytes()[:8])


if __name__ == "__main__":
main()

运行脚本的实际输出:

1
2
3
4
5
6
first JPEG end: 32346
RAR start: 32346
second JPEG start: 10371574
RAR bytes: 10339228
tail JPEG bytes: 5148
RAR signature: b'Rar!\\x1a\\x07\\x00'

此时 tail_embedded.jpg 是一个独立的 205×80 JPEG。打开它(也可以对它做 OCR 或直接放大) 能读出一个容易混淆 O/0 的 logo 文字,视觉上近似:

1
GLOzMe

Step 3: 使用图片中的密码解压

再让 7-Zip 认修复后的归档,并用从 tail_embedded.jpg 读出的候选变体测试。 通过校验的密码是 GL0zMe(第三个字符是数字 0):

1
2
7z t -pGL0zMe fixed.rar
7z x -y -pGL0zMe -so fixed.rar 'DNM Login.txt'
1
2
Everything is Ok
PapaSmurphey -- HTS{You_caught_me!}

Vulnerabilities

从闪存盘取证的场景里,拷贝的完整性本身就是证据的一部分: 本例的归档被改掉了一个签名字节、还被截断,靠魔数对照 + 字节级修复仍能还原出归档结构, 说明把文件混在图片里 + 改扩展名 + 改首字节这类手法只能阻碍直接双击查看。 需要保密的介质应整盘加密(如 BitLocker/LUKS),文件层面的伪装不构成保护。