- SQL Server之旅(1):那些給我們帶來福利的系統檢視
- SQL Server之旅(2):理解萬惡的表掃描
- SQL Server之旅(3):解惑那些背了多年聚集索引的人
- SQL Server之旅(4):你必須知道的非聚集索引掃描
- SQL Server之旅(5):確實不得不說的DBCC命令
這篇我來介紹一個winhex利器,這個工具網上有介紹,用途大著呢,可以用來玩資料修復,恢復刪除檔案等等。。。。它能夠將一個file解析成hex形式,這樣你就可以對hex進行修改,然後你就可以看到修復後的結果,為什麼要在sqlserver系列中說這個呢???很簡單呀,sqlserver的DB本質上也是一個mdf檔案,對吧,既然是檔案,我就可以利用winhex對它進行隨意的修改,然後你也知道sqlserver的資料都是以資料頁的形式封裝的,那我就可以修改它的資料頁,對不對,這樣我就可以隨便改變記錄的順序,包括槽位,記錄,頁頭等等。。。說幹就幹吧!!!
一:準備資料
我計劃在資料庫中插入三條測試資料,如圖:
1 2 3 4 5 6 7 8 9 |
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命令,檢視下三條記錄的資料頁情況,如下圖:
1 2 3 |
DBCC TRACEON(3604) DBCC IND(Ctrip,Person,-1) DBCC PAGE(Ctrip,1,78,2) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
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,也就是 (0x0) – 96 (0x60)
2. 0x76 這個就是slot1,也就是(0x1) – 118 (0x76)
2. 0x8d 這個就是slot2,也就是(0x2) – 141 (0x8d)
是不是有點意思,如果你一定要看到slot具體指向的內容,你可以繼續用上一節介紹的DBCC命令,一清二楚。
1 |
DBCC PAGE(Ctrip,1,78,1) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
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的槽位來指向記錄的,如果你大概理解了上面的操作,現在你可以修改任意資料頁的資料了,只要你找得到資料頁的偏移量,然後任由你發揮啦~~~~感謝感謝。。。