Що може зробити з віндового консоллю просунутий користувач? Напевно, випадково відформатувати свій гвинт, глибоко засмутитися і покликати на допомогу грамотного сусіда. Справжній же хакер поставить її собі на службу. Так, що вона дозволить йому досягти заповітної мети - невидимості для антивірусів. Та і не тільки…
Стандартна консоль Windows (так-так, та сама, яка з'являється, скажімо, при виклику cmd.exe, вічний об'єкт знущань линуксоидов) - здавалося б, що може бути нудніше? Але поспішу тебе запевнити: консоль в Windows - вкрай цікава і цікава штука, покопатися в її нутрощах буде не зайвим. Визначимося відразу (а то деякі можуть і не зрозуміти, про що мова) - нас в даній статті буде цікавити не доступ до MS-DOS або хитрощі командного рядка в Windows. Йтиметься про те, як взагалі існує те саме чорне вікно.
Консоль - цілком і повністю дітище CSRSS, а] [вже неодноразово писав про цю хитрою підсистемі Windows. Деяким читачам може здатися, що все це хоч і пізнавально, але досить нудно з точки зору хака. Однак раджу дочитати статтю до кінця, там точно є над чим замислитися.
Взаємодія між процесами
ОС Windows надає розробнику багатий набір інструментів для забезпечення взаємодії між процесами - це клавіатура, файли, пайпи (іменовані канали), колективна пам'ять, LPC / RPC, COM, сокети і ще дещо. Всі вони більш-менш добре документовані, зупинятися на них сенсу немає (для загального розвитку - ru.wikipedia.org/wiki/Межпроцессное_взаимодействие). Проте, мало хто замислювався, що консоль володіє таким чарівним властивістю, як забезпечення взаємодії між процесами. І це властивість дісталося консолі від підсистеми CSRSS.
Як саме консоль може виявитися в буфері між двома процесами? Виявляється, дуже легко. Наприклад, якась програма створює нову консоль викликом API-функції AllocConsole. А друга програма (читай - процес) викликає AttachConsole і таким чином приєднується до «текстовому інтерфейсу» консолі.
Що виходить: два об'єкти-процесу володіють третім об'єктом, що належить зовнішньому процесу csrss.exe. Далі, отримавши доступ до консолі, перші два «сторонніх» процесу можуть легко змінювати параметри консолі - наприклад, позицію курсора, розмір вікна консолі або його (вікна) назва. Все це проробляється викликом добре документованих функцій SetConsoleCursorInfo, SetConsoleCursorPosition, SetConsoleTitle (або їх Get-еквівалентами).
З вищесказаного спостережливий читач може зробити висновок, що через csrss.exe можна влаштувати обмін інформацією для двох або декількох процесів. Яким чином? Так через титл (назва вікна) консолі - він за один раз може вмістити 65535 байт, при цьому потенційна швидкість передачі даних в такому випадку буде дуже і дуже висока. Правда, при цьому доведеться мати на увазі, що єдиний тип даних, який підходить для обміну інформацією між двома процесами за допомогою CSRSS - це текстові рядки. Тому розробнику доведеться використовувати додаткові прийоми, щоб передавати таким чином бінарні дані.
CTRL + C - знайома ситуація?
Ти ніколи не замислювався, як консоль реагує на комбінацію CTRL + C, яка скасовує поточне виконання потоку? Взагалі консоль реагує на п'ять CTRL + ... подій. Перше - це CTRL_C_EVENT, що сигналізує про натискання клавіш CTRL + C. Друге - CTRL_BREAK_EVENT, яке використовується дебаггера. Третє - CTRL_CLOSE_EVENT, повідомляє всім процесам, спільно використовують консоль, що лавочка прикрилася. CTRL_LOGOFF_EVENT надсилається всім процесам, якщо користувач виходить з системи. Ну і нарешті, CTRL_SHUTDOWN_EVENT, яке говорить, що система вимикається. Перші два сигнали можуть бути отримані при натисканні відомих клавіш на клавіатурі або програмним способом - шляхом виклику API GenerateConsoleCtrlEvent.
Вкрай цікавий механізм, який забезпечує обробку всіх цих CTRL-подій, але від знання про існування сигналу НЕ буде користі, якщо програма не в змозі якось ці самі сигнали обробляти. І тут на допомогу приходить функція kernel32.dll! SetConsoleCtrlHandler, викликом якої можна встановити обробник CTRL-сигналів. Але там не все так просто.
Для повного з'ясування картини нам потрібно зрозуміти наступне. В контексті якого потоку відбувається виконання зареєстрованого обробника?
Можливо, це початковий потік процесу, або новостворений, або взагалі з'явився фіг знає звідки? Для того, щоб відповісти на ці запитання, Відмотаємо плівку назад, на момент створення консолі, і подивимося, що відбувається при виклику AllocConsole. Виклик цієї функції форвардом призводить до виклику winsrv! SrvAllocConsole. Ця функція, в свою чергу, передається в якості параметрів два покажчика на функції kernel32! CtrlRoutine і kernel32! PropRoutine (після чого слід виклик CsrClientCallServer з внутрішнім кодом операції 0x20224). А потім відбувається найцікавіше - при отриманні будь-якого CTRL-сигналу CSRSS створює новий (!) Потік в контексті пріаттаченного до консолі процесу: winsrv! ProcessCtrlEvents - winsrv! CreateCtrlThread - winsrv! InternalCreateCallba ckThread - kernel32! CreateRemoteThread.
Новий потік буде мати точкою входу той самий покажчик на CtrlRoutine.
хакерські смакоту
Описаний механізм, коли CSRSS рулить обработчиками консолі, можна використовувати в дуже популярною серед розробників малварі завданню - як створити, приховати або замаскувати новий потік непомітно для проактівок або АВЕР. У чесних програмах для створення нових потоків зазвичай використовують відомі функції CreateThread (Ex). Але якщо ти хочеш приховати цей факт, можна поступити наступним чином: створюємо нову консоль, реєструємо один або кілька обробників сигналів, після чого програмно генеруємо сигнали Ctrl + C або Ctrl + Break, щоб створити новий потік. Завдяки API-інтерфейсу будь-яка програма легко може реєструвати або видаляти обробники сигналів. Таким чином будь-який процес отримує недокументовану можливість підсистеми CSRSS, рівну по силі прямому викликом CreateThread (Ex).
Схематично створення потоку описаним методом буде виглядати приблизно так:
AllocConsole ();
SetConsoleCtrlHandler (threadHandler1, TRUE);
SetConsoleCtrlHandler (threadHandler2, TRUE);
GenerateConsoleCtrlEvent (CTRL_C_EVENT, GetCurrentProcessId ());
// Тут буде виконаний код
threadHandler2 (CTRL_C_EVENT)
// Тут буде виконаний код
threadHandler1 (CTRL_C_EVENT)
SetConsoleCtrlHandler (threadHandler1, FALSE);
SetConsoleCtrlHandler (threadHandler3, TRUE);
GenerateConsoleCtrlEvent (CTRL_BREAK_EVENT, GetCurrentProcessId ());
// Тут буде виконаний код
// threadHandler3 (CTRL_BREAK_EVENT)
// Тут буде виконаний код
// threadHandler2 (CTRL_BREAK_EVENT)
FreeConsole ();
Здорово, правда? І жоден Авер не впізнає про створення нових потоків. Але це тільки початок :). Як вже було сказано вище, завдяки API-функції AttachConsole тепер будь-яка програма може отримати доступ до текстового інтерфейсу консолі. І незважаючи на те, що в кожен момент часу тільки один процес може бути «власником» консолі, всі інші процеси можуть повністю контролювати саме вікно і використовувати всі функції для управління консоллю. При цьому такі процеси не тільки можуть управляти консоллю, їм ще й повідомлення про події консолі будуть приходити.
Таким от нехитрим чином робимо висновок - використання API-функції AttachConsole в кінцевому підсумку може служити своєрідною альтернативою CreateRemoteThread!
Дивимося, як це робиться:
Для цього прикладу варто відзначити одну особливість: в описаному випадку сигнал CTRL_C_EVENT працювати не буде, потрібно використовувати CTRL_BREAK_EVENT. Крім того, викликає функції GenerateConsoleCtrlEvent в стані лише ініціювати створення потоків, але він не зможе проконтролювати результат їх виконання.
Уважний читач може згадати, що при виклику основної функції створення консолі winsrv! SrvAllocConsole їй передаються два покажчика на функції CtrlRoutine і PropRoutine.
З CtrlRoutine ми начебто розібралися, але до чого тут PropRoutine? Все просто - PropRoutine відповідає за обробку властивостей вікна. Коли користувач намагається змінити властивості вікна консолі, він вибирає відповідне меню, встановлює обрану опцію і підтверджує вибрані зміни. Начебто, нічого складного, проте в надрах системи знову розгортаються дуже цікаві події.
У той самий момент, коли користувач клацає на меню «Властивості» консольного вікна, одна з функцій управління вікна (а саме winsrv! ConsoleWindowProc) отримує віконне повідомлення з такими параметрами:
- uMsg = WM_SYSCOMMAND
- wParam = 0xFFF7
- lParam = undefi ned
Що відбувається далі? Запускається механізм проектування файлу в пам'ять: викликаються функції NtCreateSection, потім NtMapViewOfSection, потім проекція заповнюється поточними установками вікна консолі. Далі слід виклик NtUnmapViewOfSection, після чого викликається NtDuplicateObject, який створює дублікат хендлом секції (в контексті процесу власника консолі!) І лише потім викликається CreateRemoteThread з переданими параметрами встановленої PropRoutine і дублікатом хендлом секції.
Зауважу, що PropertiesDlgShow не очікує закінчення роботи потоку, вона за допомогою winsrv! ConsoleWindowProc просто створює потік і повертає управління диспетчеру віконних повідомлень. Дивовижний факт - це зовсім не означає, що оновлені властивості вікна встановлюються якимось іншим способом, ніж просто функцією PropertiesDlgShow.
Майстер-клас для початківців
Таким чином виходить, що правильно реалізувавши свою функцію замість захардкоденной kernel32! PropRoutine, ми з легкістю зможемо реалізувати функціонал API-функції CreateThread (Ex). Це можна зробити шляхом перехоплення і модифікації функцій AllocConsole / AttachConsole або ж, для зовсім безбашенних, шляхом власної реалізації функції AllocConsole ().
До речі, щоб змусити консоль створити новий потік, достатньо надіслати вікна повідомлення з наступними параметрами:
SendMessage (hConsole, WM_SYSCOMMAND, 0xFFF7, 0)
Тут hConsole є звичайним HWND, отриманим викликом GetConsoleHandle (). Що отримаємо в результаті? Щоб створити новий потік в разі виклику kernel32! CtrlRoutine шляхом безлічі складних рухів, можна просто поквапитися, підмінивши kernel32! PropRoutine своєї, не зовсім чесної функцією. Це, як правило, приведе до створення нового, «невидимого» для очей АВЕР і проактівок потоку.
І наостанок поговоримо про вищезгадану console.dll, а точніше - про те, як її можна використовувати в наших брудних цілях. У Windows XP завантаження console.dll здійснювалася з моторошної помилкою - не вказувався шлях, звідки вантажити цю бібліотеку, що давало хакерам можливість її підмінити. Починаючи з Windows Vista стан справ краще не стало - там додали відносний шлях до цієї бібліотеці. З урахуванням того алгоритму, який до цих пір використовується в Windows для пошуку бібліотек, знову-таки існує хороша можливість її підміни. Перед тим як завантажити console.dll з system32, її спочатку будуть шукати в папці установки програми. Але і це ще не все. Весь описаний механізм можна заюзать для приховування Інжект свого коду в віддаленому процесі! Але вже це я залишу тобі як інформації для роздумів, тим більше, що всі необхідні для цього інгредієнти в статті показані.
висновок
Здавалося б - що може бути нудніше консолі? Але і там, якщо гарненько покопатися дебаггера, знайдеться купа цікавого - тим більше, що все знайдене можна використовувати для своїх брудних справ! То ли еще будет ... Читай свій улюблений журнал] [- обіцяю нові і захоплюючі теми! Вдалого компілювання, і щоб із тобою Сила!