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

2016年10月5日 星期三

NTFS 參考資料

由於機車的微軟沒有公開 Spec
故 NTFS 的 parsing 都是由高手努力推斷出來的結果
底下是建議的閱讀順序

先從微軟的文章了解基本資訊
https://technet.microsoft.com/en-us/library/cc781134(v=ws.10).aspx

也可以看看 wiki
https://zh.wikipedia.org/wiki/NTFS

再來可以看 Brian Carrier 所撰寫的《File System Forensic Analysis》一書
很多網路上看到的簡報都會參考它

最後則是 Linux 社群的研究結果,也是目前我覺得最正確的 Spec
https://sourceforge.net/projects/linux-ntfs/files/NTFS%20Documentation/0.6/

NTFS $MFT parsing



$MFT 是 MFT 裡的第一筆 Entry,作用是描述 MFT 本身,總共有 4 個 attributes。

藍色代表 MFT Entry Header
綠色代表 Attribute Header
粉紅色則是 Attribute 內容

底下是人工 parsing 的結果


[Entry Header]

Length 0x0198
The Entry End is 0xFFFFFF (為了 8 的倍數,後面補了 0x00000000,故長度是 0x0198)

The next attribute id is 0x0007

0x10 -- 0x0000
0x30 -- 0x0003
0x80 -- 0x0006
0xB0 -- 0x0005


[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 $MFT, it has 4 characters, it's lenth is 2 x 4 = 8.


[0x80] -- $DATA, non-resident, no name

Note: not every 0x80 attribute is non-resident, we need to check non-resident flag.

starting VCN 0x00
last VCN 0x3F

offset to the Data Runs 0x40

Data Runs

21 40 BD 04 00 00 00 00

21 40 BD 04 - 00 00 00 00 (group)

first one is header, it means one byte length, two byte offset.

length 0x40
offset 0x04BD

Because $MFT is the description of MFT. this 0x80 attribute tells us that
MFT is at 0x4BD000 (0x04BD x 4096) and the length is 262144 bytes (0x40 * 4096).
We can use starting VCN and last VCN to check this length (0x00 - 0x3F, length 0x40) too.

Move to next group

21 40 BD 04 00 00 00 -> 21 40 BD 04 - 00 00 00 00

Because header is 0x00, it only has one data run.


[0xB0] -- $BITMAP, non-resident, no name

starting VCN 0x00
last VCN 0x01

offset to the Data Runs 0x40

Data Runs

21 01 BC 04 11 01 FF 00

21 01 BC 04 - 11 01 FF - 00 (group)

Run1 21 01 BC 04

length 0x01
offset 0x04BC

Run2 11 01 FF

length 0x01
offset 0x05BB (0x04BC + 0xFF)

從 Run1 得知,data 位於 0x4BC000 (0x04BC x 4096)
每 1 個 bit 代表 1 個 Entry,1 代表使用中,0 則是未使用
Dump 出來的資料如下

01 9F FF 00 FF FF

前 16 筆 Entry 是 metafile
第 25 筆 Entry 開始是 File System 的檔案或目錄
連續的17筆資料中,有 2 筆是未使用的
如果跳到該筆 Entry,可以從 Entry Header Flag 確認,其值應該為 0x00

至於 Run2,dump 出來的值都是 0x00
故只有在 MFT 前 41筆 Entry 是有資料的 (包含保留及空的未使用)












2016年8月24日 星期三

npm uninstall all

使用 node.js 時,如果想要一口氣刪除所有的 module,可以使用下列方式

進到 node_modules 資料夾
使用 PowerShell 執行下列命令
npm uninstall (Get-ChildItem).Name

2016年8月19日 星期五

nginx 設定 PHP

假設在 nginx 設定 php 遇到奇怪的問題
比如 query string , path info 等值取不到
大部份都是設定檔的問題
其中順序也會影響
留著備忘參考

location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
    return 404;
}

fastcgi_buffer_size 128k;
fastcgi_buffers 256 16k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;

