Простой обход фаервола Kaspersky Internet Security (KIS 7)


Насколько актуальна эта статья? Впервые она была опубликована еще в 2007-м году и на данный момент можно говорить в большей части о теории и исторической ценности. Но в качестве теоретического материала и исторической ценности продолжаем.

Привет всем. Сегодня поговорим о простом, но, тем не менее, эффективном методе обхода фаервола Kaspersky Internet Security 7.0 – сетевого экрана, компонента одной из самых популярных и известных защит для персонального пользователя на сегодняшний день. Кто-то хвалит данный продукт, кто-то им восхищается, а кто-то откровенно недолюбливает =) Есть за что или нет, решать Вам. Тут будут только факты. Рассмотрим самую свежую на данное время версию 7.0.0.125. Факт обхода будет заключаться в скрытой отправке данных, лик, утечка — ну как угодно. Доказательства концепции не будет, дабы не сильно радовать наших младших братьев – скрипт-киддисов. Кому надо код можно написать вместе с отладкой за 30 минут. Меньше слов – больше дела.

Существует очень известная и чрезвычайно распространенная техника – инжект. О ней много написано, и большинство из того, что написано, уже детектируется нашем сегодяшним подопытным кроликом. Суть техники сводится к исполнению кода уже доверенного приложения, каким угодно способом. Большинство пользователей, у которых есть Интернет пользуются браузером, а стало быть и серфят Интернет с помощью браузера, а следовательно, должно быть правило в сетевом экране, которое разрешает браузеру по умолчанию передавать данные на 80 порт любому серверу. Отлично, будем этим пользоваться – инжектим код в браузер, и от его имени передаем или получаем данные. Ну, это-то знают все =) А как инжектится? Как обойти преграду проактивной защиты KIS? Просто, даже очень.

Итак, сам процесс инжекта состоит из двух этапов – 1) запись кода 2) передача ему управления. Запись кода… Тут можно много чего придумать, но я расскажу всего один секрет применимый исключительно для KIS. Вообще запись кода в чужой процесс осуществляется функцией KERNEL32!WriteProcessMemory, спускаясь ниже — ее нативным аналогом NTDLL!NtWriteVirtualMemory. Параметры конечно рассказывать не буду, для этого есть исходные источники – IDA =), шутка – Micro$oft. Обычно попытки запись в чужой процесс KIS предупреждает, но не всегда. Взлом любой защиты основан на ее допущениях. Допущение KIS – возможность записи в область виртуальной памяти, НЕ помеченной как исполняемая, конкретно – у области нет атрибута EXECUTABLE. Если данного атрибута нет – то можно писать свободно – KIS и ухом не поведет. Этот факт был получен чисто экспериментальным путем. Отлично. Запись мы сделаем. Но вот куда? Есть простое решение – это область памяти называемая стеком. Но тут кроется самое интересное – записывая код в определенное место в стеке мы убиваем одном выстрелом двух зайцев, а зайцы эти — как раз два пункта процесса инжекта. Все, наверное, уже догадались, о чем идет речь. Кто не догадался — читает дальше. С записью итак понятно, выполнять код будем тоже записью в стек – в стек потока, который дальше будет исполнять наш код. Дело в том, что потоки постоянно вызывают функции и с очень большой вероятностью адрес возврата записывают в стек. Подменив адрес возврата на свой, мы заставим поток исполнять наш код, который также записан в стеке. Знаю, что код в стеке не всегда исполняется, учитывая современные тенденции. Ну, записать код можно и не только в стек. Оставлю это Вам. Т.о. план простого инжекта вычерчивается очень четко следующими пунктами

  1. Создаем процесс, на основе исполняемого файла браузера по умолчанию
  2. После некоторого времени исполнения останавливаем поток браузера
  3. Получаем его контекст – нас интересует значение указателя стека
  4. Анализируем стек на предмет адреса возврата
  5. Пишем адрес нашего кода, который будет что-то качать или передавать по адресу в стеке, где находиться адрес возврата
  6. Возобновляем исполнение потока

В этом алгоритме есть один возможно не самый понятный момент – как анализировать стек на предмет адреса возврата. Я использую следующий метод – двигаемся «вниз» по стеку, и получаем DWORD’ы. Каждый из них может быть адресом возврата. Это можно проверить — читаем по адресу, который соответствует данному DWORD’у, и дизассембируем по адресам вниз. Если там находится инструкция CALL, значит это и есть адрес возврата. Маленькая иллюстрация:

	CALL X
RET_ADDRESS:


	PROC X
	….

Пусть мы остановили поток во время исполнения функции X, в стеке присутствуют (или могут) локальные переменные, сохраненные неизменяемые регистры (при STDCALL), просто данные помещенные в стек (временно) и конечно адрес возврата. Адрес возврата – это RET_ADDRESS. Его значение мы должны найти в результате анализа стека. При этом можно предположить, что при анализе можно полагаться на значение EBP, как правило, тоже сохраняемое в стеке – тут полезно вспомнить стандартный код пролога многих функций. И значение EBP мы можем получить из контекста. Но практика показала, что к этому нельзя привязываться, т.к. стек останавливаемого потока в контексте вызова конкретной функции может содержать просто адрес возврата и больше ничего. Дизассемблер длин плюс разбор по полям, понятно, нам для реализации техники непременно потребуется. Пустяк. Что получилось в итоге… Получился псевдокод мнимого языка с мнимыми функциями для простого обхода KIS 7:

CreateProcess(BrowserFilePath,…,ProcessInformation); // Создаем процесс
Sleep(1000);					  // Поспим немного
SuspendThread(ProcessInformation.hThread);		  // Приостанавливаем первично-порожденный поток
GetThreadContext(ProcessInformation.hThread,&Context);// Получаем контекст
ReadProcessMemory(ProcessInformation.hProcess,Context.Esp,StackChunk,1000*4);// Читаем 1000 DWORD’ов c ESP
for (i=0;i<1000;i++) // анализ стека потока
{
	ReadProcessMemory(ProcessInformation.hProcess,StackChunk[i]-10,CodeBuffer,10); 
	// читаем 10 байт – если это адрес возврата, то эти 10 байт содержат инструкцию CALL
	for (j=0;j<10;j++)
	{
		disasm(&(CodeBuffer[9-j]),&Instr);  // разбор инструкции «назад»
		if ( ( Instr.Length == j+1 ) && 
			(Instr.OpCode == 0xe8 || Instr.OpCode == 0xff || Instr.OpCode == 0x9a) 
		   ) // это CALL?
		{
			WriteProcessMemory(ProcessInformation.hProcess,
						  (Context.Esp+StackChunk[i] -  StackChunk),&ShellCodeAddress,4);
			// пишем адрес нашего shell-кода
			ResumeThread(ProcessInformation.hThread); // возобновляем исполнение потока
		}
	}
}

Респект dead_body из команды WASM за вклад в идею обхода. Памяти безвременного ушедшего кудесника кода Ms-Rem’а. Ждем официальной реакции команды разработчиков KIS. До скорой встречи. mailto – mental_mirror@freed0m.org

[C] Mental_Mirror

Источник WASM.RU

Поделиться в соц сетях

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Есть идеи, замечания, предложения? Воспользуйтесь формой Обратная связь или отправьте сообщение по адресу replay@sciencestory.ru
© 2017 Истории науки. Информация на сайте опубликована в ознакомительных целях может иметь ограничение 18+