当调用窗口过程时 - 内核在内核堆栈和用户模式下推送额外的堆栈帧称为特殊“函数”(更快的偶数标签比普通函数)KiUserCallbackDispatcher,它调用窗口过程并最终通过特殊 api 调用返回内核ZwCallbackReturn 弹出内核堆栈帧。请注意,在调用ZwCallbackReturn 之后,我们不会返回到它之后的下一条指令,而是从我们进入内核的地方返回,从那里调用用户模式回调(通常来自GetMessage 或PeekMessage)。
无论如何我们必须弹出内核堆栈帧 - 所以调用ZwCallbackReturn。因此,KiUserCallbackDispatcher 出现 unwind 异常的情况 - 设计错误,不能正常工作。在这种情况下谁打电话给ZwCallbackReturn?如果它将从KiUserCallbackDispatcher 中的__finally 处理程序调用- 这个中断展开过程(ZwCallbackReturn 我怎么说永远不会返回,但就像跳远- 将我们移动到另一个带有另一个堆栈指针和所有寄存器的地方)。即使您从catch 手动调用ZwCallbackReturn - 调用后您会在哪里?
所以需要在KiUserCallbackDispatcher SEH 处理程序之前处理异常,或者如果需要取消分配一些资源,__finally 块的最小值是多少。
KiUserCallbackDispatcher 使用SEH 处理程序调用ZwCallbackReturn,即使异常将在回调中。但是这个 SEH 处理程序的行为可能会受到RTL_USER_PROCESS_PARAMETERS.Flags 中未记录的标志的影响:
伪代码:
void KiUserCallbackDispatcher(...)
{
__try {
//...
} __except(KiUserCallbackExceptionFilter(GetExceptionInformation())) {
KiUserCallbackDispatcherContinue:
ZwCallbackReturn(0, 0, 0);
}
}
void LdrpLogFatalUserCallbackException(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT ContextRecord);
int KiUserCallbackExceptionFilter(PEXCEPTION_POINTERS pep)
{
if ( NtCurrentTeb()->ProcessEnvironmentBlock->ProcessParameters->Flags & 0x80000)
{
return EXCEPTION_EXECUTE_HANDLER;
}
LdrpLogFatalUserCallbackException(pep->ExceptionRecord, pep->ContextRecord);
return EXCEPTION_CONTINUE_EXECUTION;
}
如果未设置此标志 (0x80000)(默认情况下)- 调用 LdrpLogFatalUserCallbackException。它在内部调用UnhandledExceptionFilter,如果它不返回EXCEPTION_CONTINUE_EXECUTION - 调用ZwRaiseException 和STATUS_FATAL_USER_CALLBACK_EXCEPTION 和FirstChance = FALSE(这意味着这个异常没有传递给应用程序 - 仅用于调试器作为最后机会异常)
如果我们设置这个标志 - EXCEPTION_EXECUTE_HANDLER 将从过滤器返回 - RtlUnwindEx 将被调用(TargetIp = KiUserCallbackDispatcherContinue) - 结果__finally 处理程序将被调用并在最后ZwCallbackReturn(0, 0, 0);
在任何情况下,堆栈中高于的函数都不会将异常传递给 SEH,而不是 KiUserCallbackDispatcher(因为这里将处理异常)
所以我们需要在KiUserCallbackDispatcher下面的堆栈中处理异常或将标志设置为0x80000 - 在这种情况下,如果我们在KiUserCallbackDispatcher之前不处理异常 - 我们的__finally块(如果存在)将在@之前执行987654362@完成回调。
SetProcessUserModeExceptionPolicy在最近的windows版本中没有导出(从win8开始),但是这个api的年度代码是下一个(确切的代码):
#define PROCESS_CALLBACK_FILTER_ENABLED 0x1
BOOL WINAPI SetProcessUserModeExceptionPolicy(DWORD dwFlags)
{
PLONG pFlags = (PLONG)&NtCurrentTeb()->ProcessEnvironmentBlock->ProcessParameters->Flags;
if (dwFlags & PROCESS_CALLBACK_FILTER_ENABLED)
{
_bittestandset(pFlags, 19); // |= 0x80000
}
else
{
_bittestandreset(pFlags, 19); // &= ~0x80000
}
return TRUE;
}
测试:
WNDPROC oldproc;
LRESULT CALLBACK newproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)oldproc);
__try {
*(int*)0=0;
//RaiseException(STATUS_ACCESS_VIOLATION, 0, 0, 0);
} __finally {
DbgPrint("in finally\n");
CallWindowProc(oldproc, hwnd, uMsg, wParam, lParam);
}
return 0;
}
void test()
{
RtlGetCurrentPeb()->ProcessParameters->Flags |= 0x80000;
if (HWND hwnd = CreateWindowExW(0, WC_EDIT, L"***", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, NULL, NULL))
{
oldproc = (WNDPROC)SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)newproc);
__try {
ShowWindow(hwnd, SW_SHOW);
}__except(EXCEPTION_EXECUTE_HANDLER){
DbgPrint("no sense. never will be called\n");
}
MSG msg;
while (0 < GetMessage(&msg, hwnd, 0, 0))
{
DispatchMessage(&msg);
}
}
}
尝试评论或取消评论RtlGetCurrentPeb()->ProcessParameters->Flags |= 0x80000;行(或NtCurrentTeb()->ProcessEnvironmentBlock->ProcessParameters->Flags & 0x80000,相同)并比较效果。
无论如何,顶级 SEH 过滤器(在调用 wndproc 之前)永远不会被调用