假設我們想要從 B+Tree 找到 g:\123\456.txt 這個檔案,其概念圖大概如下圖
pretty code
2016年10月24日 星期一
2016年10月21日 星期五
NTFS B+Tree parsing
假設我們想要從 B+Tree 找到 g:\123\456.txt 這個檔案,其步驟如下
1. 從 Partition G 的第 1 個 sector 找到 NTFS BootSector,並找到 MFT 的起點
2. 找到 index 為 0x05 的 Entry dot file
從 $INDEX_ROOT Attribute (0x90)可以看出
這個 Attritube 是 resident named,其名字為 $I30
裡面儲存的 INDEX Record 其 type 為 $FILE_NAME (0x30),大小為 1K
因為大小不夠存放,故需從 $INDEX_ALLOCATION (0xA0) 找到 DataRun
也就是 LCN 為 0x2C 的位置
3. 找到 INDEX Record 的位置,並 parse 一筆一筆的 Index Entry
找到 123 這個名字的 Entry,並得知其在 MFT 的 index 為 0x2C ,此筆的大小為 0x58,
此目錄的建立時間為 2016-10-19 15:25:51 +0800 CST (1D229DA060F6283)
4. 回到 MFT index 為 0x2C 的 Entry,由於該目錄只有 1 個檔案也就是 456.txt
故沒有 $INDEX_ALLOCATION Attribute,所有的資訊都存放在 $INDEX_ROOT,
該檔案在 MFT 的 index 為 0x2D,其最後修改時間為 2016-10-21 14:48:40 +0800 CST
5. 回到 MFT index 為 0x2D 的 Entry,由於檔案過大,
故其 $DATA是另外存放在 MFT 以外的區域,其 LCN 為 0x7F 的位置
6. 最後來到 LCN 為 0x7F 的位置,其內容就是 456.txt 的內容,至此所有的 parsing 告一段落
1. 從 Partition G 的第 1 個 sector 找到 NTFS BootSector,並找到 MFT 的起點
2. 找到 index 為 0x05 的 Entry dot file
從 $INDEX_ROOT Attribute (0x90)可以看出
這個 Attritube 是 resident named,其名字為 $I30
裡面儲存的 INDEX Record 其 type 為 $FILE_NAME (0x30),大小為 1K
因為大小不夠存放,故需從 $INDEX_ALLOCATION (0xA0) 找到 DataRun
也就是 LCN 為 0x2C 的位置
3. 找到 INDEX Record 的位置,並 parse 一筆一筆的 Index Entry
找到 123 這個名字的 Entry,並得知其在 MFT 的 index 為 0x2C ,此筆的大小為 0x58,
此目錄的建立時間為 2016-10-19 15:25:51 +0800 CST (1D229DA060F6283)
4. 回到 MFT index 為 0x2C 的 Entry,由於該目錄只有 1 個檔案也就是 456.txt
故沒有 $INDEX_ALLOCATION Attribute,所有的資訊都存放在 $INDEX_ROOT,
該檔案在 MFT 的 index 為 0x2D,其最後修改時間為 2016-10-21 14:48:40 +0800 CST
5. 回到 MFT index 為 0x2D 的 Entry,由於檔案過大,
故其 $DATA是另外存放在 MFT 以外的區域,其 LCN 為 0x7F 的位置
6. 最後來到 LCN 為 0x7F 的位置,其內容就是 456.txt 的內容,至此所有的 parsing 告一段落
2016年10月17日 星期一
Read disk sector on Windows
關鍵在於磁碟機代號的名字,假設是 D 槽,其名字為 "\\.\D:"
至於是用 Win32 API or C code 實驗結果都一樣
故只要稍微修改一下,便可以用來讀指定的 NTFS Entry
當然我們也可以讀取實體的硬碟開頭,只要遵循以下規則
至於是用 Win32 API or C code 實驗結果都一樣
故只要稍微修改一下,便可以用來讀指定的 NTFS Entry
當然我們也可以讀取實體的硬碟開頭,只要遵循以下規則
| Name | Meaning |
| \\.\PhysicalDrive0 | 電腦的第 1 顆硬碟 |
| \\.\PhysicalDrive1 | 電腦的第 2 顆硬碟 |
| \\.\c: | 電腦的 C 槽 |
| \\.\c:\ | 電腦的 C 槽 file system |
#include <stdio.h>
#include <mem.h>
//-----------------------------------------------------------------------------
#define SECTOR_SIZE 512
//---------------------------------------------------------------------------
// num is sector number, it starts with 0
bool ReadSect(const char *dsk, char *buf, int num)
{
if (strlen(dsk) == 0) {
return false;
}
if (num < 0) {
return false;
}
FILE *f = fopen(dsk, "rb");
if (!f) {
return false;
}
fseek(f, num * SECTOR_SIZE, SEEK_SET);
fread(buf, SECTOR_SIZE, 1, f);
fclose(f);
return true;
}
//---------------------------------------------------------------------------
int main(void)
{
char drv[64];
memset(drv, 0x00, sizeof(drv));
char disk;
printf("Which disk do you want to read ? ");
scanf("%c", &disk);
unsigned int sector;
printf("Which sector do you want to read ? ");
scanf("%d", §or);
printf("\r\n");
// use "\\.\PhysicalDrive" to read
sprintf(drv, "\\\\.\\%c:", disk);
char buf[SECTOR_SIZE];
ReadSect(drv, buf, sector);
int line = 0;
for (int i = 0; i < SECTOR_SIZE; i++) {
if (line == 0) {
printf("0x%04X ", sector * SECTOR_SIZE + (i/16) * 16);
}
printf("%02X ", (unsigned char)buf[i]);
line++;
if (line == 16) {
printf("\n");
line = 0;
}
}
printf("\n");
return 0;
}
2016年10月11日 星期二
NTFS $Secure parsing

