楊 偉,馮賢菊,李巧君
(河南工業(yè)職業(yè)技術(shù)學(xué)院電子信息工程學(xué)院,南陽 473000)
微軟從Windows NT 6.0 操作系統(tǒng)開始,采用了基于XML 技術(shù)的二進(jìn)制XML日志文件格式。日志文件詳細(xì)記錄了系統(tǒng)、用戶、應(yīng)用程序等各種操作[1],如合法用戶對(duì)系統(tǒng)的使用情況,系統(tǒng)中的異常事件等[2]。通過這些日志數(shù)據(jù),可以監(jiān)視系統(tǒng)運(yùn)行情況,檢測(cè)和分析異常事件和錯(cuò)誤發(fā)生的原因,識(shí)別各種安全事件和威脅等[3]。但目前關(guān)于系統(tǒng)日志僅是對(duì)格式的說明,并沒有對(duì)記錄內(nèi)部二進(jìn)制XML 如何轉(zhuǎn)化為文本形式做詳細(xì)的介紹。因此,本文將對(duì)日志文件格式、元素及其記錄內(nèi)部二進(jìn)制XML 編碼轉(zhuǎn)化文本等內(nèi)容詳細(xì)介紹,并在此基礎(chǔ)上分析如何從日志文件中刪除或修改數(shù)據(jù)。
Windows日志文件分System.evtx 系統(tǒng)日志、Application.evtx 應(yīng)用程序日志、Security.evtx 安全日志三種[4],存放路徑默認(rèn)為:%System-Root%System32WinevtLogs(不同Windows 系統(tǒng),日志文件位置可能不同)[5]。文件大小默認(rèn)為20 MB,當(dāng)文件大小超出20 MB時(shí),采用“先生成先覆蓋”的原則覆蓋替換先成的日志記錄[6]。通過事件查看器可瀏覽和管理日志,通過注冊(cè)表表項(xiàng)可對(duì)其大小和路徑進(jìn)行配置。
日志文件結(jié)構(gòu)包括:文件頭、若干數(shù)據(jù)塊、尾部空數(shù)據(jù)三個(gè)部分,如圖1所示。
圖1 日志文件結(jié)構(gòu)示意圖
文件頭位于日志文件開始,大小為4KB,記錄日志文件的基本信息,常駐內(nèi)存中。實(shí)際有用的僅前面128 字節(jié),結(jié)構(gòu)如表1所示。在日志中先低位后高位,如0x80 00 00 00為128的十六進(jìn)制。
表1 文件頭結(jié)構(gòu)
文件頭前8 字節(jié)是魔字符串ElfFilex00,文件頭的開始標(biāo)記。0x08 處開始的8 字節(jié)為第一塊的塊號(hào),塊號(hào)從0 開始,常為0。0x10 處開始的8 字節(jié)為當(dāng)前使用數(shù)據(jù)塊的塊號(hào)。0x18 處開始的8 字節(jié)為下一條記錄的記錄號(hào)。新增記錄時(shí),會(huì)根據(jù)當(dāng)前使用的塊號(hào)和下一條記錄的記錄號(hào)來確定新增記錄的記錄號(hào),以及記錄寫入的位置。
從0x78 處開始的4 字節(jié)是標(biāo)志,若標(biāo)志為0x00 00 00 00,表示正常日志文件;若標(biāo)志為0x00 00 00 01,表示臟日志文件,意味著日志文件被打開修改過,文件和塊頭可能不能正確反映該文件的狀態(tài),校驗(yàn)和也可能是錯(cuò)的。當(dāng)臟文件被重新打開時(shí),日志服務(wù)會(huì)掃描整個(gè)文件,清空該標(biāo)志位,重新計(jì)算校驗(yàn)和,更新文件頭相應(yīng)字段[7]。若標(biāo)志為0x00 00 00 02,表示滿日志文件,如果保留策略不允許覆蓋舊記錄,則新記錄將不會(huì)被記錄到日志文件中,潛在的某些有價(jià)值的記錄就會(huì)丟失。從0x7c 開始的4 字節(jié)為校驗(yàn)和,校驗(yàn)文件頭中前0x78字節(jié)內(nèi)容。
數(shù)據(jù)塊大小為64 KB,由塊頭、日志記錄、未使用空間三部分組成[8]。當(dāng)日志記錄長(zhǎng)度大于當(dāng)前塊的剩余大小時(shí),會(huì)寫入新數(shù)據(jù)塊,因此數(shù)據(jù)塊中存在未使用空間。正因如此,日志文件中雖有許多數(shù)據(jù)塊,卻只有當(dāng)前使用數(shù)據(jù)塊才會(huì)被調(diào)入內(nèi)存中,這大大降低了日志服務(wù)對(duì)系統(tǒng)資源的占用和開銷。
1.2.1 塊頭
塊頭記錄數(shù)據(jù)塊的基本信息,大小為512B,結(jié)構(gòu)如表2所示。塊頭中含兩類記錄編號(hào)方式:基于日志和基于文件。在自動(dòng)維護(hù)模式下,當(dāng)日志文件到達(dá)最大值時(shí),系統(tǒng)會(huì)重命名該文件,以原文件名創(chuàng)建一個(gè)新日志文件[9]。新文件中基于日志的編號(hào)以原文件中編號(hào)為開始,而基于文件的編號(hào)則從零開始。當(dāng)清空日志時(shí),基于日志的編號(hào)才被清零[9]。記錄校驗(yàn)和校驗(yàn)塊內(nèi)所有記錄,范圍從0x200到最后記錄末尾。塊頭校驗(yàn)和校驗(yàn)塊頭前128 字節(jié)以及從129 到512字節(jié)的內(nèi)容。字符串哈希表,共64 個(gè)表項(xiàng),存儲(chǔ)的是XML 模板中常用字符串在數(shù)據(jù)塊中的偏移。在XML 中,會(huì)為每個(gè)字符串計(jì)算一個(gè)16 位的哈希值,該哈希值對(duì)64 求余的余數(shù)指向一個(gè)表項(xiàng),該表項(xiàng)存儲(chǔ)字符串在數(shù)據(jù)塊中的偏移,沖突時(shí)采用單鏈表法解決。哈希表后面是XML模板表,共32 個(gè)表項(xiàng)。這兩個(gè)表主要用于避免同一數(shù)據(jù)塊內(nèi)的某些字符串和XML 結(jié)構(gòu)的冗余聲明。此外日志文件中所有的偏移都基于當(dāng)前塊頭開始計(jì)算。
表2 塊頭結(jié)構(gòu)
1.2.2 日志記錄
日志記錄位于塊頭之后,結(jié)構(gòu)如表3所示。前4 字節(jié)是開始標(biāo)記0x2a 2a 00 00,其后4 字節(jié)是記錄長(zhǎng)度,之后8 字節(jié)為記錄的創(chuàng)建日期時(shí)間,0x18 之后是長(zhǎng)度不確定的二進(jìn)制XML 流,格式在第3 小節(jié)介紹。最后4 字節(jié)為重復(fù)的記錄長(zhǎng)度,以便快速有效遍歷日志記錄。
表3 日志記錄結(jié)構(gòu)
為減少XML 文本數(shù)據(jù)冗余,微軟采用專有的二進(jìn)制XML 編碼將日志記錄從文本形式轉(zhuǎn)換成二進(jìn)制形式,減少了日志文件占用的存儲(chǔ)空間和處理時(shí)間。轉(zhuǎn)換過程分三步:①XML 元素標(biāo)記化;②通過替換機(jī)制將結(jié)構(gòu)、內(nèi)容分離;③為重復(fù)XML結(jié)構(gòu)定義模板[10]。
XML模板如圖2所示,Events元素包括所有Event 元素。Event 元素描述日志記錄,包含兩個(gè)元素:一個(gè)是System 元素,提供記錄的大部分基本信息;另一個(gè)是EventData、BinaryEvent-Data 等元素中的一個(gè),其中EventData 使用較多。
圖2 XML結(jié)構(gòu)圖
為減少計(jì)算資源和存儲(chǔ)空間,被頻繁讀取的XML 消息被轉(zhuǎn)換為標(biāo)記。標(biāo)記分為系統(tǒng)標(biāo)記和應(yīng)用程序標(biāo)記,其中系統(tǒng)標(biāo)記被硬編碼進(jìn)生成和解析二進(jìn)制XML 的程序中,提供編號(hào)和相應(yīng)功能的靜態(tài)映射,如表4所示;應(yīng)用程序標(biāo)記主要表示元素、屬性名和XML模板。
表4 系統(tǒng)標(biāo)記
其中0x41 表示元素中包含屬性列表,0x01表示不包含屬性;0x46 表示該屬性之后有其它屬性,0x06 表示當(dāng)前屬性為最后一個(gè)屬性。以<EventID>7036</EventID>為例,根據(jù)以上規(guī)則,標(biāo)記化后為:0x01 EventID 0x02 1234 0x04。這里只是簡(jiǎn)單地介紹元素如何被標(biāo)記,在實(shí)際的XML 流中,元素名和屬性名會(huì)被標(biāo)記為應(yīng)用程序標(biāo)記,屬性和元素的值被轉(zhuǎn)化存儲(chǔ)在替換數(shù)組中。
替換機(jī)制的目的是將結(jié)構(gòu)與內(nèi)容分離。日志記錄都以<System>元素開始,<System>里面包含的元素和屬性相同,不同的只是元素內(nèi)容和屬性值。Windows 將XML 中相同結(jié)構(gòu)的二進(jìn)制序列制成模板;不同的部分用替換記號(hào)替代,元素內(nèi)容和屬性值存儲(chǔ)在替換數(shù)組中,替換記號(hào)指向數(shù)組中相對(duì)應(yīng)位置。通過模板實(shí)例、系統(tǒng)和應(yīng)用標(biāo)記可實(shí)例化一個(gè)模板,二者通過一個(gè)ID 聯(lián)系起來。這樣,在同一個(gè)數(shù)據(jù)塊中具有相同XML 結(jié)構(gòu)的記錄使用同一個(gè)XML 模板,只有出現(xiàn)不同的XML結(jié)構(gòu)時(shí)才會(huì)出現(xiàn)模板的定義,極大減少了文件的存儲(chǔ)空間。
替換記號(hào)分為正常替換記號(hào)和可選替換記號(hào),均為4字節(jié)。第1字節(jié)是替換標(biāo)記,0x0d或0x0e;接下來2字節(jié)是替換標(biāo)識(shí)符,指示模板中的值在替換數(shù)組中的下標(biāo),下標(biāo)從0開始;最后1字節(jié)是數(shù)據(jù)類型。兩者替換原則不同,前者在任何條件下都是將替換數(shù)組中的數(shù)據(jù)插入到XML 結(jié)構(gòu)中,而后者是替換數(shù)組中記號(hào)指向的元素不為空時(shí),與前者一樣直接插入;指向?yàn)榭諘r(shí),則隱藏相應(yīng)元素或?qū)傩?。?duì)于上面的例子,可完善為:0x01 EventID 0x02 0x0e 0x0003 0x06 0x04。其中0x0e 表示可選替換標(biāo)記,0x0003 為2 字節(jié)的標(biāo)識(shí),標(biāo)識(shí)該元素的值在替換數(shù)組中的下標(biāo)為3,數(shù)據(jù)類型為6。
二進(jìn)制XML 的格式如表5所示,其中模板定義偏移量,指向的位置可能位于偏移值之后,也可能指向該記錄之前的某一位置。前者說明該記錄中聲明了XML模板,包含模板定義數(shù)據(jù)。后者說明此記錄中無XML 模板,使用的XML 模板在該記錄之前已經(jīng)定義,緊跟著偏移后面的內(nèi)容為替換數(shù)組。模板定義數(shù)據(jù)中的數(shù)據(jù)大小計(jì)算的是fragment header、元素以及結(jié)束標(biāo)記的大小。
表5 二進(jìn)制流
元素的結(jié)構(gòu)如表6所示,其中元素大小包括偏移、屬性列表、關(guān)閉標(biāo)記、元素值、元素的結(jié)束標(biāo)記,即除前7個(gè)字節(jié)之外的字節(jié)數(shù)。根據(jù)偏移可找到元素名,如圖30x1249 處的0x4D 02 00 00為元素名偏移,由此找到0x124D處的元素名,前4字節(jié)未知,其后2字節(jié)0x BA 0C 為字符串哈希,0x 05 00為元素名中字符個(gè)數(shù),接下來0x 45 00 76 00 65 00 6E 00 74 00 為元素名Event的Utf-16字符串。
表6 元素的結(jié)構(gòu)
屬性列表含屬性列表大小和屬性數(shù)組,如圖3中0x1261 到0x12eb 處內(nèi)容。屬性數(shù)組中含多個(gè)屬性。屬性的第1 字節(jié)為屬性標(biāo)記,為0x06 或0x46,接下來4 字節(jié)為屬性名偏移,最后為屬性數(shù)據(jù)。屬性數(shù)據(jù)常見的為屬性值和替換,替換標(biāo)記格式已在上面介紹,屬性值格式如圖3中127e 處內(nèi)容,第1 字節(jié)為屬性值標(biāo)記0x05,其后1字節(jié)為數(shù)據(jù)類型,最后為長(zhǎng)度不確定的屬性值。
圖3 元素?cái)?shù)據(jù)
二進(jìn)制XML 的最后為替換數(shù)組,結(jié)構(gòu)如圖4所示。前4 字節(jié)為數(shù)組元素個(gè)數(shù),其后為每個(gè)數(shù)組元素的大小以及數(shù)據(jù)類型,最后為各個(gè)數(shù)組元素的實(shí)際數(shù)據(jù)。
圖4 替換數(shù)組
XML 結(jié)構(gòu)中變量元素在替換數(shù)組中的位置是固定的,它們之間的映射關(guān)系如圖5所示。
圖5 XML和替換數(shù)組映射關(guān)系
根據(jù)上面的格式分析,結(jié)合替換數(shù)組的結(jié)構(gòu),可對(duì)日志記錄中的二進(jìn)制XML 數(shù)據(jù)進(jìn)行內(nèi)容還原,將二進(jìn)制數(shù)據(jù)轉(zhuǎn)化為文本形式。
基于上述日志格式分析,可以對(duì)日志文件進(jìn)行修改、刪除操作。但由于Windows 系統(tǒng)中事件日志服務(wù)運(yùn)行在svchost.exe進(jìn)程中,該進(jìn)程是關(guān)鍵系統(tǒng)進(jìn)程,自啟動(dòng)且不可停止,若強(qiáng)行中止系統(tǒng)檢測(cè)到后會(huì)自動(dòng)關(guān)機(jī)。因此,在事件服務(wù)啟動(dòng)的情況下,由于系統(tǒng)對(duì)日志文件的保護(hù),無法修改或刪除日志文件。但我們可以備份日志文件,對(duì)備份文件中的日志記錄修改或刪除,然后用修改后的日志文件覆蓋原有日志文件來達(dá)到日志內(nèi)容修改的目的。
.NET 提供了許多操作事件日志的類,如EventLog、EventLogQuery、EventLogReader、Event-Record 等。根據(jù)上述思路,利用.NET 提供的日志操作類刪除和修改日志記錄的步驟為:
(1)在注冊(cè)表EventLog下注冊(cè)一個(gè)新日志文件,文件名與要修改的日志文件名長(zhǎng)度一致。
(2)通過指定查詢目標(biāo)和事件查詢初始化EventLogQuery 類的新實(shí)例。查詢目標(biāo)為要修改的日志文件;事件查詢語句可根據(jù)規(guī)則過濾一些要?jiǎng)h除的事件記錄。
(3)初始化EventLogReader 類的實(shí)例,利用ReadEvent方法,遍歷查詢到的事件記錄。
(4)EventRecord 提供了事件記錄的絕大部分信息,利用這些信息以及EventLog 提供的日志寫入方法,可以將事件記錄寫入新注冊(cè)的日志文件中。寫入時(shí)日志的分類、來源、用戶等信息可以指定,只需要修改相應(yīng)內(nèi)容就可保持新、舊日志信息一致。
(5)日志寫入完成后,備份新注冊(cè)的日志信息。
(6)由于無法指定事件記錄的寫入時(shí)間、事件源,且新注冊(cè)的日志文件名與原日志文件名不同,因此需要對(duì)備份文件中每條記錄的上述信息進(jìn)行修改,同時(shí)也可根據(jù)規(guī)則修改事件記錄中的其他數(shù)據(jù)。
(7)對(duì)涉及到校驗(yàn)和的地方重新生成。
(8)用修改后的文件覆蓋原有的日志文件。
在寫入新注冊(cè)的日志文件時(shí),由于事件源已經(jīng)在要修改的日志中注冊(cè),導(dǎo)致不能用原來的事件源在新注冊(cè)的日志中創(chuàng)建事件源。為了不改動(dòng)數(shù)據(jù)偏移,我們用一個(gè)新的事件源來代替原來的事件源,但其長(zhǎng)度要與原來的事件源相同。另外新產(chǎn)生的日志文件中元素Provider 中不存在屬性Guid,為了與原日志文件保存一致,在創(chuàng)建完事件源之后,需要修改注冊(cè)表中事件源的ProviderGuid 屬性值為原來日志文件中相應(yīng)事件源的ProviderGuid 值。通過上述步驟,可以實(shí)現(xiàn)事件記錄的刪除、修改操作。
本文詳盡描述了Windows 系統(tǒng)日志文件的結(jié)構(gòu)、校驗(yàn)機(jī)制等,并在此基礎(chǔ)上介紹了基于.NET 框架對(duì)日志事件記錄進(jìn)行刪除及修改的具體方法,能夠自由刪除、修改日志記錄。