<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    調(diào)試實(shí)戰(zhàn) | 通過(guò)轉(zhuǎn)儲(chǔ)文件分析程序無(wú)響應(yīng)之使用 windbg + IDA 逆向篇

    共 4818字,需瀏覽 10分鐘

     ·

    2020-12-22 18:45


    緣起

    最近,接連在項(xiàng)目中遇到了兩個(gè)界面無(wú)響應(yīng)的問(wèn)題。都只發(fā)生在客戶(hù)特定機(jī)器上,不方便直接調(diào)試,只能抓取 dump 進(jìn)行事后分析了。

    抓取 dump

    遠(yuǎn)程連上可以重現(xiàn)問(wèn)題的機(jī)器,使用 process explorer 初步觀察卡死的進(jìn)程,發(fā)現(xiàn) CPU 占用率很低,經(jīng)過(guò)一段時(shí)間的觀察,基本確定是一個(gè)死鎖問(wèn)題。在卡死的進(jìn)程上右鍵,保存完整轉(zhuǎn)儲(chǔ),壓縮,發(fā)回本地進(jìn)行分析。

    使用 windbg 進(jìn)行分析

    雙擊抓取的 dump 文件,因?yàn)橹耙呀?jīng)執(zhí)行過(guò) windbg.exe -IA,所以默認(rèn)會(huì)通過(guò) windbg 打開(kāi) dump 文件。先使用 ~*kvn 粗略瀏覽一下每個(gè)線(xiàn)程的調(diào)用棧(因?yàn)楸容^長(zhǎng),這里就不截圖了)。經(jīng)過(guò)觀察,很快鎖定了兩個(gè)值得進(jìn)一步查看的線(xiàn)程:一個(gè)是主線(xiàn)程(界面線(xiàn)程),因?yàn)槭墙缑鏌o(wú)響應(yīng),肯定要關(guān)注界面線(xiàn)程。另外一個(gè)是 7 號(hào)工作線(xiàn)程。分別看一下這兩個(gè)線(xiàn)程的調(diào)用棧。

    主線(xiàn)程的調(diào)用棧如下圖所示:

    注意上圖紅色高亮部分,主線(xiàn)程通過(guò) SleepConditionVariableCS() 進(jìn)入等待。

    看完主線(xiàn)程,再看 7 號(hào)工作線(xiàn)程的調(diào)用棧,如下圖所示:


    7 號(hào)線(xiàn)程對(duì) SendMessage() 的調(diào)用非常值得懷疑。

    猜測(cè)整個(gè)流程是這樣的:主線(xiàn)程不知由于什么原因進(jìn)入等待狀態(tài),而工作線(xiàn)程由于各種各樣的原因也進(jìn)入了等待狀態(tài)。其中 7 號(hào)線(xiàn)程最明顯,因?yàn)樗诎l(fā)消息,而主線(xiàn)程此時(shí)是無(wú)論如何也不會(huì)響應(yīng)這個(gè)消息的。

    于是,典型的死鎖再一次發(fā)生了。


    加載 AssemblyDesign_Tools.dll 的符號(hào)后,查看對(duì)應(yīng)的源碼,消除對(duì) SendMessage() 的調(diào)用,問(wèn)題解決!so easy!

    說(shuō)明:工作線(xiàn)程中并沒(méi)有直接調(diào)用 SendMessage(),而是調(diào)用了操作界面的相關(guān) API,間接調(diào)用了 SendMessage() ?給界面線(xiàn)程發(fā)消息。

    死鎖的問(wèn)題解決了,但是為什么向主線(xiàn)程發(fā)個(gè)消息就死鎖了呢?秉著打破砂鍋問(wèn)到底的原則,我又開(kāi)始折騰了。下面的內(nèi)容適合喜歡調(diào)試逆向的極客閱讀。

    深入調(diào)查

    最開(kāi)始的思路是:查看主線(xiàn)程在等待的條件變量,然后再調(diào)查哪個(gè)工作線(xiàn)程會(huì)喚醒這個(gè)條件變量。奈何 64 位下,前四個(gè)參數(shù)通過(guò)寄存器 RCX, RDX, r8, r9 進(jìn)行傳遞,如果這些寄存器沒(méi)有在棧上存儲(chǔ)一份的話(huà),很難查看具體的值。折騰一番后,確實(shí)沒(méi)找到有用的信息,而且就算找到了,也很難找出是哪個(gè)線(xiàn)程會(huì)執(zhí)行喚醒操作。

    這個(gè)死鎖問(wèn)題不像關(guān)鍵段死鎖解決起來(lái)那么直接。不能直接通過(guò)命令(!cs -l),或者查看調(diào)用棧就能直接理出頭緒??磥?lái)只能硬著頭皮逆向分析相關(guān)代碼了。

    0 號(hào)線(xiàn)程和 7 號(hào)線(xiàn)程最值得懷疑,其它線(xiàn)程基本可以排除。先看看主線(xiàn)程為什么會(huì)等待吧。

    主線(xiàn)程邏輯

    找到調(diào)用 BentleyG!Bentley::BeConditionVariable::WaitOnCondition() ?的地方,也就是 5 號(hào)棧幀。

    IDA 中打開(kāi) MobileDgn.dll,并找到這個(gè)函數(shù),然后按下神奇的 F5

    可以看到,主線(xiàn)程在陷入等待之前,向工作線(xiàn)程發(fā)送了一個(gè)任務(wù),也就是 sub_7FEDAC749A0,傳遞的參數(shù)是 v5。v5 偏移 88 的位置保存了 BeConditonVariable 類(lèi)型的變量,也就是 WaitOnCondition() 所等待的變量。猜測(cè):sub_7FEDAC749A0 內(nèi)部會(huì)喚醒這個(gè)BeConditionVariable, 如果 sub_7FEDAC749A0 被順利執(zhí)行,那么主線(xiàn)程的等待自然就結(jié)束了。

    先看看 sub_7FEDAC749A0 的反匯編代碼,當(dāng)然是直接看 F5 后的偽代碼了。

    可以很明顯的看到 sub_7FEDAC749A0 內(nèi)部調(diào)用了 Bentley::BeConditionVariable::Wake((CMFCRibbonInfo::XElementButtonUndo *)((char *)v1 + 88), 1);

    從函數(shù)名就可以猜到是用來(lái)喚醒 BeConditionVariable 的。

    如此看來(lái),sub_7FEDAC749A0 很有可能還沒(méi)有被執(zhí)行,工作線(xiàn)程就掛起了。一起來(lái)看看 SendToWorkThread 是怎么把任務(wù)發(fā)送到工作線(xiàn)程的。

    SendToWorkThread() ?會(huì)先判斷是否在工作線(xiàn)程運(yùn)行,如果是則直接執(zhí)行對(duì)應(yīng)的函數(shù),否則就根據(jù)參數(shù)生成一個(gè) RpcMessage ,然后發(fā)送這個(gè)新生成的 RpcMessage 到工作線(xiàn)程的任務(wù)隊(duì)列中。

    再來(lái)看一下生成 RpcMessage 的函數(shù),我把這個(gè)函數(shù)命名為 MakeMobileDgnRPCMessage()。

    一定要記住這里的關(guān)鍵信息,后面會(huì)根據(jù)這里的關(guān)鍵信息驗(yàn)證。HandlePaint() 傳過(guò)來(lái)的函數(shù)地址是 0x7FEDAC749A0,保存在了 rpcMsg 偏移 128 的位置。MobileDgnRPCGenericMessage 類(lèi)的虛表地址是 000007FEDAD19658

    繼續(xù)追蹤 SendAsynToWorkThread(),如下圖:

    繼續(xù)追蹤 HandleRpcMessage()(這個(gè)名字是我命名的,不是 IDA 識(shí)別的),傳給它的第一個(gè)參數(shù)是一個(gè)全局變量(姑且命名為 g_taskQueueManager),第二個(gè)參數(shù)是要發(fā)送的 rpcMsg,第三個(gè)參數(shù) v3 的值是 2,第四個(gè)參數(shù)是 1。

    這個(gè)函數(shù)比較長(zhǎng),我只逆了個(gè)大概,大體思路是先檢查是否存在,不存在則插入。如果隊(duì)列已經(jīng)滿(mǎn)了,需要等待工作線(xiàn)程從隊(duì)列中取走一些任務(wù)才返回。

    至此,基本理清了主線(xiàn)程相關(guān)的邏輯。大體是這樣的:主線(xiàn)程在處理 HandlePaint() 的時(shí)候,先發(fā)送一個(gè)任務(wù)給工作線(xiàn)程,(通過(guò) SendToWorkThread() 發(fā)送到工作線(xiàn)程的任務(wù)隊(duì)列中),然后通過(guò) BeConditionVariable::WaitOnCondition() 等待這個(gè)任務(wù)結(jié)束。

    看完界面線(xiàn)程,再來(lái)再看 7 號(hào)工作線(xiàn)程的相關(guān)邏輯。

    工作線(xiàn)程邏輯

    7 號(hào)工作線(xiàn)程,只需要關(guān)心 9 號(hào)棧幀對(duì)應(yīng)的函數(shù)。

    注意,_RunThread+0x1a3c,這個(gè)偏移有點(diǎn)大。由于缺少符號(hào),這里很有可能只是以 _RunThread 作為參照得到的一個(gè)偏移,實(shí)際對(duì)應(yīng)的是另外一個(gè)函數(shù)的代碼。使用 ub 向前查看反匯編,很快定位到正確的函數(shù)首地址。
    從上圖可知,9 號(hào)棧幀對(duì)應(yīng)的函數(shù)起始地址是 000007fe dac5fee0。怎么在 IDA 中找到這個(gè)函數(shù)呢?如果知道這個(gè)函數(shù)相對(duì)于其所在模塊的偏移,就可以算出在 IDA 中的地址了。該怎么獲取這個(gè)地址對(duì)應(yīng)的模塊基址呢?windbg 中執(zhí)行 lma address 就可以知道一個(gè)地址對(duì)應(yīng)的模塊信息了。
    得到模塊基址(0x000007fe dac10000)后,可以很簡(jiǎn)單的計(jì)算出偏移量為 0x4fee0有了這些信息就可以在 IDA 中找到這個(gè)函數(shù)了。

    小貼士:也可以在 IDA 中調(diào)整模塊基址,使其與 windbg 保持一致。這樣就不用根據(jù)偏移在 IDA 中手動(dòng)計(jì)算地址了。

    得到要查看的函數(shù)地址后(我在 IDA 中執(zhí)行了 Rebase program,所以是 000007fe dac10000),在 IDA 中直接按快捷鍵 g,輸入地址后即可跳轉(zhuǎn)到輸入的地址處。

    注意:如果在 IDA 中以 16 進(jìn)制輸入地址,請(qǐng)加上 0x 前綴,而且不要帶重音連接符。

    再次按下神奇的 F5

    至此,工作線(xiàn)程的邏輯也理清了。簡(jiǎn)單總結(jié)如下:工作線(xiàn)程是一個(gè)循環(huán),不斷從任務(wù)隊(duì)列取任務(wù)執(zhí)行,如果設(shè)置了喚醒標(biāo)記位,那么需要在執(zhí)行完任務(wù)函數(shù)后,喚醒等待的線(xiàn)程。

    驗(yàn)證猜想

    好了,花費(fèi)了這么多精力終于理清了主線(xiàn)程和工作線(xiàn)程的交互邏輯。目的只有一個(gè),就是為了更好的驗(yàn)證之前的猜想:工作線(xiàn)程還沒(méi)有來(lái)得及執(zhí)行主線(xiàn)程過(guò)來(lái)的任務(wù)就掛起了。

    如果猜測(cè)是正確的,那么工作線(xiàn)程的任務(wù)隊(duì)列中應(yīng)該還保留著這個(gè)未執(zhí)行的任務(wù)。接下來(lái)的任務(wù)就是來(lái)找到這個(gè)未執(zhí)行的任務(wù)。

    通過(guò)上面對(duì)主線(xiàn)程和工作線(xiàn)程的分析,工作線(xiàn)程的任務(wù)隊(duì)列中應(yīng)該有類(lèi)型為 MobileDgnRPCGenericMessage 的對(duì)象,并且該對(duì)象偏移 128 的位置的值為 0x7FEDAC749A0。根據(jù)這兩條關(guān)鍵信息在內(nèi)存中搜尋一下符合條件的記錄。

    windbg 中輸入命令 s -q 0 L?fffffffffffffff 000007FEDAD19658,根據(jù)虛表地址搜尋 MobileDgnRPCGenericMessage 類(lèi)型的對(duì)象。找到了兩條符合條件的記錄。

    再輸入 s -q 0 L?fffffffffffffff 7FEDAC749A0 根據(jù) sub_7FEDAC749A0 的地址搜尋包含這個(gè)地址的對(duì)象。找到四條符合條件的記錄。

    我們關(guān)心的是這兩次搜尋結(jié)果相差 128 的記錄(因?yàn)楦鶕?jù)之前的分析,workProc 存儲(chǔ)在偏移為 128 的位置)。經(jīng)過(guò)肉眼觀察及在 windbg 中計(jì)算(? 00000000300b4ea0 - 00000000300b4f20),得到了一個(gè)符合條件的對(duì)象的地址 00000000 300b4ea0。整個(gè)過(guò)程如下圖:

    找到了滿(mǎn)足上面條件的對(duì)象地址,還需要確認(rèn)這個(gè)對(duì)象是否在工作線(xiàn)程的任務(wù)隊(duì)列中。工作線(xiàn)程的任務(wù)隊(duì)列由全局變量 g_taskQueueManager 管理,該變量是一個(gè)指針,指針?biāo)诘牡刂肥?000007FEDADAA380,指針的值是 00000000024d9fa0。

    根據(jù)之前的分析猜測(cè),偏移為 8 的位置記錄了任務(wù)隊(duì)列的開(kāi)始位置,偏移 16 的位置記錄了任務(wù)隊(duì)列的結(jié)束位置,偏移 24 的位置記錄了任務(wù)隊(duì)列緩沖區(qū)結(jié)尾的位置,這個(gè)任務(wù)隊(duì)列很有可能是通過(guò) vector 管理的。在 windbg 中查看 g_taskQueueManager 的內(nèi)容。

    查看任務(wù)隊(duì)列起始位置保存的記錄信息,輸入 dq 0000000037e33ce0,然后與 s -q 0 L?fffffffffffffff 00000000300b4ea0 得到的搜索記錄對(duì)比,發(fā)現(xiàn)有一條是吻合的。
    看來(lái),主線(xiàn)程發(fā)送給工作線(xiàn)程的任務(wù)確實(shí)還沒(méi)有被執(zhí)行,工作線(xiàn)程就掛起了。

    總結(jié)

    這個(gè)偶發(fā)的掛起 bug 終于算是解決了。整個(gè)過(guò)程多虧了強(qiáng)大的 IDA 的強(qiáng)力支持。使整個(gè)分析過(guò)程簡(jiǎn)單了 N 倍。最后,對(duì)整個(gè)分析過(guò)程中用到的技術(shù)點(diǎn)做一個(gè)簡(jiǎn)單的總結(jié):

    • IDAF5 真香。
    • 可以在 IDA 中通過(guò) Rebase program 調(diào)整模塊基址。
    • IDA 中按 g 可以跳轉(zhuǎn)到輸入的地址。
    • IDA 中的地址需要有 0x 前綴,不要包含 windbg64 位地址的地址連接符。
    • 可以在 windbg 中通過(guò) lm a address 得到一個(gè)地址對(duì)應(yīng)的模塊信息。
    • windbg 中可以通過(guò) ub 進(jìn)行反向反匯編。
    • windbg 中可以根據(jù)虛表地址搜尋對(duì)應(yīng)類(lèi)型的變量在內(nèi)存中的位置。
    • 在工作線(xiàn)程調(diào)用界面相關(guān)的 API 時(shí),是通過(guò)給界面線(xiàn)程發(fā)消息的方式實(shí)現(xiàn)的。

    參考資料

    • SleepConditionVariableCS msdn[1]

    References:

    [1]

    SleepConditionVariableCS msdn: https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleepconditionvariablecs


    感謝你的分享,點(diǎn)贊和在看



    歡迎留言交流!

    瀏覽 70
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)
    評(píng)論
    圖片
    表情
    推薦
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    国产播放在线 | 激情内射国产 | www.艹 | 欧美一级中文字幕 | 老司机福利院 |