pretty code

2016年10月24日 星期一

NTFS B+Tree parsing 概念圖

假設我們想要從 B+Tree 找到 g:\123\456.txt 這個檔案,其概念圖大概如下圖


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 告一段落

2016年10月17日 星期一

Read disk sector on Windows

關鍵在於磁碟機代號的名字,假設是 D 槽,其名字為 "\\.\D:"
至於是用 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", &sector);
    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;
}

NTFS Entry 概念圖


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 程式碼

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