zoukankan      html  css  js  c++  java
  • 解剖SQLSERVER 第二篇 对数据页面头进行逆向(译)

    解剖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:	" + FreeCnt);
    sb.AppendLine("m_freeData:	" + FreeData);
    sb.AppendLine("m_flagBits:	0x" + FlagBits.ToString("x"));
    sb.AppendLine("m_lsn:		" + Lsn);
    sb.AppendLine("m_objId:	" + ObjectID);
    sb.AppendLine("m_pageId:	(" + Pointer.FileID + ":" + Pointer.PageID + ")");
    sb.AppendLine("m_type:		" + Type);
    sb.AppendLine("m_typeFlagBits:	" + "0x" + TypeFlagBits.ToString("x"));
    sb.AppendLine("pminlen:	" + Pminlen);
    sb.AppendLine("m_indexId:	" + IndexID);
    sb.AppendLine("m_slotCnt:	" + SlotCnt);
    sb.AppendLine("m_nextPage:	" + NextPage);
    sb.AppendLine("m_prevPage:	" + PreviousPage);
    sb.AppendLine("m_xactReserved:	" + XactReserved);
    sb.AppendLine("m_xdesId:	" + XdesID);
    sb.AppendLine("m_reservedCnt:	" + ReservedCnt);
    sb.AppendLine("m_ghostRecCnt:	" + GhostRecCnt);
    return sb.ToString();
    }
    }
    }

    第二篇完

  • 相关阅读:
    要养成记录技术问题的习惯
    js排序方法
    阶乘算法练习
    简易的自定义滚动条加鼠标滑轮事件结合使用
    等虚线框的拖拽
    照片墙效果
    苹果导航菜单效果
    简易封装js库
    JQ 实现切换效果
    三级菜单
  • 原文地址:https://www.cnblogs.com/lyhabc/p/4018108.html
Copyright © 2011-2022 走看看