WinDBG詳解進程初始化dll是如何載入的

来源:https://www.cnblogs.com/huangxincheng/archive/2022/11/09/16873561.html
-Advertisement-
Play Games

一:背景 1.講故事 有朋友咨詢個問題,他每次在調試 WinDbg 的時候,進程初始化斷點之前都會有一些 dll 載入到進程中,比如下麵這樣: Microsoft (R) Windows Debugger Version 10.0.25200.1003 X86 Copyright (c) Micro ...


一:背景

1.講故事

有朋友咨詢個問題,他每次在調試 WinDbg 的時候,進程初始化斷點之前都會有一些 dll 載入到進程中,比如下麵這樣:


Microsoft (R) Windows Debugger Version 10.0.25200.1003 X86
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: D:\net6\ConsoleApp1\Debug\ConsoleApplication3.exe

************* Path validation summary **************
Response                         Time (ms)     Location
Deferred                                       srv*c:\mysymbols*https://msdl.microsoft.com/download/symbols
Symbol search path is: srv*c:\mysymbols*https://msdl.microsoft.com/download/symbols
Executable search path is: 
ModLoad: 00400000 0041f000   ConsoleApplication3.exe
ModLoad: 774b0000 77653000   ntdll.dll
ModLoad: 753a0000 75490000   C:\Windows\SysWOW64\KERNEL32.DLL
ModLoad: 75900000 75b14000   C:\Windows\SysWOW64\KERNELBASE.dll
ModLoad: 79bc0000 79d36000   C:\Windows\SysWOW64\ucrtbased.dll
ModLoad: 79ba0000 79bbe000   C:\Windows\SysWOW64\VCRUNTIME140D.dll
(44c.4b0c): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=afe00000 edx=00000000 esi=774c1ff4 edi=774c25bc
eip=77561a42 esp=0019fa20 ebp=0019fa4c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!LdrpDoDebuggerBreak+0x2b:
77561a42 cc              int     3

問是否可以用 WinDbg 解讀下內部運作原理,哈哈,其實要瞭解運作原理,一定要熟知 PE 頭,那這篇就安排上。

二:理解 PE 頭結構

1. 測試代碼

為了方便講述,先上一段測試代碼,這裡故意載入 combase.dll 是為了提取 PE 中的某些數據結構,代碼如下:


#include <iostream>
#include <Windows.h>

int main(int argc, char* argv[]) {

	LoadLibrary(L"combase.dll");

	getchar();
}

其實你仔細想一想也能知道,既然能做到初始化載入,必然在 PE 頭上藏了什麼東西,這些東西讓 Windows 載入器可以順利載入諸如 ntdll.dll, KERNEL32.dll 等等,接下來一起觀察下。

2. 可視化觀察 PE 頭

要想可視化觀察 PE 頭,工具有很多,這裡使用 PPEE 工具,截圖如下:

從圖中可以看到,其實初始化載入什麼,由可選頭中的 DIRECTORY_ENTRY_IMPORT 數據目錄項決定,哪這裡包含了哪些初始化 dll 呢? 可以選中右邊的 DIRECTORY_ENTRY_IMPORT 項即可,如下圖所示:

肯定有朋友說,WinDbg 上顯示的是 5 個,你這裡才 3 個,還有 2 個為什麼沒有? 很簡單,多餘的 ntdll.dllKERNELBASE.dll 必然是依賴項哈。

3. 用 WinDbg 深入探究

玩 WinDbg 都喜歡刨根問底,拿可視化 PPEE 肯定忽悠不過去,那好吧,我們用 C 中的結構體去解剖它。

  1. DOS Header 節

這一塊信息在源碼中是用 ntdll!_IMAGE_DOS_HEADER 結構來承載的,可以用 dt 輸出,起始點就是我們的 ConsoleApplication3.dll 在進程的首位置,即: 0x400000


0:000> dt 0x400000 _IMAGE_DOS_HEADER 
ConsoleApplication3!_IMAGE_DOS_HEADER
   +0x000 e_magic          : 0x5a4d
   +0x002 e_cblp           : 0x90
   +0x004 e_cp             : 3
   ...
   +0x024 e_oemid          : 0
   +0x026 e_oeminfo        : 0
   +0x028 e_res2           : [10] 0
   +0x03c e_lfanew         : 0n232

  1. NT Header 節

接下來就是 NT Header 節,它在源碼中是由 _IMAGE_NT_HEADERS 結構來承載的,起始位置的偏移已經保存在上面的 e_lfanew 欄位中,即 0n232


