磁碟底層與資料救援(一):檔案刪除後,資料去哪了?
聲明:本文所有操作都在一個 100MB 的普通檔案上進行,不會碰到你電腦的真實硬碟,請勿把文中任何指令的目標改成 /dev/sda 這類實體裝置
這篇做四件事:
- 用一個 100MB 的檔案當假硬碟,親手分割、格式化
- 不透過檔案系統,直接從硬碟上把檔案內容挖出來
- 刪掉檔案,看「刪除」到底做了什麼
- 在檔名、inode 都沒了的情況下,把檔案救回來
我踩過的坑會用「新手坑」標出來,那才是這篇的重點。
環境
WSL2,任何 Linux 都可以。不需要實體硬碟。
sudo apt update && sudo apt install -y sleuthkit xxd
三條安全規則:
- 所有指令的目標只有
~/lab/disk.img或掛在它上面的/dev/loop0 - 全文不會出現任何
/dev/sd* - 每個指令下去之前,先看一眼目標是什麼——這個習慣比任何工具都重要
Step 1:做一顆假硬碟
mkdir -p ~/lab && cd ~/lab
dd if=/dev/zero of=disk.img bs=1M count=100
xxd -a -l 512 disk.img
00000000: 0000 0000 0000 0000 0000 0000 0000 0000 ................
*
000001f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
disk.img 是一個 100MB 全 0 的檔案,從現在起我們把它「當成」一顆空白硬碟,跟 WSL 自己的 .vhdx 是同一個把戲:Windows 看它是個檔案,WSL 看它是整顆碟。
xxd -a 把重複的零行折成 *,
新手坑:dd 印的 105 MB, 100 MiB 是同一個數字,廠商標十進位(1TB = 10¹²),系統顯示二進位(1TB 碟顯示 931 GiB),買 1TB「少了 70GB」就是這回事,後面算位置這兩種單位會一直出現。
Step 2:硬碟只認識 sector
硬碟不認識檔案,也不認識資料夾,它只認識一格一格編了號的格子,叫 sector,每格 512 bytes,
這顆碟有 204800 格,編號 0 到 204799。
重點:後面所有東西——分割表、檔案系統、刪除的檔案怎麼撈——都只是在回答一個問題:哪一格裝了什麼。
Step 3:分割
printf 'label: dos\n,,L\n' | sfdisk disk.img
sfdisk 報告的重點:
Disk disk.img: 100 MiB, 104857600 bytes, 204800 sectors
Units: sectors of 1 * 512 = 512 bytes
...
Device Boot Start End Sectors Size Id Type
disk.img1 2048 204799 202752 99M 83 Linux

分割區就是在那排格子上畫一條線:「第 2048 格到第 204799 格算一區」,只是圈了範圍,裡面還是空的。
disk.img1 是分割區的名字,慣例跟 /dev/sda → /dev/sda1 一樣,報告裡的 disk.img2: Done. 只是 sfdisk 去看第二個要不要切、發現沒寫就收工,實際只有一個分割區。
表格四個數字:
- Start 2048:從第 2048 格開始
- End 204799:最後一格,也是整顆碟的最後一格
- Sectors 202752:204799 − 2048 + 1
- Size 99M:202752 × 512 ≈ 99 MiB
新手坑一:2048 跟硬碟大小無關,是固定的,不管 100MB 還是 2TB,現代工具都從第 2048 格開始畫,2048 × 512 = 1 MiB,硬碟/SSD 內部讀寫單位比 512 大,分割區從整數 MiB 開始才不會跨塊,這叫對齊(alignment),換 500MB 的碟,變的只有 End 和 Sectors。
新手坑二:碟 100 MiB、分割區 99 MiB,少的 1 MiB 不是「那張表」,表只佔第 0 格 512 bytes,少掉的是第 0 到 2047 格整段:第 0 格是表,第 1 到 2047 格是對齊留白,2048 × 512 = 1 MiB,剛好。
Step 4:讀 MBR
第 0 格叫 MBR,512 bytes,分三段:
- 前 446 bytes:開機程式碼
- 接著 64 bytes:分割表,4 筆 × 16 bytes
- 最後 2 bytes:簽章
55 AA
xxd -l 512 disk.img
有東西的只有最後幾行:
000001b0: 0000 0000 0000 0000 bc9c ea4c 0000 0020 ...........L...
000001c0: 2100 83be 320c 0008 0000 0018 0300 0000 !...2...........
000001d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001f0: 0000 0000 0000 0000 0000 0000 0000 55aa ..............U.
第一筆分割表項目從 offset 0x1be 開始,16 bytes,格式是規格書定死的,就像申請表上「姓名欄 10 格、生日欄 8 格」是印好的,你照格子讀:
第 0 byte 開機旗標(1 byte)
第 1–3 byte 舊定址 CHS(3 bytes,現代不看)
第 4 byte 類型(1 byte)
第 5–7 byte 舊定址 CHS(3 bytes,不看)
第 8–11 byte 起始 sector(4 bytes)
第 12–15 byte sector 數(4 bytes)
用 -g1 只印那 16 bytes,一個 byte 一格:
xxd -g1 -s 0x1be -l 16 disk.img
000001be: 00 20 21 00 83 be 32 0c 00 08 00 00 00 18 03 00

