7 min read

磁碟底層與資料救援(二):不用 strings | grep,用鑑識流程解 picoCTF Disk, disk, sleuth!

磁碟底層與資料救援(二):不用 strings | grep,用鑑識流程解 picoCTF Disk, disk, sleuth!

聲明:本文是 picoCTF「Disk, disk, sleuth!」的解題記錄,所有操作都在下載的 image 檔上進行,不會碰到真實硬碟。

為什麼第二課是實戰

第一課的碟是自己造的:檔案自己放、inode 自己查、block 自己算,這一課換一顆「別人的碟」:picoCTF 2021 的 Disk, disk, sleuth!,一個小型 Linux 系統的硬碟 image,題目只說裡面有一個 flag,

先講清楚:這題用 strings image | grep picoCTF 三秒就出來,但這篇不走那條,走的是鑑識的流程——看結構、列名單、縮範圍、最後才碰內容,flag 是順便的,流程才是要帶走的。

環境

工具還是 Sleuth Kit,題目檔下載後,如果是在 WSL,Windows 的下載資料夾要透過 /mnt/ 拿:

cp /mnt/d/Download/dds1-alpine.flag.img.gz ~/lab/ && cd ~/lab
gunzip dds1-alpine.flag.img.gz

Sleuth Kit 的工具名有規則,看懂就不用背:第一個字母是「看哪一層」,後面是「做什麼」。

  1. 前綴:mm = 分割表,f = 檔名,i = inode,blk = block,j = journal
  2. 後綴:ls = 列出來,stat = 印詳細資料,cat = 把內容倒出來

所以 mmls = 列分割表,fls = 列檔名,istat = 看一張 inode,icat = 倒一個檔案的內容。

Step 1:看結構

mmls dds1-alpine.flag.img
DOS Partition Table
Offset Sector: 0
Units are in 512-byte sectors
      Slot      Start        End          Length       Description
