解剖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(); } } }
第二篇完
文章列表