文章出處

解剖SQLSERVER 第二篇  對數據頁面頭進行逆向(譯)

http://improve.dk/reverse-engineering-sql-server-page-headers/

在開發OrcaMDF 的時候第一個挑戰就是解析數據頁面頭部,我們知道數據頁面分兩部分,96字節的頁面頭部和8096字節的數據行

大神 Paul Randal 寫了一篇文章很好的描述了頁頭結構,然而,即使文章描述得很詳細,但是我還是找不出任何關于頁頭存儲的格式

每一個字段的數據類型和他們的順序

 

我們可以使用DBCC PAGE命令,我填充一些隨機數據進去數據頁面,然后把頁面dump出來

頁面號是(1:101):

DBCC TRACEON (3604)
DBCC PAGE (TextTest, 1, 101, 2)

結果分兩部分,首先,我們獲得DBCC PAGE已經格式化好的頁面內容,dump出來的內容的第二部分是96字節的頁面頭

開始動手了,我們需要找出頁面頭部的這些數據值對應的數據類型是什么

為了簡單,我們需要關注一些唯一值以便我們不會獲取到某些存在含糊的數值

我們從m_freeCnt這個字段開始,我們看到m_freeCnt的值是4066,而數據行大小是8060,所以很明顯,m_freeCnt的數據類型不可能是tinyint

m_freeCnt不太可能使用int類型,一種有依據的猜測是m_freeCnt有可能使用smallint類型,這讓數據行足夠容納 0-8060 字節空間的數據

smallint:從-2^15(-32,768)到2^15-1(32,767)的整數數據 存儲大小為 2 個字節,本人也覺得m_freeCnt這個字段的值不可能太大

現在,4066這個十進制數換成十六進制是0x0FE2. 字節交換,變成0xE20F,現在我們知道,我們已經匹配到m_freeCnt的數據類型了

另外,我們已經知道頁頭里面第一個字段的數據類型和位置

/*
    Bytes    Content
    -----    -------
    00-27    ?
    28-29    FreeCnt (smallint)
    30-95    ?
*/

繼續我們的查找,我們看到m_freeData =3895,換算成十六進制是0x0F37 字節交換后0x370F 

我們發現m_freeCnt這個字段存儲在m_freeCnt的后面

使用這個技巧,我們能匹配存儲在頁頭的并且沒有含糊的唯一數據值

不過 ,對于m_level這個字段,他跟m_xactReserved字段,m_reservedCnt字段,m_ghostRecCnt字段的值是一樣的

我們怎麼知道0這個值哪個才屬于m_level字段? 并且我們怎麼找出他的數據類型呢?這有可能是tinyint 到bigint類型

 

我們請出Visual Studio,然后shutdown SQLSERVER

把mdf文件拖入VS,VS會打開hex編輯器,我們根據頁面偏移算出頁面位置

101 * 8192 = 827,392 

 

看著紅色框給我們標出的字節內容,他已經標識出我們的頁面頭內容,并且確定了我們已經跳轉到正確的位置

 

現在我們會填一些數值進去mdf文件里面然后保存文件,請不要胡亂在生產數據庫上進行測試

現在我們啟動SQLSERVER,然后再次運行DBCC PAGE命令

DBCC TRACEON (3604)
DBCC PAGE (TextTest, 1, 101, 2)

可以注意到,現在頁面頭變成了這樣

有幾個數值變了,m_xactReserved 字段先前的數值是0,現在變成了30806,將這個數字轉換成十六進制并進行字節交換得到0x5678

看一下頁面頭,現在我們已經識別出另外一個字段的值和數據類型(smallint)

我們更新一下我們頁頭表格

/*
    Bytes    Content
    -----    -------
    00-27    ?
    28-29    FreeCnt (smallint)
    30-49    ?
    50-51    XactReserved (smallint)
    30-95    ?
*/

沿著這種方法繼續,把頁頭進行混亂修改,將修改后的頁頭和DBCC PAGE的輸出進行關聯,有可能找出這些字段的數據類型

