驅動中檢查應用層地址有效
之前有個驅動,其中有個功能要用到解析pe結構,傳入個imagebase基地址然後解析這個pe檔案結構返回資訊。後來一個同事維護了一段時間,他辭職之後,測試的告訴我功能失效了,我就把程式碼拿過來看了看,原來這傢伙誤用了一個函式MmIsAddressValid,他在解析PE結構的時候,用了一個函式MmIsAddressValid,我看他的意思是想判斷一個地址是否有效就用,這個函式名字看起來好像是這個意思,所以很多本來"有效"的地址就返回了false導致了整個功能的失效。
然後我的另外一個同事就刪除了這段程式碼:
if (!MmIsAddressValid(DbgDir))
break;
結果果不其然馬上就有藍屏出現了,抓了一個dump顯示在windbg藍屏程式碼如下:PAGE_FAULT_IN_NONPAGED_AREA加上符號之後,發現就奔潰就在去掉MmIsAddressValid的下一行,試圖解引一個地址出事了,簡單除錯了下發現這個地址是一個 核心模組裡面,模組imagebase大概是0xFFFFF8001137000,imagesize是0xD2000,那個要解引得地址是0xFFFFF8001208500沒超出imagesize,但很接近了,實際上是被釋放了,直接解引肯定會出事。
其實windbg後面有行小字就說的很清楚了:Invalid system memory was referenced. This cannot be protected by try-except,it must beprotected by a Probe.簡單點說就是告訴要用 try-except和ProbeForRead(), ProbeForRead()本身只是檢測了對齊數值和記憶體區間是否越過使用者所能訪問的最高地址頂端,其他並沒有多做判斷。如果地址"無效",將丟擲異常。
簡而言之如果要檢查的地址是應用層地址,用ProbeForRead()再外面使用try-except捕獲異常即可。所以正確的檢查應用層地址有效的程式碼應該這樣寫:
__try
{
ProbeForRead(DbgDir, sizeof(IMAGE_DEBUG_DIRECTORY), 1);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("EXCEPTION_EXECUTE_HANDLER\n"));
}
要校驗核心地址是否"有效",情況就有點複雜了,MmIsAddressVaild()不僅判斷記憶體地址是否可讀,還要判斷該地址是否會引起頁面錯誤,也就是說訪問該地址是否會引起記憶體管理器進行換頁操作。我們來嘗試一個有趣的例子:
VOID Test() { NTSTATUS Status = 0; PUCHAR Buf = NULL; SIZE_T CodeLength = ALIGN_UP_BY(100, PAGE_SIZE); Status = ZwAllocateVirtualMemory(NtCurrentProcess(), (PVOID*)&Buf, 0, &CodeLength, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (Buf) { if (MmIsAddressValid(Buf)) { KdPrint(("MmIsAddressValid1")); } else { KdPrint(("!MmIsAddressValid1")); } *Buf = "a"; if (MmIsAddressValid(Buf)) { KdPrint(("MmIsAddressValid2")); } else { KdPrint(("!MmIsAddressValid2")); } } }
MEM_RESERVE和MEM_COMMIT,前者表示保留虛存區間,即只分配未經物理對映的虛存區間,後者表示實際對映虛存區間到物理儲存,兩者並起來就表示一次到位的分配建立物理對映的虛存區間。但實際上即使有MEM_COMMIT
本部落格旨在提供穩定和良好風格的程式碼。