好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

解剖SQLSERVER第二篇对数据页面头进行逆向(译)

解剖 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第二篇对数据页面头进行逆向(译)的详细内容...

  阅读:38次