Дело было на XP... На днях прикидывал пути решения регулярной на форумах задачки: скрытный (в смысле - скрыв окна) запуск приложения. И один из вариантов с WH_CBT преподнес сюрприз, отмеченный мнемоникой в заголовке. Суть в том, что совершенно легальные действия по изменению Z-order после нажатия стандартного сочетания Win+D или соответствующей кнопки на Quick Launch приводят к немедленной перезагрузке с извещением о серьезной ошибке... В аттаче - минимальный рабочий пример (+ исходник): обычный шаблон создания простого окна, только перед вызовом CreateWindowEx - в своем потоке ставим хук WH_CBT, на HCBT_CREATEWND устанавливаем наше окно в самый низ Z-order, и затем хук снимаем. Вот и все. Полученное окно спрятано за "рабочий стол", поэтому на таскбаре оставлена кнопка, контроля ради. Ну, и теперь - Win+D ! P.S. Проверил только на XP-pro-sp2, и, конечно, очень интересует, как там на других WinOS? Не поленитесь, дайте знать.
Не все уязвимости эксплуатируемы (не все что катится, то круглое.. даже дополнить по смыслу). Но ошибка очень интересна. Браво kero.
Спасибо, братцы, за внимание! А кстати, сначала и хотел обозвать заметку именно "Z-уязвимость", но потом решил, что не так поймут
Вариация: 1) SetWindowsHookEx(WH_CBT,offset HookProc,0,(GetCurrentThreadId)) - заменено на SetWindowsHookEx(WH_CBT,offset HookProc,hInst,0) 2) GetClassName(wParam,addr buf,MAX_PATH) + lstrcmpi(addr buf,offset _wnd) - заменено на lstrcmpi(CBT_CREATEWND.CREATESTRUCT.lpszClass,offset _wnd) У меня (XP-pro-sp2) BSOD сохранился, а у вас?
kero на XP SP3 благополучуно запускается, так же завершается. бсода нет, но и окна тоже. если запускать в ольке - окно есть. бсода нет.
Windows 7 Build 7600 x86 (physical host) - BSOD'а нет на обеих версиях; окно присутствует, по Win-D просто сворачивается.
AntiB Derek RamMerLabs , приветствую! Я правильно понял - все вами сказанное относится к варианту #2 ? Или и к #1 ? Уточните, plz. А пока - #3, с MB (грубо, но коротко): Код (Text): .data _wnd db "wnd",0 .data? hhook dd ? .code HookProc proc uses ebx edi nCode:UINT,wParam:WPARAM,lParam:LPARAM .if nCode==HCBT_CREATEWND mov ebx,lParam assume ebx:PTR CBT_CREATEWND mov edi,[ebx].lpcs assume edi:PTR CREATESTRUCT .if [edi].lpszClass==WC_DIALOG invoke GetDesktopWindow mov edx,eax invoke GetWindow,eax,GW_CHILD invoke GetWindow,eax,GW_HWNDLAST ; (Progman) invoke GetWindow,eax,GW_CHILD ; (SHELLDLL_DefView) mov [ebx].hWndInsertAfter,eax .endif assume edi:nothing assume ebx:nothing xor eax,eax .else invoke CallNextHookEx,hhook,nCode,wParam,lParam .endif ret HookProc endp start: invoke GetModuleHandle,0 invoke SetWindowsHookEx,WH_CBT,offset HookProc,eax,0 mov hhook,eax invoke MessageBox,0,offset _wnd,offset _wnd,0 invoke UnhookWindowsHookEx,hhook invoke ExitProcess,eax end start
#1 #2 #3 ОС Windows XP Pro SP3 x86 (VMware) BSOD BSOD BSOD (msgbox скрыт) Windows 2000 Pro SP4 X86 (VMware) BSOD BSOD BSOD (msgbox скрыт) Windows Server 2003 SP2 x86 (VMware) BSOD BSOD BSOD (msgbox скрыт) Windows 7 Build 7600 x86 (physical host) - - - (msgbox отображается)
Derek Ага, спасибо. Вы - первый, кто привел пример виндов без бсод Значит, Windows 7 Build 7600 x86 ...
kero, спасибо за информацию. Всем остальным тоже спасибо. Посмотрели, оценили, сделали небольшой write-up. (Тред захвачен, лол). Part 1/2 Код (Text): kd> vertarget Windows 7 Kernel Version 7600 MP (1 procs) Free x86 compatible Product: WinNt, suite: TerminalServer SingleUserTS Built by: 7600.16385.x86fre.win7_rtm.090713-1255 Machine Name: Kernel base = 0x82854000 PsLoadedModuleList = 0x8299c810 Debug session time: Wed Oct 7 21:40:26.171 2009 (GMT+3) System Uptime: 0 days 0:09:12.800 kd> g Access violation - code c0000005 (!!! second chance !!!) win32k!ValidateParentDepth+0x58: 8f4445dd cmp dword ptr [eax+2Ch],0 kd> kn99 # ChildEBP RetAddr 00 8cd01c9c 8f440dc4 win32k!ValidateParentDepth+0x58 01 8cd01cb4 8f41848e win32k!ValidateNewParent+0x28 02 8cd01cf4 8f418799 win32k!xxxSetParent+0x66 03 8cd01d24 8289742a win32k!NtUserSetParent+0x89 04 8cd01d24 772664f4 nt!KiFastCallEntry+0x12a 05 0020fba4 76b5c6a2 ntdll!KiFastSystemCallRet 06 0020fba8 75d31490 USER32!NtUserSetParent+0xc 07 0020fbc8 75d3153a SHELL32!CDesktopBrowser::_SwapParents+0x30 08 0020fbfc 75d315d8 SHELL32!CDesktopBrowser::_Raise+0xd5 09 0020fc14 75bf6749 SHELL32!CDesktopBrowser::_OnRaise+0x26 0a 0020fc44 75c28423 SHELL32!CDesktopBrowser::_WndProcBS+0x772 0b 0020fc64 76b686ef SHELL32!CDesktopBrowser::s_DesktopWndProc+0x67 0c 0020fc90 76b68876 USER32!InternalCallWinProc+0x23 0d 0020fd08 76b689b5 USER32!UserCallWinProcCheckWow+0x14b 0e 0020fd68 76b68e9c USER32!DispatchMessageWorker+0x35e 0f 0020fd78 75c2903e USER32!DispatchMessageW+0xf 10 0020fdac 75c28bfb SHELL32!CDesktopBrowser::_PeekForAMessage+0x153 11 0020fdc8 75b8b497 SHELL32!CDesktopBrowser::_MessageLoop+0x1e 12 0020fdd4 007930b9 SHELL32!SHDesktopMessageLoop+0x29 13 0020fe80 0079aa68 Explorer!wWinMain+0x54a 14 0020ff14 75af1174 Explorer!_initterm_e+0x1b1 15 0020ff20 7727b3f5 kernel32!BaseThreadInitThunk+0xe 16 0020ff60 7727b3c8 ntdll!__RtlUserThreadStart+0x70 17 0020ff78 00000000 ntdll!_RtlUserThreadStart+0x1b Можно видеть, что бсод получаем, когда explorer.exe пытается переназначить родителя одному из окон. Зачем он это делает, не суть есть важно, потому что на самом деле это действие лишь манифистирует проблему, возникшую раньше. BOOL win32k!ValidateParentDepth(PWND pwnd, PWND pwndNewParent); pwnd – указатель на структуру окна, родителя которой предстоит сменить. pwndNewParent – указатель на структуру окна, которое должно стать новым родителем для pwnd. В данном случае pwnd указывает на окно с классом "SHELLDLL_DefView", являющееся дочерним относительно top-level окна "Program Manager" (его класс имеет имя "Progman"). Его потенциальный новый родитель (pwndNewParent) – top-level окно класса "WorkerW". Назначение функции - проверить, не превысит ли максимальная глубина новосоздаваемых связей Родитель-Потомок значения переменной win32k!gNestedWindowLimit ( == 0x32 по умолчанию). Если превысит, то функцию вернёт "FALSE" и репарентирование не будет произведено. Максимальная глубина считается как число вертикальных связей от DesktopWindow до pwndNewParent + число вертикальных связей от pwnd до самого нижнего элемента его поддерева. Расклад окон, слегка похожий на реальный: Применяемая система связей между окнами позволяет выполнить обход дерева (который нужен для узнавания глубины) без использования рекурсии (и без её эмуляции через списки и т.п.), чем и пользуется процедура ValidateParentDepth(). Так вот. В этой функции имеется небольшая ошибка, которая не представляет совершенно никакой угрозы безопасности: если у pwnd есть дочерние окна, то вместо максимальной глубины поддерева с вершиной в pwnd подсчитывается максимальная глубина среди поддеревьев, начинающихся с pwnd и со следующих за ним саблингов. Например, мы хотим присвоить в качестве нового родителя для Child A1 окно Child B3. Правильный подход: Child B1 <–> Window B <–> Desktop Window [2] + Child A1 <–> Child A11 [1] == 3. Реальный (неверный) подход: Child B1 <–> Window B <–> Desktop Window [2] + max( Child A1 <–> Child A11 [1], Child A2 <–> Child A21 [1] + Child A22 <–> Child A221 [1] == [2] ) == 4. А вот если бы мы присваивали Child B3 в качестве родителя для Child A2, то оба подхода дали бы ответ "4". Эта ошибка очень незначительна, однако если бы её не было, то представленный вариант кода не вызвал бы бсод. kero удалось поломать нормальный расклад окон: Видно, что окно "wnd" является следующим в Z-порядке относительно "SHELLDLL_DefView", однако у этих двух окон разные родители! Такого в принципе быть не должно – и, вероятно, в дебажной версии win32k один бы наткнулся на ассерт едва попытавшись провернуть нечто подобное. Условием конца обхода для ValidateParentDepth() должно было бы являться достижение pwnd – однако из-за ошибки таким условием является достижение _родителя_ pwnd (настоящего, ещё не изменённого). Поскольку порядок окон поломан, функция "пропускает" окно Progman'а и продолжает подниматься вверх, доходя до самого верхнего DesktopWindow, далее до нуля, и, наконец, пытается прочитать указатель на следующего сиблинга у нуль-окна, чем вызывает фатальную ошибку страницы. Интересно, что, даже аллоцировав память по нулевому адресу и создав там подложную оконную структуру, мы ничего не добъёмся от рассмотренной процедуры – ValidateParentDepth() ничего не изменяет и вызывает лишь одну функцию, на раннем этапе выполнения. Необходимо рассматривать корень проблемы. TLDR: Функция ValidateParentDepth() содержит незначительную ошибку, благодаря которой проявилась ошибка реальная. Эксплуатация незначительной ошибки видится невозможной.