# 1) Mô tả đề & hint
**Title:** Lost in file system (Medium)
**Cho:** 1 file `.img` \~3 GiB (USB image)
**Triệu chứng:** Partition table có vẻ hỏng → FTK/Autopsy không nhận ra filesystem.
**Hint:** *Liên quan đến NTFS boot sector.*
**Ý tưởng tổng quát:**
Khi bảng phân vùng hỏng, OS/forensic suite không tự tìm thấy volume. Nhưng nếu bên trong có **NTFS**, ta vẫn có thể:
* Tự **quét raw image** để tìm **NTFS boot sector** (“NTFS ” ở offset 0x03, chữ ký 0x55AA ở 0x1FE).
* Xác thực thông số, tính vị trí **backup boot sector** (NTFS lưu ở **sector cuối** của volume).
* Mount volume bằng **offset byte** hoặc dùng Sleuth Kit để duyệt nội dung.
* Nếu cần, **phục hồi** dữ liệu (file đã xoá), và xử lý thêm các “bẫy” (ví dụ file ảnh bị cắt header).
---
# 2) Công cụ dùng
* **Sleuth Kit**: `fsstat`, `fls`, `icat`, `tsk_recover`
* **ntfs-3g**: mount NTFS với tuỳ chọn ADS
* **xxd / hexdump / strings / dd**: kiểm tra & vá byte
* (tuỳ) **pngcheck / exiftool / binwalk / zsteg**: nếu cần mổ xẻ ảnh
* **Script quét** (mình viết sẵn): `ntfs_boot_tools.py`
```python!
#!/usr/bin/env python3
"""
ntfs_boot_tools.py
------------------
Scan a raw disk image for NTFS volumes, parse their boot sectors,
and (optionally) repair a corrupted primary boot sector by copying
the backup boot sector from the end of the volume.
Usage:
# List candidate NTFS volumes (with parsed fields)
python ntfs_boot_tools.py scan /path/to/disk.img
# Verify a specific candidate (by byte offset) and compare primary vs backup BS
python ntfs_boot_tools.py verify /path/to/disk.img --offset-bytes 1048576
# Repair (write!) the primary boot sector from the backup for the given offset
# Writes to a *new* file ending with .repaired.img to keep the original safe
python ntfs_boot_tools.py repair /path/to/disk.img --offset-bytes 1048576
Notes:
- Offsets are in BYTES (not sectors). If you have start LBA, use offset = LBA * 512.
- This script assumes a 512-byte boot sector size when copying (standard for NTFS).
- Always work on a COPY of the image. The 'repair' subcommand writes to a new file.
"""
import argparse
import os
import struct
from dataclasses import dataclass
NTFS_OEM = b"NTFS "
SECTOR_SIZE_CANDIDATES = (512, 1024, 2048, 4096)
@dataclass
class NTFSBoot:
offset_bytes: int # start of the NTFS volume within the image
bytes_per_sector: int
sectors_per_cluster: int
total_sectors: int # 64-bit
mft_lcn: int # Logical Cluster Number
mftmirr_lcn: int
clusters_per_mft_record: int # signed int8 or int32 if negative
clusters_per_index_record: int # signed int8 or int32 if negative
valid_signature: bool
def parse_ntfs_boot(buf: bytes, offset_bytes: int) -> NTFSBoot | None:
# Basic sanity: "NTFS " at offset 0x03 and 0x55AA at 0x1FE
if len(buf) < 512:
return None
if buf[3:11] != NTFS_OEM:
return None
valid_sig = buf[510:512] == b'\x55\xaa'
bps = struct.unpack_from("<H", buf, 0x0B)[0]
spc = buf[0x0D]
# Reserved sectors for NTFS is typically 0
# total_sectors (Q) at 0x28
total_sectors = struct.unpack_from("<Q", buf, 0x28)[0]
mft_lcn = struct.unpack_from("<Q", buf, 0x30)[0]
mftmirr_lcn = struct.unpack_from("<Q", buf, 0x38)[0]
# clusters per MFT/index record can be signed int8 or a negative power-of-two encoded
def parse_cp_record(off: int) -> int:
val = struct.unpack_from("b", buf, off)[0]
if val < 0:
# size in bytes is 2**(-val); we store the raw signed byte
return val
return val
cpm = parse_cp_record(0x40)
cpi = parse_cp_record(0x44)
# sanity checks
if bps not in SECTOR_SIZE_CANDIDATES: # typical
return None
if spc == 0 or spc > 128:
return None
if total_sectors == 0:
return None
return NTFSBoot(
offset_bytes=offset_bytes,
bytes_per_sector=bps,
sectors_per_cluster=spc,
total_sectors=total_sectors,
mft_lcn=mft_lcn,
mftmirr_lcn=mftmirr_lcn,
clusters_per_mft_record=cpm,
clusters_per_index_record=cpi,
valid_signature=bool(valid_sig),
)
def human(n: int) -> str:
for unit in ["B","KiB","MiB","GiB","TiB"]:
if n < 1024 or unit == "TiB":
return f"{n:.0f} {unit}"
n /= 1024
return f"{n:.0f} B"
def scan(path: str, max_hits: int = 50) -> list[NTFSBoot]:
hits: list[NTFSBoot] = []
bs = 512 # scan on 512-byte boundaries
chunk = 1024 * 1024
size = os.path.getsize(path)
with open(path, "rb") as f:
pos = 0
while pos < size and len(hits) < max_hits:
data = f.read(chunk)
if not data:
break
# scan within this chunk at 512B alignment
for off in range(0, len(data) - 512 + 1, bs):
if data[off+3:off+11] == NTFS_OEM:
buf = data[off:off+512]
boot = parse_ntfs_boot(buf, pos + off)
if boot:
hits.append(boot)
pos += len(data)
return hits
def describe(boot: NTFSBoot) -> str:
size_bytes = boot.total_sectors * boot.bytes_per_sector
clustersz = boot.bytes_per_sector * boot.sectors_per_cluster
return (
f"Offset: {boot.offset_bytes} bytes ({human(boot.offset_bytes)})\n"
f" Bytes/sector: {boot.bytes_per_sector}\n"
f" Sectors/cluster: {boot.sectors_per_cluster}\n"
f" Cluster size: {clustersz} bytes\n"
f" Total sectors: {boot.total_sectors} => Volume size ~ {human(size_bytes)}\n"
f" $MFT LCN: {boot.mft_lcn}\n"
f" $MFTMirr LCN: {boot.mftmirr_lcn}\n"
f" Boot signature 0x55AA present: {boot.valid_signature}\n"
)
def read_sector(path: str, abs_byte_off: int, size: int = 512) -> bytes:
with open(path, "rb") as f:
f.seek(abs_byte_off)
return f.read(size)
def verify(path: str, offset_bytes: int) -> None:
primary = read_sector(path, offset_bytes, 512)
boot = parse_ntfs_boot(primary, offset_bytes)
if not boot:
print("❌ No valid NTFS boot sector at that offset.")
return
print("Primary boot sector:")
print(describe(boot))
# Compute backup boot sector absolute offset (last sector of the volume)
vol_size_bytes = boot.total_sectors * boot.bytes_per_sector
backup_abs = boot.offset_bytes + vol_size_bytes - 512
backup = read_sector(path, backup_abs, 512)
boot2 = parse_ntfs_boot(backup, backup_abs)
print("Backup boot sector:")
if boot2:
print(describe(boot2))
else:
print("⚠️ Backup does not parse as valid NTFS boot sector.")
def repair(path: str, offset_bytes: int) -> str:
# Create a copy for safety
out_path = path + ".repaired.img"
if os.path.exists(out_path):
raise SystemExit(f"Output already exists: {out_path}")
print(f"Creating copy: {out_path} (this may take a while)...")
# Copy in chunks
bs = 1024 * 1024
total = os.path.getsize(path)
with open(path, "rb") as src, open(out_path, "wb") as dst:
while True:
data = src.read(bs)
if not data:
break
dst.write(data)
# Parse from the copy and perform the write there
primary = read_sector(out_path, offset_bytes, 512)
boot = parse_ntfs_boot(primary, offset_bytes)
if not boot:
raise SystemExit("No valid-looking NTFS boot sector header at the given offset in the copy. Aborting.")
vol_size_bytes = boot.total_sectors * boot.bytes_per_sector
backup_abs = boot.offset_bytes + vol_size_bytes - 512
backup = read_sector(out_path, backup_abs, 512)
if backup[3:11] != NTFS_OEM or backup[510:512] != b'\x55\xaa':
raise SystemExit("Backup boot sector does not look valid. Aborting.")
# Write backup over primary in the copy
with open(out_path, "r+b") as f:
f.seek(offset_bytes)
f.write(backup)
print("✅ Wrote backup boot sector over primary (in the *copy*).")
return out_path
def main():
ap = argparse.ArgumentParser()
sub = ap.add_subparsers(dest="cmd", required=True)
ap_scan = sub.add_parser("scan", help="Scan image for NTFS boot sectors")
ap_scan.add_argument("image")
ap_scan.add_argument("--max-hits", type=int, default=50)
ap_verify = sub.add_parser("verify", help="Compare primary vs backup boot sector for a candidate")
ap_verify.add_argument("image")
ap_verify.add_argument("--offset-bytes", type=int, required=True)
ap_repair = sub.add_parser("repair", help="Copy backup boot sector over primary (writes to a new .repaired.img)")
ap_repair.add_argument("image")
ap_repair.add_argument("--offset-bytes", type=int, required=True)
args = ap.parse_args()
if args.cmd == "scan":
hits = scan(args.image, args.max_hits)
if not hits:
print("No NTFS boot sectors found.")
return
for i, h in enumerate(hits, 1):
print(f"[{i}] Candidate NTFS volume at offset {h.offset_bytes} bytes")
print(describe(h))
elif args.cmd == "verify":
verify(args.image, args.offset_bytes)
elif args.cmd == "repair":
out = repair(args.image, args.offset_bytes)
print(f"Output image: {out}")
else:
ap.print_help()
if __name__ == "__main__":
main()
```
---
# 3) Bước 1 – Quét image tìm NTFS boot sector
Chạy script:
```bash!
python3 ntfs_boot_tools.py scan recovery_USB.img
```
**Kết quả thực tế:**
```bash!
┌──(kali㉿kali)-[~/Desktop/CHALL4N6ATHONG]
└─$ python3 ntfs_boot_tools.py scan recovery_USB.img
[1] Candidate NTFS volume at offset 16777216 bytes
Offset: 16777216 bytes (16 MiB)
Bytes/sector: 512
Sectors/cluster: 8
Cluster size: 4096 bytes
Total sectors: 6258559 => Volume size ~ 3 GiB
$MFT LCN: 262144
$MFTMirr LCN: 2
Boot signature 0x55AA present: True
[2] Candidate NTFS volume at offset 3221159424 bytes
Offset: 3221159424 bytes (3 GiB)
Bytes/sector: 512
Sectors/cluster: 8
Cluster size: 4096 bytes
Total sectors: 6258559 => Volume size ~ 3 GiB
$MFT LCN: 262144
$MFTMirr LCN: 2
Boot signature 0x55AA present: True
```
**Nhận xét:**
* \[1] = **primary boot sector** của NTFS ở **offset 16 MiB**.
* \[2] = sector gần **cuối volume** → nhiều khả năng là **backup boot sector**.
---
# 4) Bước 2 – So sánh primary vs. backup
Xác thực từ offset primary:
```bash
python3 ntfs_boot_tools.py verify recovery_USB.img --offset-bytes 16777216
```
**Kết quả thực tế:**
```bash!
┌──(kali㉿kali)-[~/Desktop/CHALL4N6ATHONG]
└─$ python3 ntfs_boot_tools.py verify recovery_USB.img --offset-bytes 16777216
Primary boot sector:
Offset: 16777216 bytes (16 MiB)
Bytes/sector: 512
Sectors/cluster: 8
Cluster size: 4096 bytes
Total sectors: 6258559 => Volume size ~ 3 GiB
$MFT LCN: 262144
$MFTMirr LCN: 2
Boot signature 0x55AA present: True
Backup boot sector:
⚠ Backup does not parse as valid NTFS boot sector.
```
* Primary: **OK**
* Backup: **“không parse được”** theo vị trí *chuẩn* (tính từ TotalSectors).
Kiểm tra tay 2 sector “cuối volume”:
```bash
# sector “chuẩn” (expected) theo công thức: toàn 0x00
xxd -g 1 -l 512 -s 3221158912 recovery_USB.img
# sector kế tiếp (+512 B): có NTFS Boot Sector hợp lệ
xxd -g 1 -l 512 -s 3221159424 recovery_USB.img
# -> thấy "EB 52 90 4E 54 46 53 20 20 20 20 ... 55 AA"
```
```bash!
┌──(kali㉿kali)-[~/Desktop/CHALL4N6ATHONG]
└─$ xxd -g 1 -l 512 -s 3221158912 recovery_USB.img
bffefc00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefc10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefc20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefc30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefc40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefc50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefc60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefc70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefc80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefc90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefca0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefcb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefcc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefcd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefce0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefcf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefd00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefd10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefd20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefd30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefd40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefd50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefd60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefd70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefd90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefda0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefdb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefdc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefdd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefde0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefdf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
┌──(kali㉿kali)-[~/Desktop/CHALL4N6ATHONG]
└─$ xxd -g 1 -l 512 -s 3221159424 recovery_USB.img
bffefe00: eb 52 90 4e 54 46 53 20 20 20 20 00 02 08 00 00 .R.NTFS .....
bffefe10: 00 00 00 00 00 f8 00 00 3f 00 ff 00 00 80 00 00 ........?.......
bffefe20: 00 00 00 00 80 00 80 00 7f 7f 5f 00 00 00 00 00 .........._.....
bffefe30: 00 00 04 00 00 00 00 00 02 00 00 00 00 00 00 00 ................
bffefe40: f6 00 00 00 01 00 00 00 35 c4 4c 72 f5 4c 72 ae ........5.Lr.Lr.
bffefe50: 00 00 00 00 fa 33 c0 8e d0 bc 00 7c fb 68 c0 07 .....3.....|.h..
bffefe60: 1f 1e 68 66 00 cb 88 16 0e 00 66 81 3e 03 00 4e ..hf......f.>..N
bffefe70: 54 46 53 75 15 b4 41 bb aa 55 cd 13 72 0c 81 fb TFSu..A..U..r...
bffefe80: 55 aa 75 06 f7 c1 01 00 75 03 e9 dd 00 1e 83 ec U.u.....u.......
bffefe90: 18 68 1a 00 b4 48 8a 16 0e 00 8b f4 16 1f cd 13 .h...H..........
bffefea0: 9f 83 c4 18 9e 58 1f 72 e1 3b 06 0b 00 75 db a3 .....X.r.;...u..
bffefeb0: 0f 00 c1 2e 0f 00 04 1e 5a 33 db b9 00 20 2b c8 ........Z3... +.
bffefec0: 66 ff 06 11 00 03 16 0f 00 8e c2 ff 06 16 00 e8 f...............
bffefed0: 4b 00 2b c8 77 ef b8 00 bb cd 1a 66 23 c0 75 2d K.+.w......f#.u-
bffefee0: 66 81 fb 54 43 50 41 75 24 81 f9 02 01 72 1e 16 f..TCPAu$....r..
bffefef0: 68 07 bb 16 68 52 11 16 68 09 00 66 53 66 53 66 h...hR..h..fSfSf
bffeff00: 55 16 16 16 68 b8 01 66 61 0e 07 cd 1a 33 c0 bf U...h..fa....3..
bffeff10: 0a 13 b9 f6 0c fc f3 aa e9 fe 01 90 90 66 60 1e .............f`.
bffeff20: 06 66 a1 11 00 66 03 06 1c 00 1e 66 68 00 00 00 .f...f.....fh...
bffeff30: 00 66 50 06 53 68 01 00 68 10 00 b4 42 8a 16 0e .fP.Sh..h...B...
bffeff40: 00 16 1f 8b f4 cd 13 66 59 5b 5a 66 59 66 59 1f .......fY[ZfYfY.
bffeff50: 0f 82 16 00 66 ff 06 11 00 03 16 0f 00 8e c2 ff ....f...........
bffeff60: 0e 16 00 75 bc 07 1f 66 61 c3 a1 f6 01 e8 09 00 ...u...fa.......
bffeff70: a1 fa 01 e8 03 00 f4 eb fd 8b f0 ac 3c 00 74 09 ............<.t.
bffeff80: b4 0e bb 07 00 cd 10 eb f2 c3 0d 0a 41 20 64 69 ............A di
bffeff90: 73 6b 20 72 65 61 64 20 65 72 72 6f 72 20 6f 63 sk read error oc
bffeffa0: 63 75 72 72 65 64 00 0d 0a 42 4f 4f 54 4d 47 52 curred...BOOTMGR
bffeffb0: 20 69 73 20 63 6f 6d 70 72 65 73 73 65 64 00 0d is compressed..
bffeffc0: 0a 50 72 65 73 73 20 43 74 72 6c 2b 41 6c 74 2b .Press Ctrl+Alt+
bffeffd0: 44 65 6c 20 74 6f 20 72 65 73 74 61 72 74 0d 0a Del to restart..
bffeffe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bffefff0: 00 00 00 00 00 00 8a 01 a7 01 bf 01 00 00 55 aa ..............U.
```
**Kết luận:** đề **gài bẫy off-by-one**: backup boot sector bị đẩy **+1 sector** so với vị trí chuẩn; sector “chuẩn” bị zero hoá.
> Điểm quan trọng: **primary BS vẫn tốt** → có thể mount/duyệt dữ liệu luôn, không cần repair.
---
# 5) Bước 3 – Mount volume theo offset (vượt mặt partition table hỏng)
Mount read-only, bật hiển thị file hệ thống & ADS:
```bash
sudo mkdir -p /mnt/ntfs
sudo mount -o ro,loop,offset=16777216,show_sys_files,streams_interface=windows recovery_USB.img /mnt/ntfs
ls -la /mnt/ntfs
# thấy $MFT, $LogFile, $Boot, System Volume Information, ...
```
(Autopsy/FTK không thấy vì **partition table** hỏng. Khi add image, hãy **nhập offset thủ công**: Start sector = 16 MiB / 512 = **32768**, FS = NTFS.)
```bash!
──(kali㉿kali)-[~/Desktop/CHALL4N6ATHONG]
└─$ sudo mkdir -p /mnt/ntfs
[sudo] password for kali:
Sorry, try again.
[sudo] password for kali:
┌──(kali㉿kali)-[~/Desktop/CHALL4N6ATHONG]
└─$ sudo mount -o ro,loop,offset=16777216,show_sys_files,streams_interface=windows recovery_USB.img /mnt/ntfs
┌──(kali㉿kali)-[~/Desktop/CHALL4N6ATHONG]
└─$ ls -la /mnt/ntfs
total 8008
drwxrwxrwx 1 root root 4096 Sep 16 09:37 .
drwxr-xr-x 15 root root 4096 Sep 16 12:56 ..
-rwxrwxrwx 1 root root 2560 Sep 16 09:33 '$AttrDef'
-rwxrwxrwx 1 root root 0 Sep 16 09:33 '$BadClus'
-rwxrwxrwx 1 root root 97792 Sep 16 09:33 '$Bitmap'
-rwxrwxrwx 1 root root 8192 Sep 16 09:33 '$Boot'
drwxrwxrwx 1 root root 0 Sep 16 09:33 '$Extend'
-rwxrwxrwx 1 root root 7684096 Sep 16 09:33 '$LogFile'
-rwxrwxrwx 1 root root 262144 Sep 16 09:33 '$MFT'
-rwxrwxrwx 1 root root 4096 Sep 16 09:33 '$MFTMirr'
-rwxrwxrwx 1 root root 0 Sep 16 09:33 '$Secure'
-rwxrwxrwx 1 root root 131072 Sep 16 09:33 '$UpCase'
-rwxrwxrwx 1 root root 0 Sep 16 09:33 '$Volume'
drwxrwxrwx 1 root root 0 Sep 16 09:33 'System Volume Information'
```
---
# 6) Bước 4 – Dò cấu trúc với Sleuth Kit (không cần mount)
```bash
# Thông số FS (xác nhận)
fsstat -o 32768 recovery_USB.img
# Liệt kê đệ quy (cả deleted & ADS)
fls -o 32768 -r -m / recovery_USB.img | head -200
# Chỉ lọc các entry có dấu ':' (ADS)
fls -o 32768 -r recovery_USB.img | grep ':'
```
**Kết quả thực tế:**
```bash!
┌──(kali㉿kali)-[~/Desktop/CHALL4N6ATHONG]
└─$ fsstat -o 32768 recovery_USB.img
FILE SYSTEM INFORMATION
--------------------------------------------
File System Type: NTFS
Volume Serial Number: AE724CF5724CC435
OEM Name: NTFS
Volume Name: USB
Version: Windows XP
METADATA INFORMATION
--------------------------------------------
First Cluster of MFT: 262144
First Cluster of MFT Mirror: 2
Size of MFT Entries: 1024 bytes
Size of Index Records: 4096 bytes
Range: 0 - 256
Root Directory: 5
CONTENT INFORMATION
--------------------------------------------
Sector Size: 512
Cluster Size: 4096
Total Cluster Range: 0 - 782318
Total Sector Range: 0 - 6258558
$AttrDef Attribute Values:
$STANDARD_INFORMATION (16) Size: 48-72 Flags: Resident
$ATTRIBUTE_LIST (32) Size: No Limit Flags: Non-resident
$FILE_NAME (48) Size: 68-578 Flags: Resident,Index
$OBJECT_ID (64) Size: 0-256 Flags: Resident
$SECURITY_DESCRIPTOR (80) Size: No Limit Flags: Non-resident
$VOLUME_NAME (96) Size: 2-256 Flags: Resident
$VOLUME_INFORMATION (112) Size: 12-12 Flags: Resident
$DATA (128) Size: No Limit Flags:
$INDEX_ROOT (144) Size: No Limit Flags: Resident
$INDEX_ALLOCATION (160) Size: No Limit Flags: Non-resident
$BITMAP (176) Size: No Limit Flags: Non-resident
$REPARSE_POINT (192) Size: 0-16384 Flags: Non-resident
$EA_INFORMATION (208) Size: 8-8 Flags: Resident
$EA (224) Size: 0-65536 Flags:
$LOGGED_UTILITY_STREAM (256) Size: 0-65536 Flags: Non-resident
┌──(kali㉿kali)-[~/Desktop/CHALL4N6ATHONG]
└─$ fls -o 32768 -r -m / recovery_USB.img
0|/$AttrDef ($FILE_NAME)|4-48-2|r/rr-xr-xr-x|0|0|82|1757990014|1757990014|1757990014|1757990014
0|/$AttrDef|4-128-1|r/rr-xr-xr-x|0|0|2560|1757990014|1757990014|1757990014|1757990014
0|/$BadClus ($FILE_NAME)|8-48-3|r/rr-xr-xr-x|0|0|82|1757990014|1757990014|1757990014|1757990014
0|/$BadClus|8-128-2|r/rr-xr-xr-x|0|0|0|1757990014|1757990014|1757990014|1757990014
0|/$BadClus:$Bad|8-128-1|r/rr-xr-xr-x|0|0|3204378624|1757990014|1757990014|1757990014|1757990014
0|/$Bitmap ($FILE_NAME)|6-48-2|r/rr-xr-xr-x|0|0|80|1757990014|1757990014|1757990014|1757990014
0|/$Bitmap|6-128-4|r/rr-xr-xr-x|0|0|97792|1757990014|1757990014|1757990014|1757990014
0|/$Boot ($FILE_NAME)|7-48-2|r/rr-xr-xr-x|48|0|76|1757990014|1757990014|1757990014|1757990014
0|/$Boot|7-128-1|r/rr-xr-xr-x|48|0|8192|1757990014|1757990014|1757990014|1757990014
0|/$Extend ($FILE_NAME)|11-48-3|d/dr-xr-xr-x|0|0|80|1757990014|1757990014|1757990014|1757990014
0|/$Extend|11-144-4|d/dr-xr-xr-x|0|0|656|1757990014|1757990014|1757990014|1757990014
0|/$Extend/$Deleted ($FILE_NAME)|29-48-1|d/dr-xr-xr-x|0|0|82|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$Deleted|29-144-2|d/dr-xr-xr-x|0|0|48|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$ObjId ($FILE_NAME)|25-48-1|r/rr-xr-xr-x|0|0|78|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$ObjId:$O|25-144-2|r/rr-xr-xr-x|0|0|48|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$Quota ($FILE_NAME)|24-48-1|r/rr-xr-xr-x|0|0|78|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$Quota:$O|24-144-3|r/rr-xr-xr-x|0|0|88|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$Quota:$Q|24-144-2|r/rr-xr-xr-x|0|0|208|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$Reparse ($FILE_NAME)|26-48-1|r/rr-xr-xr-x|0|0|82|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$Reparse:$R|26-144-2|r/rr-xr-xr-x|0|0|48|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata ($FILE_NAME)|27-48-1|d/dr-xr-xr-x|0|0|88|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata|27-144-2|d/dr-xr-xr-x|0|0|336|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$Repair ($FILE_NAME)|28-48-1|r/rr-xr-xr-x|0|0|80|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$Repair|28-128-4|r/rr-xr-xr-x|0|0|0|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$Repair:$Config|28-128-2|r/rr-xr-xr-x|0|0|8|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$Repair:$Corrupt|28-128-6|r/rr-xr-xr-x|0|0|2359296|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$Repair:$Verify|28-128-8|r/rr-xr-xr-x|0|0|512000|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$Txf ($FILE_NAME)|31-48-1|d/dr-xr-xr-x|0|0|74|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$Txf|31-144-2|d/dr-xr-xr-x|0|0|48|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$TxfLog ($FILE_NAME)|30-48-1|d/dr-xr-xr-x|0|0|80|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$TxfLog|30-144-2|d/dr-xr-xr-x|0|0|568|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$TxfLog/$Tops ($FILE_NAME)|32-48-1|r/rr-xr-xr-x|0|0|76|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$TxfLog/$Tops|32-128-2|r/rr-xr-xr-x|0|0|100|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$TxfLog/$Tops:$T|32-128-4|r/rr-xr-xr-x|0|0|1048576|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$TxfLog/$TxfLog.blf ($FILE_NAME)|33-48-2|r/rrwxrwxrwx|0|0|88|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$TxfLog/$TxfLog.blf|33-128-1|r/rrwxrwxrwx|0|0|65536|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$TxfLog/$TxfLogContainer00000000000000000001 ($FILE_NAME)|34-48-2|r/rrwxrwxrwx|0|0|138|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$TxfLog/$TxfLogContainer00000000000000000001|34-128-1|r/rrwxrwxrwx|0|0|2097152|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$TxfLog/$TxfLogContainer00000000000000000002 ($FILE_NAME)|35-48-2|r/rrwxrwxrwx|0|0|138|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$RmMetadata/$TxfLog/$TxfLogContainer00000000000000000002|35-128-1|r/rrwxrwxrwx|0|0|2097152|1757990015|1757990015|1757990015|1757990015
0|/$Extend/$UsnJrnl ($FILE_NAME)|39-48-1|r/rr-xr-xr-x|0|0|82|1757990019|1757990019|1757990019|1757990019
0|/$Extend/$UsnJrnl:$J|39-128-3|r/rr-xr-xr-x|0|0|1128|1757990019|1757990019|1757990019|1757990019
0|/$Extend/$UsnJrnl:$Max|39-128-4|r/rr-xr-xr-x|0|0|32|1757990019|1757990019|1757990019|1757990019
0|/$LogFile ($FILE_NAME)|2-48-2|r/rr-xr-xr-x|0|0|82|1757990014|1757990014|1757990014|1757990014
0|/$LogFile|2-128-1|r/rr-xr-xr-x|0|0|7684096|1757990014|1757990014|1757990014|1757990014
0|/$MFT ($FILE_NAME)|0-48-3|r/rr-xr-xr-x|0|0|74|1757990014|1757990014|1757990014|1757990014
0|/$MFT|0-128-6|r/rr-xr-xr-x|0|0|262144|1757990014|1757990014|1757990014|1757990014
0|/$MFTMirr ($FILE_NAME)|1-48-2|r/rr-xr-xr-x|0|0|82|1757990014|1757990014|1757990014|1757990014
0|/$MFTMirr|1-128-1|r/rr-xr-xr-x|0|0|4096|1757990014|1757990014|1757990014|1757990014
0|/$Secure ($FILE_NAME)|9-48-7|r/rr-xr-xr-x|0|0|80|1757990014|1757990014|1757990014|1757990014
0|/$Secure:$SDS|9-128-8|r/rr-xr-xr-x|0|0|263544|1757990014|1757990014|1757990014|1757990014
0|/$Secure:$SDH|9-144-11|r/rr-xr-xr-x|0|0|56|1757990014|1757990014|1757990014|1757990014
0|/$Secure:$SII|9-144-14|r/rr-xr-xr-x|0|0|56|1757990014|1757990014|1757990014|1757990014
0|/$UpCase ($FILE_NAME)|10-48-2|r/rr-xr-xr-x|0|0|80|1757990014|1757990014|1757990014|1757990014
0|/$UpCase|10-128-1|r/rr-xr-xr-x|0|0|131072|1757990014|1757990014|1757990014|1757990014
0|/$UpCase:$Info|10-128-4|r/rr-xr-xr-x|0|0|32|1757990014|1757990014|1757990014|1757990014
0|/$Volume ($FILE_NAME)|3-48-1|r/rr-xr-xr-x|0|0|80|1757990014|1757990014|1757990014|1757990014
0|/$Volume|3-128-3|r/rr-xr-xr-x|0|0|0|1757990014|1757990014|1757990014|1757990014
0|/System Volume Information ($FILE_NAME)|36-48-2|d/dr-xr-xr-x|0|0|116|1757990018|1757990018|1757990018|1757990018
0|/System Volume Information|36-144-1|d/dr-xr-xr-x|0|0|280|1757990019|1757990019|1757990019|1757990018
0|/System Volume Information/IndexerVolumeGuid ($FILE_NAME)|38-48-2|r/rrwxrwxrwx|0|0|100|1757990019|1757990019|1757990019|1757990019
0|/System Volume Information/IndexerVolumeGuid|38-128-1|r/rrwxrwxrwx|0|0|76|1757990019|1757990019|1757990019|1757990019
0|/System Volume Information/WPSettings.dat ($FILE_NAME)|37-48-2|r/rrwxrwxrwx|0|0|94|1757990018|1757990018|1757990018|1757990018
0|/System Volume Information/WPSettings.dat|37-128-1|r/rrwxrwxrwx|0|0|12|1757990019|1757990018|1757990018|1757990018
0|/lab ($FILE_NAME) (deleted)|41-48-2|-/drwxrwxrwx|0|0|72|1757990213|1757990213|1757990213|1757990213
0|/lab (deleted)|41-144-1|-/drwxrwxrwx|0|0|48|1757990276|1757990276|1757990276|1757990213
0|/lab/flag.png ($FILE_NAME) (deleted)|40-48-5|-/rrwxrwxrwx|0|0|82|1757990127|1757988938|1757990207|1757990127
0|/lab/flag.png (deleted)|40-128-3|-/rrwxrwxrwx|0|0|6183|1757990127|1757988938|1757990219|1757990127
0|/$OrphanFiles|256|V/V---------|0|0|0|0|0|0|0
┌──(kali㉿kali)-[~/Desktop/CHALL4N6ATHONG]
└─$ fls -o 32768 -r recovery_USB.img | grep ':'
r/r 4-128-1: $AttrDef
r/r 8-128-2: $BadClus
r/r 8-128-1: $BadClus:$Bad
r/r 6-128-4: $Bitmap
r/r 7-128-1: $Boot
d/d 11-144-4: $Extend
+ d/d 29-144-2: $Deleted
+ r/r 25-144-2: $ObjId:$O
+ r/r 24-144-3: $Quota:$O
+ r/r 24-144-2: $Quota:$Q
+ r/r 26-144-2: $Reparse:$R
+ d/d 27-144-2: $RmMetadata
++ r/r 28-128-4: $Repair
++ r/r 28-128-2: $Repair:$Config
++ r/r 28-128-6: $Repair:$Corrupt
++ r/r 28-128-8: $Repair:$Verify
++ d/d 31-144-2: $Txf
++ d/d 30-144-2: $TxfLog
+++ r/r 32-128-2: $Tops
+++ r/r 32-128-4: $Tops:$T
+++ r/r 33-128-1: $TxfLog.blf
+++ r/r 34-128-1: $TxfLogContainer00000000000000000001
+++ r/r 35-128-1: $TxfLogContainer00000000000000000002
+ r/r 39-128-3: $UsnJrnl:$J
+ r/r 39-128-4: $UsnJrnl:$Max
r/r 2-128-1: $LogFile
r/r 0-128-6: $MFT
r/r 1-128-1: $MFTMirr
r/r 9-128-8: $Secure:$SDS
r/r 9-144-11: $Secure:$SDH
r/r 9-144-14: $Secure:$SII
r/r 10-128-1: $UpCase
r/r 10-128-4: $UpCase:$Info
r/r 3-128-3: $Volume
d/d 36-144-1: System Volume Information
+ r/r 38-128-1: IndexerVolumeGuid
+ r/r 37-128-1: WPSettings.dat
-/d * 41-144-1: lab
+ -/r * 40-128-3: flag.png
V/V 256: $OrphanFiles
```
→ Thấy thư mục **/lab** & **flag.png** đã **deleted** (inode/attr `40-128-3`).
---
# 7) Bước 5 – Phục hồi file đã xoá
**Cách A – icat theo inode/attr (nhanh-gọn):**
```bash
istat -o 32768 recovery_USB.img 40-128-3 # xem chi tiết
icat -o 32768 recovery_USB.img 40-128-3 > flag.png
file flag.png
```
```bash!
┌──(kali㉿kali)-[~/Desktop/CHALL4N6ATHONG]
└─$ istat -o 32768 recovery_USB.img 40-128-3
MFT Entry Header Values:
Entry: 40 Sequence: 2
$LogFile Sequence Number: 2128984
Not Allocated File
Links: 1
$STANDARD_INFORMATION Attribute Values:
Flags: Archive
Owner ID: 0
Security ID: 264 (S-1-5-21-252484922-3200378321-683739697-1000)
Last User Journal Update Sequence Number: 896
Created: 2025-09-16 09:35:27.271860000 (+07)
File Modified: 2025-09-16 09:15:38.529650000 (+07)
MFT Modified: 2025-09-16 09:36:59.437564900 (+07)
Accessed: 2025-09-16 09:35:27.273867300 (+07)
$FILE_NAME Attribute Values:
Flags: Archive
Name: flag.png
Parent MFT Entry: 41 Sequence: 1
Allocated Size: 8192 Actual Size: 6183
Created: 2025-09-16 09:35:27.271860000 (+07)
File Modified: 2025-09-16 09:15:38.529650000 (+07)
MFT Modified: 2025-09-16 09:36:47.910818700 (+07)
Accessed: 2025-09-16 09:35:27.273867300 (+07)
Attributes:
Type: $STANDARD_INFORMATION (16-0) Name: N/A Resident size: 72
Type: $FILE_NAME (48-5) Name: N/A Resident size: 82
Type: $DATA (128-3) Name: N/A Non-Resident size: 6183 init_size: 6183
2245 2246
┌──(kali㉿kali)-[~/Desktop/CHALL4N6ATHONG]
└─$ icat -o 32768 recovery_USB.img 40-128-3 > flag.png
┌──(kali㉿kali)-[~/Desktop/CHALL4N6ATHONG]
└─$ xxd flag.png | head
00000000: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000010: 0000 04bf 0000 01f9 0806 0000 0060 459c .............`E.
00000020: 4100 0000 0173 5247 4200 aece 1ce9 0000 A....sRGB.......
00000030: 0004 6741 4d41 0000 b18f 0bfc 6105 0000 ..gAMA......a...
00000040: 0009 7048 5973 0000 0ec3 0000 0ec3 01c7 ..pHYs..........
00000050: 6fa8 6400 0017 bc49 4441 5478 5eed dd3f o.d....IDATx^..?
00000060: 6b5b 79be c7f1 6fd4 0816 66ab 9907 b020 k[y...o...f....
00000070: 08c6 e046 6c63 6e71 e1b2 6048 e174 e902 ...Flcnq..`H.t..
00000080: eadc 2c66 b9c1 5c6e 9122 a549 e32e 9d60 ..,f..\n.".I...`
00000090: ba74 36ec 2c7a 04e9 dc18 4c08 08f6 01cc .t6.,z....L.....
┌──(kali㉿kali)-[~/Desktop/CHALL4N6ATHONG]
└─$
```
**Cách B – Recover hàng loạt:**
```bash
mkdir out
tsk_recover -o 32768 -a recovery_USB.img out
# -> tìm out/lab/flag.png
```
---
# 8) Bước 6 – Vá file ảnh bị “cắt đầu”
`xxd flag.png` cho thấy **16 byte đầu = 0x00** (PNG signature + IHDR length/type bị xoá).
Từ offset `0x10` đã có **IHDR data** hợp lệ (width=1215, height=505, 8-bit RGBA, …), `sRGB`, `gAMA`, `pHYs`, `IDAT` vẫn nguyên.
**Cách vá: ghi đè 16 byte đầu** bằng **PNG signature** + **IHDR length/type**:
```bash
cp flag.png flag.orig.png # backup
# 16 byte: 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52
echo '89504e470d0a1a0a0000000d49484452' | xxd -r -p | dd of=flag.png bs=1 seek=0 conv=notrunc
file flag.png
# pngcheck -v flag.png # nếu có
# xdg-open flag.png # mở ảnh lấy flag
```


---
# 9) Tóm tắt logic & vì sao cách này hiệu quả
* **Partition table hỏng** chỉ làm công cụ “tự động” bị mù; **volume NTFS bên trong vẫn nguyên**.
* Dùng **signature-based scan** tìm boot sector & **tính offset** → bypass partition table.
* **Backup BS off-by-one** là bẫy: sector chuẩn bị zero, sector kế tiếp chứa BS hợp lệ.
* Mount theo **offset 16 MiB**, hoặc dùng **Sleuth Kit** (khỏi mount) để liệt kê & phục hồi.
* File **flag.png** bị **xoá** và **cắt header**; chỉ cần **icat** + **vá 16 byte đầu** là xong.
---
# 10) Phụ lục – Script quét & sửa NTFS boot sector
## 10.1. Script quét/verify/repair (đã dùng ở trên)
* Chức năng:
* `scan` – quét “NTFS ”, parse boot-sector, in thông số + offset
* `verify` – so sánh primary vs. backup (tính chuẩn)
* `repair` – **(tuỳ chọn)** chép **backup→primary** nhưng **viết vào bản copy** `*.repaired.img`
**Cách dùng nhanh:**
```bash
# Quét ứng viên NTFS
python3 ntfs_boot_tools.py scan recovery_USB.img
# So sánh primary/backup cho offset cụ thể (bytes)
python3 ntfs_boot_tools.py verify recovery_USB.img --offset-bytes 16777216
# (Tuỳ chọn) Sửa primary từ backup (viết sang .repaired.img)
python3 ntfs_boot_tools.py repair recovery_USB.img --offset-bytes 16777216
```
> Trong bài này **không cần repair** vì primary BS OK. Nếu muốn “sửa tay” bẫy off-by-one (copy sector **6291327** → **32768** trên bản copy), dùng:
>
> ```bash
> cp --sparse=always recovery_USB.img recovery_USB.img.repaired.img
> dd if=recovery_USB.img of=recovery_USB.img.repaired.img bs=512 count=1 skip=6291327 seek=32768 conv=notrunc
> ```
## 10.2. Tính toán tham khảo
* **Start offset (bytes)** = **16 MiB** = `16777216`
* **Start LBA** = `16777216 / 512` = **32768**
* **TotalSectors** = `6258559` (từ boot sector)
* **Backup (expected)** = `offset + TotalSectors*512 - 512` = `3221158912` (bị **zero**)
* **Backup (actual)** = `expected + 512` = `3221159424` (BS hợp lệ)
* `$MFT` LCN = `262144` → Absolute offset = `16777216 + 262144*4096 = 1090519040` (có “FILE” magic)
---