第 4 個 byte 83 = 表格裡的 Id 83(Linux)。第 8–11 個 00 08 00 00,第 12–15 個 00 18 03 00,
這裡三個坑,每個都很小,踩到就全錯:
- xxd 的兩兩一組只是排版 預設輸出
0008 0000的空格是給人眼看的,跟資料無關,硬碟上是 512 個 byte 連續排成一串,沒有分隔,分組只能靠數 offset,看的時候把空格當不存在。 - offset 從 0 開始數 第一個 byte 是第 0 個,offset 是「距離開頭幾個 byte」,開頭那個距離是 0,我從 1 數,抓到
00 00 18 03,整個歪掉,xxd 左欄000001c0同理:那行第一個 byte 就是第0x1c0個。 - 數字倒著放,而且不能多補 0
00 08 00 00看起來不像 2048(0x800),因為電腦把最小的 byte 放最前面,要倒過來讀:00 00 08 00→0x800→ 2048,這叫 little-endian,倒的時候前面的00可以不寫(0318就是318),但後面不能補 0——我把00 18 03 00倒成03180000,等於把 318 寫成 3180,大 16 倍,正確是00 03 18 00→0x31800→ 202752 = Sectors。
驗算:
printf '%d\n' 0x800
printf '%d\n' 0x31800
順便看 0x1b8 的 bc9c ea4c,倒過來 0x4cea9cbc = sfdisk 報告的 Disk identifier,到這裡 sfdisk 印的每個數字都在 512 bytes 裡找到了。
Step 5:格式化成 ext4
分割區只是圈地,要放檔案,得先建一套記帳系統:哪些格子用掉了、每個檔案叫什麼、內容在哪幾格,這套記帳系統叫檔案系統,ext4 是其中一種(NTFS、FAT32 是別種),格式化 = 把空白帳本寫進分割區,指令叫 mkfs。
層次:硬碟 → 分割表 → 分割區 → 檔案系統 → 資料夾和檔案,你自己的系統當年也走過一樣的流程,只是別人做完了。
先給分割區一個裝置檔:
sudo losetup -f --show -o $((2048*512)) disk.img
-o 是 offset,2048*512 就是跳過前面 1 MiB,從分割區起點開始算,印出 /dev/loop0(號碼可能不同,下面自行替換),從現在起對 /dev/loop0 做的事,都是寫進 disk.img 第 2048 格之後那段,
sudo xxd -a -l 4096 /dev/loop0
sudo mkfs.ext4 /dev/loop0
mkfs 報告要記的一行:
Creating filesystem with 101376 1k blocks and 25376 inodes
兩個新詞:
- block ext4 不用 sector 記帳,它把格子併成 block,這顆碟選了 1k:1 block = 1024 bytes = 2 sectors(碟大一點會選 4k = 8 sectors),從現在起 ext4 內部的位置全是「第幾個 block」,202752 sectors ÷ 2 = 101376 blocks,對得上。
- inode 每個檔案一張編號的登記卡,記大小、時間、內容在哪幾個 block,25376 inodes = 最多 25376 個檔案,登記卡上沒有檔名,檔名記在資料夾裡,這件事後面很關鍵。
重點:block 大小是 mkfs 決定的,不固定,看任何 ext4 碟第一件事就是問「這顆的 block 多大」,不然後面算位置全錯。
Step 6:手動走一遍「開檔案」
sudo mkdir -p /mnt/lab
sudo mount /dev/loop0 /mnt/lab
echo "hello disk forensics" | sudo tee /mnt/lab/secret.txt
sync
ls -li /mnt/lab
11 drwx------ 2 root root 12288 Sep 3 12:13 lost+found
13 -rw-r--r-- 1 root root 21 Sep 3 12:20 secret.txt
-i 印 inode 號碼:secret.txt 是 13 號(11 號 lost+found 是 mkfs 自己建的),sync 確保資料真的落到碟上,Linux 會先放記憶體。
你打 cat secret.txt,作業系統背後做三步:
- 去資料夾查「secret.txt 是幾號 inode」→ 13
- 翻 13 號 inode,看「內容在第幾個 block」
- 算出那個 block 在碟上的位置,去讀 bytes
現在用手做,第一步 ls -li 做完了,第二步用 Sleuth Kit 的 istat,它不透過檔案系統,直接讀碟上的 inode:
sudo istat /dev/loop0 13
inode: 13
Allocated
...
size: 21
num of links: 1
Inode Times:
Accessed: 2026-09-03 12:20:49 (CST)
File Modified: 2026-09-03 12:20:49 (CST)
Inode Modified: 2026-09-03 12:20:49 (CST)
File Created: 2026-09-03 12:20:49 (CST)
Direct Blocks:
8705
大小 21、四個時間、內容在第 8705 個 block,沒有檔名。
第三步:block 1024 bytes,內容從第 8705 × 1024 個 byte 開始。
sudo xxd -s $((8705*1024)) -l 64 /dev/loop0
00880400: 6865 6c6c 6f20 6469 736b 2066 6f72 656e hello disk foren
00880410: 7369 6373 0a00 0000 0000 0000 0000 0000 sics............
再進一步,不透過 loop0,直接在 disk.img 裡找同一個位置,要加上分割區起點那 1 MiB:
xxd -s $((2048*512 + 8705*1024)) -l 64 disk.img
00980400: 6865 6c6c 6f20 6469 736b 2066 6f72 656e hello disk foren
00980410: 7369 6373 0a00 0000 0000 0000 0000 0000 sics............
左邊位置 00880400 和 00980400 差 0x100000 = 1 MiB,正好是分割區起點,整條鏈對上了,後面那堆 0 是 block 有 1024 bytes、檔案只用 21。
重點:你沒開檔案、沒經過檔案系統,純靠「第幾格 × 多大」把內容挖出來了,作業系統只肯幫你處理正常的檔案;檔案被刪、資料夾壞、分割表沒了,它就說找不到,但 inode 和 block 可能都還在碟上,整個資料救援和鑑識,就是在各種壞掉的情況下把這三步接起來。
Step 7:刪除實驗
sudo rm /mnt/lab/secret.txt
sync
ls -li /mnt/lab
sudo xxd -s $((8705*1024)) -l 64 /dev/loop0
11 drwx------ 2 root root 12288 Sep 3 12:13 lost+found
00880400: 6865 6c6c 6f20 6469 736b 2066 6f72 656e hello disk foren
00880410: 7369 6373 0a00 0000 0000 0000 0000 0000 sics............
檔案從資料夾消失,內容還在,
刪除只做三件事:
- 把資料夾裡的名字劃掉
- 把 13 號 inode 標成「空的,可重用」
- 把第 8705 格標成「空的,可重用」
它不會把那 1024 bytes 清成 0,因為擦掉要花時間,也沒必要——下次有新檔案直接覆蓋就好。內容會一直躺著,直到某個新檔案被分配到第 8705 格。
重點:資料救援第一鐵律——發現誤刪,立刻停止寫入,每多存一個檔、多開一次瀏覽器,都是在丟骰子看會不會覆蓋到那格。
再看 inode:
sudo istat /dev/loop0 13
inode: 13
Not Allocated
...
size: 0
num of links: 0
...
Deleted: 2026-09-03 12:26:10 (CST)
Direct Blocks:
Not Allocated、size 變 0、Direct Blocks 空了——ext4 刪除時把「內容在哪幾格」清掉了,但多了一行 Deleted:檔案什麼時候被刪的,碟上自己留了時間,對鑑識來說這行很值錢。

