難譯 | windbg 樂趣之道(上)
前言
Yarden Shafir 分享了兩篇非常通俗易懂的,關(guān)于 windbg 新引入的調(diào)試數(shù)據(jù)模型的文章。鏈接如下:
part1:https://medium.com/@yardenshafir2/windbg-the-fun-way-part-1-2e4978791f9b
part2:https://medium.com/@yardenshafir2/windbg-the-fun-way-part-2-7a904cba5435
本文是第一部分的譯文。在有道詞典、必應(yīng)詞典、谷歌翻譯的大力幫助下完成,感謝以上翻譯工具,我只是一個(gè)搬運(yùn)工。強(qiáng)烈建議英文好的朋友閱讀原文,因?yàn)樵诜g的過程中不可避免的按我的理解做了調(diào)整。
說明:
- 翻譯已征得原作者同意。
translation-permission
鴿了太久了,對(duì)不住原作者
標(biāo)題實(shí)在想不到更好的翻譯了
作者的 github
以下是譯文!
不久前,WinDbg 添加了對(duì)新調(diào)試數(shù)據(jù)模型(debugger data model)的支持,這一變化徹底改變了我們使用 WinDbg 的方式。不再有可怕的 MASM 命令和晦澀的語法。無需再將地址或參數(shù)復(fù)制到記事本以便在后續(xù)命令中使用它們。不用再一遍又一遍地運(yùn)行相同的命令(傳遞不同的地址)來遍歷列表或數(shù)組。
這是該指南的第一部分,因?yàn)槲艺J(rèn)為實(shí)際上不會(huì)有人從頭到尾讀完長(zhǎng)達(dá) 8000 字的 WinDbg 命令解釋。所以,我準(zhǔn)備了 2 篇 ?4000 字的文章!這樣會(huì)好些,對(duì)吧?
在第一篇文章中,我們將學(xué)習(xí)如何使用這個(gè)新數(shù)據(jù)模型的基礎(chǔ)知識(shí) —— 使用自定義寄存器和新的內(nèi)置寄存器,遍歷對(duì)象,使用匿名類型來搜索、過濾、自定義對(duì)象。最后,我們將學(xué)習(xí)如何以一種比以前更好、更簡(jiǎn)單的方式解析數(shù)組和列表。
在這兩篇文章中,我們將學(xué)習(xí)這個(gè)數(shù)據(jù)模型為我們提供的更復(fù)雜、更高級(jí)的方法和特性。現(xiàn)在我們都知道將會(huì)發(fā)生什么,準(zhǔn)備好一杯咖啡,讓我們開始吧!
這個(gè)數(shù)據(jù)模型,可以在 WinDbg 中通過 dx 命令使用,是一個(gè)極其強(qiáng)大的工具,能夠定義自定義變量、結(jié)構(gòu)體、函數(shù)并使用很多其它新功能。它還允許我們使用 LINQ(一種建立在 SQL 數(shù)據(jù)庫語言之上的自然查詢語言)進(jìn)行查詢和過濾信息。
這個(gè)數(shù)據(jù)模型已經(jīng)被文檔化了,甚至在 GitHub 上還有使用范例。此外,所有模塊都有對(duì)應(yīng)的文檔,可以在調(diào)試器中通過 dx -v <method> 查看。(您也可以通過運(yùn)行不帶 -v 的 dx <method> 命令獲得相同的文檔 ) :
dx?-v?Debugger.Utility.Collections.FromListEntry
Debugger.Utility.Collections.FromListEntry?[FromListEntry(ListEntry,?[<ModuleName?|?ModuleObject>],?TypeName,?FieldExpression)?—?Method?which?converts?a?LIST_ENTRY?specified?by?the?‘ListEntry’?parameter?of?types?whose?name?is?specified?by?the?string?‘TypeName’?and?whose?embedded?links?within?that?type?are?accessed?via?an?expression?specified?by?the?string?‘FieldExpression’?into?a?collection?object.?If?an?optional?module?name?or?object?is?specified,?the?type?name?is?looked?up?in?the?context?of?such?module]
除此之外,還有一些外部文檔,但我覺得有些事情需要進(jìn)一步解釋,并且這個(gè)特性值得更多的關(guān)注。
譯注:
debugger data model文檔鏈接:https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/data-model-cpp-overview)使用范例
github鏈接:https://github.com/microsoft/WinDbg-Samples)
自定義寄存器(Custom Registers)
首先,NatVis 允許我們添加自定義寄存器。有點(diǎn)像 MASM 中的 @$t1、@$t2、@$t3 等。只是,現(xiàn)在你可以起任何想要的名字,并且可以指定類型:
dx?@$myString?=?"My?String"
dx?@$myInt?=?123
我們可以使用 dx @$vars 來查看所有變量,使用 dx @$vars.Remove("var name") ?來移除某個(gè)變量,或者使用 @$vars.Clear() 清除所有變量。我們還可以使用 dx 來處理更復(fù)雜的結(jié)構(gòu),比如 EPROCESS。您可能知道,公共調(diào)試符號(hào)(public PDBs)中的符號(hào)不包含類型信息。使用舊調(diào)試器,這并不總是一個(gè)問題,因?yàn)樵?MASM 中,反正也沒有類型,我們可以使用 poi 命令對(duì)指針進(jìn)行解引用。
0: kd> dt nt!_EPROCESS poi(nt!PsInitialSystemProcess)
+0x000 Pcb : _KPROCESS
+0x2e0 ProcessLock : _EX_PUSH_LOCK
+0x2e8 UniqueProcessId : (null)
...
但當(dāng)變量不是指針時(shí),事情就變得更混亂了,比如 PsIdleProcess:
0:?kd>?dt?nt!_KPROCESS?@@masm(nt!PsIdleProcess)
???+0x000?Header???????????:?_DISPATCHER_HEADER
???+0x018?ProfileListHead??:?_LIST_ENTRY?[?0x00000048`0411017e?-?0x00000000`00000004?]
???+0x028?DirectoryTableBase?:?0xffffb10b`79f08010
???+0x030?ThreadListHead???:?_LIST_ENTRY?[?0x00001388`00000000?-?0xfffff801`1b401000?]
???+0x040?ProcessLock??????:?0
???+0x044?ProcessTimerDelay?:?0
???+0x048?DeepFreezeStartTime?:?0xffffe880`00000000
???...
我們必須先使用顯式的 MASM 操作符來獲取 PsIdleProcess 的地址,然后將它當(dāng)作 EPROCESS 顯示出來。通過使用 dx,我們可以更聰明地直接使用 C 風(fēng)格的類型轉(zhuǎn)換對(duì)符號(hào)進(jìn)行強(qiáng)制轉(zhuǎn)換。但是當(dāng)我們嘗試把 nt!Psinitialsystemprocess 轉(zhuǎn)換為一個(gè)指向 EPROCESS 的指針:
dx @$systemProc = (nt!_EPROCESS*)nt!PsInitialSystemProcess
Error: No type (or void) for object at Address 0xfffff8074ef843a0
我們得到了一個(gè)錯(cuò)誤。
就像我提到的那樣,符號(hào)沒有類型。我們不能轉(zhuǎn)換沒有類型的東西。所以我們需要獲取符號(hào)的地址,并轉(zhuǎn)換成一個(gè)指向我們想要的類型的指針(在當(dāng)前示例中,PsInitialSystemProcess 已經(jīng)是一個(gè)指向 EPROCESS 的指針,所以我們需要將其地址轉(zhuǎn)換為一個(gè)指向 EPROCESS 指針的指針)。
dx @$systemProc = *(nt!_EPROCESS**)&nt!PsInitialSystemProcess
現(xiàn)在,我們有了一個(gè)有類型的變量,我們可以像在 C 中那樣訪問它的字段:
0: kd> dx @$systemProc->ImageFileName
@$systemProc->ImageFileName [Type: unsigned char [15]]
[0] : 0x53 [Type: unsigned char]
[1] : 0x79 [Type: unsigned char]
[2] : 0x73 [Type: unsigned char]
[3] : 0x74 [Type: unsigned char]
[4] : 0x65 [Type: unsigned char]
[5] : 0x6d [Type: unsigned char]
[6] : 0x0 [Type: unsigned char]
我們還可以對(duì)它進(jìn)行類型轉(zhuǎn)換以得到更好的輸出:
dx?(char*)@$systemProc->ImageFileName
(char*)@$systemProc->ImageFileName??:
0xffffc10c8e87e710?:?"System"?[Type:?char?*]
我們還可以使用 ToDisplayString 將它從 char* 轉(zhuǎn)換為 string。我們有兩個(gè)選項(xiàng) —— ToDisplayString("s"),將輸出結(jié)果轉(zhuǎn)換為字符串并將引號(hào)作為字符串的一部分,或者ToDisplayString("sb"),將刪除引號(hào):
dx?((char*)@$systemProc->ImageFileName).ToDisplayString("s")
((char*)@$systemProc->ImageFileName).ToDisplayString("s")?:
"System"
????Length???????????:?0x8
dx?((char*)@$systemProc->ImageFileName).ToDisplayString("sb")
((char*)@$systemProc->ImageFileName).ToDisplayString("sb")?:
System
????Length???????????:?0x6
內(nèi)置寄存器(Built-in Registers)
這很有趣,但對(duì)于進(jìn)程(和其他一些東西),還有一種更簡(jiǎn)單的方法。WinDbg 中的 NatVis 為我們提供了一些有用的“免費(fèi)”寄存器 —— curframe, curprocess, cursession, curstack 和 curthread。不難通過它們的名字猜出對(duì)應(yīng)的內(nèi)容,但還是看一看吧:
@$curframe
提供有關(guān)當(dāng)前幀的信息。我自己從來沒用過,但它也許有用:
dx?-r1?@$curframe.Attributes
@$curframe.Attributes????????????????
????InstructionOffset?:?0xfffff8074ebda1e1
????ReturnOffset?????:?0xfffff80752ad2b61
????FrameOffset??????:?0xfffff80751968830
????StackOffset??????:?0xfffff80751968838
????FuncTableEntry???:?0x0
????Virtual??????????:?1
????FrameNumber??????:?0x0
@$curprocess
一個(gè)包含當(dāng)前進(jìn)程信息的容器。但不是 EPROCESS (盡管包含它)。它包含了關(guān)于當(dāng)前進(jìn)程的易于訪問的信息,比如線程、加載的模塊、句柄等。
dx?@$curprocess
@$curprocess?????????????????:?System?[Switch?To]
????KernelObject?????[Type:?_EPROCESS]
????Name?????????????:?System
????Id???????????????:?0x4
????Handle???????????:?0xf0f0f0f0
????Threads?????????
????Modules?????????
????Environment?????
????Devices?????????
????Io
我們不僅可以訪問 EPROCESS(通過 KernelObject ),還可以使用其他字段。例如,我們可以通過 @$curprocess.Io.Handles 訪問進(jìn)程持有的所有句柄。@$curprocess.Io.Handles 會(huì)返回一個(gè)由句柄值索引的句柄數(shù)組:
dx?@$curprocess.Io.Handles
@$curprocess.Io.Handles????????????????
????[0x4]???????????
????[0x8]???????????
????[0xc]???????????
????[0x10]??????????
????[0x14]??????????
????[0x18]??????????
????[0x1c]??????????
????[0x20]??????????
????[0x24]??????????
????[0x28]
????...
System 進(jìn)程有很多句柄,這只是開始的幾個(gè)!讓我們來看看第一個(gè)(我們也可以通過 @$curprocess.Io.Handles[0x4] 來訪問它):
dx?@$curprocess.Io.Handles.First()
@$curprocess.Io.Handles.First()????????????????
????Handle???????????:?0x4
????Type?????????????:?Process
????GrantedAccess????:?Delete?|?ReadControl?|?WriteDac?|?WriteOwner?|?Synch?|?Terminate?|?CreateThread?|?VMOp?|?VMRead?|?VMWrite?|?DupHandle?|?CreateProcess?|?SetQuota?|?SetInfo?|?QueryInfo?|?SetPort
????Object???????????[Type:?_OBJECT_HEADER]
我們可以看到句柄,句柄對(duì)應(yīng)的對(duì)象類型,它被授予的訪問權(quán)限,甚至有一個(gè)指向?qū)ο蟊旧淼闹羔槪ɑ蛘吒鼫?zhǔn)確地說,它指向的是對(duì)象頭)!
這個(gè)寄存器還有很多東西可以探索,我鼓勵(lì)您去調(diào)查它們,但我不會(huì)全部展示。
順便說一下,我是否提過 dx 允許 tab 補(bǔ)全?
@$cursession
顧名思義,這個(gè)寄存器為我們提供關(guān)于當(dāng)前調(diào)試會(huì)話的信息:
dx?@$cursession
@$cursession?????????????????:?Remote?KD:?KdSrv:Server=@{<Local>},Trans=@{NET:Port=55556,Key=1.2.3.4,Target=192.168.251.21}
????Processes???????
????Id???????????????:?0
????Devices?????????
????Attributes
因此,我們可以獲得關(guān)于調(diào)試會(huì)話的信息,這總是很有趣。但是還可以找到更多有用的東西,比如 Processes 字段,它是所有進(jìn)程的數(shù)組,由 PID 索引。讓我們選一個(gè)吧:
dx?@$cursession.Processes[0x1d8]
@$cursession.Processes[0x1d8]?????????????????:?smss.exe?[Switch?To]
????KernelObject?????[Type:?_EPROCESS]
????Name?????????????:?smss.exe
????Id???????????????:?0x1d8
????Handle???????????:?0xf0f0f0f0
????Threads?????????
????Modules?????????
????Environment?????
????Devices?????????
????Io
現(xiàn)在我們可以獲得每個(gè)進(jìn)程的全部有用信息!我們還可以通過過濾來搜索進(jìn)程(例如,通過進(jìn)程名、加載到進(jìn)程中的特定模塊、命令行中的字符串等等)。我稍后再解釋這些。
@$curstack
這個(gè)寄存器只包含一個(gè)字段 —— 幀,它以一種易于處理的方式向我們顯示當(dāng)前調(diào)用棧:
dx?@$curstack.Frames
@$curstack.Frames????????????????
????[0x0]????????:?nt!DbgBreakPointWithStatus?+?0x1?[Switch?To]
????[0x1]????????:?kdnic!TXTransmitQueuedSends?+?0x125?[Switch?To]
????[0x2]????????:?kdnic!TXSendCompleteDpc?+?0x14d?[Switch?To]
????[0x3]????????:?nt!KiProcessExpiredTimerList?+?0x169?[Switch?To]
????[0x4]????????:?nt!KiRetireDpcList?+?0x4e9?[Switch?To]
????[0x5]????????:?nt!KiIdleLoop?+?0x7e?[Switch?To]
@$curthread
為我們提供當(dāng)前線程的相關(guān)信息,就像 @$curprocess:
dx?@$curthread
@$curthread?????????????????:?nt!DbgBreakPointWithStatus+0x1?(fffff807`4ebda1e1)??[Switch?To]
????KernelObject?????[Type:?_ETHREAD]
????Id???????????????:?0x0
????Stack???????????
????Registers???????
????Environment
KernelObject 字段包含了 ETHREAD, ?Environment 字段包含了 TEB,除此之外,還包括線程 ID、調(diào)用棧和寄存器。
dx?@$curthread.Registers
@$curthread.Registers????????????????
????User????????????
????Kernel??????????
????SIMD????????????
????FloatingPoint
寄存器被方便地分為用戶寄存器、內(nèi)核寄存器、SIMD 寄存器和浮點(diǎn)寄存器,我們可以分別查看它們:
dx?-r1?@$curthread.Registers.Kernel
@$curthread.Registers.Kernel
????cr0??????????????:?0x80050033
????cr2??????????????:?0x207b8f7abbe
????cr3??????????????:?0x6d4002
????cr4??????????????:?0x370678
????cr8??????????????:?0xf
????gdtr?????????????:?0xffff9d815ffdbfb0
????gdtl?????????????:?0x57
????idtr?????????????:?0xffff9d815ffd9000
????idtl?????????????:?0xfff
????tr???????????????:?0x40
????ldtr?????????????:?0x0
????kmxcsr???????????:?0x1f80
????kdr0?????????????:?0x0
????kdr1?????????????:?0x0
????kdr2?????????????:?0x0
????kdr3?????????????:?0x0
????kdr6?????????????:?0xfffe0ff0
????kdr7?????????????:?0x400
搜索和過濾(Searching and Filtering)
我們?cè)谇懊婧?jiǎn)要提到過,NatVis 允許我們做一些非常有用的操作 —— 通過類似 SQL 中的 Select、Where、Orderderby 及其它方法對(duì)信息進(jìn)行查找、過濾和排序。
例如,讓我們嘗試找到所有沒啟用 high entropy ASLR 的進(jìn)程。該信息存儲(chǔ)在 EPROCESS->MitigationFlags 字段中,HighEntropyASLREnabled 的值是 0x20 (所有值都可以在這里和公共符號(hào)中找到)。
首先,我們聲明一個(gè)值為 0x20 的新寄存器,這只是為了使代碼更具可讀性:
0:?kd>?dx?@$highEntropyAslr?=?0x20
@$highEntropyAslr?=?0x20?:?32
然后創(chuàng)建查詢來遍歷所有進(jìn)程,只選擇那些沒有設(shè)置 HighEntropyASLREnabled 標(biāo)志位的進(jìn)程:
dx?-r1?@$cursession.Processes.Where(p?=>?(p.KernelObject.MitigationFlags?&?@$highEntropyAslr)?==?0)
@$cursession.Processes.Where(p?=>?(p.KernelObject.MitigationFlags?&?@$highEntropyAslr)?==?0)????????????????
????[0x910]??????????:?spoolsv.exe?[Switch?To]
????[0xb40]??????????:?IpOverUsbSvc.exe?[Switch?To]
????[0x1610]?????????:?explorer.exe?[Switch?To]
????[0x1d8c]?????????:?OneDrive.exe?[Switch?To]
或者我們可以直接檢查 MitigationFlagsValues 并獲得相同的結(jié)果:
dx?-r1?@$cursession.Processes.Where(p?=>?(p.KernelObject.MitigationFlagsValues.HighEntropyASLREnabled?==?0))
@$cursession.Processes.Where(p?=>?(p.KernelObject.MitigationFlagsValues.HighEntropyASLREnabled?==?0))
????[0x910]??????????:?spoolsv.exe?[Switch?To]
????[0xb40]??????????:?IpOverUsbSvc.exe?[Switch?To]
????[0x1610]?????????:?explorer.exe?[Switch?To]
????[0x1d8c]?????????:?OneDrive.exe?[Switch?To]
我們還可以使用 Select() 來顯示正在遍歷的對(duì)象的某些特定屬性。這里我們只查看每個(gè)進(jìn)程的線程數(shù):
dx?@$cursession.Processes.Select(p?=>?p.Threads.Count())
@$cursession.Processes.Select(p?=>?p.Threads.Count())????????????????
????[0x0]????????????:?0x6
????[0x4]????????????:?0xeb
????[0x78]???????????:?0x4
????[0x1d8]??????????:?0x5
????[0x244]??????????:?0xe
????[0x294]??????????:?0x8
????[0x2a0]??????????:?0x10
????[0x2f8]??????????:?0x9
????[0x328]??????????:?0xa
????[0x33c]??????????:?0xd
????[0x3a8]??????????:?0x2c
????[0x3c0]??????????:?0x8
????[0x3c8]??????????:?0x8
????[0x204]??????????:?0x15
????[0x300]??????????:?0x1d
????[0x444]??????????:?0x3f
????...
我們還可以通過在命令末尾加上 ,d 來以十進(jìn)制格式顯示所有內(nèi)容。(b 表示以二進(jìn)制顯示,o 表示以八進(jìn)制顯示,s 表示以字符串顯示):
dx?@$cursession.Processes.Select(p?=>?p.Threads.Count()),?d@$cursession.Processes.Select(p?=>?p.Threads.Count()),?d????????????????
????[0]??????????????:?6
????[4]??????????????:?235
????[120]????????????:?4
????[472]????????????:?5
????[580]????????????:?14
????[660]????????????:?8
????[672]????????????:?16
????[760]????????????:?9
????[808]????????????:?10
????[828]????????????:?13
????[936]????????????:?44
????[960]????????????:?8
????[968]????????????:?8
????[516]????????????:?21
????[768]????????????:?29
????[1092]???????????:?63
????...
再看一個(gè)稍微復(fù)雜一點(diǎn)的示例 —— 查看某個(gè)特定進(jìn)程中每個(gè)線程的理想處理器(我隨機(jī)選擇了一個(gè)進(jìn)程,只是為了查看非 System 進(jìn)程的一些信息):
dx?-r1?@$cursession.Processes[0x1b2c].Threads.Select(t?=>?t.Environment.EnvironmentBlock.CurrentIdealProcessor.Number)
@$cursession.Processes[0x1b2c].Threads.Select(t?=>?t.Environment.EnvironmentBlock.CurrentIdealProcessor.Number)????????????????
????[0x1b30]?????????:?0x1?[Type:?unsigned?char]
????[0x1b40]?????????:?0x2?[Type:?unsigned?char]
????[0x1b4c]?????????:?0x3?[Type:?unsigned?char]
????[0x1b50]?????????:?0x4?[Type:?unsigned?char]
????[0x1b48]?????????:?0x5?[Type:?unsigned?char]
????[0x1b5c]?????????:?0x0?[Type:?unsigned?char]
????[0x1b64]?????????:?0x1?[Type:?unsigned?char]
我們還可以使用 OrderBy 來獲得更好的輸出,例如獲取按字母順序排序的進(jìn)程列表:
dx?-r1?@$cursession.Processes.OrderBy(p?=>?p.Name)
@$cursession.Processes.OrderBy(p?=>?p.Name)????????????????
????[0x1848]?????????:?ApplicationFrameHost.exe?[Switch?To]
????[0x0]????????????:?Idle?[Switch?To]
????[0xb40]??????????:?IpOverUsbSvc.exe?[Switch?To]
????[0x106c]?????????:?LogonUI.exe?[Switch?To]
????[0x754]??????????:?MemCompression?[Switch?To]
????[0x187c]?????????:?MicrosoftEdge.exe?[Switch?To]
????[0x1b94]?????????:?MicrosoftEdgeCP.exe?[Switch?To]
????[0x1b7c]?????????:?MicrosoftEdgeSH.exe?[Switch?To]
????[0xb98]??????????:?MsMpEng.exe?[Switch?To]
????[0x1158]?????????:?NisSrv.exe?[Switch?To]
????[0x1d8c]?????????:?OneDrive.exe?[Switch?To]
????[0x78]???????????:?Registry?[Switch?To]
????[0x1ed0]?????????:?RuntimeBroker.exe?[Switch?To]
????...
如果我們希望按降序排列,可以使用 OrderByDescending。
但是,如果我們想要查看多個(gè)屬性該怎么辦呢?有辦法。
匿名類型(Anonymous Types)我們可以聲明一個(gè)自定義的類型,它將是未命名的并且只在查詢作用域中有效,語法如下:Select(x => new {var1 = x.A, var2 = x.B,…})。
我們把它用在前面的示例中。假設(shè)我們想要顯示每個(gè)進(jìn)程的進(jìn)程名和線程數(shù):
dx?@$cursession.Processes.Select(p?=>?new?{Name?=?p.Name,?ThreadCount?=?p.Threads.Count()})
@$cursession.Processes.Select(p?=>?new?{Name?=?p.Name,?ThreadCount?=?p.Threads.Count()})????????????????
????[0x0]???????????
????[0x4]???????????
????[0x78]??????????
????[0x1d8]?????????
????[0x244]?????????
????[0x294]?????????
????[0x2a0]?????????
????[0x2f8]?????????
????[0x328]
????...
但是現(xiàn)在我們只看到進(jìn)程容器,而不是實(shí)際的信息。為了查看信息本身,我們需要使用 -r2 來多顯示一層。r 后面的數(shù)字表示遞歸層數(shù)。默認(rèn)值是 -r1, -r0 將不顯示輸出,-r2 將顯示兩層,以此類推。
dx?-r2?@$cursession.Processes.Select(p?=>?new?{Name?=?p.Name,?ThreadCount?=?p.Threads.Count()})
@$cursession.Processes.Select(p?=>?new?{Name?=?p.Name,?ThreadCount?=?p.Threads.Count()})????????????????
????[0x0]???????????
????????Name?????????????:?Idle
????????ThreadCount??????:?0x6
????[0x4]???????????
????????Name?????????????:?System
????????ThreadCount??????:?0xeb
????[0x78]??????????
????????Name?????????????:?Registry
????????ThreadCount??????:?0x4
????[0x1d8]?????????
????????Name?????????????:?smss.exe
????????ThreadCount??????:?0x5
????[0x244]?????????
????????Name?????????????:?csrss.exe
????????ThreadCount??????:?0xe
????[0x294]?????????
????????Name?????????????:?wininit.exe
????????ThreadCount??????:?0x8
????[0x2a0]?????????
????????Name?????????????:?csrss.exe
????????ThreadCount??????:?0x10
????...
這看起來已經(jīng)好多了,但是我們可以使用新的表格視圖讓它看起來更好(通過 -g 選項(xiàng)):
dx?-g?@$cursession.Processes.Select(p?=>?new?{Name?=?p.Name,?ThreadCount?=?p.Threads.Count()})
output-of-dx-g-cursession.Processes.Select這看起來太棒了。是的,這些標(biāo)題是可點(diǎn)擊的,點(diǎn)擊標(biāo)題會(huì)將表格排序!
如果我們想看到以十進(jìn)制顯示的 PID 和線程數(shù),我們可以在命令的末尾加上 ,d:
dx?-g?@$cursession.Processes.Select(p?=>?new?{Name?=?p.Name,?ThreadCount?=?p.Threads.Count()}),d
數(shù)組和鏈表(Arrays and Lists)
dx 為我們提供了一種新的、更簡(jiǎn)單的方法來處理數(shù)組和列表。
讓我們先來看看數(shù)組,語法是 dx *(TYPE(*)[Size])<pointer to array start>。
作為示例,我們將轉(zhuǎn)儲(chǔ) PsInvertedFunctionTable 中的內(nèi)容,它的 TableEntry 字段包含了最多容納 256 個(gè)緩存模塊的數(shù)組。
首先,我們將獲得該符號(hào)的指針,并將其轉(zhuǎn)換為 _INVERTED_FUNCTION_TABLE:
dx?@$inverted?=?(nt!_INVERTED_FUNCTION_TABLE*)&nt!PsInvertedFunctionTable
@$inverted?=?(nt!_INVERTED_FUNCTION_TABLE*)&nt!PsInvertedFunctionTable?????????????????:?0xfffff8074ef9b010?[Type:?_INVERTED_FUNCTION_TABLE?*]
????[+0x000]?CurrentSize??????:?0xbe?[Type:?unsigned?long]
????[+0x004]?MaximumSize??????:?0x100?[Type:?unsigned?long]
????[+0x008]?Epoch????????????:?0x19e?[Type:?unsigned?long]
????[+0x00c]?Overflow?????????:?0x0?[Type:?unsigned?char]
????[+0x010]?TableEntry???????[Type:?_INVERTED_FUNCTION_TABLE_ENTRY?[256]]
現(xiàn)在我們可以創(chuàng)建數(shù)組了。不幸的是,數(shù)組的大小必須是靜態(tài)的,不能使用變量,所以我們需要根據(jù) CurrentSize 手動(dòng)輸入(或者將它設(shè)置為 256,這是整個(gè)數(shù)組的大?。?。我們可以用表格視圖很好地顯示它:
dx?-g?@$tableEntry?=?*(nt!_INVERTED_FUNCTION_TABLE_ENTRY(*)[0xbe])@$inverted->TableEntry
output-of-dx-g-tableEntry-_INVERTED_FUNCTION_TABLE_ENTRY或者,我們可以使用 Take() 方法來獲得相同的結(jié)果,該方法接收一個(gè)數(shù)字并顯示指定數(shù)量的集合元素:
dx?-g?@$inverted->TableEntry->Take(@$inverted->CurrentSize)
我們也可以用同樣的方法來查看 UserInvertedFunctionTable (在我們切換到非 System 進(jìn)程的用戶態(tài)空間后),從 nt!KeUserInvertedFunctionTable 開始:
dx?@$inverted?=?*(nt!_INVERTED_FUNCTION_TABLE**)&nt!KeUserInvertedFunctionTable
@$inverted?=?*(nt!_INVERTED_FUNCTION_TABLE**)&nt!KeUserInvertedFunctionTable?????????????????:?0x7ffa19e3a4d0?[Type:?_INVERTED_FUNCTION_TABLE?*]
????[+0x000]?CurrentSize??????:?0x2?[Type:?unsigned?long]
????[+0x004]?MaximumSize??????:?0x200?[Type:?unsigned?long]
????[+0x008]?Epoch????????????:?0x6?[Type:?unsigned?long]
????[+0x00c]?Overflow?????????:?0x0?[Type:?unsigned?char]
????[+0x010]?TableEntry???????[Type:?_INVERTED_FUNCTION_TABLE_ENTRY?[256]]
????
dx?-g?@$inverted->TableEntry->Take(@$inverted->CurrentSize)
dx-inverted-_INVERTED_FUNCTION_TABLE當(dāng)然,我們可以使用 Select()、Where() 或者其他函數(shù)來過濾、排序或者選擇特定的字段進(jìn)行輸出,以得到完全滿足我們需求的結(jié)果。
接下來要處理的是列表 —— Windows 中到處都是鏈表,你可以在任何地方找到它們。進(jìn)程、線程、模塊、DPCs、IRPs、還有更多。
幸運(yùn)的是,新數(shù)據(jù)模型提供了一個(gè)非常有用的方法—— Debugger.Utiilty.Collections.FromListEntry,它接收一個(gè)鏈表頭,鏈表中的對(duì)象類型和類型中包含 LIST_ENTRY 的字段名稱,并返回一個(gè)包含所有列表內(nèi)容的容器。
讓我們以轉(zhuǎn)儲(chǔ)系統(tǒng)中所有的句柄表為例。我們的起點(diǎn)將是符號(hào) nt!HandleTableListHead,鏈表中的對(duì)象類型是 nt!HANDLE_TABLE ,連接列表的字段是 HandleTableList:
dx?-r2?Debugger.Utility.Collections.FromListEntry(*(nt!_LIST_ENTRY*)&nt!HandleTableListHead,?"nt!_HANDLE_TABLE",?"HandleTableList")
Debugger.Utility.Collections.FromListEntry(*(nt!_LIST_ENTRY*)&nt!HandleTableListHead,?"nt!_HANDLE_TABLE",?"HandleTableList")????????????????
????[0x0]????????????[Type:?_HANDLE_TABLE]
????????[+0x000]?NextHandleNeedingPool?:?0x3400?[Type:?unsigned?long]
????????[+0x004]?ExtraInfoPages???:?0?[Type:?long]
????????[+0x008]?TableCode????????:?0xffff8d8dcfd18001?[Type:?unsigned?__int64]
????????[+0x010]?QuotaProcess?????:?0x0?[Type:?_EPROCESS?*]
????????[+0x018]?HandleTableList??[Type:?_LIST_ENTRY]
????????[+0x028]?UniqueProcessId??:?0x4?[Type:?unsigned?long]
????????[+0x02c]?Flags????????????:?0x0?[Type:?unsigned?long]
????????[+0x02c?(?0:?0)]?StrictFIFO???????:?0x0?[Type:?unsigned?char]
????????[+0x02c?(?1:?1)]?EnableHandleExceptions?:?0x0?[Type:?unsigned?char]
????????[+0x02c?(?2:?2)]?Rundown??????????:?0x0?[Type:?unsigned?char]
????????[+0x02c?(?3:?3)]?Duplicated???????:?0x0?[Type:?unsigned?char]
????????[+0x02c?(?4:?4)]?RaiseUMExceptionOnInvalidHandleClose?:?0x0?[Type:?unsigned?char]
????????[+0x030]?HandleContentionEvent?[Type:?_EX_PUSH_LOCK]
????????[+0x038]?HandleTableLock??[Type:?_EX_PUSH_LOCK]
????????[+0x040]?FreeLists????????[Type:?_HANDLE_TABLE_FREE_LIST?[1]]
????????[+0x040]?ActualEntry??????[Type:?unsigned?char?[32]]
????????[+0x060]?DebugInfo????????:?0x0?[Type:?_HANDLE_TRACE_DEBUG_INFO?*]
????[0x1]????????????[Type:?_HANDLE_TABLE]
????????[+0x000]?NextHandleNeedingPool?:?0x400?[Type:?unsigned?long]
????????[+0x004]?ExtraInfoPages???:?0?[Type:?long]
????????[+0x008]?TableCode????????:?0xffff8d8dcb651000?[Type:?unsigned?__int64]
????????[+0x010]?QuotaProcess?????:?0xffffb90a530e4080?[Type:?_EPROCESS?*]
????????[+0x018]?HandleTableList??[Type:?_LIST_ENTRY]
????????[+0x028]?UniqueProcessId??:?0x78?[Type:?unsigned?long]
????????[+0x02c]?Flags????????????:?0x10?[Type:?unsigned?long]
????????[+0x02c?(?0:?0)]?StrictFIFO???????:?0x0?[Type:?unsigned?char]
????????[+0x02c?(?1:?1)]?EnableHandleExceptions?:?0x0?[Type:?unsigned?char]
????????[+0x02c?(?2:?2)]?Rundown??????????:?0x0?[Type:?unsigned?char]
????????[+0x02c?(?3:?3)]?Duplicated???????:?0x0?[Type:?unsigned?char]
????????[+0x02c?(?4:?4)]?RaiseUMExceptionOnInvalidHandleClose?:?0x1?[Type:?unsigned?char]
????????[+0x030]?HandleContentionEvent?[Type:?_EX_PUSH_LOCK]
????????[+0x038]?HandleTableLock??[Type:?_EX_PUSH_LOCK]
????????[+0x040]?FreeLists????????[Type:?_HANDLE_TABLE_FREE_LIST?[1]]
????????[+0x040]?ActualEntry??????[Type:?unsigned?char?[32]]
????????[+0x060]?DebugInfo????????:?0x0?[Type:?_HANDLE_TRACE_DEBUG_INFO?*]
????...
看到 QuotaProcess 字段了嗎?該字段指向當(dāng)前句柄表所屬的進(jìn)程。由于每個(gè)進(jìn)程都有一個(gè)句柄表,這允許我們以一種不為人所知的方式枚舉系統(tǒng)中的所有進(jìn)程。這種方法在過去被 rootkit 用于枚舉進(jìn)程,而不被 EDR 產(chǎn)品檢測(cè)到。因此,要實(shí)現(xiàn)這一點(diǎn),我們只需要從句柄表列表中的每個(gè)條目中選擇 QuotaProcess (通過 Select() 方法)。為了讓輸出結(jié)果更易讀,我們還可以創(chuàng)建一個(gè)包含進(jìn)程名、PID 和 EPROCESS 指針的匿名容器:
dx?-r2?(Debugger.Utility.Collections.FromListEntry(*(nt!_LIST_ENTRY*)&nt!HandleTableListHead,?"nt!_HANDLE_TABLE",?"HandleTableList")).Select(h?=>?new?{?Object?=?h.QuotaProcess,?Name?=?((char*)h.QuotaProcess->ImageFileName).ToDisplayString("s"),?PID?=?(__int64)h.QuotaProcess->UniqueProcessId})
(Debugger.Utility.Collections.FromListEntry(*(nt!_LIST_ENTRY*)&nt!HandleTableListHead,?"nt!_HANDLE_TABLE",?"HandleTableList")).Select(h?=>?new?{?Object?=?h.QuotaProcess,?Name?=?((char*)h.QuotaProcess->ImageFileName).ToDisplayString("s"),?PID?=?(__int64)h.QuotaProcess->UniqueProcessId})
[0x0]????????????:?Unspecified?error?(0x80004005)
[0x1]
????Object???????????:?0xffffb10b70906080?[Type:?_EPROCESS?*]
????Name?????????????:?"Registry"
????PID??????????????:?120?[Type:?__int64]
[0x2]
????Object???????????:?0xffffb10b72eba0c0?[Type:?_EPROCESS?*]
????Name?????????????:?"smss.exe"
????PID??????????????:?584?[Type:?__int64]
[0x3]
????Object???????????:?0xffffb10b76586140?[Type:?_EPROCESS?*]
????Name?????????????:?"csrss.exe"
????PID??????????????:?696?[Type:?__int64]
[0x4]
????Object???????????:?0xffffb10b77132140?[Type:?_EPROCESS?*]
????Name?????????????:?"wininit.exe"
????PID??????????????:?772?[Type:?__int64]
[0x5]
????Object???????????:?0xffffb10b770a2080?[Type:?_EPROCESS?*]
????Name?????????????:?"csrss.exe"
????PID??????????????:?780?[Type:?__int64]
[0x6]
????Object???????????:?0xffffb10b7716d080?[Type:?_EPROCESS?*]
????Name?????????????:?"winlogon.exe"
????PID??????????????:?852?[Type:?__int64]
...
第一條結(jié)果屬于 System 進(jìn)程,它沒有 QuotaProcess,這就是查詢返回錯(cuò)誤的原因。但對(duì)于數(shù)組中的其他元素,應(yīng)該是完美的。如果我們想讓輸出結(jié)果更漂亮,可以在執(zhí)行 Select() 之前過濾掉 QuotaProcess == 0 的條目:
dx?-r2?(Debugger.Utility.Collections.FromListEntry(*(nt!_LIST_ENTRY*)&nt!HandleTableListHead,?“nt!_HANDLE_TABLE”,?“HandleTableList”)).Where(h?=>?h.QuotaProcess?!=?0).Select(h?=>?new?{?Object?=?h.QuotaProcess,?Name?=?((char*)h.QuotaProcess->ImageFileName).ToDisplayString("s"),?PID?=?h.QuotaProcess->UniqueProcessId})
如前所示,我們也可以在表格視圖中顯示這個(gè)列表,或者使用任何 LINQ 查詢來使輸出滿足我們的需要。
第一部分到此結(jié)束,但不要擔(dān)心,第二部分就在這里。它包含了全部新奇的 dx 方法,比如一個(gè)新的反匯編器,自定義方法,可以真正工作的條件斷點(diǎn),以及更多。

translation-permission