SQL Server之旅(6):使用winHex利器加深理解数据页
原文出处: 一线码农
这篇我来介绍一个winhex利器,这个工具网上有介绍,用途大着呢,可以用来玩数据修复,恢复删除文件等等。。。。它能够将一个file解析成hex形式,这样你就可以对hex进行修改,然后你就可以看到修复后的结果,为什么要在sqlserver系列中说这个呢???很简单呀,sqlserver的DB本质上也是一个mdf文件,对吧,既然是文件,我就可以利用winhex对它进行随意的修改,然后你也知道sqlserver的数据都是以数据页的形式封装的,那我就可以修改它的数据页,对不对,这样我就可以随便改变记录的顺序,包括槽位,记录,页头等等。。。说干就干吧!!!
一:准备数据
我计划在数据库中插入三条测试数据,如图:
DROP TABLE dbo.Person CREATE TABLE Person(ID INT IDENTITY,NAME VARCHAR(5),Age INT) INSERT dbo.Person VALUES('amy',20) INSERT dbo.Person VALUES('anna',25) INSERT dbo.Person VALUES('smart',28) SELECT * FROM dbo.Person
接下来通过上一章介绍的DBCC命令,查看下三条记录的数据页情况,如下图:
DBCC TRACEON(3604) DBCC IND(Ctrip,Person,-1) DBCC PAGE(Ctrip,1,78,2)
DATA: Memory Dump @0x00000000100EA000 00000000100EA000: 01010400 00800001 00000000 00000c00 †................ 00000000100EA010: 00000000 00000300 3f000000 551fa500 †........?...U... 00000000100EA020: 4e000000 01000000 8e000000 66000000 †N...........f... 00000000100EA030: 03000000 00000000 00000000 00000000 †................ 00000000100EA040: 01000000 00000000 00000000 00000000 †................ 00000000100EA050: 00000000 00000000 00000000 00000000 †................ 00000000100EA060: 30000c00 01000000 14000000 03000001 †0............... 00000000100EA070: 00160061 6d793000 0c000200 00001900 †...amy0......... 00000000100EA080: 00000300 00010017 00616e6e 6130000c †.........anna0.. 00000000100EA090: 00030000 001c0000 00030000 01001800 †................ 00000000100EA0A0: 736d6172 74000000 00000000 00000000 †smart........... 00000000100EA0B0: 00000000 00000000 00000000 00000000 †................ .... 00000000100EBFC0: 20202020 20202020 20202020 20202020 † 00000000100EBFD0: 20202020 20200000 00000000 00000000 † .......... 00000000100EBFE0: 00000000 00000000 00000000 00000000 †................ 00000000100EBFF0: 00000000 00000000 1f0b8d00 76006000 †............v.`. OFFSET TABLE: Row - Offset 2 (0x2) - 141 (0x8d) 1 (0x1) - 118 (0x76) 0 (0x0) - 96 (0x60)
我想大家现在都清楚了,数据页中的一条条存储记录都是通过页尾的槽位指向的,具体可以参见前几篇对数据页的介绍,比如你看到页尾的:
8d0076006000了吗?要注意,这些都是按照字节逆序来的。
1. 6000 这个就是slot0,也就是 (0×0) – 96 (0×60)
2. 0×76 这个就是slot1,也就是(0×1) – 118 (0×76)
2. 0x8d 这个就是slot2,也就是(0×2) – 141 (0x8d)
是不是有点意思,如果你一定要看到slot具体指向的内容,你可以继续用上一节介绍的DBCC命令,一清二楚。
DBCC PAGE(Ctrip,1,78,1)
PAGE: (1:78) BUFFER: BUF @0x0000000083FD8E00 bpage = 0x0000000083ADC000 bhash = 0x0000000000000000 bpageno = (1:78) bdbid = 8 breferences = 0 bUse1 = 2495 bstat = 0x1c0000b blog = 0xbbbbbbbb bnext = 0x0000000000000000 PAGE HEADER: Page @0x0000000083ADC000 m_pageId = (1:78) m_headerVersion = 1 m_type = 1 m_typeFlagBits = 0x4 m_level = 0 m_flagBits = 0x8000 m_objId (AllocUnitId.idObj) = 63 m_indexId (AllocUnitId.idInd) = 256 Metadata: AllocUnitId = 72057594042056704 Metadata: PartitionId = 72057594041204736 Metadata: IndexId = 0 Metadata: ObjectId = 341576255 m_prevPage = (0:0) m_nextPage = (0:0) pminlen = 12 m_slotCnt = 3 m_freeCnt = 8021 m_freeData = 165 m_reservedCnt = 0 m_lsn = (142:102:3) m_xactReserved = 0 m_xdesId = (0:0) m_ghostRecCnt = 0 m_tornBits = 0 Allocation Status GAM (1:2) = ALLOCATED SGAM (1:3) = ALLOCATED PFS (1:1) = 0x61 MIXED_EXT ALLOCATED 50_PCT_FULL DIFF (1:6) = CHANGED ML (1:7) = NOT MIN_LOGGED DATA: Slot 0, Offset 0x60, Length 22, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Record Size = 22 Memory Dump @0x000000000F7FC060 0000000000000000: 30000c00 01000000 14000000 03000001 †0............... 0000000000000010: 00160061 6d79††††††††††††††††††††††††...amy Slot 1, Offset 0x76, Length 23, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Record Size = 23 Memory Dump @0x000000000F7FC076 0000000000000000: 30000c00 02000000 19000000 03000001 †0............... 0000000000000010: 00170061 6e6e61††††††††††††††††††††††...anna Slot 2, Offset 0x8d, Length 24, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Record Size = 24 Memory Dump @0x000000000F7FC08D 0000000000000000: 30000c00 03000000 1c000000 03000001 †0............... 0000000000000010: 00180073 6d617274 †††††††††††††††††††...smart OFFSET TABLE: Row - Offset (0x2) - 141 (0x8d) (0x1) - 118 (0x76) (0x0) - 96 (0x60) DBCC 执行完毕。如果 DBCC 输出了错误信息,请与系统管理员联系。
仔细观察下上面的蓝色字体,有没有总结出各个slot槽位对应的记录内容,比如:
slot0槽位指向的记录内容: amy => 616d79。
slot1槽位指向的记录内容: anna => 616e6e61。
slot2槽位指向的记录内容: smart => 736d617274。
这里你要知道,这里都是16进制表示的,所以2个16进制对应一个字节。
二:使用WinHex修改数据
我们大家都知道,sqlserver引擎会通过扫描slot槽位来呈现数据,就像上面的记录那样,依次扫描slot0…slot1….slot2…来呈现数据,如下图:
上面这个截图没什么稀奇的地方,大家也觉得见怪不怪的,那下面就有一个想法来了,如果我通过winHex来交换slot0和slot1的顺序,那效果会是怎样???按照常理说,这时候引擎还是按照slot槽位依次扫描,这时候应该会将ID=2的记录先喷出来,然后再喷出ID=1,ID=3。。。事实是不是这样子呢?好奇吧,我们来看看。。。
三:相关步骤
1. 我们知道Ctrip数据库是联机的,我们要修改它必须先脱机,然后再关掉数据页的一致性校验(这个也是数据库的保护机制,防止第三方恶意的去篡改数据),这个应该大家都明白,如下图:
2. 从网上下载一个破解版的winhex,然后打开本地的Ctrip.mdf文件,调整winhex的编辑模式为默认的可读写,如图:
3. 我们知道一个数据页的大小是8KB=8192B,那么第78号数据页的起始位置的偏移量应该就是:78*8192=638976,然后通过快捷键Alt+G打开偏移量列表,键入638976,如下图:
找到记录的内容之后,我们再来找槽位,槽位的开始位置在78号数据页的末尾,那怎么算呢?这个算法也很简单,offset=79*8192-1=647167。说干就干。
当你真的找到了偏移量,是不是很兴奋呢?下面要做的就是把60和76交换一下,也就是将slot0和slot1交换,看看怎么样????
4. 交换完毕后,ctrl+s保存,然后让Ctrip数据库联机,并使用Sql语句查看下现在的效果???
当你看到这张图的时候,是不是已经疯了。。。。这样我就非常肯定的论证了,引擎真的就是通过依次扫描slot的槽位来指向记录的,如果你大概理解了上面的操作,现在你可以修改任意数据页的数据了,只要你找得到数据页的偏移量,然后任由你发挥啦~~~~感谢感谢。。。