0:000> dt 0x400000+0n232 _IMAGE_NT_HEADERS
ConsoleApplication3!_IMAGE_NT_HEADERS
   +0x000 Signature        : 0x4550
   +0x004 FileHeader       : _IMAGE_FILE_HEADER
   +0x018 OptionalHeader   : _IMAGE_OPTIONAL_HEADER

  1. _IMAGE_DATA_DIRECTORY

在 PPEE 的第一張截圖中,我們查看的是 Data Directorys 數組中的第二項 DIRECTORY_ENTRY_IMPORT 內容,它裡面定義了我們需要初始化導入的 dll,我們可以用 dt r3 展開一下,然後一直點點點就好了,簡化後如下:


0:000> dt -r3 0x400000+0n232 _IMAGE_NT_HEADERS
ConsoleApplication3!_IMAGE_NT_HEADERS
   +0x000 Signature        : 0x4550
   +0x004 FileHeader       : _IMAGE_FILE_HEADER
      +0x000 Machine          : 0x14c
      ...
      +0x012 Characteristics  : 0x103
   +0x018 OptionalHeader   : _IMAGE_OPTIONAL_HEADER
      +0x000 Magic            : 0x10b
      +0x002 MajorLinkerVersion : 0xe ''
      ...
      +0x05c NumberOfRvaAndSizes : 0x10
      +0x060 DataDirectory    : [16] _IMAGE_DATA_DIRECTORY
         +0x000 VirtualAddress   : 0
         +0x004 Size             : 0
0:000> dx -r1 (*((ConsoleApplication3!_IMAGE_DATA_DIRECTORY (*)[16])0x400160))
(*((ConsoleApplication3!_IMAGE_DATA_DIRECTORY (*)[16])0x400160))                 [Type: _IMAGE_DATA_DIRECTORY [16]]
    [0]              [Type: _IMAGE_DATA_DIRECTORY]
    [1]              [Type: _IMAGE_DATA_DIRECTORY]
    [2]              [Type: _IMAGE_DATA_DIRECTORY]
    ...
    [15]             [Type: _IMAGE_DATA_DIRECTORY]

0:000> dx -r1 (*((ConsoleApplication3!_IMAGE_DATA_DIRECTORY *)0x400168))
(*((ConsoleApplication3!_IMAGE_DATA_DIRECTORY *)0x400168))                 [Type: _IMAGE_DATA_DIRECTORY]
    [+0x000] VirtualAddress   : 0x1b1cc [Type: unsigned long]
    [+0x004] Size             : 0x50 [Type: unsigned long]

從輸出的 VirtualAddress=0x1b1cc 中可以看到,我們 PPEE 截圖二中的 DIRECTORY_ENTRY_IMPORT 真實內容是在偏移 0x1b1cc 處,它是一個 combase!_IMAGE_IMPORT_DESCRIPTOR 結構體,輸出如下:


0:005> dt 0x400000+0x1b1cc combase!_IMAGE_IMPORT_DESCRIPTOR
   +0x000 Characteristics  : 0x1b21c
   +0x000 OriginalFirstThunk : 0x1b21c
   +0x004 TimeDateStamp    : 0
   +0x008 ForwarderChain   : 0
   +0x00c Name             : 0x1b40c
   +0x010 FirstThunk       : 0x1b000

到這裡就很關鍵了,涉及到如下幾點信息:

  • 載入的 dll 名字是什麼?

可以從 Name 欄位提取,參考如下代碼:


0:005> da 0x400000+0x1b40c
0041b40c  "KERNEL32.dll"

  • 載入的 方法名 是什麼?

這需要提取 OriginalFirstThunk 欄位,這裡是一個 _IMAGE_IMPORT_BY_NAME類型的指針數組,代碼如下:


0:005> dp 0x400000+0x1b21c
0041b21c  0001b3e8 0001b3fc 0001b954 0001b942
0041b22c  0001b934 0001b924 0001b912 0001b906
0041b23c  0001b8fa 0001b8ea 0001b8d6 0001b8ba
...

0:005> dt 0x400000+0x0001b3e8 combase!_IMAGE_IMPORT_BY_NAME
   +0x000 Hint             : 0x382
   +0x002 Name             : [1]  "I"

0:005> da 0x400000+0x0001b3e8+0x2
0041b3ea  "IsDebuggerPresent"

結合上面的輸出,我們知道 IsDebuggerPresent() 是屬於 KERNEL32.dll 下的,有了這兩點信息,Windows 載入器就可以用 LoadLibraryGetProcAddress 方法將其載入到進程中了,轉化為 C 代碼大概是這樣的。


