解剖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[];
Type = (PageType)header[];
TypeFlagBits = header[];
Level = header[];
FlagBits = BitConverter.ToInt16(header, );
IndexID = BitConverter.ToInt16(header, );
PreviousPage = new PagePointer(BitConverter.ToInt16(header, ), BitConverter.ToInt32(header, ));
Pminlen = BitConverter.ToInt16(header, );
NextPage = new PagePointer(BitConverter.ToInt16(header, ), BitConverter.ToInt32(header, ));
SlotCnt = BitConverter.ToInt16(header, );
ObjectID = BitConverter.ToInt32(header, );
FreeCnt = BitConverter.ToInt16(header, );
FreeData = BitConverter.ToInt16(header, );
Pointer = new PagePointer(BitConverter.ToInt16(header, ), BitConverter.ToInt32(header, ));
ReservedCnt = BitConverter.ToInt16(header, );
Lsn = "(" + BitConverter.ToInt32(header, ) + ":" + BitConverter.ToInt32(header, ) + ":" + BitConverter.ToInt16(header, ) + ")";
XactReserved = BitConverter.ToInt16(header, );
XdesID = "(" + BitConverter.ToInt16(header, ) + ":" + BitConverter.ToInt32(header, ) + ")";
GhostRecCnt = BitConverter.ToInt16(header, );

