> ## Content Index
> Fetch the complete content index at: https://taiwanding.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# 磁碟底層與資料救援（一）：檔案刪除後，資料去哪了？
- URL: https://taiwanding.com/ci-die-di-ceng-yu-zi-liao-jiu-yuan-yi-dang-an-shan-chu-hou-zi-liao-qu-na-liao/
- Published: 2026-09-03T05:57:50.000Z
- Updated: 2026-09-03T05:57:50.000Z
- Author: Kevin Chen
- Tags: hard drive, 資料救援, 檔案系統, Linux, 磁碟底層

聲明：本文所有操作都在一個 100MB 的普通檔案上進行，不會碰到你電腦的真實硬碟，請勿把文中任何指令的目標改成 `/dev/sda` 這類實體裝置

這篇做四件事：

1. 用一個 100MB 的檔案當假硬碟，親手分割、格式化
2. 不透過檔案系統，直接從硬碟上把檔案內容挖出來
3. 刪掉檔案，看「刪除」到底做了什麼
4. 在檔名、inode 都沒了的情況下，把檔案救回來

我踩過的坑會用「新手坑」標出來，那才是這篇的重點。

## 環境

WSL2，任何 Linux 都可以。不需要實體硬碟。

```bash
sudo apt update && sudo apt install -y sleuthkit xxd

```

三條安全規則：

1. 所有指令的目標只有 `~/lab/disk.img` 或掛在它上面的 `/dev/loop0`
2. 全文不會出現任何 `/dev/sd*`
3. 每個指令下去之前，先看一眼目標是什麼——這個習慣比任何工具都重要

## Step 1：做一顆假硬碟

```bash
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：分割

```bash
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

```

![](https://taiwanding.com/content/images/2026/09/fig1-sector-partition.jpg)

分割區就是在那排格子上畫一條線：「第 2048 格到第 204799 格算一區」，只是圈了範圍，裡面還是空的。

`disk.img1` 是分割區的名字，慣例跟 `/dev/sda` → `/dev/sda1` 一樣，報告裡的 `disk.img2: Done.` 只是 sfdisk 去看第二個要不要切、發現沒寫就收工，實際只有一個分割區。

表格四個數字：

1. Start 2048：從第 2048 格開始
2. End 204799：最後一格，也是整顆碟的最後一格
3. Sectors 202752：204799 − 2048 + 1
4. 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，分三段：

1. 前 446 bytes：開機程式碼
2. 接著 64 bytes：分割表，4 筆 × 16 bytes
3. 最後 2 bytes：簽章 `55 AA`

```bash
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 一格：

```bash
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

```

