[Вступление] Хочу сразу заметить, что изначально статья писалась для Windows XP, замечания по Windows Vista/7 в самом конце статьи. Недавно по работе, а именно в рамках разворачивания информационного стенда компании с её сайтом, возникла одна интересная задача - заменить ctrl+alt+del (CAD) на свою комбинацию клавиш, чтоб пользователь не мог никуда уйти из положенной области просмотра и нахулиганить. Казалось бы ничего сложного, просто комбинация клавиш... Однако, поискав в интернетах, я не нашел готового решения - в разных местах люди писали мол комбинация "зашита" в код Windows и поменять её нереал и т.д. Тогда было решено обратиться к дяде Руссиновичу и узнать, что же там за хитрости такие с этой магической комбинацией клавиш. [Кто виноват и что делать?] Открыв небезызвестную книгу Windows Internals, я прозрел - не надо лезть в самые дебри Windows, не надо писать драйверы для перехвата нажатий клавиатуры, всё оказалось вроде не так сложно. После нажатия ctrl+alt+del система заботливо сообщает процессу winlogon.exe о нажатии этой комбинации, и не только этой, после чего winlogon предпринимает действия типа вызова меню в котором можно поменять пользователя, вызвать диспетчер задач и ещё кучу всего. Естественно, было решено перехватывать нашу магическую комбинацию здесь. Итак процесс определен, а как же это сделать? Данный процесс владеет собственным окном, которое имеет класс "SAS window class", а само окно называется "SAS window", ему то и посылаются сообщения о нажатых клавишах. Кстати SAS расшифровывается как Secure Attention Sequence, именно этой безопасной комбинацией и является ctrl+alt+del. Чтобы перехватить данную комбинацию надо неким образом подменить оконную функцию обработки сообщений. И тут на помощь нам приходить старый добрый сплайсинг функций (майкрософт называет это метод detour). [От слов к делу] Итак, нам нужно перехватить оконную функцию winlogon.exe, там некоторым образом обработать сообщение о том, что нажата SAS, и зарегистрировать свою комбинацию клавиш, которая и подменит ctrl+alt+del. Для всех вышеописанных действий необходимо написать инжектор и dll, код которых приведу ниже и поясню пару моментов, которые вызвали затруднения, однако саму процедуру сплайсинга не буду подробно описывать ибо по этой теме есть куча статей, например http://www.rsdn.ru/article/baseserv/IntercetionAPI.xml и http://wasm.ru/series.php?sid=8. Код dll: Code: #include "windows.h" WNDPROC g_OldSASProc; HWND g_hSASwnd; bool flag=true; LRESULT WINAPI NewSASProc( HWND hSAS, UINT msg, WPARAM wParam, LPARAM lParam ) { int id; if(flag) { id=GlobalAddAtom(L"Alt_Shift_A"); RegisterHotKey(g_hSASwnd, id, MOD_ALT | MOD_SHIFT, 0x41); flag=false; } if(WM_HOTKEY == msg){ if( MAKELONG( MOD_CONTROL | MOD_ALT, VK_DELETE ) == lParam ){ return 1; } if( MAKELONG( MOD_CONTROL | MOD_SHIFT, VK_ESCAPE ) == lParam ){ return 1; } if( MAKELONG( MOD_ALT | MOD_SHIFT, 0x41 ) == lParam ){ lParam=MAKELONG(MOD_CONTROL | MOD_ALT, VK_DELETE); return CallWindowProc( g_OldSASProc, hSAS, msg, wParam, lParam ); } } return CallWindowProc( g_OldSASProc, hSAS, msg, wParam, lParam ); } BOOL WINAPI DllMain( HINSTANCE hinstDll, DWORD fdwReason, PVOID fImpLoad ) { if( DLL_PROCESS_ATTACH == fdwReason ){ g_hSASwnd = FindWindow(L"SAS window class", L"SAS window"); if( g_hSASwnd != NULL ) g_OldSASProc = (WNDPROC) SetWindowLong( g_hSASwnd, GWL_WNDPROC, (LONG) NewSASProc ); else MessageBox(NULL, L"Can't get window handle!", L"Error!", 0); } if( DLL_PROCESS_DETACH == fdwReason ){ if( g_hSASwnd != NULL) SetWindowLong( g_hSASwnd, GWL_WNDPROC, (LONG) g_OldSASProc ); } return TRUE; } Что же тут происходит? Когда библиотека загружается в адресное пространство winlogon.exe отрабатывает DllMain с флагом DLL_PROCESS_ATTACH. Тут то мы и заменяем оконную функцию на свою NewSASProc, здесь же я изначально пытался зарегистрировать нажатие своей собственной комбинации клавиш с помощью RegisterHotKey(), однако как выяснилось позже, регистрация горячих клавиш для окна должна производится тем же потоком, что и создало окно, поэтому RegisterHotKey() перекочевало в новую оконную функцию, после чего всё заработало. В NewSASProc, если пользователь нажимает ctrl+alt+del или ctrl+shift+esc (вызов диспетчера задач) мы не делаем ничего, если же нажата комбинация alt+shift+a, то мы передаем управление оригинальной оконной функции, присвоив перед этим lParam значение CAD. Теперь осталось подгрузить нашу библиотеку в winlogon.exe. Для этого потребуется отдельная программа инжектор: Code: #include <windows.h> #include <stddef.h> #pragma pack(1) _declspec(align(1)) struct INJECTORCODE { BYTE instr_push_loadlibrary_arg; //инструкция push DWORD loadlibrary_arg; //аргумент push WORD instr_call_loadlibrary; //инструкция call [] DWORD adr_from_call_loadlibrary; BYTE instr_push_exitthread_arg; DWORD exitthread_arg; WORD instr_call_exitthread; DWORD adr_from_call_exitthread; DWORD addr_loadlibrary; DWORD addr_exitthread; //адрес функции ExitTHread BYTE libraryname[100]; //имя и путь к загружаемой библиотеке }; BOOL InjectDll(DWORD pid, char *lpszDllName) { HANDLE hProcess; BYTE *p_code; INJECTORCODE cmds; DWORD wr, id; //открыть процесс с нужным доступом hProcess=OpenProcess(PROCESS_CREATE_THREAD|PROCESS_VM_WRITE| PROCESS_VM_OPERATION, FALSE, pid); if(hProcess == NULL) { MessageBox(NULL, L"You have not enough rights to attach dlls", L"Error!", 0); return FALSE; } //зарезервировать память в процессе p_code = (BYTE*)VirtualAllocEx(hProcess, 0, sizeof(INJECTORCODE), MEM_COMMIT, PAGE_EXECUTE_READWRITE); if(p_code==NULL) { MessageBox(NULL, L"Unable to alloc memory in remote process", L"Error!", 0); return FALSE; } //инициализировать машинный код cmds.instr_push_loadlibrary_arg = 0x68; //машинный код инструкции push cmds.loadlibrary_arg = (DWORD)((BYTE*)p_code + offsetof(INJECTORCODE, libraryname)); cmds.instr_call_loadlibrary = 0x15ff; //машинный код инструкции call cmds.adr_from_call_loadlibrary = (DWORD)(p_code + offsetof(INJECTORCODE, addr_loadlibrary)); cmds.instr_push_exitthread_arg = 0x68; cmds.exitthread_arg = 0; cmds.instr_call_exitthread = 0x15ff; cmds.adr_from_call_exitthread = (DWORD)(p_code + offsetof(INJECTORCODE, addr_exitthread)); cmds.addr_loadlibrary = (DWORD)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA"); cmds.addr_exitthread = (DWORD)GetProcAddress(GetModuleHandle(L"kernel32.dll"),"ExitThread"); if(strlen(lpszDllName)>99) { MessageBox(NULL, L"Dll Name too long", L"Error!", 0); return FALSE; } strcpy((char*)cmds.libraryname, lpszDllName ); /*После инициализации cmds в мнемонике ассемблера выглядит следующим образом: push adr_library_name ;аргумент ф-ции loadlibrary call dword ptr [loadlibrary_adr] ; вызвать LoadLibrary push exit_thread_arg ;аргумент для ExitThread call dword ptr [exit_thread_adr] ;вызвать ExitThread */ //записать машинный код по зарезервированному адресу WriteProcessMemory(hProcess, p_code, &cmds, sizeof(cmds), &wr); //выполнить машинный код HANDLE z = CreateRemoteThread(hProcess, NULL, 0, (unsigned long (__stdcall *)(void *))p_code, 0, 0, &id); //ожидать завершения удаленного потока WaitForSingleObject(z, INFINITE); //освободить память VirtualFreeEx(hProcess, (void*)p_code, sizeof(cmds), MEM_RELEASE); return TRUE; } int main() { HWND g_hSASwnd; DWORD pid; HDESK hDeskPrev = GetThreadDesktop(GetCurrentThreadId()); HDESK hDesktop = OpenDesktop(L"Winlogon",0,0,MAXIMUM_ALLOWED); SetThreadDesktop(hDesktop); g_hSASwnd = FindWindow(L"SAS window class", L"SAS window"); GetWindowThreadProcessId(g_hSASwnd, &pid); SetThreadDesktop(hDeskPrev); CloseDesktop(hDesktop); InjectDll(pid,"C:\\sas.dll"); return 0; } Инжектор получает доступ к памяти winlogon.exe, записывает туда необходимый код, запускает удаленно поток для выполнения этого кода, который в свою очередь загружает нашу библиотеку при помощи LoadLibrary, и на этом работа заканчивается. Однако, в процессе написания инжектора возникли проблемы по нахождению PID'а winlogon.exe. Казалось бы, нам известно название окна, мы можем получить его хэндл с помощью FindWindow (так же как в библиотеке выше) и, заюзав функцию GetWindowThreadProcessId, получить так необходимый PID. Но вот что-то так не выходило, FindWindow неизменно возвращала 0. И тут на помощь снова пришел дядя Руссинович, который поведал, что данное окно находится в другом десктопе с названием Winlogon, и сначала надо поток привязать к этому десктопу, а потом уже искать нужные окна и т.д. Надо заметить, что в нашей dll не надо выполнять шаманств с дестопами, так как мы итак уже в нужном рабочем столе, ибо в winlogon.exe. Собственно на этом и всё, один нюанс - для доступа к десктопу Winlogon необходимы системные права, посему для запуска инжектора использовалась тулза всем знакомого дяди - PsExec. [Заключение] Таким вот образом была укрощена магическая комбинация CAD. Естественно, данную технику можно использовать как в хороших целях, так и в плохих (например писать winlock), но в данный момент в разработке находятся методы противодействия сплайсингу, так что ждите следующие статьи посвященные этому. Кому интересны подробности разворачивания информационного стенда на основе браузера Opera, милости прошу в ПМ. Всех благ и успехов в освоении глубин операционных систем =) [Замечания/дополнения] Замечания касаются только процесса inject dll в новых версиях Windows. С выходом Windows Vista/7 разработчики постарались залатать различные бреши в безопасности, внедрив технологию UAC и реализовав разделение пользовательских сессий. Попробуем разобраться, что представляют из себя эти виды защиты. Начнем с UAC. В Windows Vista и 7 разработчики решили оставить для пользователей только две группы: сообственно обычные пользователи и администраторы. С группой обычных пользователей все ясно - нет прав, либо иди гуляй, либо получай доступ от имени того у кого есть права (необходимо ввести логин/пароль). Если же в систему входит член группы админов, то для него создается два маркера доступа, один действительно админский позволяющий творить все, что душе угодно, а другой с обычным уровнем доступа. Причем после входа админа в систему первый пользовательский процесс, по умолчанию это Userinit.exe (можно понапихать своих процессов через реестр), запускается именно с маркером обычного уровня доступа, остальные процессы такие как explorer.exe наследуют этот уровень доступа. Сделано это все для того, что бы пользователи могли увидеть, что приложению необходим повышенный уровень привелегий (например для изменения данных в системных директориях и ветках реестра, чем пользуются многие малвари). Чем это может помешать провести inject dll? Да по большому счету ничем, кроме того, что придется согласиться повысить уровень привелегий приложения, кликнув на ОК во всем занкомом окошке. А учитывая, что большинство пользователей, из-за отсутсвия компьютерной грамотности, всегда тычут в OK, либо и вовсе отключают надоедливый UAC, то данный вид защиты не эффективный. Для чего может понадобиться повысить привелегии при инжекте? Для получения привелегии SeDebugPrivilege, которая позволяет выполнять функциию OpenProcess(), вне зависимости от дескриптора защиты открываемого процесса. Итак, а теперь камень преткновения inject dll в Windows Vista/7 - изоляция терминальных сессий. Каждый пользователь, авторизованный в системе теперь имеет собственную сессию, у админа одна, у NT AUTHORITY\SYSTEM другая и соотвественно процессы запущенные от разных пользователей выполняются в разных сессиях (в Windows XP все выполнялось в одной сессии). На что же это влияет при inject dll? Оказывается вам не удасться выполнить CreateRemoteThread(), если вызывающий её процесс и процесс, в котром необходимо удаленно создать поток находятся в разных сессиях, или что тоже самое запущенны от разных пользователей. Если все происходит в одной сессии, т.е. в процессах запущенных одним пользователем, то инжект работает как часы, аналогично Win XP. А как же быть если хочется заинжектиться в системный процесс типа winlogon.exe, в который мы так удачно внедрялись выше под Windows XP? На этот вопрос есть ответ тут. В кратце - авторы предлагают на выбор два метода. Либо использование недокументированной функции NtCreateThreadEx() из ntdll.dll с параметрами взятыми с потолка, скорее всего они были получены в ходе реверса процесса запуска потоков от системы. Либо создавать сервис, который будет запускаться от имени системы и дальше мы сможем инжектиться только в системные процессы за счет него. Первый способ интересен, однако такой функции нет в XP и она недокументирована. Второй способ годится только для инжекта в системные процессы, но не пользьзовательские, да и вообще создание сервиса, его регистрация требует некоторых дополнительных усилий. А теперь одна удивительная вещь - если инжект запускать от имени системы все той же PsExec.exe (для этого запускаем консоль от имени админа и там набираем что-то типа psexec.exe -s -i inject.exe), то все проходит просто на ура, наверно ясно почему, в свете предыдущих объяснений. Однако стоит заметить, что в Windos Vista/7 не получится перехватить CAD кодом написанным выше, по причине того, что Winlogon был изменен и функцией FindWindow(L"SAS window class", L"SAS window") вы окно не найдете, потому что окна с таким классом и именем тупо нет. VERte][, 10.11.11
методы противодействия сплайсингу давно уже разработаны неким Flintta с, тогда ещё более менее уважаемого форума, ксакепа, и его практически готовое решение валяеться на васме.
Включие обратно! Пуск - Выполнить - "gpedit.msc" Там "Конфигурация пользователя" - "Административные шаблоны" - "Система" - "Сделать недоступными средства редактирования реестра" - сделайте в "Отключена". В том же районе ""Конфигурация пользователя" - "Административные шаблоны" - "Система" - "Возможности Ctrl+Alt+Del" - "Запретить диспетчер задач" - в "Отключена".
Как символично Автор Bill Geits )) Но, уважаемый Билл, есть нюанс, например этот http://www.xakep.ru/post/57477/
От себя хочу сделать важное дополнение. Нафига извращаться с шелкодом? Под Win XP kernel32.dll подгружена во все процессы по одинаковому адресу. по этому ход такой 1) открыли процесс 2) записали туда имя DLL с полным путём 3) в своей проге через GetProcAddress получили адрес LoadLibraryA 4) CreateRemoteThread на адрес LoadLibrary а в параметре - адрес памяти где хранится строка имени dll 4) Далее WaitForSingleObject чтобы дождаться заверщения потока 5) GetExitThreadCode (или както так) чтобы получить код возврата поток. Если ноль, значит DLL не грузанулась. Иначе её Адрес в памяти. 6) Освобождаем память занятую под строчку с именем DLL И еще важно знать что под Win 7 такое не прокатит. потому что там запрещено создавать удаленные потоки в чужом адресном пространстве. (хотя контекст перебивать можно вроде)
в плохих целях особо не используешь,т.к. отслеживание оконного класса ДЗ и сокрытие его,либо банальное отключение произведут нужный эффект.А за статейку +.
Ломаю голову.. а что мешало просто ограничить юзерам права? Видать были на то причины. За статью спасибо, думаю использовать это можно не только в целях CAD.
Добавил в статью информацию о проведении инжекта в Windows Vista/7 (раздел замечания/дополнения), он вполне возможен. В данный момент пытаюсь программно поправить номер сессии в токене, чтоб можно было не используя psexec.exe проводить инжект в системные процессы. Вращаюсь возле SetTokenInformation, однако для правки номера сессии нужна привелегия Act as Part of the Operating System, которую я пока не знаю, как получить, если есть идеи - велкам. Второй вариант, получить токен через OpenProcessToken() у системного процесса и сделать его дубль для запуска своего процесса, пока ещё этот метод не пробовал ибо придется писать дополнительный лаунчер для инжектора.