藍色代表 MFT Entry Header
綠色代表 Attribute Header
粉紅色則是 Attribute Name or Attribute 內容
底下是人工 parsing 的結果
[Entry Header]
Length 0x02F8
The Entry End is 0xFFFFFF (為了 8 的倍數,後面補了 0x00000000,故長度是 0x02F8)
The next attribute id is 0x000F
0x10 -- 0x0000
0x30 -- 0x0007
0x80 -- 0x0008
0x90 -- 0x000B
0x90 -- 0x000E
0xA0 -- 0x0009
0xA0 -- 0x000C
0xB0 -- 0x000A
0xB0 -- 0x000D
[0x10] -- $STANDARD_INFORMATION
The file creation time is "1601-01-01, 00:00 UTC" + (0x01D21B378809B277 / pow(10,7)) (seconds)
[0x30] -- $FILE_NAME
The name of this entry is $Secure, it has 7 characters, it's lenth is 2 x 7 = 14.
[0x80] -- $DATA, non-resident, named
starting VCN 0x00
last VCN 0x40
attribute name is $SDS, it has 4 characters (Name length on offset 0x09, one byte).
offset to the Data Runs 0x48
Data Runs
11 41 2D 00 00 00 00 00
11 41 2D - 00 00 00 00 00 (group)
first one is header, it means one byte length, one byte offset.
length 0x41
offset 0x2D
Move to next group
11 41 2D 00 00 00 00 00 -> 11 41 2D - 00 00 00 00 00
Because header is 0x00, it only has one data run.
[0x90] -- $INDEX_ROOT, resident, named
attribute name is $SDH, it has 4 characters (Name length on offset 0x09, one byte).
[0x90] -- $INDEX_ROOT, resident, named
attribute name is $SII, it has 4 characters (Name length on offset 0x09, one byte).
[0xA0] -- $INDEX_ALLOCATION, non-resident, named
attribute name is $SDH, it has 4 characters (Name length on offset 0x09, one byte).
[0xA0] -- $INDEX_ALLOCATION, non-resident, named
attribute name is $SII, it has 4 characters (Name length on offset 0x09, one byte).
[0xB0] -- $BITMAP, resident, named
attribute name is $SDH, it has 4 characters (Name length on offset 0x09, one byte).
[0xB0] -- $BITMAP, resident, named
attribute name is $SII, it has 4 characters (Name length on offset 0x09, one byte).
2016年10月7日 星期五
NTFS Entry timestamp
Time stamps are stored in 64-bit integer values:
Number of 0.1μs since 1601-01-01, 00:00 UTC.
底下是使用 Go 語言撰寫的轉換 timestamp 程式碼
Number of 0.1μs since 1601-01-01, 00:00 UTC.
底下是使用 Go 語言撰寫的轉換 timestamp 程式碼
package main
import (
"fmt"
"strconv"
"time"
)
func main() {
fmt.Printf("timestamp (little endian case): ")
var input string
fmt.Scanln(&input)
count := len(input)
if count > 16 {
fmt.Println("error input")
return
}
if count % 2 != 0 {
input = "0" + input
count += 1
}
var s string
for i := count - 1; i >= 0; i -= 2 {
s += string(input[i-1]) + string(input[i])
}
n, err := strconv.ParseUint(s, 16, 64)
if err != nil {
fmt.Println("error input")
return
}
fmt.Println("")
base := time.Date(1601, time.January, 1, 0, 0, 0, 0, time.UTC)
sec := base.Unix() + int64(n / 10000000)
fmt.Println(time.Unix(sec, 0))
}
2016年10月6日 星期四
NTFS 基本概念
Volume
可以是 1 個 partition,1 個硬碟,甚至是多個硬碟
簡單起見,就當成是 1 個分割區
Sector
實際硬碟存取的最小單位,通常是 512 bytes
Cluster
叢集,OS 存取硬碟的最小邏輯單位
分割區格式化時會決定此值的大小,預設值是 4096 bytes
NTFS Boot Sector
位於分割區最前面的第 1 個 cluster
裡面會有 MFT 的起始位置,1 個 cluster 等於幾個 sector,1個 sector 有幾個 bytes
MFT (Master File Table)
NTFS File System 的核心概念
每一個檔案或是目錄都是 1 或多筆的 Entry,儲存在此區域
如果資料超過 1 筆 Entry 的大小,則可能會存放在 MFT 以外
可以把 MFT 想成是關聯式資料庫,Entry 是 Row,而 Attributes 則是欄位
MFT Entry
MFF 裡面的每筆 Record
前面 16 筆是系統保留的 Entry,名字前會帶有 '$' 且第一個字母為大寫
其作用是描述 MFT 及 NTFS 本身,也可以稱為 File System Metadata Files
$MFT
$MFTMirr
$LogFile 等
MFT Entry Attribute
描述每 1 筆 Entry,1 筆 Entry 可能會有多筆 Attributes
例如 $STANDARD_INFORMATION,$FILE_NAME 等
MFT Entry resident Attrubute
此筆 Attribute 的資料存放在 Entry 中,可由 Attribute 裡的 flag 看出是否是 resident
MFT Entry non-resident Attrubute
此筆 Attribute 的資料存放在 Entry 以外,可由 Attribute 裡的 flag 看出是否是 resident
LCN (Logical Cluster Number)
實際的 Cluster 位置(號碼),功用是類似 index
VCN (Virtual Cluster Number)
虛擬的 Cluster 位置(號碼),功用是類似 index
可以是 1 個 partition,1 個硬碟,甚至是多個硬碟
簡單起見,就當成是 1 個分割區
Sector
實際硬碟存取的最小單位,通常是 512 bytes
Cluster
叢集,OS 存取硬碟的最小邏輯單位
分割區格式化時會決定此值的大小,預設值是 4096 bytes
NTFS Boot Sector
位於分割區最前面的第 1 個 cluster
裡面會有 MFT 的起始位置,1 個 cluster 等於幾個 sector,1個 sector 有幾個 bytes
MFT (Master File Table)
NTFS File System 的核心概念
每一個檔案或是目錄都是 1 或多筆的 Entry,儲存在此區域
如果資料超過 1 筆 Entry 的大小,則可能會存放在 MFT 以外
可以把 MFT 想成是關聯式資料庫,Entry 是 Row,而 Attributes 則是欄位
MFT Entry
MFF 裡面的每筆 Record
前面 16 筆是系統保留的 Entry,名字前會帶有 '$' 且第一個字母為大寫
其作用是描述 MFT 及 NTFS 本身,也可以稱為 File System Metadata Files
$MFT
$MFTMirr
$LogFile 等
MFT Entry Attribute
描述每 1 筆 Entry,1 筆 Entry 可能會有多筆 Attributes
例如 $STANDARD_INFORMATION,$FILE_NAME 等
MFT Entry resident Attrubute
此筆 Attribute 的資料存放在 Entry 中,可由 Attribute 裡的 flag 看出是否是 resident
MFT Entry non-resident Attrubute
此筆 Attribute 的資料存放在 Entry 以外,可由 Attribute 裡的 flag 看出是否是 resident
LCN (Logical Cluster Number)
實際的 Cluster 位置(號碼),功用是類似 index
VCN (Virtual Cluster Number)
虛擬的 Cluster 位置(號碼),功用是類似 index
訂閱:
文章 (Atom)