![](https://taiwanding.com/content/images/2026/09/fig2-mbr-entry.jpg)

第 4 個 byte `83` \= 表格裡的 Id 83（Linux）。第 8–11 個 `00 08 00 00`，第 12–15 個 `00 18 03 00`，

這裡三個坑，每個都很小，踩到就全錯：

1. xxd 的兩兩一組只是排版 預設輸出 `0008 0000` 的空格是給人眼看的，跟資料無關，硬碟上是 512 個 byte 連續排成一串，沒有分隔，分組只能靠數 offset，看的時候把空格當不存在。
2. offset 從 0 開始數 第一個 byte 是第 0 個，offset 是「距離開頭幾個 byte」，開頭那個距離是 0，我從 1 數，抓到 `00 00 18 03`，整個歪掉，xxd 左欄 `000001c0` 同理：那行第一個 byte 就是第 `0x1c0` 個。
3. 數字倒著放，而且不能多補 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。

驗算：

```bash
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。

層次：硬碟 → 分割表 → 分割區 → 檔案系統 → 資料夾和檔案，你自己的系統當年也走過一樣的流程，只是別人做完了。

先給分割區一個裝置檔：

```bash
sudo losetup -f --show -o $((2048*512)) disk.img

```

`-o` 是 offset，`2048*512` 就是跳過前面 1 MiB，從分割區起點開始算，印出 `/dev/loop0`（號碼可能不同，下面自行替換），從現在起對 `/dev/loop0` 做的事，都是寫進 disk.img 第 2048 格之後那段，

```bash
sudo xxd -a -l 4096 /dev/loop0
sudo mkfs.ext4 /dev/loop0

```

mkfs 報告要記的一行：

```
Creating filesystem with 101376 1k blocks and 25376 inodes

```

兩個新詞：

1. block ext4 不用 sector 記帳，它把格子併成 block，這顆碟選了 1k：1 block = 1024 bytes = 2 sectors（碟大一點會選 4k = 8 sectors），從現在起 ext4 內部的位置全是「第幾個 block」，202752 sectors ÷ 2 = 101376 blocks，對得上。
2. inode 每個檔案一張編號的登記卡，記大小、時間、內容在哪幾個 block，25376 inodes = 最多 25376 個檔案，登記卡上沒有檔名，檔名記在資料夾裡，這件事後面很關鍵。

重點：block 大小是 mkfs 決定的，不固定，看任何 ext4 碟第一件事就是問「這顆的 block 多大」，不然後面算位置全錯。

## Step 6：手動走一遍「開檔案」

```bash
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`，作業系統背後做三步：

1. 去資料夾查「secret.txt 是幾號 inode」→ 13
2. 翻 13 號 inode，看「內容在第幾個 block」
3. 算出那個 block 在碟上的位置，去讀 bytes

現在用手做，第一步 `ls -li` 做完了，第二步用 Sleuth Kit 的 `istat`，它不透過檔案系統，直接讀碟上的 inode：

```bash
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 開始。

```bash
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：

```bash
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：刪除實驗

```bash
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............

```

檔案從資料夾消失，內容還在，

刪除只做三件事：

1. 把資料夾裡的名字劃掉
2. 把 13 號 inode 標成「空的，可重用」
3. 把第 8705 格標成「空的，可重用」

它不會把那 1024 bytes 清成 0，因為擦掉要花時間，也沒必要——下次有新檔案直接覆蓋就好。內容會一直躺著，直到某個新檔案被分配到第 8705 格。

重點：資料救援第一鐵律——發現誤刪，立刻停止寫入，每多存一個檔、多開一次瀏覽器，都是在丟骰子看會不會覆蓋到那格。

再看 inode：

```bash
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`：檔案什麼時候被刪的，碟上自己留了時間，對鑑識來說這行很值錢。

![](https://taiwanding.com/content/images/2026/09/fig3-delete-states.jpg)

### 「rm 就找不回來」是真的嗎？

不是騙人，是說得太粗，「內容還在」和「找得回來」是兩件事，中間差一張地圖。

1. 地圖沒了 我們還撈得到，是因為知道 8705，真實情況你不知道，而 ext4 刪除時把 inode 上的地圖清掉了，一個大檔案散在幾千個不連續的格子，沒地圖，內容全在也拼不回來。
2. 隨時被覆蓋 系統一直在寫 log、暫存、瀏覽器快取，格子隨時會被拿走。
3. SSD 更狠 作業系統會用 TRIM 告訴 SSD「這格空了」，SSD 主動清掉，rm 過幾分鐘內容就真的沒了。

關鍵句：「找不回來」的真正意思不是內容消失，是路標被拆了，對一般實務上八成成立，對會找路標備份、會憑內容特徵掃碟的人不是。

### inode 被搶走

再放兩個檔案：

```bash
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、內容抄一份。

```bash
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"：

```bash
sudo grep -a -b -o "hello disk forensics" /dev/loop0

```

```
8913920:hello disk forensics

```

`-a` 把碟當純文字硬讀，`-b` 印命中的 byte 位置，8913920 ÷ 1024 = 8705，跟 inode 被拆掉前寫的一樣——這次沒有任何帳本，純靠掃內容找到。

救出來：

```bash
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 的限制：

1. 檔名沒了
2. 時間沒了
3. 檔案不連續就會拼錯

## 收尾

```bash
sudo umount /mnt/lab
sudo losetup -d /dev/loop0

```

`disk.img` 留著，下一課還會用，

## 結語

這一課真正學到的只有四件事：

1. 硬碟是一排編號的格子，所有事情都是在回答「哪一格裝了什麼」
2. MBR、ext4 的帳本，格式都是規格書定死的。拿著格式表數 offset 就讀得出來——從 0 數、無視 xxd 的空格、數字倒著讀
3. 「開檔案」是三步：查名字 → 翻 inode → 去 block 讀，會手動走這三步的人，在檔案被刪、帳本壞掉時還撈得回來
4. 刪除只是做記號。內容還在 ≠ 找得回來，中間差一張地圖

Happy Carving！