000:  Meta      0000000000   0000000000   0000000001   Primary Table (#0)
001:  -------   0000000000   0000002047   0000002048   Unallocated
002:  000:000   0000002048   0000262143   0000260096   Linux (0x83)

跟第一課自己切的那顆長得一模一樣:第 0 格是分割表、1 到 2047 是 1 MiB 對齊留白、分割區從 2048 開始,260096 個 sector 約 127 MiB,類型 0x83 Linux。

重點:記住 2048,後面每個指令都要用 -o 2048 告訴工具「分割區從這裡開始」,跟第一課 losetup 的 -o 是同一件事,只是單位是 sector 不是 byte。

Step 2:列根目錄

fls -o 2048 dds1-alpine.flag.img
d/d 10161:      home
d/d 11: lost+found
r/r 12: .dockerenv
d/d 2033:       bin
d/d 8129:       boot
d/d 6097:       dev
d/d 16257:      etc
d/d 28449:      lib
...
d/d 26418:      root
...
d/d 20324:      var
V/V 32513:      $OrphanFiles

d/d 是資料夾、r/r 是檔案,冒號前是 inode 編號,兩個線索:

  1. .dockerenv 這不是裝在實體機上的 Linux,是一個 Docker container 被倒成硬碟 image,container 通常很乾淨,多出來的東西會很顯眼。
  2. $OrphanFiles Sleuth Kit 自己加的虛擬資料夾,放「inode 還在、但名單上已經沒名字」的檔案,刪一半的、被藏的東西會出現在這,真實案子裡也是第一批要看的地方。

以「有人故意藏東西」來想,優先順序:root(登入者的家)、home(有沒有其他使用者)、$OrphanFiles

Step 3:一格一格翻,還是整顆倒出來

fls 後面接 inode 編號,就是「列這個資料夾裡面的東西」:

fls -o 2048 dds1-alpine.flag.img 26418   # root
fls -o 2048 dds1-alpine.flag.img 10161   # home

兩個都是空的,空的也是資訊:root 沒被用過、沒有其他使用者。

一格一格翻適合摸清結構,實務上第一步是整顆倒出來:-r 遞迴到底、-p 印完整路徑,存成檔案,之後所有判斷都在這個清單上做。

fls -o 2048 -r -p dds1-alpine.flag.img > listing.txt
wc -l listing.txt
grep -c '^\*' listing.txt
1973 listing.txt
0

1973 個項目,0 個開頭有 *(被刪掉但名字還在名單上的),所以 flag 是以正常檔案的身分放著的,不用走復原那條路,問題只是「在哪」,

Step 4:排除「有人新增的目錄」

container 預設幾個目錄應該是空的,從清單裡看它們底下有沒有東西:

grep -E ': (home|root|tmp|opt|srv|mnt|media)(/|$)' listing.txt

只印出目錄本身那幾行,沒有多出來的檔案,所以放 flag 的人沒走「新增檔案」那條懶路,東西藏在現有檔案裡,或塞在不顯眼的路徑。

Step 5:時間線

換一個真正鑑識會用的角度:時間,image 是一次建好的,兩千個檔案的修改時間會擠在同一段;之後被人動過的,時間會脫隊。

fls -l 每行多印四個時間和大小,再用 sort 照修改時間排:

fls -o 2048 -r -p -l dds1-alpine.flag.img | sort -t$'\t' -k3 | tail -15

新手坑:-l 的欄位順序是「類型 編號」、「路徑」、然後才是修改時間,所以是第 3 欄不是第 2 欄,我第一次用 -k2 排,結果整排都是 var/ 開頭——那是照路徑排的。

r/r 30729:  lib/modules/.../tcp_vegas.ko   2021-02-17 04:55:28 ...
r/r 28871:  lib/modules/.../scsi_transport_fc.ko   2021-02-17 04:55:28 ...
r/r 28598:  lib/modules/.../poly1305_generic.ko   2021-02-17 04:55:28 ...
d/d 8129:   boot                2021-02-17 04:55:29 ...
r/r 8136:   boot/ldlinux.sys    2021-02-17 04:55:29 ...
r/r 8137:   boot/ldlinux.c32    2021-02-17 04:55:29 ...
r/r 8138:   boot/syslinux.cfg   2021-02-17 04:55:29 ...   219

全部 04:55:28,只有 boot 底下晚一秒,時間線在這題幫不上太多——整顆碟是腳本一秒內產出的,不是人用了很久的機器,container image 都這樣。

但這個「晚一秒」還是能讀:最後被動的是開機設定 boot/syslinux.cfg,219 bytes 的純文字,想藏在「看起來是系統檔、但其實是人手寫的」地方,這種小設定檔剛好合適,反正它很小,直接看。

Step 6:讀內容

icat -o 2048 dds1-alpine.flag.img 8138
DEFAULT linux
  SAY Now booting the kernel from SYSLINUX...
  SAY picoCTF{Redacted}
 LABEL linux
  KERNEL /boot/vmlinuz-virt
  APPEND ro root=/dev/sda1 rootfstype=ext3 initrd=/boot/initramfs-virt

SAY 是開機時印一行字給使用者看的指令,第一行是正常的,第二行不是開機設定該有的東西。

順便看 APPEND 那行的 rootfstype=ext3:這顆碟是 ext3,跟 ext4 同一家,一樣有 journal,第三課會用到。

那暴力搜可不可以?

可以,而且有三種,各有涵蓋不到的地方:

  1. strings image | grep picoCTF 直接掃整顆碟,連被刪的、journal 裡的舊版、沒被任何檔案佔用的空格子都掃到,範圍最大,缺點:檔案碎掉時字串會斷;flag 被 base64、壓縮、拆成兩半就看不到。
  2. tsk_recover 照名單把每個檔案 icat 出來、用原本路徑存到資料夾,等於「把每張 inode 都倒一遍」的迴圈,之後用一般 grep、find 去搜,「照地圖走」,每個檔案的邊界都對,碎掉的也會拼好,但只涵蓋名單上有的東西。
tsk_recover -o 2048 -a dds1-alpine.flag.img out/
grep -r picoCTF out/

-a 只倒正常檔案,-e 連 orphan 和被刪的一起倒,這題倒出 1565 個檔案,比 listing 的 1973 少,差的是資料夾和 symlink,那些沒有內容可倒,

3. 縮範圍 就是這篇走的路,慢,但不依賴「知道 flag 長什麼樣」。

關鍵句:真實案子沒有 picoCTF{,你要找的可能是一封信、一個帳號、某天晚上發生了什麼,不知道長什麼樣的東西 grep 不到,專業做法是兩條並行:靠假設縮範圍,同時拿已知關鍵字掃整顆碟,互相印證,這篇練的是前者。

結語

這一課帶走的四件事:

  1. 拿到一顆碟的順序:mmls 看結構 → fls 倒名單 → 排除 → 縮範圍 → 最後才 icat 看內容
  2. 空的目錄、0 個刪除檔,都是資訊,每一步都在縮小範圍
  3. 時間線是鑑識的基本功,這題剛好沒用,但排序前先確認欄位在第幾欄
  4. 暴力搜是正規手法,不是作弊,但要知道它漏哪裡

Happy Hunting!