Блокируем Ctrl-Alt-DelИсточник: pblog
Доброго времени суток. На многих форумах программистов очень часто встречается вопрос "Как заблокировать комбинацию клавиш Ctrl-Alt-Del?". В этой статье я расскажу, как можно реально заблокировать комбинацию клавиш Ctrl-Alt-Del без каких-либо извращений с заменой файлов и прочего. Статья не рассчитана на новичков, читатель этой статьи как минимум должен знать, что такое инжект и запуск удалённых потоков. Доброго времени суток. На многих форумах программистов очень часто встречается вопрос "Как заблокировать комбинацию клавиш Ctrl-Alt-Del?". В этой статье я расскажу, как можно реально заблокировать комбинацию клавиш Ctrl-Alt-Del без каких-либо извращений с заменой файлов и прочего. Статья не рассчитана на новичков, читатель этой статьи как минимум должен знать, что такое инжект и запуск удалённых потоков. Первое что хотелось бы сказать - это то, что комбинация клавиш Ctrl-Alt-Del предназначена не только для вызова диспетчера задач. В первую очередь она предназначена для вызова окна процесса Winlogon, в котором у нас имеется возможность завершить текущий сеанс, завершить работу компьютера, и, разумеется, вызвать диспетчер задач. Те, кто ставил в WinXP настройки повышенной безопасности, тем знакомо вот это окно. Сначала подумаем, как можно заблокировать диспетчер задач Windows. Многим сразу придёт в голову идея об удалении или замене файла taskmgr.exe. Во-первых это не так просто так как в Windows XP введена система защиты файлов и при любом изменении её системных файлов она их сразу восстановит. Резервные копии важных файлов находятся в папке system32dllcache. Папка system32 и system32dllcache взаимно связаны и восстанавливаются, друг от друга удаление из system32dllcache влечёт восстановление из system32. Поэтому надо удалять (или заменять) файл taskmgr.exe из обеих папок одновременно. Ладно, мы удалили (или заменили) этот файл, но радость не долгая, выводится сообщение о повреждении системных файлов Windows и просьба вставить диск с дистрибутивом для обновления системы. В принципе и это тоже можно обойти. К чему я веду? К тому, что замена или удаление файлов это не вариант, да и "через жопу" это получается. Более внимательные пользователи заметят, что диспетчер задач Windows не запускается два раза, т.е. если он уже запущен, то во второй раз он уже не запустится. И возникает идея о скрытии окна диспетчера, окно скрыто, а он работает и в результате, как ни нажимай Ctrl-Alt-Del или Ctrl-Shift-Esc мы ничего не получим. Следующий код блокирует и разблокирует диспетчер задач Windows. uses shellapi; procedure TForm1.Button1Click(Sender: TObject); var TDWH:THandle; begin TDWH:=FindWindow(nil,'Диспетчер задач Windows'); if TDWH=0 then ShellExecute(0,'open','taskmgr.exe',nil,nil,SW_HIDE) else ShowWindow(TDWH,SW_HIDE); end; procedure TForm1.Button2Click(Sender: TObject); var TDWH:THandle; begin TDWH:=FindWindow(nil, 'Диспетчер задач Windows'); ShowWindow(TDWH,SW_SHOWNORMAL); end; Всё очень просто. Также вдобавок можно перемещать окно диспетчера далеко за пределы экрана. Итак, задача блокировки диспетчера задач успешно решена. Но наша цель это комбинация клавиш Ctrl-Alt-Del или Ctrl-Shift-Esc. Первая идея, которая возникает, когда надо перехватить комбинации клавиш Ctrl-Alt-Del или Ctrl-Shift-Esc - это воспользоваться стандартным механизмом хуков. Но те, кто уже пытался так сделать, скажут, что через стандартный механизм хуков нельзя перехватить ни одну из важных комбинаций. И возникает такое ощущение, что эти комбинации можно перехватить разве что, написав драйвер-фильтр режима ядра. Но есть другой способ. Итак, наша цель - это процесс winlogon.exe, этот процесс управляет входом пользователей в систему и выходом из нее. Процесс winlogon.exe один из основных процессов отвечающих за безопасность системы. Этот процесс нельзя завершить из диспетчера задач, но можно завершить любой другой сторонней программой. Вот только после завершения winlogon.exe сразу буден сгенерирован BSOD. Итак, процесс winlogon.exe очень важен. В нём находится окно с заголовком "SAS window" и классом "SAS Window class" именно этому и только этому окну посылается сообщение WM_HOTKEY, когда были нажаты комбинации Ctrl-Alt-Del или Ctrl-Shift-Esc и ещё несколько важных комбинаций. Найти это окно через функцию FindWindow нельзя, вернее можно, но только будучи в контексте процесса winlogon. Из обычной программы найти это окно нельзя. Итак, наша задача подменить оконную функцию окна "SAS window" и отлавливать в нём сообщения WM_HOTKEY, если нажаты эти волшебные комбинации, то просто не вызываем оригинальную оконную функцию. Но чтобы, всё это сделать, нам надо быть в контексте процесса winlogon. Самый простой способ - это внедрить в него нашу DLL. Вот собственно и код этой DLL library HookDLL; uses Windows,Messages; var OldWndProc: pointer; ThreadID:DWORD; const CtrlAltDel_CODE = (VK_DELETE shl 16) or (MOD_CONTROL or MOD_ALT); CtrlShiftEsc_CODE = (VK_ESCAPE shl 16) or (MOD_CONTROL or MOD_SHIFT); function GethWnd: HWND; begin result:= FindWindow('SAS Window class','SAS window'); end; function NewWndProc(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; begin if msg=WM_HOTKEY then begin if (lParam=CtrlAltDel_CODE) or (lParam = CtrlShiftEsc_CODE) then result:= 0 else result:= CallWindowProc(OldWndProc, hWnd, Msg, wParam, lParam); end else result:= CallWindowProc(OldWndProc, hWnd, Msg, wParam, lParam); end; procedure SetWndProc(hWnd: HWND); begin OldWndProc:= pointer(SetWindowLong(hWnd, GWL_WNDPROC, cardinal(@NewWndProc))); end; procedure UnSetWndProc(hWnd: HWND); begin SetWindowLong(hWnd, GWL_WNDPROC, cardinal(OldWndProc)); end; function HookThread(PARAM1:DWORD):DWORD; stdcall; var ThrID:DWORD; begin Result:=0; SetWndProc(GethWnd); sleep(60*1000); UnSetWndProc(GethWnd); sleep(10); CreateThread(nil,0,GetProcAddress(GetModuleHandle('kernel32'),'FreeLibrary'),pointer(hInstance),0,ThrID); end; begin CreateThread(nil,0,@HookThread,nil,0,ThreadID); end. Код этой DLL блокирует комбинации ровно на минуту, потом DLL выгружается из памяти winlogon. Так же можно изменить функцию NewWndProc так чтобы перенаправить заблокированные комбинации на другие, также можно заблокировать любую комбинацию клавиш которую нельзя перехватить через стандартный механизм хуков. А вот и код, который загрузит нашу DLL в процесс winlogon. function Start(ProcessID: Cardinal; DllFileName: string): Boolean; var hProcess, hTh: THandle; BytesWritten, ThreadID, DllNameLen: Cardinal; LoadLibraryProc, MemPtr: Pointer; ExitCode: DWord; begin Result := false; SetDebugPriv(); hProcess := OpenProcess(PROCESS_CREATE_THREAD or PROCESS_VM_OPERATION or PROCESS_VM_WRITE,true, ProcessID); if hProcess = 0 then exit; DllNameLen := Length(DllFileName) + 1; MemPtr := VirtualAllocEx(hProcess, nil, DllNameLen, MEM_COMMIT, PAGE_READWRITE); if MemPtr nil then begin //3 if WriteProcessMemory(hProcess, MemPtr, PChar(DllFileName), DllNameLen, BytesWritten) then begin //2 LoadLibraryProc := GetProcAddress(GetModuleHandle('kernel32.dll'), 'LoadLibraryA'); hTh := CreateRemoteThread(hProcess, nil, 0, LoadLibraryProc, MemPtr, 0, ThreadID); if hTh 0 then begin //1 if (WaitForSingleObject(hTh, INFINITE) = WAIT_OBJECT_0) and GetExitCodeThread(hTh, ExitCode) then Result := ExitCode 0; CloseHandle(hTh); end; //1 end; //2 VirtualFreeEx(hProcess, MemPtr, 0, MEM_RELEASE); end; //3 CloseHandle(hProcess); end; Эта процедура принимает ID процесса winlogon и путь к нашей DLL. Разумеется, до этого надо позаботиться о получении привилегии "SeDebugPrivilege" чтобы можно было открыть процесс winlogon c нужным нам доступом. Полный код программы и DLL смотрите в архиве с исходниками в конце статьи. Итак, приступим. Нам надо будет внедрить в процесс winlogon код, который будет находить окно и изменять оконную процедуру целевого окна и код самой оконной процедуры. Разумеется, внедряемый код должен быть базонезависимым, так как мы заранее не знаем, по какому адресу мы его скопируем, и ему нужны адреса используемых им API функций. На Delphi сделать это будет уже проблематично. И поэтому сделаем мы это на ассемблере, так как на нём проще всего написать базонезависимый код. Сначала приведу код, который будет выполняться в контексте процесса winlogon section '.remcode' code executable readable writeable ;---------------------------------------- ; REMOTE CODE START --------------------- ;---------------------------------------- RemoteCodeStart: call .delta .delta: pop ebp sub ebp, .delta lea eax, [ebp+SAS_WndTitle] push eax lea eax, [ebp+SAS_WndClass] push eax call [ebp+pFindWindowA] mov [ebp+SAS_WndHandle], eax test eax, eax jnz @f jmp .thrend @@: lea eax, [ebp+NewWindowProc] push eax push GWL_WNDPROC push [ebp+SAS_WndHandle] call [ebp+pSetWindowLongA] mov [ebp+OldWindowProc], eax push 10000 call [ebp+pSleep] push [ebp+OldWindowProc] push GWL_WNDPROC push [ebp+SAS_WndHandle] call [ebp+pSetWindowLongA] .thrend: push 0 call [ebp+pExitThread] proc NewWindowProc hwnd, wmsg, wparam, lparam call .delta .delta: pop eax sub eax, .delta ; delta offset -> eax cmp [wmsg], WM_HOTKEY jnz .continue cmp [lparam], CtrlAltDel_CODE jz .exit_proc cmp [lparam], CtrlShiftEsc_CODE jz .exit_proc .continue: push [lparam] push [wparam] push [wmsg] push [hwnd] push [eax+OldWindowProc] call [eax+pCallWindowProcA] jmp .return .exit_proc: xor eax, eax .return: ret endp SAS_WndClass db 'SAS Window class',0 SAS_WndTitle db 'SAS window',0 SAS_WndHandle dd 0 OldWindowProc dd 0 pFindWindowA dd 0 pSetWindowLongA dd 0 pCallWindowProcA dd 0 pExitThread dd 0 pSleep dd 0 RemoteCodeEnd: RemoteCodeSize equ RemoteCodeEnd-RemoteCodeStart ;---------------------------------------- ; REMOTE CODE END ----------------------- ;---------------------------------------- В начале обоих процедур мы сначала вычисляем дельта-смещение, дельта смещение нам нужно, для того чтобы узнать, на сколько отличаются адреса переменных перемещённых с нашим кодом от тех адресов, которые поставил нам компилятор во время компиляции. Зная, дельта-смещение мы можем спокойно обращаться к переменным, не боясь возникновения ошибок доступа к памяти. Приводить полный код программы, я думаю, смысла не имеет, поэтому только приведу код, который подготавливает переменные, копирует код и запускает поток в процессе winlogon invoke OpenProcess, PROCESS_CREATE_THREAD or PROCESS_VM_OPERATION or PROCESS_VM_WRITE, FALSE, [proc_entry.th32ProcessID] mov [WinlogonProcHandle], eax test eax, eax jnz @f jmp .exit @@: mov eax, [FindWindow] mov [pFindWindowA], eax mov eax, [SetWindowLong] mov [pSetWindowLongA], eax mov eax, [ExitThread] mov [pExitThread], eax mov eax, [CallWindowProc] mov [pCallWindowProcA], eax mov eax, [Sleep] mov [pSleep], eax invoke VirtualAllocEx, [WinlogonProcHandle], 0, RemoteCodeSize, MEM_COMMIT+MEM_RESERVE, PAGE_EXECUTE_READWRITE mov [RemoteThreadBaseAddress], eax invoke WriteProcessMemory, [WinlogonProcHandle], eax, RemoteCodeStart, RemoteCodeSize, Writed invoke CreateRemoteThread, [WinlogonProcHandle], 0, 0, [RemoteThreadBaseAddress], 0, 0, RemoteThreadID .exit: invoke ExitProcess, 0 Сначала мы открываем процесс winlogon с нужным нам уровнем доступа (код получения привилегий и получения ID winlogon я не привёл). Потом заполняем переменные, в которых будут храниться адреса используемых API функций, потом выделяем кусок памяти в процессе winlogon, потом копируем туда код, который будет выполняться в его контексте, потом запускаем удалённый поток. Полный код программы приведён в архиве с исходниками к этой статье. |