# 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 ``` ![image](https://hackmd.io/_uploads/rynReFIogl.png) ![flag](https://hackmd.io/_uploads/ryP1-tUjlx.png) --- # 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) ---