在 Windows NT/2000/XP 環境下,作業系統接管了實體記憶體的直接存取,而 BIOS 資料位於實體記憶體的 F000:0000 位置。本文將詳細說明如何透過系統底層 API 映射實體記憶體,從而在應用層讀取 BIOS 資訊。
關鍵在於使用 ntdll.dll 中的三個原生 API:
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer; // 指向 L"\\Device\\PhysicalMemory"
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length; // 0x18
HANDLE RootDirectory; // 0
PUNICODE_STRING ObjectName; // 指向 UNICODE_STRING
ULONG Attributes; // 0x40
PVOID SecurityDescriptor; // 0
PVOID SecurityQualityOfService; // 0
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
以下程式碼示範如何映射 BIOS 區域 (F000:0000 - F000:FFFF):
// 全域函式指標宣告
typedef DWORD (__stdcall *ZWOS)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES);
typedef DWORD (__stdcall *ZWMV)(HANDLE, HANDLE, PVOID*, ULONG, ULONG,
PLARGE_INTEGER, PSIZE_T, DWORD, ULONG, ULONG);
typedef DWORD (__stdcall *ZWUMV)(HANDLE, PVOID);
// 主程式邏輯
UNICODE_STRING uStr = {0};
OBJECT_ATTRIBUTES objAttr = {0};
HANDLE hSection = NULL;
DWORD baseAddr = 0;
LARGE_INTEGER offset = {0};
SIZE_T viewSize = 0xFFFF;
wchar_t physMem[] = L"\\Device\\PhysicalMemory";
uStr.Buffer = physMem;
uStr.Length = 0x2C;
uStr.MaximumLength = 0x2E;
objAttr.Length = 24;
objAttr.Attributes = 64;
objAttr.ObjectName = &uStr;
// 載入 ntdll.dll 並取得函式位址
HMODULE hNtdll = LoadLibrary(L"ntdll.dll");
ZWOS ZwOpenS = (ZWOS)GetProcAddress(hNtdll, "ZwOpenSection");
ZWMV ZwMapV = (ZWMV)GetProcAddress(hNtdll, "ZwMapViewOfSection");
ZWUMV ZwUnmapV = (ZWUMV)GetProcAddress(hNtdll, "ZwUnmapViewOfSection");
// 開啟實體記憶體區段
ZwOpenS(&hSection, 4, &objAttr);
// 映射 BIOS 區域 (0xF0000, 大小 64KB)
offset.LowPart = 0xF0000;
ZwMapV(hSection, (HANDLE)-1, &baseAddr, 0, 0xFFFF,
&offset, &viewSize, 1, 0, 2);
// 此時 baseAddr 指向映射後的虛擬位址,可直接讀取 BIOS 內容
// 使用完畢後需呼叫 ZwUnmapViewOfSection 解除映射
\Device\PhysicalMemoryWin32_BIOS) 或 SMBIOS 表格查詢替代本文靈感來源於聯想安裝驗證程式的逆向分析,感謝相關技術人員的間接貢獻。