Блокируем Ctrl-Alt-Del

Источник: pblog

Доброго времени суток. На многих форумах программистов очень часто встречается вопрос "Как заблокировать комбинацию клавиш Ctrl-Alt-Del?". В этой статье я расскажу, как можно реально заблокировать комбинацию клавиш Ctrl-Alt-Del без каких-либо извращений с заменой файлов и прочего. Статья не рассчитана на новичков, читатель этой статьи как минимум должен знать, что такое инжект и запуск удалённых потоков.

Доброго времени суток. На многих форумах программистов очень часто встречается вопрос "Как заблокировать комбинацию клавиш Ctrl-Alt-Del?". В этой статье я расскажу, как можно реально заблокировать комбинацию клавиш Ctrl-Alt-Del без каких-либо извращений с заменой файлов и прочего. Статья не рассчитана на новичков, читатель этой статьи как минимум должен знать, что такое инжект и запуск удалённых потоков.

   Первое что хотелось бы сказать - это то, что комбинация клавиш Ctrl-Alt-Del предназначена не только для вызова диспетчера задач. В первую очередь она предназначена для вызова окна процесса Winlogon, в котором у нас имеется возможность завершить текущий сеанс, завершить работу компьютера, и, разумеется, вызвать диспетчер задач. Те, кто ставил в WinXP настройки повышенной безопасности, тем знакомо вот это окно.
Окно безопасности Windows NT
   Итак, блокировка замена или удаление либо замена его на свой файл самого диспетчера задач Windows (файл taskmgr.exe в системной папке windows) просто делает невозможным его вызов, но по-прежнему (при определённых настройках) комбинация Ctrl-Alt-Del работает и вышеуказанное окно безопасности по-прежнему выводится. А вызвать диспетчер задач можно также через комбинацию клавиш Ctrl-Shift-Esc без вывода этого окна.

   Сначала подумаем, как можно заблокировать диспетчер задач 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 смотрите в архиве с исходниками в конце статьи.
Итак, блокировка Ctrl-Alt-Del и Ctrl-Shift-Esc реализована. Но некоторым не нравится использование DLL. И как всегда возникает вопрос "А можно ли всё сделать без DLL". Ну, разумеется, можно все это сделать и без DLL, через инжект нашего кода в процесс winlogon.

   Итак, приступим. Нам надо будет внедрить в процесс 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, потом копируем туда код, который будет выполняться в его контексте, потом запускаем удалённый поток. Полный код программы приведён в архиве с исходниками к этой статье.
   Наступил статьи конец, кто дочитал тот молодец. Итог, мы написали две программы-каркаса, которые блокируют комбинацию Ctrl-Alt-Del.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=21841