解剖 SQLSERVER 第二 篇 对 数据 页面 头 进行 逆向 (译) http://improve.dk/reverse-engineering-sql-server-page-headers/ 在开发OrcaMDF 的时候第一个挑战就是解析 数据 页面 头部,我们知道 数据 页面 分两部分,96字节的 页面 头部和8096字节的 数据
解剖 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 )
可以注意到,现在 页面 头变成了这样
看一下 页面 头,现在我们已经识别出另外一个字段的值和 数据 类型(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 ?
*/
我认为这些应该都是为将来某种用途使用的保留字节。好了, 我们已经获得页头格式,读取每个字段就很简单了
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();
}
}
}
第二 篇完
查看更多关于解剖SQLSERVER第二篇对数据页面头进行逆向(译)的详细内容...