如果你看到下面的消息,你就知道已經把頁面頭部搞混亂了

 

你應該覺得自豪的,沒有人能修好你胡亂修改出來的錯誤

我已經編好了一個頁頭結構表

/*
    Bytes    Content
    -----    -------
    00    HeaderVersion (tinyint)
    01    Type (tinyint)
    02    TypeFlagBits (tinyint)
    03    Level (tinyint)
    04-05    FlagBits (smallint)
    06-07    IndexID (smallint)
    08-11    PreviousPageID (int)
    12-13    PreviousFileID (smallint)
    14-15    Pminlen (smallint)
    16-19    NextPageID (int)
    20-21    NextPageFileID (smallint)
    22-23    SlotCnt (smallint)
    24-27    ObjectID (int)
    28-29    FreeCnt (smallint)
    30-31    FreeData (smallint)
    32-35    PageID (int)
    36-37    FileID (smallint)
    38-39    ReservedCnt (smallint)
    40-43    Lsn1 (int)
    44-47    Lsn2 (int)
    48-49    Lsn3 (smallint)
    50-51    XactReserved (smallint)
    52-55    XdesIDPart2 (int)
    56-57    XdesIDPart1 (smallint)
    58-59    GhostRecCnt (smallint)
    60-95    ?
*/

我不確定頁頭的其他字節跟DBCC PAGE輸出的字段對應關系,我測試過的所有頁面這些字節似乎都存儲為0

我認為這些應該都是為將來某種用途使用的保留字節。好了, 我們已經獲得頁頭格式,讀取每個字段就很簡單了

HeaderVersion = header[0];
Type = (PageType)header[1];
TypeFlagBits = header[2];
Level = header[3];
FlagBits = BitConverter.ToInt16(header, 4);
IndexID = BitConverter.ToInt16(header, 6);
PreviousPage = new PagePointer(BitConverter.ToInt16(header, 12), BitConverter.ToInt32(header, 8));
Pminlen = BitConverter.ToInt16(header, 14);
NextPage = new PagePointer(BitConverter.ToInt16(header, 20), BitConverter.ToInt32(header, 16));
SlotCnt = BitConverter.ToInt16(header, 22);
ObjectID = BitConverter.ToInt32(header, 24);
FreeCnt = BitConverter.ToInt16(header, 28);
FreeData = BitConverter.ToInt16(header, 30);
Pointer = new PagePointer(BitConverter.ToInt16(header, 36), BitConverter.ToInt32(header, 32));
ReservedCnt = BitConverter.ToInt16(header, 38);
Lsn = "(" + BitConverter.ToInt32(header, 40) + ":" + BitConverter.ToInt32(header, 44) + ":" + BitConverter.ToInt16(header, 48) + ")";
XactReserved = BitConverter.ToInt16(header, 50);
XdesID = "(" + BitConverter.ToInt16(header, 56) + ":" + BitConverter.ToInt32(header, 52) + ")";
GhostRecCnt = BitConverter.ToInt16(header, 58);

 

大家可以看一下我寫的pageheader類

