Что может сделать с виндовой консолью продвинутый пользователь? Наверное, случайно отформатировать свой винт, глубоко расстроиться и позвать на помощь грамотного соседа. Настоящий же хакер поставит ее себе на службу. Так, что она позволит ему достичь заветной цели – невидимости для антивирусов. Да и не только…

 

Введение

Стандартная консоль 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!

Смотрим, как это делается:

  • запускаем процесс А;
  • запускаем процесс Б;
  • процесс А вызывает AllocConsole();
  • процесс Б вызывает AttachConsole();
  • процесс Б устанавливает обработчик событий SetConsoleCtrlHandler( threadHandler, TRUE );
  • процесс А генерирует событие GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, GetCurrentProcessId());
  • в адресном пространстве процесса «Б» в новом потоке запускается threadHandler.

Для этого примера стоит отметить одну особенность: в описанном случае сигнал 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.

Что же происходит на самом деле? Итак, смотрим: сама по себе функция не делает каких-либо занимательных вещей, зато она подгружает одну библиотеку в адресное пространство процесса, при этом загрузка DLL проходит тривиально, вызовом LoadLibraryW, которая не (!) проверяет, что именно она грузит, а лишь подгружает библиотеку по захардкоденному (ну и словечко ты изобрел! – прим. Лозовского) пути. Загружаемая библиотека console.dll и осуществляет все те операции, которые мы видим на экране при вызове меню «Настройки» консоли.

 

Мастер-класс для начинающих

Таким образом получается, что правильно реализовав свою функцию вместо захардкоденной 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, ее сначала будут искать в папке установки программы. Но и это еще не все. Весь описанный механизм можно заюзать для сокрытия инжекта своего кода в удаленном процессе! Но уж это я оставлю тебе в качестве информации к размышлению, тем более, что все необходимые для этого ингредиенты в статье показаны.

 

Заключение

Казалось бы — что может быть скучнее консоли? Но и там, если хорошенько покопаться дебаггером, найдется куча интересного — тем более, что все найденное можно использовать для своих грязных делишек! То ли еще будет… Читай свой любимый журнал ][ — обещаю новые и захватывающие темы! Удачного компилирования, и да пребудет с тобой Сила!

 

Links

Неплохо написано о межпроцессном взаимодействии в MSDN: http://goo.gl/bTwhz. И вообще, почаще заглядывай в MSDN!

Оставить мнение

Check Also

В гостях у чертёнка. FreeBSD глазами линуксоида

Порог вхождения новичка в мир Linux за последние десять-пятнадцать лет ощутимо снизился. О…