x86 反彙編/Microsoft Windows
Windows 作業系統 是一個受歡迎的逆向工程目標,原因很簡單:作業系統本身(市場份額,已知弱點)及其大多數應用程式都不是開源或免費的。Windows 機器上的大多數軟體都沒有捆綁原始碼,而且大多數軟體的文件不足或根本沒有文件。有時,瞭解軟體功能的唯一方法(或者確定某個軟體是惡意軟體還是合法軟體)是進行逆向工程並檢查結果。
Windows 作業系統可以很容易地分為兩類:Windows9x 和 WindowsNT。
Windows9x 核心最初是為了跨越 16 位 - 32 位邊界而編寫的。基於 9x 核心的作業系統是 Windows 95、Windows 96、Windows 98 和 Windows Me。Windows9x 系列作業系統以容易出現錯誤和系統不穩定而聞名。作業系統本身實際上是其前身 MS-DOS 的 32 位擴充套件。9x 系列的一個重要問題是它們都基於使用 ANSI 格式來儲存字串,而不是 Unicode。
Windows Me 釋出後,Windows9x 核心的開發就結束了。
WindowsNT 核心系列最初是作為企業級伺服器和網路軟體編寫的。WindowsNT 比 Windows9x 核心更強調穩定性和安全性(儘管可以爭論這種強調是否足夠)。它還在內部使用 Unicode 處理所有字串操作,在使用不同語言時提供了更大的靈活性。基於 WindowsNT 核心的作業系統是:Windows NT(版本 3.1、3.11、3.2、3.5、3.51 和 4.0)、Windows 2000(NT 5.0)、Windows XP(NT 5.1)、Windows Server 2003(NT 5.2)、Windows Vista(NT 6.0)、Windows 7(NT 6.1)、Windows 7.1(NT 6.11)、Windows 8(NT 6.2)、Windows 8.1(NT 6.3)和 Windows 10(NT 10.0)。
Microsoft Xbox 和 Xbox 360 也執行 NT 的一個變體,該變體是從 Windows 2000 分叉出來的。Microsoft 未來的大多數作業系統產品都以某種形式基於 NT。
記憶體被組織成預設大小為 4096 位元組的“頁”。系統或任何應用程式當前未使用的頁面可能會被寫入硬碟上的一個特殊區域,稱為“分頁檔案”。使用分頁檔案可能會提高某些系統的效能,儘管在某些情況下 HDD 的 I/O 延遲較高實際上會降低效能。
32 位 Windows NT 允許每個程序最多使用 4 GiB 的虛擬記憶體地址空間。預設情況下,這被分成 2 GiB 使用者記憶體和 2 GiB 核心記憶體。
在某些 32 位版本和版本中,可以使用 /3GB 開關啟動作業系統,它將此分成 3 GiB 使用者記憶體和 1 GiB 核心記憶體。只有使用大型記憶體標誌編譯的 32 位應用程式才能在這種模式下使用最多 3 GiB。/3GB 開關在 64 位 Windows 中不受支援,但帶有大型記憶體標誌的 32 位應用程式在 64 位 Windows 上可以訪問最多 4 GiB。64 位應用程式不受此限制。
從 Pentium Pro CPU 開始,一些 32 位版本和版本可以使用物理地址擴充套件(/PAE 開關)訪問 4 GiB 以上的記憶體,最多 64 GiB。支援 PAE 的 32 位應用程式(例如,某些版本的 32 位 Microsoft SQL Server 和 32 位 Microsoft Exchange Server)可以訪問此記憶體。但是,需要特殊的配置。
Windows 架構是高度分層的。程式設計師進行的函式呼叫可能在實際執行任何操作之前被重定向 3 次或更多次。從使用者模式應用程式呼叫 Win32 函式會產生不可忽略的效能損失。但是,其好處同樣不可忽略:在 Windows 系統的較高層編寫的程式碼更容易編寫。涉及初始化多個數據結構和呼叫多個子函式的複雜操作可以透過只調用一個單一的更高層函式來完成。
Win32 API 包含 3 個模組:KERNEL32、USER32 和 GDI32。KERNEL32 建立在 NTDLL 之上,大多數對 KERNEL32 函式的呼叫只是被重定向到 NTDLL 函式呼叫。USER32 和 GDI32 都基於 WIN32K(一個核心模式模組,負責 Windows 的“外觀和感覺”),儘管 USER32 也對 GDI32 中更原始的函式進行了許多呼叫。這些以及 NTDLL 都為 Windows NT 核心 NTOSKRNL 提供了一個介面(見下文)。
NTOSKRNL 也部分建立在 HAL(硬體抽象層)之上,但本書不會過多考慮這種互動。這種分層的目的是允許將處理器變體問題(例如資源位置)與核心本身區分開來。因此,稍有不同的系統配置只需要一個不同的 HAL 模組,而不是一個完全不同的核心模組。
在經過不同層級的子程式過濾後,大多數 API 呼叫都需要與作業系統的一部分進行互動。服務是透過“軟體中斷”提供的,傳統上是透過“int 0x2e”指令提供的。這將執行控制權切換到 NT 執行程式/核心,在那裡處理請求。這裡應該指出,核心模式使用的堆疊與使用者模式堆疊不同。這在核心和使用者之間提供了一層額外的保護。函式完成後,控制權將返回到使用者應用程式。
英特爾和 AMD 都提供了一組額外的指令,以允許更快的系統呼叫,英特爾的“SYSENTER”指令和 AMD 的 SYSCALL 指令。
WinNT 和 Win9x 系統都使用 Win32 API。但是,WinNT 版本的 API 具有更多功能和安全結構,以及 Unicode 支援。大多數 Win32 API 可以分解為 3 個獨立的元件,每個元件執行一個獨立的任務。
Kernel32.dll 是 KERNEL 子系統的所在地,它實現了非圖形函式。KERNEL 中的一些 API 是:堆 API、虛擬記憶體 API、檔案 I/O API、執行緒 API、系統物件管理器和其他類似系統服務。kernel32.dll 的大部分功能在 ntdll.dll 中實現,但在未公開的函式中。Microsoft 傾向於釋出 kernel32 的文件並保證這些 API 不會改變,然後將大部分工作放在其他庫中,這些庫隨後不會被記錄。
gdi32.dll 是實現 GDI 子系統的庫,在那裡執行原始圖形操作。GDI 將其大部分呼叫轉移到 WIN32K,但它確實包含一個 GDI 物件管理器,例如筆、畫刷和裝置上下文。GDI 物件管理器和 KERNEL 物件管理器是完全分開的。
USER 子系統位於 user32.dll 庫檔案中。該子系統控制 USER 物件的建立和操作,USER 物件是常見的螢幕專案,例如視窗、選單、游標等。USER 將設定要繪製的物件,但透過呼叫 GDI(進而多次呼叫 WIN32K)來執行實際的繪製操作,或者有時甚至直接呼叫 WIN32K。USER 使用 GDI 物件管理器。
原生 API,在此稱為 NTDLL 子系統,是一系列未公開的 API 函式呼叫,它處理 KERNEL32 執行的大部分工作。微軟也不保證原生 API 在不同版本之間保持一致,因為 Windows 開發人員會修改軟體。這帶來了原生 API 呼叫在未經警告的情況下被刪除或更改的風險,從而破壞了使用它的軟體。
NTDLL 子系統位於 ntdll.dll 中。該庫包含許多 API 函式呼叫,它們都遵循特定的命名方案。每個函式都有一個字首:Ldr、Nt、Zw、Csr、Dbg 等,所有具有特定字首的函式都遵循特定的規則。
"官方" 原生 API 通常僅限於字首為 Nt 或 Zw 的函式。這些呼叫實際上在使用者模式下是相同的:相關的 匯出條目 對映到記憶體中的同一個地址。但是,在核心模式下,Zw* 系統呼叫存根將先前模式設定為核心模式,確保不執行某些引數驗證例程。字首 "Zw" 的來源尚不清楚;這個字首是由於它本身沒有任何意義而選擇的[1]。
在實際實現中,系統呼叫存根只是載入兩個暫存器,其中包含描述原生 API 呼叫的所需值,然後執行軟體中斷(或sysenter指令)。
大多數其他字首都很模糊,但已知的包括
- Rtl 代表 "執行時庫",這些呼叫在執行時提供幫助功能(例如 RtlAllocateHeap)
- Csr 代表 "客戶端伺服器執行時",它表示位於 csrss.exe 中的 win32 子系統的介面
- Dbg 函式存在於啟用除錯例程和操作
- Ldr 提供了從 DLL 和其他模組資源載入、操作和檢索資料的能力
許多函式,尤其是執行時庫例程,在 ntdll.dll 和 ntoskrnl.exe 之間共享。大多數原生 API 函式以及從核心匯出的其他僅限核心模式的函式對於驅動程式編寫者非常有用。因此,微軟在 Microsoft Server 2003 Platform DDK 中提供了關於許多原生 API 函式的文件。DDK(驅動程式開發工具包)可以免費下載。
該模組是 Windows NT 的 "'執行器'",它提供了原生 API 以及核心本身所需的所有功能,核心本身負責維護機器狀態。預設情況下,所有中斷和核心呼叫都以某種方式透過 ntoskrnl 傳遞,使其成為 Windows 本身中最重要的程式。它的許多函式都是為了供裝置驅動程式使用而匯出的(所有這些函式都帶有各種字首,類似於 NTDLL)。
該模組是 "Win32 核心",它位於更低級別、更原始的 NTOSKRNL 之上。WIN32K 負責 Windows 的 "外觀和感覺",該程式碼的許多部分自 Win9x 版本以來基本保持不變。該模組提供了許多導致 USER 和 GDI 按預期方式執行的具體指令。它負責將來自 USER 和 GDI 庫的 API 呼叫轉換為您在顯示器上看到的圖片。
隨著 64 位處理器的出現,64 位軟體成為必需品。因此,建立了 Win64 API 來利用新的硬體。重要的是要注意,許多函式呼叫的格式在 Win32 和 Win64 中是相同的,除了指標的大小以及特定於 64 位地址空間的其他資料型別。
微軟釋出了其 Windows 作業系統的新版本,名為 "Windows Vista"。Windows Vista 可能更廣為人知的是它的開發代號 "Longhorn"。微軟聲稱 Vista 基本上是從頭開始編寫的,因此可以認為 Vista API 和系統架構與以前 Windows 版本的 API 和架構之間存在根本差異。Windows Vista 於 2007 年 1 月 30 日釋出。
Windows CE 是微軟在小型裝置上的產品。它主要使用與桌面系統相同的 Win32 API,但它的架構略有不同。本書中的一些示例可能會考慮 Windows CE。
最近的 Windows Service Pack 試圖實現一個名為 "不可執行記憶體" 的系統,其中某些頁面可以被標記為 "不可執行"。該系統的目的是透過不允許將控制權傳遞給攻擊者插入到記憶體緩衝區中的程式碼來防止一些最常見的安全漏洞。例如,載入到溢位文字緩衝區中的 shellcode 無法執行,從而阻止了攻擊。但是,這種機制的有效性還有待觀察。
COM 以及一整套與 COM 相關或實際上是帶有花哨名稱的 COM 的技術,是反向工程 Windows 二進位制檔案時需要考慮的另一個因素。COM、DCOM、COM+、ActiveX、OLE、MTS 和 Windows DNA 都是同一主題或類似主題的名稱,因此它們可以被視為屬於同一標題。簡而言之,COM 是一種以統一、跨平臺和跨語言的方式匯出面向物件類的方法。本質上,COM 是 .NET 版本 0 beta。使用 COM,用多種語言編寫的元件可以匯出、匯入、例項化、修改和銷燬在另一個檔案中(最常是 DLL)定義的物件。雖然 COM 提供了跨平臺(在某種程度上)和跨語言功能,但每個 COM 物件都編譯為原生二進位制檔案,而不是像 Java 或 .NET 這樣的中間格式。因此,COM 不需要虛擬機器來執行此類物件。
由於 COM 的工作方式,透過簡單地檢查可執行檔案,很難感知 COM 元件匯出的許多方法和資料結構。如果建立程式設計師使用了 ATL 等庫來簡化他們的程式設計體驗,情況會更糟。不幸的是,對於反向工程師來說,這會將可執行檔案的內容簡化為 "位元海洋",到處都是指標和資料結構。
RPC 是一個通用術語,指的是允許執行在一部機器上的程式呼叫實際在另一部機器上執行的函式的技術。通常情況下,這透過將所有需要用於該過程的資料(包括儲存在第一臺機器上的任何狀態資訊)進行 *序列化*,並將它們構建成單個數據結構,然後透過某種通訊方式傳輸到第二臺機器來實現。第二臺機器然後執行請求的動作,並返回包含任何結果和可能改變的狀態資訊的資料包到發起機器。
在 Windows NT 中,RPC 通常透過兩個同名庫來處理,其中一個庫生成 RPC 請求並接收 RPC 返回,如使用者模式程式請求的那樣,另一個庫響應 RPC 請求並透過 RPC 返回結果。一個典型的例子是列印後臺處理程式,它包含兩個部分:RPC 存根 spoolss.dll 和後臺處理程式本身以及 RPC 服務提供程式 spoolsv.exe。在大多數獨立的機器中,使用兩個模組透過 RPC 通訊似乎有些過度,為什麼不簡單地將它們合併成一個單一的例程?然而,在網路列印中,這種設計是有意義的,因為 RPC 服務提供程式可以駐留在遠離本地機器的遠端機器上,該機器具有遠端印表機,本地機器可以完全以與控制本地機器上的印表機相同的方式控制遠端機器上的印表機。