fastcgi_read_timeout 3610;

include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_index index.php;
fastcgi_pass 127.0.0.1:9090;

Golang on Ubuntu

假設在 Ubuntu 安裝完 Golang
可以用 go env GOROOT
來確定 golang 安裝的路徑

接著執行下面指令,就可以開始使用 golang 了

export GOROOT=/usr/loca/golang-1.6(假設查出來是此目錄)
export GOPATH=$HOME/GoHome
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

2016年8月16日 星期二

Alternate Domain Names

一般來說,在設定 CDN 時有 2 種方式:

1. 直接將 CDN 給的 domain 拿來做 URL,假設 CDN 的 domain 是 abc.com,則要做 CDN 的檔案的 URL 都是 abc.com/xxxx。
2. 仍然使用自己的 domain,假設自己的 domain 是 def.com,需要在 DNS 服務提供者建立一筆 CNAME record,此時 def.com/xxx 的 URL 便會導向到 abc.com/xxx。

2016年7月29日 星期五

Android OTA 差分包

1. Copy "/source/android/out/target/product/xxx/obj/PACKAGING/target_files_intermediates/xxx.zip" to old.zip
2. Modify source code and rebuild project
3. Copy "/source/android/out/target/product/xxx/obj/PACKAGING/target_files_intermediates/xxx.zip" to new.zip
4. Move to "/source/android" path, create "OTA" folder, copy old.zip and new.zip to this folder
5. Run "./build/tools/releasetools/ota_from_target_files -i OTA/old.zip OTA/new.zip OTA/diff.zip"
6. If there is no error, you will see the below message

1 的檔案是製作 OTA 檔案過程中的產物,差分包需要這個才能產生 patch,故不能拿平常 build 出來的 OTA 檔案。

另外,由於是用 old.zip 比較出來的差分包,故設備上的版本需和 old.zip 一樣,不然升級會失敗。

2016年7月22日 星期五

grep examples

# 挑出超過 1000 ms 的資料
110.txt: 100%    800 (longest request)
111.txt: 100%   2538 (longest request)
112.txt: 100%   1209 (longest request)
113.txt: 100%   2341 (longest request)
114.txt: 100%    829 (longest request)
115.txt: 100%   2506 (longest request)
116.txt: 100%    829 (longest request)
117.txt: 100%   2372 (longest request)
118.txt: 100%    862 (longest request)
119.txt: 100%   5681 (longest request)
120.txt: 100%    803 (longest request)

ANS: grep -E "%\s{3}\S"
前面一定要加入 % ,不然就如圖片所示,有 4個空白的列也會符合。








The Apache Hints of Windows Platform

Apache 2.4.23

# Check Compiled modules
C:\Apache\bin>httpd -l
Compiled in modules:
  core.c
  mod_win32.c
  mpm_winnt.c
  http_core.c
  mod_so.c

# 記憶體調校
如果做壓力測試時,發現 httpd 的虛擬記憶體不斷的上升,此時可以調整以下參數
1. Keep Alive:
關掉 Keep Alive,或是減少 MaxKeepAliveRequests 以及減少 KeepAliveTimeout,Apache 為了保持連線,會保留記憶體而未釋放。
2. MaxConnectionsPerChild (after 2.3.9) or MaxRequestsPerChild (before 2.3.9):
Windows 的 Apache 共有 2 個 httpd.exe process,其中 1個是子進程,負責處理所有的 requests。當設置這 2 個參數後,如果達到 Max 的 Connections or Requests,母進程便會殺掉子進程,因此就能釋放原本被佔用的記憶體,此預設值為 0 表示無限制。

# AcceptPathInfo
This directive controls whether requests that contain trailing pathname information that follows an actual filename (or non-existent file in an existing directory) will be accepted or rejected.
example: /test/here.html/more
如果啟用的話,則 /more 便會視為 PATH_INFO,此預設值為 Default,處理 scripts 時 accepted,其他則 rejected。