难译 | 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的過(guò)程中不可避免的按我的理解做了調(diào)整。
第一部分譯文在這里。
以下是譯文!
歡迎來(lái)到我試圖讓您享受在 Windows 上調(diào)試的第 2 部分(哇,我真是個(gè)書(shū)呆子)!
在第一部分中,我們已經(jīng)了解了新調(diào)試數(shù)據(jù)模型的基本知識(shí)——使用新對(duì)象、使用自定義寄存器、搜索和過(guò)濾輸出、聲明匿名類型以及解析列表和數(shù)組。在這一部分中,我們將學(xué)習(xí)如何在 dx 中使用傳統(tǒng)命令(譯注: 原文是 legacy commands,我理解是在引入 dx 之前就存在的一些命令,比如 dt、u 等),了解令人驚嘆的新反匯編器,創(chuàng)建合成方法和類型,查看斷點(diǎn)的奇妙變化,并在調(diào)試器中使用文件系統(tǒng)。
聽(tīng)起來(lái)有很多內(nèi)容。因?yàn)榇_實(shí)有很多內(nèi)容。我們開(kāi)始吧!
傳統(tǒng)命令(Legacy Commands)
新數(shù)據(jù)模型完全改變了調(diào)試體驗(yàn)。但有時(shí)確實(shí)需要使用我們已經(jīng)習(xí)慣的舊命令或擴(kuò)展(這些功能在 dx 中沒(méi)有對(duì)應(yīng)的實(shí)現(xiàn))。
我們?nèi)匀豢梢酝ㄟ^(guò) Debugger.Utility.Control.ExecuteCommand 在 dx 中使用這些舊命令,它讓我們可以將傳統(tǒng)命令作為 dx 查詢的一部分運(yùn)行。例如,我們可以使用傳統(tǒng)的 u 命令查看第二個(gè)棧幀中 RIP 所指向的地址對(duì)應(yīng)的反匯編。
由于 dx 輸出默認(rèn)是十進(jìn)制的,而傳統(tǒng)命令只接受十六進(jìn)制輸入,我們首先需要使用 ToDisplayString("x") 將其轉(zhuǎn)換為十六進(jìn)制:
dx?Debugger.Utility.Control.ExecuteCommand("u?"?+?@$curstack.Frames[1].Attributes.InstructionOffset.ToDisplayString("x"))Debugger.Utility.Control.ExecuteCommand("u?"?+?@$curstack.Frames[1].Attributes.InstructionOffset.ToDisplayString("x"))????????????????[0x0]?????:?kdnic!TXTransmitQueuedSends+0x125:[0x1]?????:?fffff807`52ad2b61?4883c430????????add?????rsp,30h[0x2]?????:?fffff807`52ad2b65?5b??????????????pop?????rbx[0x3]?????:?fffff807`52ad2b66?c3??????????????ret[0x4]?????:?fffff807`52ad2b67?4c8d4370????????lea?????r8,[rbx+70h][0x5]?????:?fffff807`52ad2b6b?488bd7??????????mov?????rdx,rdi[0x6]?????:?fffff807`52ad2b6e?488d4b60????????lea?????rcx,[rbx+60h][0x7]?????:?fffff807`52ad2b72?4c8b15d7350000??mov?????r10,qword?ptr?[kdnic!_imp_ExInterlockedInsertTailList?(fffff807`52ad6150)][0x8]?????:?fffff807`52ad2b79?e8123af8fb??????call????nt!ExInterlockedInsertTailList?(fffff807`4ea56590)另一個(gè)有用的傳統(tǒng)命令是 !irp。這個(gè)命令為我們提供了大量關(guān)于 IRP 的信息,所以沒(méi)必要大費(fèi)周章的使用 dx 重新實(shí)現(xiàn)它。
我們將嘗試對(duì) lsass.exe 進(jìn)程中所有的 IRP 執(zhí)行 !irp 命令。讓我們看看整個(gè)過(guò)程:
首先,我們需要找到 lsass.exe 進(jìn)程的容器。我們已經(jīng)知道如何使用 Where() 來(lái)做到這一點(diǎn)。我們選擇返回的第一個(gè)進(jìn)程。通常應(yīng)該只有一個(gè) lsass,除非機(jī)器上有服務(wù)隔離。
譯注: 服務(wù)隔離對(duì)應(yīng)的原文是 server silos,在網(wǎng)上找到一個(gè)解釋 :A silo in IT is an isolated point in a system where data is kept and segregated from other parts of the architecture.
dx?@$lsass?=?@$cursession.Processes.Where(p?=>?p.Name?==?"lsass.exe").First()然后,我們需要遍歷進(jìn)程中每個(gè)線程的 IrpList 并獲得 IRP 本身。我們可以很容易地通過(guò)前面提到的 FromListEntry() 做到這一點(diǎn)。我們只選取包含 IRP 的線程:
dx?-r4?@$irpThreads?=?@$lsass.Threads.Select(t?=>?new?{irp?=?Debugger.Utility.Collections.FromListEntry(t.KernelObject.IrpList,?"nt!_IRP",?"ThreadListEntry")}).Where(t?=>?t.irp.Count()?!=?0)@$irpThreads?=?@$lsass.Threads.Select(t?=>?new?{irp?=? Debugger.Utility.Collections.FromListEntry(t.KernelObject.IrpList,?"nt!_IRP",?"ThreadListEntry")}).Where(t?=>?t.irp.Count()?!=?0)????????????????[0x384]?????????irp?????????????[0x0]??[Type:?_IRP][<Raw?View>]??[Type:?_IRP]IoStack???????:?Size?=?12,?Current?IRP_MJ_DIRECTORY_CONTROL?/?0x2?for?Device?for?"\FileSystem\Ntfs"CurrentThread?:?0xffffb90a59477080?[Type:?_ETHREAD?*][0x1]??[Type:?_IRP][<Raw?View>]??[Type:?_IRP]IoStack???????:?Size?=?12,?Current?IRP_MJ_DIRECTORY_CONTROL?/?0x2?for?Device?for?"\FileSystem\Ntfs"CurrentThread?:?0xffffb90a59477080?[Type:?_ETHREAD?*]我們可以在這里停一下,點(diǎn)擊其中一個(gè) IRP 的 IoStack (或者運(yùn)行 -r5 來(lái)查看全部?jī)?nèi)容)可以得到調(diào)用棧信息:
dx?@$irpThreads.First().irp[0].IoStack@$irpThreads.First().irp[0].IoStack??:?Size?=?12,?Current?IRP_MJ_DIRECTORY_CONTROL?/?0x2?for?Device?for?"\FileSystem\Ntfs"[0]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][1]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][2]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][3]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][4]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][5]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][6]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][7]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][8]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][9]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][10]?????????????:?IRP_MJ_DIRECTORY_CONTROL?/?0x2?for?Device?for?"\FileSystem\Ntfs"?[Type:?_IO_STACK_LOCATION][11]?????????????:?IRP_MJ_DIRECTORY_CONTROL?/?0x2?for?Device?for?"\FileSystem\FltMgr"?[Type:?_IO_STACK_LOCATION]最后,我們將遍歷每個(gè)線程,并且遍歷線程中的每個(gè) IRP,并對(duì)其執(zhí)行 Executeccommand !IRP <irp address>。這里我們也需要類型轉(zhuǎn)換和 ToDisplayString("x") 來(lái)匹配傳統(tǒng)命令所期望的格式。( !irp 的輸出很長(zhǎng),所以我們將其截?cái)?#xff0c;只關(guān)注感興趣的數(shù)據(jù)):
dx?-r3?@$irpThreads.Select(t?=>?t.irp.Select(i?=>?Debugger.Utility.Control.ExecuteCommand("!irp?"?+?((__int64)&i).ToDisplayString("x"))))@$irpThreads.Select(t?=>?t.irp.Select(i?=>?Debugger.Utility.Control.ExecuteCommand("!irp?"?+?((__int64)&i).ToDisplayString("x"))))????????????????[0x384]?????????[0x0]???????????[0x0]????????????:?Irp?is?active?with?12?stacks?11?is?current?(=?0xffffb90a5b8f4d40)[0x1]????????????:??No?Mdl:?No?System?Buffer:?Thread?ffffb90a59477080:??Irp?stack?trace.??[0x2]????????????:??????cmd??flg?cl?Device???File?????Completion-Context[0x3]????????????:??[N/A(0),?N/A(0)]...[0x34]???????????:?Irp?Extension?present?at?0xffffb90a5b8f4dd0:[0x1]???????????[0x0]????????????:?Irp?is?active?with?12?stacks?11?is?current?(=?0xffffb90a5bd24840)[0x1]????????????:??No?Mdl:?No?System?Buffer:?Thread?ffffb90a59477080:??Irp?stack?trace.??[0x2]????????????:??????cmd??flg?cl?Device???File?????Completion-Context[0x3]????????????:??[N/A(0),?N/A(0)]...[0x34]???????????:?Irp?Extension?present?at?0xffffb90a5bd248d0:!irp 提供給我們的大部分信息都可以通過(guò)使用 dx 解析 IRP 并轉(zhuǎn)儲(chǔ)其 IoStack 獲得。但是有一些信息如果不使用傳統(tǒng)命令則很難獲得。比如, IrpExtension 是否存在及其地址,以及可能與 IRP 關(guān)聯(lián)的 Mdl 的信息。
反匯編器(Disassembler)
我們以 u 命令為例,下面的例子展示的是:在 dx 中通過(guò) Debugger.Utility.Code.CreateDisassember 和 DisassembleBlock 創(chuàng)建可以遍歷和搜索的反匯編代碼:
dx?-r3?Debugger.Utility.Code.CreateDisassembler().DisassembleBlocks(@$curstack.Frames[1].Attributes.InstructionOffset)Debugger.Utility.Code.CreateDisassembler().DisassembleBlocks(@$curstack.Frames[1].Attributes.InstructionOffset)????????????????[0xfffff80752ad2b61]?:?Basic?Block?[0xfffff80752ad2b61?-?0xfffff80752ad2b67)StartAddress?????:?0xfffff80752ad2b61EndAddress???????:?0xfffff80752ad2b67Instructions????[0xfffff80752ad2b61]?:?add?????????rsp,30h[0xfffff80752ad2b65]?:?pop?????????rbx[0xfffff80752ad2b66]?:?ret[0xfffff80752ad2b67]?:?Basic?Block?[0xfffff80752ad2b67?-?0xfffff80752ad2b7e)StartAddress?????:?0xfffff80752ad2b67EndAddress???????:?0xfffff80752ad2b7eInstructions????[0xfffff80752ad2b67]?:?lea?????????r8,[rbx+70h][0xfffff80752ad2b6b]?:?mov?????????rdx,rdi[0xfffff80752ad2b6e]?:?lea?????????rcx,[rbx+60h][0xfffff80752ad2b72]?:?mov?????????r10,qword?ptr?[kdnic!__imp_ExInterlockedInsertTailList?(fffff80752ad6150)][0xfffff80752ad2b79]?:?call????????ntkrnlmp!ExInterlockedInsertTailList?(fffff8074ea56590)[0xfffff80752ad2b7e]?:?Basic?Block?[0xfffff80752ad2b7e?-?0xfffff80752ad2b80)StartAddress?????:?0xfffff80752ad2b7eEndAddress???????:?0xfffff80752ad2b80Instructions????[0xfffff80752ad2b7e]?:?jmp?????????kdnic!TXTransmitQueuedSends+0xd0?(fffff80752ad2b0c)[0xfffff80752ad2b80]?:?Basic?Block?[0xfffff80752ad2b80?-?0xfffff80752ad2b81)StartAddress?????:?0xfffff80752ad2b80EndAddress???????:?0xfffff80752ad2b81Instructions...只選擇 Instructions 并將其扁平化顯示,整理后的版本如下:
dx?-r2?Debugger.Utility.Code.CreateDisassembler().DisassembleBlocks(@$curstack.Frames[1].Attributes.InstructionOffset).Select(b?=>?b.Instructions).Flatten()Debugger.Utility.Code.CreateDisassembler().DisassembleBlocks(@$curstack.Frames[1].Attributes.InstructionOffset).Select(b?=>?b.Instructions).Flatten()????????????????[0x0]???????????[0xfffff80752ad2b61]?:?add?????????rsp,30h[0xfffff80752ad2b65]?:?pop?????????rbx[0xfffff80752ad2b66]?:?ret[0x1]???????????[0xfffff80752ad2b67]?:?lea?????????r8,[rbx+70h][0xfffff80752ad2b6b]?:?mov?????????rdx,rdi[0xfffff80752ad2b6e]?:?lea?????????rcx,[rbx+60h][0xfffff80752ad2b72]?:?mov?????????r10,qword?ptr?[kdnic!__imp_ExInterlockedInsertTailList?(fffff80752ad6150)][0xfffff80752ad2b79]?:?call????????ntkrnlmp!ExInterlockedInsertTailList?(fffff8074ea56590)[0x2]???????????[0xfffff80752ad2b7e]?:?jmp?????????kdnic!TXTransmitQueuedSends+0xd0?(fffff80752ad2b0c)[0x3]???????????[0xfffff80752ad2b80]?:?int?????????3[0x4]???????????[0xfffff80752ad2b81]?:?int?????????3...合成方法(Synthetic Methods)
新調(diào)試數(shù)據(jù)模型提供的另一個(gè)功能是允許我們創(chuàng)建并使用自定義函數(shù)。語(yǔ)法如下:
0:?kd>?dx?@$multiplyByThree?=?(x?=>?x?*?3)@$multiplyByThree?=?(x?=>?x?*?3)0:?kd>?dx?@$multiplyByThree(5)@$multiplyByThree(5)?:?15我們也可以創(chuàng)建包含多個(gè)參數(shù)的函數(shù):
0:?kd>?dx?@$add?=?((x,?y)?=>?x?+?y)@$add?=?((x,?y)?=>?x?+?y)0:?kd>?dx?@$add(5,?7)@$add(5,?7)??????:?12或者,如果我們真的想要玩的更高端一些,我們可以將這些函數(shù)應(yīng)用到前面的反匯編輸出中,以便找出 ZwSetInformationProcess 函數(shù)中所有的內(nèi)存寫入指令。為此,我們需要對(duì)每條指令進(jìn)行一些檢查,以便知道它是否是內(nèi)存寫入指令:
指令至少有兩個(gè)操作數(shù)嗎?
例如,ret 指令的操作數(shù)是 0,而 jmp <address> 指令的操作數(shù)是 1。我們只關(guān)心將一個(gè)值寫入某個(gè)位置的情況,這總是需要兩個(gè)操作數(shù)。為了驗(yàn)證這一點(diǎn),我們需要檢查每條指令的 Operands.Count() > 1。
是否是內(nèi)存引用操作? 我們只對(duì)內(nèi)存寫入操作感興趣,并希望忽略像 mon r10, rcx 這樣的指令。為此,我們將檢查每條指令的 Operands[0].Attributes.IsMemoryReference == true。我們檢查 Operands[0] ,因?yàn)樗悄康牟僮鲾?shù)。如果我們想查找內(nèi)存讀取指令,我們應(yīng)該檢查源操作數(shù),它在 Operands[1] 中。
目的操作數(shù)是輸出嗎?
我們想過(guò)濾掉引用內(nèi)存但沒(méi)有寫入內(nèi)存的指令。我們將使用 Operands[0].IsOutput == true 進(jìn)行檢查。
作為最后一個(gè)過(guò)濾器,我們希望忽略棧內(nèi)存寫入操作,它看起來(lái)像 mov [rsp+0x18],1 或 mov [rbp-0x10],rdx。
我們將檢查第一個(gè)操作數(shù)的寄存器,并確保其索引不是 rsp 索引(0x14)或 rbp 索引(0x15)。
我們將編寫一個(gè)函數(shù) @$isMemWrite,它接收一個(gè)代碼塊并且只返回內(nèi)存寫入指令(基于上面的檢查)。然后我們可以創(chuàng)建一個(gè)反匯編器,反匯編我們的目標(biāo)函數(shù)并且只輸出其中的內(nèi)存寫入指令:
dx?-r0?@$rspId?=?0x14dx?-r0?@$rbpId?=?0x15dx?-r0?@$isMemWrite?=?(b?=>?b.Instructions.Where(i?=>?i.Operands.Count()?>?1?&&?i.Operands[0].Attributes.IsOutput?&&?i.Operands[0].Registers[0].Id?!=?@$rspId?&&?i.Operands[0].Registers[0].Id?!=?@$rbpId?&&?i.Operands[0].Attributes.IsMemoryReference))dx?-r0?@$findMemWrite?=?(a?=>?Debugger.Utility.Code.CreateDisassembler().DisassembleBlocks(a).Select(b?=>?@$isMemWrite(b)))dx?-r2?@$findMemWrite(&nt!ZwSetInformationProcess).Where(b?=>?b.Count()?!=?0)@$findMemWrite(&nt!ZwSetInformationProcess).Where(b?=>?b.Count()?!=?0)?????????[0xfffff8074ebd23d4][0xfffff8074ebd23e9]?:?mov?????????qword?ptr?[r10+80h],rax[0xfffff8074ebd23f5]?:?mov?????????qword?ptr?[r10+44h],rax[0xfffff8074ebd2415][0xfffff8074ebd2421]?:?mov?????????qword?ptr?[r10+98h],r8[0xfffff8074ebd2428]?:?mov?????????qword?ptr?[r10+0F8h],r9[0xfffff8074ebd2433]?:?mov?????????byte?ptr?gs:[5D18h],al[0xfffff8074ebd25b2][0xfffff8074ebd25c3]?:?mov?????????qword?ptr?[rcx],rax[0xfffff8074ebd25c9]?:?mov?????????qword?ptr?[rcx+8],rax[0xfffff8074ebd25d0]?:?mov?????????qword?ptr?[rcx+10h],rax[0xfffff8074ebd25d7]?:?mov?????????qword?ptr?[rcx+18h],rax[0xfffff8074ebd25df]?:?mov?????????qword?ptr?[rcx+0A0h],rax[0xfffff8074ebd263f][0xfffff8074ebd264f]?:?and?????????byte?ptr?[rax+5],0FDh[0xfffff8074ebd26da][0xfffff8074ebd26e3]?:?mov?????????qword?ptr?[rcx],rax[0xfffff8074ebd26e9]?:?mov?????????qword?ptr?[rcx+8],rax[0xfffff8074ebd26f0]?:?mov?????????qword?ptr?[rcx+10h],rax[0xfffff8074ebd26f7]?:?mov?????????qword?ptr?[rcx+18h],rax[0xfffff8074ebd26ff]?:?mov?????????qword?ptr?[rcx+0A0h],rax[0xfffff8074ebd2708]?:?mov?????????word?ptr?[rcx+72h],ax...作為另外一個(gè)項(xiàng)目,它幾乎結(jié)合了上面提到的所有內(nèi)容 —— 實(shí)現(xiàn) dx 版本的 !apc 。為了簡(jiǎn)化,我們只尋找內(nèi)核 APC。要做到這一點(diǎn),需要如下幾個(gè)步驟:
使用 @$cursession.Processes 遍歷所有進(jìn)程,以找到這些進(jìn)程:包含 ?KTHREAD.ApcState.KernelApcPending 值為 1 的線程。
在對(duì)應(yīng)進(jìn)程中創(chuàng)建一個(gè)容器,只包含那些包含掛起內(nèi)核 APC 的線程,忽略其它線程。
對(duì)于每個(gè)線程,遍歷 KTHREAD.ApcState.ApcListHead[0] (包含內(nèi)核 APC)并收集感興趣的信息。可以通過(guò)前面介紹的 FromListHead() 方法實(shí)現(xiàn)。為了使我們的容器盡可能地與 !apc 相似,我們將只獲取 KernelRoutine 和 RundownRoutine,盡管在你的實(shí)現(xiàn)中,你可能會(huì)發(fā)現(xiàn)其他感興趣的字段。
為了讓容器更容易遍歷,我們將收集進(jìn)程名、進(jìn)程 ID、 EPROCESS 地址以及線程 ID 和 ETHREAD 地址。
在我們的實(shí)現(xiàn)中,我們創(chuàng)建了幾個(gè)輔助函數(shù):
@$printLn —— 運(yùn)行傳統(tǒng)命令 ln,以獲取指定地址對(duì)應(yīng)的符號(hào)信息。
**@$extractBetween** —— 提取兩個(gè)字符串之間的字符串,用于從 `@$printLn` 的輸出中獲取子字符串。
**@$printSymbol** —— 傳遞一個(gè)地址給 `@$printLn,并且使用 @$extractSymbol` 提取符號(hào)名。
@$apcsForThread —— 找到一個(gè)線程中的所有內(nèi)核 APC,并且創(chuàng)建一個(gè)包含 KernelRoutine 和 RundownRoutine 的容器。
在得到所有進(jìn)程(包含掛起的內(nèi)核 APC 的線程)后,將其保存到 @$procWithKernelApcs 寄存器中,然后在一個(gè)單獨(dú)的命令中使用 @$apcsForThread 獲取 APC 信息。我們將 EPPROCESS 和 ETHREAD 指針強(qiáng)制轉(zhuǎn)換為 void*,這樣當(dāng)我們顯示最終結(jié)果時(shí) dx 就不會(huì)顯示整個(gè)結(jié)構(gòu)。
這是我解決問(wèn)題的方法,但也有其他方法,你的方法不一定和我的完全相同!
我想出的腳本是:
dx?-r0?@$printLn?=?(a?=>?Debugger.Utility.Control.ExecuteCommand(“l(fā)n?“+((__int64)a).ToDisplayString(“x”)))dx?-r0?@$extractBetween?=?((x,y,z)?=>?x.Substring(x.IndexOf(y)?+?y.Length,?x.IndexOf(z)?—?x.IndexOf(y)?—?y.Length))dx?-r0?@$printSymbol?=?(a?=>?@$extractBetween(@$printLn(a)[3],?“?“,?“|”))dx?-r0?@$apcsForThread?=?(t?=>?new?{TID?=?t.Id,?Object?=?(void*)&t.KernelObject,?Apcs?=?Debugger.Utility.Collections.FromListEntry(*(nt!_LIST_ENTRY*)&t.KernelObject.Tcb.ApcState.ApcListHead[0],?“nt!_KAPC”,?“ApcListEntry”).Select(a?=>?new?{?Kernel?=?@$printSymbol(a.KernelRoutine),?Rundown?=?@$printSymbol(a.RundownRoutine)})})dx?-r0?@$procWithKernelApc?=?@$cursession.Processes.Select(p?=>?new?{Name?=?p.Name,?PID?=?p.Id,?Object?=?(void*)&p.KernelObject,?ApcThreads?=?p.Threads.Where(t?=>?t.KernelObject.Tcb.ApcState.KernelApcPending?!=?0)}).Where(p?=>?p.ApcThreads.Count()?!=?0)dx?-r6?@$procWithKernelApc.Select(p?=>?new?{?Name?=?p.Name,?PID?=?p.PID,?Object?=?p.Object,?ApcThreads?=?p.ApcThreads.Select(t?=>?@$apcsForThread(t))})它會(huì)產(chǎn)生以下輸出:
dx?-r6?@$procWithKernelApc.Select(p?=>?new?{?Name?=?p.Name,?PID?=?p.PID,?Object?=?p.Object,?ApcThreads?=?p.ApcThreads.Select(t?=>?@$apcsForThread(t))})@$procWithKernelApc.Select(p?=>?new?{?Name?=?p.Name,?PID?=?p.PID,?Object?=?p.Object,?ApcThreads?=?p.ApcThreads.Select(t?=>?@$apcsForThread(t))})[0x15b8]????????Name?????????????:?SearchUI.exeLength???????????:?0xcPID??????????????:?0x15b8Object???????????:?0xffffb90a5b1300c0?[Type:?void?*]ApcThreads??????[0x159c]????????TID??????????????:?0x159cObject???????????:?0xffffb90a5b14f080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x1528]????????TID??????????????:?0x1528Object???????????:?0xffffb90a5aa6b080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x16b4]????????TID??????????????:?0x16b4Object???????????:?0xffffb90a59f1e080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x16a0]????????TID??????????????:?0x16a0Object???????????:?0xffffb90a5b141080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x16b8]????????TID??????????????:?0x16b8Object???????????:?0xffffb90a5aab20c0?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x1740]????????TID??????????????:?0x1740Object???????????:?0xffffb90a5ab362c0?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x1780]????????TID??????????????:?0x1780Object???????????:?0xffffb90a5b468080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x1778]????????TID??????????????:?0x1778Object???????????:?0xffffb90a5b6f7080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x17d0]????????TID??????????????:?0x17d0Object???????????:?0xffffb90a5b1e8080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x17d4]????????TID??????????????:?0x17d4Object???????????:?0xffffb90a5b32f080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x17f8]????????TID??????????????:?0x17f8Object???????????:?0xffffb90a5b32e080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0xb28]?????????TID??????????????:?0xb28Object???????????:?0xffffb90a5b065600?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x1850]????????TID??????????????:?0x1850Object???????????:?0xffffb90a5b6a5080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList雖然輸出結(jié)果沒(méi)有 !apc 美觀,但已經(jīng)很美觀了。
我們還可以將其輸出成表格,顯示相關(guān)進(jìn)程的信息并且可以分別瀏覽每個(gè)進(jìn)程中的 APC:
dx?-g?@$procWithKernelApc.Select(p?=>?new?{?Name?=?p.Name,?PID?=?p.PID,?Object?=?p.Object,?ApcThreads?=?p.ApcThreads.Select(t?=>?@$apcsForThread(t))}) dx-g-procWithKernelApc.Select但是請(qǐng)等一下,這些包含 nt!EmpCheckErrataList 的 APC 是怎么回事?為什么 SearchUI.exe 里到處都是?這個(gè)進(jìn)程和 erratas 有什么關(guān)系?(譯注: 函數(shù) nt!EmpCheckErrataList 中包含 Errata。)
秘密在于實(shí)際上并沒(méi)有 APC 會(huì)調(diào)用 nt!EmpCheckErrataList。不,符號(hào)也沒(méi)有錯(cuò)。(譯注: 哈哈,感覺(jué)作者在跟讀者討論問(wèn)題)
我們之所以看到這樣的現(xiàn)象,是因?yàn)榫幾g器很智能 —— 當(dāng)編譯器看到不同的函數(shù)具有相同的代碼的時(shí)候,編譯器會(huì)讓它們?nèi)恐赶蛲欢未a,而不是多次復(fù)制這段代碼。你可能會(huì)認(rèn)為這不是一個(gè)會(huì)經(jīng)常發(fā)生的事情,但讓我們看看 nt!EmpCheckErrataList 的反匯編代碼(這次使用舊辦法):
u?EmpCheckErrataListnt!EmpCheckErrataList: fffff807`4eb86010?c20000??????????ret?????0 fffff807`4eb86013?cc??????????????int?????3 fffff807`4eb86014?cc??????????????int?????3 fffff807`4eb86015?cc??????????????int?????3 fffff807`4eb86016?cc??????????????int?????3這實(shí)際上只是一個(gè)存根。它可能是一個(gè)尚未實(shí)現(xiàn)的函數(shù)(EmpCheckErrataList 可能是這種情況),也可能是出于某些原因被故意定義為存根函數(shù)。這些 APC 的 KernelRoutine/RundownRoutine 真正對(duì)應(yīng)的函數(shù)是 nt!KiSchedulerApcNop,它被故意設(shè)計(jì)成存根函數(shù),并且已經(jīng)多年了。我們可以看到它具有相同的代碼并且指向同一地址:
u?nt!KiSchedulerApcNopnt!EmpCheckErrataList: fffff807`4eb86010?c20000??????????ret?????0 fffff807`4eb86013?cc??????????????int?????3 fffff807`4eb86014?cc??????????????int?????3 fffff807`4eb86015?cc??????????????int?????3 fffff807`4eb86016?cc??????????????int?????3那么為什么我們會(huì)看到這么多這樣的 APC 呢?
當(dāng)一個(gè)線程被掛起時(shí),系統(tǒng)會(huì)創(chuàng)建一個(gè)信號(hào)量,并將一個(gè)等待該信號(hào)量的 APC 發(fā)送到該線程。線程將一直等待,直到有人喚醒它,然后系統(tǒng)將釋放信號(hào)量,線程將停止等待并恢復(fù)運(yùn)行。APC 本身不需要做很多事情,但它必須有一個(gè) KernelRoutine 和一個(gè) RundownRoutine,所以系統(tǒng)使用了存根。這個(gè)存根使用了這些函數(shù)(包含存根代碼)中的一個(gè),這次是 nt!EmpCheckErrataList,但在下一個(gè)版本中可能是一個(gè)不同的函數(shù)名。
任何對(duì)掛起機(jī)制感興趣的人都可以查看 ReactOS。這些函數(shù)的代碼發(fā)生了一點(diǎn)變化,并且存根函數(shù)名從 KiSuspendNop 變成了 KiSchedulerApcNop,但總體設(shè)計(jì)保持相似。
我跑題了,這不是本篇文章應(yīng)該討論的內(nèi)容。讓我們回到 WinDbg,接著講合成類型:
合成類型(Synthetic Types)
在介紹完合成方法后,我們還可以添加自定義的命名類型(named types),并使用它們來(lái)解析之前無(wú)法解析的數(shù)據(jù)。
例如,讓我們嘗試打印 PspCreateProcessNotifyRoutine 數(shù)組,該數(shù)組包含所有已注冊(cè)的進(jìn)程通知例程 —— 這些函數(shù)由驅(qū)動(dòng)程序注冊(cè),在進(jìn)程啟動(dòng)時(shí)會(huì)收到一個(gè)通知。但是這個(gè)數(shù)組不包含指向注冊(cè)例程的指針。相反,它包含指向未文檔化的結(jié)構(gòu)體 EX_CALLBACK_ROUTINE_BLOCK 的指針。
因此,為了解析這個(gè)數(shù)組,我們需要確保 WinDbg 認(rèn)識(shí)這個(gè)類型 —— 我們需要使用合成類型。我們首先要?jiǎng)?chuàng)建一個(gè)頭文件,里面包含我們想要定義的所有類型(我使用 c:\temp\header.h)。在本例中,我們只定義了 EX_CALLBACK_ROUTINE_BLOCK,可以在 ReactOS 中找到它的定義:
typedef?struct?_EX_CALLBACK_ROUTINE_BLOCK {_EX_RUNDOWN_REF?RundownProtect;void*?Function;void*?Context; }?EX_CALLBACK_ROUTINE_BLOCK,?*PEX_CALLBACK_ROUTINE_BLOCK;現(xiàn)在我們可以讓 WinDbg 加載這個(gè)頭文件并將里面的類型添加到 nt 模塊中:
dx?Debugger.Utility.Analysis.SyntheticTypes.ReadHeader("c:\\temp\\header.h",?"nt")Debugger.Utility.Analysis.SyntheticTypes.ReadHeader("c:\\temp\\header.h",?"nt")?????????????????:?ntkrnlmp.exe(header.h)ReturnEnumsAsObjects?:?falseRegisterSyntheticTypeModels?:?falseModule???????????:?ntkrnlmp.exeHeader???????????:?header.hTypes這會(huì)為我們返回一個(gè)對(duì)象,通過(guò)這個(gè)對(duì)象,我們可以查看添加到此模塊的所有類型。現(xiàn)在,類型已經(jīng)定義好了,我們可以通過(guò) ?CreateInstance 使用它:
dx?Debugger.Utility.Analysis.SyntheticTypes.CreateInstance("_EX_CALLBACK_ROUTINE_BLOCK",?*(__int64*)&nt!PspCreateProcessNotifyRoutine)Debugger.Utility.Analysis.SyntheticTypes.CreateInstance("_EX_CALLBACK_ROUTINE_BLOCK",?*(__int64*)&nt!PspCreateProcessNotifyRoutine)????????????????RundownProtect???[Type:?_EX_RUNDOWN_REF]Function?????????:?0xfffff8074cbdff50?[Type:?void?*]Context??????????:?0x6e496c4102031400?[Type:?void?*]需要注意的是 CreateInstance 只接受 __int64 作為輸入,所以任何其他類型都必須強(qiáng)制轉(zhuǎn)換。提前知道這一點(diǎn)很好,因?yàn)榉祷氐腻e(cuò)誤消息并不總是那么容易理解的。
現(xiàn)在如果查看輸出結(jié)果,特別是 Context,有些東西似乎很奇怪。實(shí)際上,如果我們?cè)噲D轉(zhuǎn)儲(chǔ) Function,我們會(huì)看到它并不指向任何代碼:
dq?0xfffff8074cbdff50 fffff807`4cbdff50??????????`?????????????????`???????? fffff807`4cbdff60??????????`?????????????????`???????? fffff807`4cbdff70??????????`?????????????????`???????? fffff807`4cbdff80??????????`?????????????????`???????? fffff807`4cbdff90??????????`?????????????????`????????發(fā)生了什么?
不是我們強(qiáng)制轉(zhuǎn)換到 EX_CALLBACK_ROUTINE_BLOCK 出了問(wèn)題,而是我們轉(zhuǎn)換的地址有問(wèn)題。如果我們轉(zhuǎn)儲(chǔ) PspCreateProcessNotifyRoutine 中的值,我們可能會(huì)看到它的值:
dx?((void**[0x40])&nt!PspCreateProcessNotifyRoutine).Where(a?=>?a?!=?0)((void**[0x40])&nt!PspCreateProcessNotifyRoutine).Where(a?=>?a?!=?0)[0]??????????????:?0xffffb90a530504ef?[Type:?void?*?*][1]??????????????:?0xffffb90a532a512f?[Type:?void?*?*][2]??????????????:?0xffffb90a53da9d5f?[Type:?void?*?*][3]??????????????:?0xffffb90a53da9ccf?[Type:?void?*?*][4]??????????????:?0xffffb90a53e5d15f?[Type:?void?*?*][5]??????????????:?0xffffb90a571469ef?[Type:?void?*?*][6]??????????????:?0xffffb90a5714722f?[Type:?void?*?*][7]??????????????:?0xffffb90a571473df?[Type:?void?*?*][8]??????????????:?0xffffb90a597d989f?[Type:?void?*?*]所有這些地址的低半字節(jié)都是 0xF,而我們知道 x64 機(jī)器中的指針總會(huì)對(duì)齊到 8 字節(jié),通常是 0x10。這是因?yàn)槲抑斑^(guò)度簡(jiǎn)化了 —— 這些不是指向 EX_CALLBACK_ROUTINE_BLOCK 的指針,它們實(shí)際上是包含 EX_RUNDOWN_REF 的 EX_CALLBACK 結(jié)構(gòu)體(另一種不在公共符號(hào)中的類型)。但是為了使這個(gè)示例更簡(jiǎn)單,我將把它們當(dāng)作與 ~0xF 執(zhí)行與操作后的簡(jiǎn)單指針,因?yàn)檫@對(duì)于我們的目的來(lái)說(shuō)已經(jīng)足夠好了。如果你選擇寫一個(gè)驅(qū)動(dòng)程序來(lái)處理 PspCreateProcessNotifyRoutine,請(qǐng)不要使用這個(gè) hack,查看 ReactOS 并正確地做事情。😊
因此,要修復(fù)我們的命令,我們只需要在對(duì)地址進(jìn)行強(qiáng)制類型轉(zhuǎn)換之前將其對(duì)齊到 0x10。我們可以像下面這樣做:
<address>?&?0xFFFFFFFFFFFFFFF0或更好的版本:
<address>?&?~0xF讓我們?cè)谖覀兊拿钪惺褂盟?#xff1a;
dx?Debugger.Utility.Analysis.SyntheticTypes.CreateInstance("_EX_CALLBACK_ROUTINE_BLOCK",?(*(__int64*)&nt!PspCreateProcessNotifyRoutine)?&?~0xf)Debugger.Utility.Analysis.SyntheticTypes.CreateInstance("_EX_CALLBACK_ROUTINE_BLOCK",?(*(__int64*)&nt!PspCreateProcessNotifyRoutine)?&?~0xf)????????????????RundownProtect???[Type:?_EX_RUNDOWN_REF]Function?????????:?0xfffff8074ea7f310?[Type:?void?*]Context??????????:?0x0?[Type:?void?*]看起來(lái)好些了。這次讓我們確認(rèn) Function 確實(shí)指向了一個(gè)函數(shù):
ln?0xfffff8074ea7f310 Browse?module Set?bu?breakpoint(fffff807`4ea7f310)???nt!ViCreateProcessCallback???| (fffff807`4ea7f330)???nt!RtlStringCbLengthW Exact?matches:nt!ViCreateProcessCallback?(void)看起來(lái)好多了!現(xiàn)在我們可以將這種類型轉(zhuǎn)換定義為一個(gè)合成方法,并使用它來(lái)獲取數(shù)組中所有例程的函數(shù)地址:
dx?-r0?@$getCallbackRoutine?=?(a?=>?Debugger.Utility.Analysis.SyntheticTypes.CreateInstance("_EX_CALLBACK_ROUTINE_BLOCK",?(__int64)(a?&?~0xf)))dx?((void**[0x40])&nt!PspCreateProcessNotifyRoutine).Where(a?=>?a?!=?0).Select(a?=>?@$getCallbackRoutine(a).Function)((void**[0x40])&nt!PspCreateProcessNotifyRoutine).Where(a?=>?a?!=?0).Select(a?=>?@$getCallbackRoutine(a).Function)????????????????[0]??????????????:?0xfffff8074ea7f310?[Type:?void?*][1]??????????????:?0xfffff8074ff97220?[Type:?void?*][2]??????????????:?0xfffff80750a41330?[Type:?void?*][3]??????????????:?0xfffff8074f8ab420?[Type:?void?*][4]??????????????:?0xfffff8075106d9f0?[Type:?void?*][5]??????????????:?0xfffff807516dd930?[Type:?void?*][6]??????????????:?0xfffff8074ff252c0?[Type:?void?*][7]??????????????:?0xfffff807520b6aa0?[Type:?void?*][8]??????????????:?0xfffff80753a63cf0?[Type:?void?*]如果我們能看到符號(hào)而不是地址,這會(huì)更有趣。我們已經(jīng)知道如何通過(guò)執(zhí)行傳統(tǒng)命令 ln 來(lái)獲取符號(hào),但這一次我們將使用 .printf。
首先,我們將編寫一個(gè)輔助函數(shù) @$getsym,它將運(yùn)行命令 printf "%y", <address>:
dx?-r0?@$getsym?=?(x?=>?Debugger.Utility.Control.ExecuteCommand(".printf\"%y\",?"?+?((__int64)x).ToDisplayString("x"))[0])然后我們使用這個(gè)輔助函數(shù)打印每個(gè)函數(shù)地址對(duì)應(yīng)的符號(hào):
dx?((void**[0x40])&nt!PspCreateProcessNotifyRoutine).Where(a?=>?a?!=?0).Select(a?=>?@$getsym(@$getCallbackRoutine(a).Function))((void**[0x40])&nt!PspCreateProcessNotifyRoutine).Where(a?=>?a?!=?0).Select(a?=>?@$getsym(@$getCallbackRoutine(a).Function))????????????????[0]??????????????:?nt!ViCreateProcessCallback?(fffff807`4ea7f310)[1]??????????????:?cng!CngCreateProcessNotifyRoutine?(fffff807`4ff97220)[2]??????????????:?WdFilter!MpCreateProcessNotifyRoutineEx?(fffff807`50a41330)[3]??????????????:?ksecdd!KsecCreateProcessNotifyRoutine?(fffff807`4f8ab420)[4]??????????????:?tcpip!CreateProcessNotifyRoutineEx?(fffff807`5106d9f0)[5]??????????????:?iorate!IoRateProcessCreateNotify?(fffff807`516dd930)[6]??????????????:?CI!I_PEProcessNotify?(fffff807`4ff252c0)[7]??????????????:?dxgkrnl!DxgkProcessNotify?(fffff807`520b6aa0)[8]??????????????:?peauth+0x43cf0?(fffff807`53a63cf0)你看,好太多了!
斷點(diǎn)(Breakpoints)
條件斷點(diǎn)(Conditional Breakpoint)
調(diào)試時(shí),條件斷點(diǎn)是一個(gè)巨大的痛點(diǎn)。在舊的 MASM 語(yǔ)法中,它們幾乎不可用。我花了幾個(gè)小時(shí)試圖讓它們以我想要的方式工作,但結(jié)果是如此糟糕,以至于我甚至不知道我在試圖做什么,更不用說(shuō)為什么它不過(guò)濾任何東西或者如何修復(fù)它。
好吧,這些日子已經(jīng)過(guò)去了。我們現(xiàn)在可以在條件斷點(diǎn)中使用 dx 查詢,語(yǔ)法為:bp /w "dx query" <address>。
例如,假設(shè)我們正在調(diào)試一個(gè) Wow64 進(jìn)程打開(kāi)文件的問(wèn)題。NtOpenProcess 函數(shù)一直在被調(diào)用,但是我們只關(guān)心來(lái)自 Wow64 進(jìn)程的調(diào)用,這并不是現(xiàn)代操作系統(tǒng)上的大多數(shù)進(jìn)程(譯注: Wow64 進(jìn)程指的是運(yùn)行在 64 位系統(tǒng)上的 32 位進(jìn)程,64 位進(jìn)程逐漸成為主流)。因此,為了避免在走狗屎運(yùn)之前無(wú)助的經(jīng)歷 100 次調(diào)試中斷或者避免與 MASM 風(fēng)格的條件斷點(diǎn)做斗爭(zhēng),我們可以這樣做:
bp?/w?"@$curprocess.KernelObject.WoW64Process?!=?0" nt!NtOpenProcess然后讓系統(tǒng)運(yùn)行起來(lái),當(dāng)再次中斷時(shí),我們可以檢查它是否奏效:
Breakpoint?3?hit nt!NtOpenProcess: fffff807`2e96b7e0?4883ec38????????sub?????rsp,38hdx?$curprocess.KernelObject.WoW64Process@$curprocess.KernelObject.WoW64Process??:?0xffffc10f5163b390?[Type:?_EWOW64PROCESS?*][+0x000]?Peb??????????????:?0xf88000?[Type:?void?*][+0x008]?Machine??????????:?0x14c?[Type:?unsigned?short][+0x00c]?NtdllType????????:?PsWowX86SystemDll?(1)?[Type:?_SYSTEM_DLL_TYPE]dx?@$curprocess.Name@$curprocess.Name?:?IpOverUsbSvc.exeLength???????????:?0x10觸發(fā)斷點(diǎn)的進(jìn)程是一個(gè) WoW64 進(jìn)程!對(duì)于那些曾經(jīng)嘗試過(guò)在 MASM 中使用條件斷點(diǎn)的人來(lái)說(shuō),dx 真是一個(gè)大救星。
其它斷點(diǎn)選項(xiàng)(Other Breakpoint Options)
在 Debugger.Utility.Control 下,還有其他一些有趣的斷點(diǎn)選項(xiàng):
SetBreakpointAtSourceLocation —— 允許我們?cè)谀K對(duì)應(yīng)的源文件中設(shè)置斷點(diǎn),語(yǔ)法如下:
dx Debugger.Utility.Control.SetBreakpointAtSourceLocation("MyModule!myFile.cpp", "172")
SetBreakpointAtOffset —— 在函數(shù)內(nèi)部偏移設(shè)置斷點(diǎn) —— dx Debugger.Utility.Control.SetBreakpointAtOffset("NtOpenFile", 8, “nt")
SetBreakpointForReadWrite —— 類似于傳統(tǒng)的 ba 命令,但其語(yǔ)法更具可讀性。它允許我們?cè)O(shè)置斷點(diǎn),以便在任何讀或?qū)懩硞€(gè)地址時(shí)中斷。它的默認(rèn)配置為 type = Hardware Write 并且 size = 1。(譯注: 原文中的函數(shù)名是 SetBreakpointForReadWriteFile,應(yīng)該是筆誤。)
比如,讓我們?cè)谧x取 Ci!g_CiOptions(一個(gè) 4 字節(jié)大小的變量)時(shí)中斷下來(lái):
讓系統(tǒng)繼續(xù)運(yùn)行,斷點(diǎn)幾乎立即命中:
0:?kd>?g Breakpoint?0?hit CI!CiValidateImageHeader+0x51b: fffff807`2f6fcb1b?740c?je?CI!CiValidateImageHeader+0x529?(fffff807`2f6fcb29)CI!CiValidateImageHeader 在驗(yàn)證鏡像頭時(shí)會(huì)讀取此全局變量。在這個(gè)特定的示例中,我們將看到讀取這個(gè)變量是很頻繁的,修改此變量則加有趣,因?yàn)樗梢韵蛭覀冋故敬鄹暮灻?yàn)證的嘗試。
值得注意的是,這些命令不僅僅是設(shè)置了一個(gè)斷點(diǎn),實(shí)際上會(huì)返回給我們一個(gè)可以操作的對(duì)象,包含屬性 IsEnabled , Condition(允許我們?cè)O(shè)置一個(gè)條件),PassCount(告訴我們這個(gè)斷點(diǎn)已經(jīng)訪問(wèn)了多少次),還有更多其它屬性。
文件系統(tǒng)(FileSystem)
在 Debugger.Utility 下提供了 FileSystem 模塊,允許我們?cè)谡{(diào)試器中查詢和控制主機(jī)上的文件系統(tǒng)(不是正在被調(diào)試的機(jī)器):
dx?-r1?Debugger.Utility.FileSystemDebugger.Utility.FileSystemCreateFile?[CreateFile(path,?[disposition])?-?Creates?a?file?at?the?specified?path?and?returns?a?file?object.??'disposition'?can?be?one?of?'CreateAlways'?or?'CreateNew']CreateTempFile???[CreateTempFile()?-?Creates?a?temporary?file?in?the?%TEMP%?folder?and?returns?a?file?object]CreateTextReader?[CreateTextReader(file?|?path,?[encoding])?-?Creates?a?text?reader?over?the?specified?file.??If?a?path?is?passed?instead?of?a?file,?a?file?is?opened?at?the?specified?path.??'encoding'?can?be?'Utf16',?'Utf8',?or?'Ascii'.??'Ascii'?is?the?default]CreateTextWriter?[CreateTextWriter(file?|?path,?[encoding])?-?Creates?a?text?writer?over?the?specified?file.??If?a?path?is?passed?instead?of?a?file,?a?file?is?created?at?the?specified?path.??'encoding'?can?be?'Utf16',?'Utf8',?or?'Ascii'.??'Ascii'?is?the?default]CurrentDirectory?:?C:\WINDOWS\system32DeleteFile???????[DeleteFile(path)?-?Deletes?a?file?at?the?specified?path]FileExists???????[FileExists(path)?-?Checks?for?the?existance?of?a?file?at?the?specified?path]OpenFile?????????[OpenFile(path)?-?Opens?a?file?read/write?at?the?specified?path]TempDirectory????:?C:\Users\yshafir\AppData\Local\Temp我們可以創(chuàng)建文件、打開(kāi)文件、寫入文件、刪除文件或檢查某個(gè)文件是否存在于某個(gè)特定路徑中。來(lái)看一個(gè)簡(jiǎn)單的例子,讓我們轉(zhuǎn)儲(chǔ)當(dāng)前目錄(C:\Windows\System32)的內(nèi)容:
dx?-r1?Debugger.Utility.FileSystem.CurrentDirectory.FilesDebugger.Utility.FileSystem.CurrentDirectory.Files????????????????[0x0]????????????:?C:\WINDOWS\system32\07409496-a423-4a3e-b620-2cfb01a9318d_HyperV-ComputeNetwork.dll[0x1]????????????:?C:\WINDOWS\system32\1[0x2]????????????:?C:\WINDOWS\system32\103[0x3]????????????:?C:\WINDOWS\system32\108[0x4]????????????:?C:\WINDOWS\system32\11[0x5]????????????:?C:\WINDOWS\system32\113...[0x44]???????????:?C:\WINDOWS\system32\93[0x45]???????????:?C:\WINDOWS\system32\98[0x46]???????????:?C:\WINDOWS\system32\@AppHelpToast.png[0x47]???????????:?C:\WINDOWS\system32\@AudioToastIcon.png[0x48]???????????:?C:\WINDOWS\system32\@BackgroundAccessToastIcon.png[0x49]???????????:?C:\WINDOWS\system32\@bitlockertoastimage.png[0x4a]???????????:?C:\WINDOWS\system32\@edptoastimage.png[0x4b]???????????:?C:\WINDOWS\system32\@EnrollmentToastIcon.png[0x4c]???????????:?C:\WINDOWS\system32\@language_notification_icon.png[0x4d]???????????:?C:\WINDOWS\system32\@optionalfeatures.png[0x4e]???????????:?C:\WINDOWS\system32\@VpnToastIcon.png[0x4f]???????????:?C:\WINDOWS\system32\@WiFiNotificationIcon.png[0x50]???????????:?C:\WINDOWS\system32\@windows-hello-V4.1.gif[0x51]???????????:?C:\WINDOWS\system32\@WindowsHelloFaceToastIcon.png[0x52]???????????:?C:\WINDOWS\system32\@WindowsUpdateToastIcon.contrast-black.png[0x53]???????????:?C:\WINDOWS\system32\@WindowsUpdateToastIcon.contrast-white.png[0x54]???????????:?C:\WINDOWS\system32\@WindowsUpdateToastIcon.png[0x55]???????????:?C:\WINDOWS\system32\@WirelessDisplayToast.png[0x56]???????????:?C:\WINDOWS\system32\@WwanNotificationIcon.png[0x57]???????????:?C:\WINDOWS\system32\@WwanSimLockIcon.png[0x58]???????????:?C:\WINDOWS\system32\aadauthhelper.dll[0x59]???????????:?C:\WINDOWS\system32\aadcloudap.dll[0x5a]???????????:?C:\WINDOWS\system32\aadjcsp.dll[0x5b]???????????:?C:\WINDOWS\system32\aadtb.dll[0x5c]???????????:?C:\WINDOWS\system32\aadWamExtension.dll[0x5d]???????????:?C:\WINDOWS\system32\AboutSettingsHandlers.dll[0x5e]???????????:?C:\WINDOWS\system32\AboveLockAppHost.dll[0x5f]???????????:?C:\WINDOWS\system32\accessibilitycpl.dll[0x60]???????????:?C:\WINDOWS\system32\accountaccessor.dll[0x61]???????????:?C:\WINDOWS\system32\AccountsRt.dll[0x62]???????????:?C:\WINDOWS\system32\AcGenral.dll...我們可以選擇刪除其中的一個(gè)文件:
dx?-r1?Debugger.Utility.FileSystem.CurrentDirectory.Files[1].Delete()或者通過(guò) DeleteFile 刪除:
dx?Debugger.Utility.FileSystem.DeleteFile(“C:\\WINDOWS\\system32\\71”)注意,在這個(gè)模塊中,路徑必須使用雙反斜杠("\\"),就像我們自己調(diào)用 Win32 API 時(shí)一樣。
作為最后一個(gè)練習(xí),我們將把在這里學(xué)到的東西放在一起 —— 我們將在內(nèi)核變量上創(chuàng)建一個(gè)斷點(diǎn),從調(diào)用棧中獲取訪問(wèn)它的符號(hào),并將訪問(wèn)它的符號(hào)寫入主機(jī)上的一個(gè)文件中。
讓我們把它分成幾個(gè)步驟:
打開(kāi)一個(gè)文件,以便寫入結(jié)果。
創(chuàng)建一個(gè) text writer,我們將使用它寫入文件。
創(chuàng)建訪問(wèn)變量的斷點(diǎn)。在本例中,我們將選擇 nt!PsInitialSystemProcess 并設(shè)置讀斷點(diǎn)。我們將使用舊的 MASM 語(yǔ)法來(lái)設(shè)置一個(gè)斷點(diǎn),每次斷點(diǎn)命中時(shí),會(huì)執(zhí)行一個(gè) dx 命令并繼續(xù)運(yùn)行:ba r4 <address> "dx <command>; g”
我們的命令將使用 @$curstack 來(lái)獲取訪問(wèn)該變量的函數(shù)地址,然后使用前面編寫的 @$getsym 輔助函數(shù)來(lái)查找該地址對(duì)應(yīng)的符號(hào)。然后使用 text writer 將結(jié)果寫入文件。
最后,關(guān)閉文件。
整合在一起:
dx?-r0?@$getsym?=?(x?=>?Debugger.Utility.Control.ExecuteCommand(".printf\"%y\",?"?+?((__int64)x).ToDisplayString("x"))[0])dx?@$tmpFile?=?Debugger.Utility.FileSystem.TempDirectory.OpenFile("log.txt")dx?@$txtWriter?=?Debugger.Utility.FileSystem.CreateTextWriter(@$tmpFile)ba?r4?nt!PsInitialSystemProcess?"dx?@$txtWriter.WriteLine(@$getsym(@$curstack.Frames[0].Attributes.InstructionOffset));?g"我們讓系統(tǒng)想運(yùn)行多久就運(yùn)行多久,當(dāng)我們想停止記錄日志時(shí),我們可以禁用或清除斷點(diǎn)并使用 dx @$tmpFile.Close() 關(guān)閉文件。
現(xiàn)在我們可以打開(kāi) @$tmpFile 并查看結(jié)果:
view-tempFile就是這樣!記錄關(guān)于調(diào)試器的信息是多么簡(jiǎn)單啊!
這就是我們 WinDbg 系列的全部?jī)?nèi)容!本系列的所有腳本都將被上傳到 github ,還有一些新的沒(méi)有包含在這里的腳本。我鼓勵(lì)您進(jìn)一步研究這個(gè)數(shù)據(jù)模型,因?yàn)槲覀兩踔翛](méi)有涵蓋它包含的所有不同方法。編寫你自己的工具,并與世界分享它們 :)
譯注: github 地址是 https://github.com/yardenshafir/WinDbg_Scripts)
盡管本指南很長(zhǎng),但這些甚至還不是新數(shù)據(jù)模型中的所有可能選項(xiàng)。我甚至沒(méi)有提到新增的對(duì) ?Javascript 的支持!
你可以在這篇精彩的文章中獲得更多關(guān)于在 WinDbg 中使用 Javascript 的信息,以及對(duì)令人興奮的 TTD (time travel debugging) 的新支持。
總結(jié)
以上是生活随笔為你收集整理的难译 | windbg 乐趣之道(下)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 生活话语
- 下一篇: 用Jplayer做的一个带动画的播放器