コース目次 / 第1章

ファイルの終わりの、その先 — carving

PNGファイルの末尾にzipを追記して隠します。fileコマンドは気づかないこと、binwalkが埋め込まれたzip署名を検出できることを確認し、バイト列を切り出して復元します。

第1章 / 全5章目安 約12分この章のゴール: ファイル末尾に追記されたデータをbinwalkで検出し、切り出して復元できるようになる

隠しファイルを、作る

make_carving.py として保存します。PNG画像を1つ手作りして(Pillow等の画像ライブラリは使わず、PNG形式の仕様どおりにチャンクを組み立てます)、その末尾にzipアーカイブを追記します。

import struct, zlib, subprocess

def chunk(tag, data):
    return struct.pack('>I', len(data)) + tag + data + struct.pack('>I', zlib.crc32(tag + data))

width, height = 8, 8
sig = b'\x89PNG\r\n\x1a\n'
ihdr = chunk(b'IHDR', struct.pack('>IIBBBBB', width, height, 8, 2, 0, 0, 0))
row = b'\x00' + bytes([120, 180, 220] * width)  # filter byte + RGB pixels
idat = chunk(b'IDAT', zlib.compress(row * height))
iend = chunk(b'IEND', b'')
png = sig + ihdr + idat + iend

with open('photo.png', 'wb') as f:
    f.write(png)

with open('flag.txt', 'w') as f:
    f.write('flag{carved_from_the_png_tail}\n')
subprocess.run(['zip', 'hidden.zip', 'flag.txt'], check=True)

hidden_zip = open('hidden.zip', 'rb').read()
with open('combo.png', 'wb') as f:
    f.write(png + hidden_zip)

print('combo.png size:', len(png) + len(hidden_zip))
python3 make_carving.py

combo.png が、今回のチャレンジファイルです。これ以降、あなたは中身(flagの値)を知らないふりをして進めてください。

fileコマンドで、見てみる

file combo.png
combo.png: PNG image data, 8 x 8, 8-bit/color RGB, non-interlaced

fileは「先頭のマジックバイトからPNGだと判定した」だけで、ファイル全体を見ているわけではありません。PNGはIENDチャンクで論理的に終わりますが、OS上のファイルとしては、その後ろにいくらでもバイト列を続けられます。画像ビューアはIENDより後を無視するので、見た目には何も分かりません。

binwalkで、中を掘る

binwalk combo.png
DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PNG image, 8 x 8, 8-bit/color RGB, non-interlaced
41            0x29            Zlib compressed data, default compression
74            0x4A            Zip archive data, at least v1.0 to extract, compressed size: 31, uncompressed size: 31, name: flag.txt
249           0xF9            End of Zip archive, footer length: 22

binwalkは、ファイル全体をスキャンして、既知のファイル形式の「署名(マジックバイト)」を探します。オフセット74に、zipアーカイブの署名(PK\x03\x04)が見つかりました——これがPNGのIENDより後ろに追記した部分です。

切り出して、復元する

binwalkが報告したオフセットを使って、Pythonでzip部分を切り出します。

data = open('combo.png', 'rb').read()
idx = data.find(b'PK\x03\x04')
print('zip署名の位置:', idx)
with open('carved.zip', 'wb') as f:
    f.write(data[idx:])
python3 carve.py
unzip -p carved.zip flag.txt
zip署名の位置: 74
flag{carved_from_the_png_tail}

復元できました。 binwalkで「何が・どこに」埋め込まれているかを見つけ、Pythonでその位置からのバイト列を切り出す——これがcarvingの基本の型です。

こうなっていればOK

卒業まであと3章です。

この章はまだ完了していません。