大家可以看一下我写的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 != )
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[];
Type = (PageType)header[];
TypeFlagBits = header[];
Level = header[];
FlagBits = BitConverter.ToInt16(header, );
IndexID = BitConverter.ToInt16(header, );
PreviousPage = new PagePointer(BitConverter.ToInt16(header, ), BitConverter.ToInt32(header, ));
Pminlen = BitConverter.ToInt16(header, );
NextPage = new PagePointer(BitConverter.ToInt16(header, ), BitConverter.ToInt32(header, ));
SlotCnt = BitConverter.ToInt16(header, );
ObjectID = BitConverter.ToInt32(header, );
FreeCnt = BitConverter.ToInt16(header, );
FreeData = BitConverter.ToInt16(header, );
Pointer = new PagePointer(BitConverter.ToInt16(header, ), BitConverter.ToInt32(header, ));
ReservedCnt = BitConverter.ToInt16(header, );
Lsn = "(" + BitConverter.ToInt32(header, ) + ":" + BitConverter.ToInt32(header, ) + ":" + BitConverter.ToInt16(header, ) + ")";
XactReserved = BitConverter.ToInt16(header, );
XdesID = "(" + BitConverter.ToInt16(header, ) + ":" + BitConverter.ToInt32(header, ) + ")";
GhostRecCnt = BitConverter.ToInt16(header, );
}
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 第二篇 对数据页面头进行逆向(译)的更多相关文章

  1. 解剖SQLSERVER 第一篇 数据库恢复软件商的黑幕(有删减版)

    解剖SQLSERVER 第一篇  数据库恢复软件商的黑幕(有删减版) 这一系列,我们一起来解剖SQLSERVER 在系列的第一篇文章里本人可能会得罪某些人,但是作为一位SQLSERVER MVP,在我 ...

  2. 解剖SQLSERVER 完结篇 关于Internals Viewer源代码

    解剖SQLSERVER 完结篇 关于Internals Viewer源代码 大家可能都用过Internals Viewer这个软件 <查看SQLSERVER内部数据页面的小插件Internals ...

  3. 解剖SQLSERVER 第九篇 OrcaMDF现在能通过系统DMVs显示元数据(译)

    解剖SQLSERVER 第九篇  OrcaMDF现在能通过系统DMVs显示元数据(译) http://improve.dk/orcamdf-now-exposes-metadata-through-s ...

  4. SQL Server Reporting Service(SSRS) 第二篇 SSRS数据分组Parent Group

    SQL Server Reporting Service(SSRS) 第一篇 我的第一个SSRS例子默认使用Table进行简单的数据显示,有时为了进行更加直观的数据显示,我们需要按照某个字段对列表进行 ...

  5. PowerBI 第二篇:数据建模

    在分析数据时,不可能总是对单个数据表进行分析,有时需要把多个数据表导入到PowerBI中,通过多个表中的数据及其关系来执行一些复杂的数据分析任务,因此,为准确计算分析的结果,需要在数据建模中,创建数据 ...

  6. 第二篇:数据可视化 - 基本API

    前言 数据可视化是数据挖掘非常重要的一个环节,它不单在查阅了解数据环节使用到,在整个数据挖掘的流程中都会使用到. 因为数据可视化不单可以形象地展示数据,让你对数据有更好的总体上的了解,而且还可以让你清 ...

  7. PowerBI开发 第二篇:数据建模

    在分析数据时,不可能总是对单个数据表进行分析,有时需要把多个数据表导入到PowerBI中,通过多个表中的数据及其关系来执行一些复杂的数据分析任务,因此,为准确计算分析的结果,需要在数据建模中,创建数据 ...

  8. XSS的原理分析与解剖(第二篇)[转]

    0×01 前言: 上节(http://www.freebuf.com/articles/web/40520.html)已经说明了xss的原理及不同环境的构造方法.本期来说说XSS的分类及挖掘方法. 当 ...

  9. sklearn 第二篇:数据预处理

    sklearn.preprocessing包提供了几个常用的转换函数,用于把原始特征向量转换为更适合估计器的表示. 转化器(Transformer)用于对数据的处理,例如标准化.降维以及特征选择等,提 ...

随机推荐

  1. [工具.tfs]可视化的TFS命令工具——Team Foundation Sidekicks

    工具介绍:http://www.attrice.info/cm/tfs/index.htm Team Foundation Sidekicks is a suite of tools for Micr ...

  2. 64位windows 7下成功配置TortoiseGit使用Github服务器

    最近感觉自己电脑上的代码太乱了,东一块.西一块……于是决定使用正规的源代码管理软件来管理自己以后写的代码.以前做小项目的时候用过TortoiseSVN,感觉不错,但是速度上有点慢,于是决定尝试一下新东 ...

  3. 有趣的代码: fixTypeof

    typeof 可以匹配对象的类型,但是他的能力很弱,比如 typeof new String('123')会显示的object这是我们不想看到的结果很久以前JQ的作者通过Object.prototyp ...

  4. 自动化-Appium

    1.手把手教你 Android 标准 APP 的四大自动化测试法宝:https://testerhome.com/topics/5846 2.中文 Appium API 文档:https://test ...

  5. C语言之数组,字符串,指针

    一. 数组的定义 1.  数组初始化 初始化方式 int a[3] = {10, 9, 6}; int a[3] = {10,9}; int a[] = {11, 7, 6}; int a[4] = ...

  6. (Python)继承

    面向对象的另一个特性是继承,继承可以更好的代码重用. 例如一个学校里面的成员有老师.学生.老师和学生都有共同的属性名字和年纪.但老师还有它自己的属性,如工资.学生也有它的属性,如成绩. 因此我们可以设 ...

  7. webrtc进阶-信令篇-之三:信令、stun、turn、ice

    webRTC支持点对点通讯,但是webRTC仍然需要服务端:  . 协调通讯过程中客户端之间需要交换元数据,    如一个客户端找到另一个客户端以及通知另一个客户端开始通讯.  . 需要处理NAT(网 ...

  8. OD使用教程12

    载入程序输入关键字: 双击进入程序 仔细看发现并没有跳转直接跳到这个mov,往上看发现retn上面有一个push,在这种编写手法当中这种组合相当于一个jmp, 跳到离它最近的一个值(在这就是004A5 ...

  9. hdoj 1002 A+B(2)

    Problem Description I have a very simple problem for you. Given two integers A and B, your job is to ...

  10. nexus2.1.2的配置

    最近在学习maven,逐渐接触到私服的搭建,也就着手学习使用nexus了,在http://www.sonatype.org/nexus/go网站上nexus最新版本的是,不过版本要同jvm的版本匹对, ...