「rm 就找不回來」是真的嗎?
不是騙人,是說得太粗,「內容還在」和「找得回來」是兩件事,中間差一張地圖。
- 地圖沒了 我們還撈得到,是因為知道 8705,真實情況你不知道,而 ext4 刪除時把 inode 上的地圖清掉了,一個大檔案散在幾千個不連續的格子,沒地圖,內容全在也拼不回來。
- 隨時被覆蓋 系統一直在寫 log、暫存、瀏覽器快取,格子隨時會被拿走。
- SSD 更狠 作業系統會用 TRIM 告訴 SSD「這格空了」,SSD 主動清掉,rm 過幾分鐘內容就真的沒了。
關鍵句:「找不回來」的真正意思不是內容消失,是路標被拆了,對一般實務上八成成立,對會找路標備份、會憑內容特徵掃碟的人不是。
inode 被搶走
再放兩個檔案:
echo A | sudo tee /mnt/lab/a.txt >/dev/null
sudo cp /mnt/lab/a.txt /mnt/lab/b.txt
sync; ls -li /mnt/lab
13 -rw-r--r-- 1 root root 2 Sep 3 12:32 a.txt
14 -rw-r--r-- 1 root root 2 Sep 3 12:32 b.txt
11 drwx------ 2 root root 12288 Sep 3 12:13 lost+found
a.txt 拿到 13 號——secret.txt 剛被劃掉的那張卡,「可重用」不是說說,下一個新檔案馬上拿走,卡上原本 secret.txt 的資料已被蓋掉,b.txt 拿到 14,證明複製是做了獨立的新檔案:新 inode、新 block、內容抄一份。
sudo istat /dev/loop0 13 | tail -2
sudo istat /dev/loop0 14 | tail -2
sudo xxd -s $((8705*1024)) -l 32 /dev/loop0
Direct Blocks:
9217
Direct Blocks:
9218
00880400: 6865 6c6c 6f20 6469 736b 2066 6f72 656e hello disk foren
00880410: 7369 6373 0a00 0000 0000 0000 0000 0000 sics............
inode 立刻被重用,block 卻沒有,a.txt 拿到 9217,8705 沒被碰,hello 還在,這次 ext4 往後找新格子,沒回頭撿剛空出來的,所以「內容還在」的窗口通常比「inode 還在」長。這種行為每個檔案系統都不一樣,記住這件事本身就是功力。
現在的狀況是最壞的:檔名沒了、inode 被別人拿走、地圖被清掉,只剩內容躺在碟上。
Step 8:救回來
地圖沒了,就整顆碟一格一格掃,找內容本身的特徵。我們知道檔案裡有 "hello disk forensics":
sudo grep -a -b -o "hello disk forensics" /dev/loop0
8913920:hello disk forensics
-a 把碟當純文字硬讀,-b 印命中的 byte 位置,8913920 ÷ 1024 = 8705,跟 inode 被拆掉前寫的一樣——這次沒有任何帳本,純靠掃內容找到。
救出來:
sudo dd if=/dev/loop0 bs=1024 skip=8705 count=1 2>/dev/null | tr -d '\0' > recovered.txt
cat recovered.txt
bs=1024 skip=8705 count=1 = 以 1024 為單位,跳過 8705 個,抓 1 個,tr -d '\0' 把格子尾巴的 0 去掉。
這招叫 carving:不靠帳本,靠內容特徵。真實檔案你不知道內容,但檔案格式有固定開頭——JPEG 是 FF D8 FF、PDF 是 %PDF、ZIP 是 PK,PhotoRec 就是拿幾百種格式的開頭掃整顆碟,掃到就從那格往後抓。
carving 的限制:
- 檔名沒了
- 時間沒了
- 檔案不連續就會拼錯
收尾
sudo umount /mnt/lab
sudo losetup -d /dev/loop0
disk.img 留著,下一課還會用,
結語
這一課真正學到的只有四件事:
- 硬碟是一排編號的格子,所有事情都是在回答「哪一格裝了什麼」
- MBR、ext4 的帳本,格式都是規格書定死的。拿著格式表數 offset 就讀得出來——從 0 數、無視 xxd 的空格、數字倒著讀
- 「開檔案」是三步:查名字 → 翻 inode → 去 block 讀,會手動走這三步的人,在檔案被刪、帳本壞掉時還撈得回來
- 刪除只是做記號。內容還在 ≠ 找得回來,中間差一張地圖
Happy Carving!
Member discussion