UnhandledExceptionFilter 未被調用分析一例

有時候會遇到 crash 的時候沒有生成 dump file. 因爲 unhandledexceptionfilter 沒有被調用. 而沒有被調用的原因, 文檔上說有時候的確不會調用. 比如在處理異常的時候再次發生異常.

最近發現在 space 打開的情況下, unhandledexceptionfilter 不被調用, 此時, 會出現系統默認的 crash 對話框. 這個對話框的出現意味着 kernel32!UnhandledExceptionFilter 被調用了, 雖然在 2000, xp, vista 裏, 出現對話框的具體實現都不一樣, 但都是從 kernel32!UnhandledExceptionFilter 開始的.

這個函數並不長. 它會調用 IsDebugPortPresent(), 如果存在調試器, 則直接返回, 讓調試器處理異常. 否則調用用戶通過 SetUnhandledExceptionFilter() 註冊的處理函數. 如果要調試自己的處理函數,可以在 IsDebugPortPresent() 函數返回之後, 將 eax 置 0.

bp 772b5a38 "r eax=0;g";bp connect!_UnhandledExceptionFilter

 

地址 772b5a38 是在 vista enterprise build 6000 中反彙編 kernel32!UnhandledExceptionFilter 得到的. 當然, 隨着 windows 版本的不同地址是不一樣的.

在 space 打開時, 觸發異常, 的確沒有進到 connect!_UnhandledExceptionFilter 中. 懷疑是不是我們的處理函數被替換了. SetUnhandledExceptionFilter() 將用戶註冊的函數地址存到全局變量 kernel32!BasepCurrentTopLevelFilter 中, 查看該變量的值, 卻發現不是一個函數地址. 查看 SetUnhandledExceptionFilter() 的代碼, 才知道它會將函數地址用 RtlEncodePointer() 加密. 而在調用這個函數時會使用 RtlDecodePointer() 解密. 既然每次調用 SetUnhandledExceptionFilter() 都會返回之前的處理函數的地址. 於是在手動觸發異常之前先來一個 ASSERT:

 	ATLASSERT(_UnhandledExceptionFilter == SetUnhandledExceptionFilter(_UnhandledExceptionFilter));

 

運行程序, 居然真的 ASSERT 了. 有其他的代碼在調用 SetUnhandledExceptionFilter(), 遂在其上加斷點, 不看不知道, 一看嚇一跳. 原來很多的 dll 都會設置自己的異常處理函數.

爲了確保我們的處理函數能夠調用, 過河拆橋, 在我們調用 SetUnhandledExceptionFilter() 之後, 給 SetUnhandledExceptionFilter() 打一個補丁, 以後再有調用, 則直接返回. 打補丁的代碼很簡單:

void PatchSetUnhandledExceptionFilter()
{
	static BYTE RETURN_CODE[] = { 0xc2, 0x04, 0x00}; // __asm ret 4
	MEMORY_BASIC_INFORMATION   mbi;
	DWORD dwOldProtect = 0;
	DWORD pfnSetFilter =(DWORD)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "SetUnhandledExceptionFilter");
	VirtualQuery((void *)pfnSetFilter, &mbi, sizeof(mbi) );
	VirtualProtect( (void *)pfnSetFilter, sizeof(RETURN_CODE), PAGE_READWRITE, &dwOldProtect);

	WriteProcessMemory(GetCurrentProcess(),	(void *)pfnSetFilter,	RETURN_CODE,	sizeof(RETURN_CODE), NULL);
	VirtualProtect((void *)pfnSetFilter, sizeof(RETURN_CODE), mbi.Protect, 0);
	FlushInstructionCache(GetCurrentProcess(), (void *)pfnSetFilter, sizeof(RETURN_CODE));
}

 

在 kernel32!UnhandledExceptionFilter() 的起始處直接寫上 __asm ret 4.

測試通過.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章