typedef BOOL(CALLBACK* DeubbgerFunc)();

int main(int   argc, char* argv[])
{
	HMODULE hModule = LoadLibrary(L"KERNEL32.dll");

	DeubbgerFunc func = (DeubbgerFunc)GetProcAddress(hModule, "IsDebuggerPresent");

	BOOL b= func();
}

  • func 函數地址會保存嗎?

當然會保存了,會放在 _IMAGE_IMPORT_DESCRIPTOR 結構下的 FirstThunk 欄位中,這是一個函數地址的指針數組,可以用 dds 觀察。


0:005> dt 0x400000+0x1b1cc combase!_IMAGE_IMPORT_DESCRIPTOR
   +0x000 Characteristics  : 0x1b21c
   +0x000 OriginalFirstThunk : 0x1b21c
   +0x004 TimeDateStamp    : 0
   +0x008 ForwarderChain   : 0
   +0x00c Name             : 0x1b40c
   +0x010 FirstThunk       : 0x1b000

0:005> dds 0x400000+0x1b000
0041b000  753c20d0 KERNEL32!IsDebuggerPresentStub
0041b004  753c16c0 KERNEL32!LoadLibraryWStub
0041b008  753c2e80 KERNEL32!GetCurrentProcess
0041b00c  753bf550 KERNEL32!GetProcAddressStub
0041b05c  753b9910 KERNEL32!TerminateProcessStub
...

還有一點要註意,如果你在代碼中使用 IsDebuggerPresent() 方法的話,它會從 0041b000 位置上取函數地址,參考如下彙編代碼:

三:總結

對初學者來說,搞懂這些還是有一定困難的,我在網上找了一份很好的參考圖,大家可以對照著這張圖理解,在此感謝作者。

  • INT 是 Windows 需要載入的函數名列表。
  • IAT 是存放 GetProcAddress 返回函數地址的列表。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • “心有所向,日復一日,必有精進” 前言: 想必大家看完我之前寫的搭建redis伺服器,大家都已經把redis搭建起來了吧~如果沒有搭建起來的小可愛請移步這裡哦~[從0到1搭建redis6](https://www.cnblogs.com/qsmm/p/16871488.html "從0到1搭建red ...
  • 文章有點長,我決定用半個小時來和你分享~😂 廢話不多說,上代碼。。。 基於Seata 1.5.2,項目中用 seata-spring-boot-starter 1. SeataDataSourceAutoConfiguration SeataDataSourceAutoConfiguration ...
  • 1、開發文檔 微信開發文檔:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1 安全規範:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3 1、簽名演算法 (簽 ...
  • 簡介: 命令模式,又稱之為動作模式或者事務模式,屬於行為型的設計模式。 將不同的請求封裝成不同的請求對象,以便使用不同的請求; 角色都會用飯館來舉例子: 命令下達者:顧客 命令接受者:服務員 命令本身: 菜單 命令執行者:廚師 適用場景: Laravel的事件調度機制有用到了命令模式。 想要解耦服務 ...
  • Java 基礎一 【註釋】 comment 對代碼進行解釋說明1.Java規範的註釋有3種單行註釋://多行註釋:/* */文檔註釋(java特有)2.單行註釋和多行註釋的作用:對所寫的程式進行解釋說明,增強可讀性。方便自己,方便別人。可以調試所寫的代碼3.特點單行註釋和多行註釋,註釋了的內容不參與 ...
  • RPC(Remote Procedure Call) 是 Hadoop 服務通信的關鍵庫,支撐上層分散式環境下複雜的進程間(Inter-Process Communication, IPC)通信邏輯,是分散式系統的基礎。允許運行於一臺電腦上的程式像調用本地方法一樣,調用另一臺電腦的子程式。由於 ...
  • 作為緩存系列專欄的第四篇文章,我們將在上一篇的基礎之上進行升華,一起思考如何構建一個完整且通用的本地緩存框架,併在過程中體會緩存實現的關鍵點與架構設計的思路。 ...
  • 2022年11月8日.NET 7正式發佈 .NET仍然是最快、最受歡迎、最值得信賴的平臺之一,其龐大的.NET軟體包生態系統包括33萬多個軟體包。 .NET 7為您的應用程式帶來了更高的性能和C# 11/F# 7、.NET MAUI、ASP.NET Core/Blazor、Web APIs、WinF ...
一周排行
    -Advertisement-
    Play Games
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...