using System;
using System.Text;
namespace OrcaMDF.Core.Engine.Pages
{
public class PageHeader
{
public short FreeCnt { get; private set; }
public short FreeData { get; private set; }
public short FlagBits { get; private set; }
public string Lsn { get; private set; }
public int ObjectID { get; private set; }
public PageType Type { get; private set; }
public short Pminlen { get; private set; }
public short IndexID { get; private set; }
public byte TypeFlagBits { get; private set; }
public short SlotCnt { get; private set; }
public string XdesID { get; private set; }
public short XactReserved { get; private set; }
public short ReservedCnt { get; private set; }
public byte Level { get; private set; }
public byte HeaderVersion { get; private set; }
public short GhostRecCnt { get; private set; }
public PagePointer NextPage { get; private set; }
public PagePointer PreviousPage { get; private set; }
public PagePointer Pointer { get; private set; }
public PageHeader(byte[] header)
{
if (header.Length != 96)
throw new ArgumentException("Header length must be 96.");
/*
                Bytes    Content
                -----    -------
                00        HeaderVersion (tinyint)
                01        Type (tinyint)
                02        TypeFlagBits (tinyint)
                03        Level (tinyint)
                04-05    FlagBits (smallint)
                06-07    IndexID (smallint)
                08-11    PreviousPageID (int)
                12-13    PreviousFileID (smallint)
                14-15    Pminlen (smallint)
                16-19    NextPageID (int)
                20-21    NextPageFileID (smallint)
                22-23    SlotCnt (smallint)
                24-27    ObjectID (int)
                28-29    FreeCnt (smallint)
                30-31    FreeData (smallint)
                32-35    PageID (int)
                36-37    FileID (smallint)
                38-39    ReservedCnt (smallint)
                40-43    Lsn1 (int)
                44-47    Lsn2 (int)
                48-49    Lsn3 (smallint)
                50-51    XactReserved (smallint)
                52-55    XdesIDPart2 (int)
                56-57    XdesIDPart1 (smallint)
                58-59    GhostRecCnt (smallint)
                60-63    Checksum/Tornbits (int)
                64-95    ?
            */
HeaderVersion = header[0];
Type = (PageType)header[1];
TypeFlagBits = header[2];
Level = header[3];
FlagBits = BitConverter.ToInt16(header, 4);
IndexID = BitConverter.ToInt16(header, 6);
PreviousPage = new PagePointer(BitConverter.ToInt16(header, 12), BitConverter.ToInt32(header, 8));
Pminlen = BitConverter.ToInt16(header, 14);
NextPage = new PagePointer(BitConverter.ToInt16(header, 20), BitConverter.ToInt32(header, 16));
SlotCnt = BitConverter.ToInt16(header, 22);
ObjectID = BitConverter.ToInt32(header, 24);
FreeCnt = BitConverter.ToInt16(header, 28);
FreeData = BitConverter.ToInt16(header, 30);
Pointer = new PagePointer(BitConverter.ToInt16(header, 36), BitConverter.ToInt32(header, 32));
ReservedCnt = BitConverter.ToInt16(header, 38);
Lsn = "(" + BitConverter.ToInt32(header, 40) + ":" + BitConverter.ToInt32(header, 44) + ":" + BitConverter.ToInt16(header, 48) + ")";
XactReserved = BitConverter.ToInt16(header, 50);
XdesID = "(" + BitConverter.ToInt16(header, 56) + ":" + BitConverter.ToInt32(header, 52) + ")";
GhostRecCnt = BitConverter.ToInt16(header, 58);
}
public override string ToString()
{
var sb = new StringBuilder();
sb.AppendLine("m_freeCnt:\t" + FreeCnt);
sb.AppendLine("m_freeData:\t" + FreeData);
sb.AppendLine("m_flagBits:\t0x" + FlagBits.ToString("x"));
sb.AppendLine("m_lsn:\t\t" + Lsn);
sb.AppendLine("m_objId:\t" + ObjectID);
sb.AppendLine("m_pageId:\t(" + Pointer.FileID + ":" + Pointer.PageID + ")");
sb.AppendLine("m_type:\t\t" + Type);
sb.AppendLine("m_typeFlagBits:\t" + "0x" + TypeFlagBits.ToString("x"));
sb.AppendLine("pminlen:\t" + Pminlen);
sb.AppendLine("m_indexId:\t" + IndexID);
sb.AppendLine("m_slotCnt:\t" + SlotCnt);
sb.AppendLine("m_nextPage:\t" + NextPage);
sb.AppendLine("m_prevPage:\t" + PreviousPage);
sb.AppendLine("m_xactReserved:\t" + XactReserved);
sb.AppendLine("m_xdesId:\t" + XdesID);
sb.AppendLine("m_reservedCnt:\t" + ReservedCnt);
sb.AppendLine("m_ghostRecCnt:\t" + GhostRecCnt);
return sb.ToString();
}
}
}

 

 

第二篇完


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()