информационная безопасность
без паники и всерьез
 подробно о проекте
Rambler's Top100Все любят медЗа кого нас держат?Портрет посетителя
BugTraq.Ru
Русский BugTraq
 Анализ криптографических сетевых... 
 Модель надежности двухузлового... 
 Специальные марковские модели надежности... 
 Три миллиона электронных замков... 
 Doom на газонокосилках 
 Умер Никлаус Вирт 
главная обзор RSN блог библиотека закон бред форум dnet о проекте
bugtraq.ru / библиотека / программирование
БИБЛИОТЕКА
вход в библиотеку
книги
безопасность
программирование
криптография
internals
www
телефония
underground
беллетристика
разное
обзор: избранное
конкурс
рейтинг статей
обсуждение




Подписка:
BuqTraq: Обзор
RSN
БСК
Закон есть закон




Программы-невидимки
Tanaka
Опубликовано: dl, 25.11.01 22:37

Меня всегда интересовал вопрос о программах-невидимках. Представьте, есть резидентный код, который выполняет некие действия на вашем компьютере, а обнаружить его вы не можете. Возмутительно, не правда ли? А потому иметь такую программу мне очень хотелось.

В Интернете эта тема также широко обсуждалась, обсуждается и, думаю, будет обсуждаться. Среди огромного количества мнений, суждений и прочего словесного мусора, мне попался вердикт одного гуру, гласивший: "Вы не можете скрыть процесс от функции NtQuerySystemInformation." Это было похоже на окончательный приговор. Хрустальная мечта моего детства была готова разбиться раз и навсегда. Но:

Очень надеюсь, что о программах-невидимках мечтал не только я. Вам, мои духовные собратья, и посвящается эта статейка. Серьезным дядям (в народе их кличут системными администраторами), работа которых заключается в удушении свободы простого народа : виноват, простых юзеров, читать это не следует. Все дальнейшие рассуждения приводятся для процессора i386 ОС Windows NT 4.0 (SP4 или SP6), так как в Unix системах я не разбираюсь. Что "как"? Да, вы не ослышались! Каюсь!! Простите!!! Позор на мою седую голову!!!! Пощады!!!! Ну а теперь, после того как самая интеллектуальная часть аудитории громко хлопнула дверью, вступление можно закончить и перейти к делу.

Сначала поговорим о том, как и для чего, собственно, используются программы-невидимки. Предположим, что вы зашли в Интернет и закачали замечательную программку, картинку, документ или что-то еще. А потом оказалось, что злобные хакеры заложили в эту программку вирус или троянского коня. Как опытный и продвинутый юзер, вы сразу запускаете Task Manager и видите там ЕГО. После чего ОН убивается кнопочкой End Process, и вы спокойно можете наслаждаться праведно выкачанным добром. Некоторые особо злобные и зубастые хакеры пытаются запустить драйвер, но, к счастью для нас, для этого необходимы права администратора. Это обычное развитие событий. А теперь предположим, что в Task Manager вы ЕГО не видите. Что делать? Обычные юзеры могут подумать, что на этот раз его пронесло, и в файле трояна не оказалось. Но это не наши юзеры. Нормальный параноидальный юзер сразу почувствует подвох и будет прав.

Сразу хочу отметить, речь не идет о классическом вирусе. Безусловно, вирусы - прекраснейшее изобретение человечества, но у них есть один серьезнейший недостаток. А именно, работает он всего ОДИН раз при запуске инфицированной программы. Выполнив свой кусочек кода, он вынужден отдать управление программе-носителю. Если же он хочет стать резидентом, то ему опять же надо где-то "жить", то есть создавать процесс.

Так возможно ли это: быть резидентом (это звучит гордо!) и не "светиться" в Task Manager? Отвечаю, ЭТО ВОЗМОЖНО! Позвоните прямо сейчас, и всего за 49.99$ ваша мечта станет ре: Гм, простите, никак не выведу эту рекламную бациллу (язык не поворачивается, назвать ее благородным словом "вирус").

Более того, что нам этот микрософтовский Task Manager или Spy++, любой мало-мальски серьезный программер напишет свой Task Manager. Поэтому будем прятаться от всего их порочного семейства до седьмого колена включительно.

Сердцем любого Task Manager является функция NtQuerySystemInformation. Остальное - это оболочки, рамочки и прочая мишура. Хотя эта функция и недокументирована компанией Microsoft, но в Интернете про нее написано предостаточно. Поэтому здесь в подробности вникать не будем, а скажем, что всю информацию, которую вы видите на экране, предоставляет эта, и только эта, функция. И увы, гуру был прав! Вы действительно не можете спрятать процесс от NtQuerySystemInformation (по крайней мере, я не знаю, как это можно сделать). Но кто сказал, что резидентами становятся только процессы! Ведь кто такой резидент? Это всего навсего кусочек памяти, который периодически получает управление. Поэтому помимо процесса резидентом может быть драйвер, поток (thread), волокно (fiber), APC и DPC процедуры, функции обработки исключений и прерываний. Возможно, и еще куча всего такого, о чем я не знаю. (Кстати, если говорить совсем строго, то исполняется не процесс, а его потоки, поэтом процесс резидентом не является).

Итак, на сегодняшний день, я знаю следующие способы прятать резидентный код:

  1. Загружать драйвер. Это настолько примитивно, что обсуждать этот способ не будем. Кроме всего прочего, NtQuerySystemInformation предоставляет информацию и о драйверах, хотя Task Manager этого не делает. Ну и потом, ведь есть SoftIce!
  2. Создавать поток в заданном процессе. Это способ подробно описан у Джеффри Рихтера в книге "Windows для профессионалов". Также есть информация у Мэтта Питрека и в Интернете. Этот способ намного более интересен и оригинален. Но на сегодняшний день это уже классика, и удивить этим невозможно. Резидентный код обнаружить в этом случае довольно тяжело. В Task Manager у одного из процессов появляется дополнительный поток, но кто из нас на память скажет, сколько должно быть потоков у Explorer.exe? Помимо Task Manager можно посмотреть список загруженных библиотек, но опять же, кто из нас на память скажет, какие библиотеки должны быть загружены в Explorer.exe. Наконец, есть возможность посмотреть время создания потоков. А вот тут разница будет. Одним словом, будьте внимательны к потокам в резидентных процессах.
  3. Всевозможные Rootkit. Это загрузка драйвера, который перехватывает обращения к NtQuerySystemInformation и подставляет свои данные. К сожалению, подробности изложить не могу, так как сам плохо представляю себе этот предмет. Недостаток все тот же - необходимы права администратора! Если же у вас на машине все-таки запустили драйвер, то дела ваши плохи.
  4. Hooking. Относительно свежий способ. Основан на функции SetWindowsHookEx. Насколько я понимаю, на нем основаны "невидимые" закладки под клавиатуру, мышь и всего того, на что сажается hook. В Интернете способ широко не описан, поэтому не буду отнимать хлеб у его создателей. При таком способе новый поток не создается. (Если кто-нибудь знает почему, то объясните мне, пожалуйста.) Но библиотека, откуда вызывается функция обработки ловушки, грузится во ВСЕ существующие и вновь создаваемые процессы. Поэтому, запустите свой процесс, и если в нем появилась библиотека, которую вы не предусмотрели, то самое время перегрузить машину.
  5. Переключение контекста потока. В данном случае резидентный код исполняется в контексте какого-либо уже запущенного потока. Между прочим, сама система широко использует потоки пользователей для собственных нужд. Как пишет Хелен Кастер в книге "Основы Windows NT и NTFS" система "похищает" поток и использует его для обработки прерываний, вызова DPC и системных APC функций. То есть немалая часть операционной системы представляет собой программы-невидимки. В Интернете я не встречал упоминания о данном способе и собираюсь остановиться на нем поподробнее.

При использовании данного метода не создаются ни новый процесс, ни новый поток. Загрузка библиотеки в другие процессы тоже не требуется. Таким образом, единственным косвенным признаком существования постороннего резидентного кода является разовое увеличение используемой памяти. Но память - наиболее активно расходуемый ресурс. Практически все программы постоянно выделяют и освобождают память и определить, какое количество занятых килобайт является нормой для того или иного процесса, очень затруднительно.

Разумеется, есть и недостатки. Во-первых, резидентный код может выполняться, только когда система передает управление потоку-носителю. Но возможна ситуация, что поток имеет весьма низкий приоритет, и из-за вытесняющей многозадачности редко будет получать управление. Более того, поток может перейти в состояние ожидания, и оставаться в нем неопределенно долго. Если же поток будет завершен, то завершится и резидентный код. Суммируя, работа резидентного кода в решающей степени зависит от потока-носителя.

Вкратце, алгоритм предлагаемого метода состоит в следующем:

  1. Необходимо найти подходящий процесс, то есть процесс, имеющий постоянно работающие потоки с приоритетом нормальный или выше (>8). Во избежание трудностей с правами, имеет смысл использовать пользовательский процесс. Для нахождения такого процесса естественно использовать все ту же функцию NtQuerySystemInformation.
  2. После того, как нужный процесс найден, и определены идентификаторы его потоков, загрузим в пространство данного процесса сам исполняемый резидентный код. Чтобы не возиться с ассемблером, я это делаю в виде библиотеки, но это вопрос предпочтений. Процедура загрузки библиотеки в заданный процесс подробно описан у Джеффри Рихтера.
  3. Приостанавливаем один из потоков процесса, сохраняем его стек и текущий контекст, создаем новый стек, изменяем контекст потока так, чтобы указатель на текущую команду (регистр Eip) указывал на наш резидентный код, переходим на новый стек, "пробуждаем" поток. Теперь, если мы нигде не просчитались (хе-хе), то при получении потоком процессорного времени будет выполняться наш резидентный код. Естественно, это не все, так как в нашу задачу не входит нарушить нормальное исполнение процесса (что мы, хакеры что ли?).
  4. После того, как резидентный код отработал, необходимо перейти на старый стек и восстановить старый контекст. Теперь поток (по-моему) должен работать в нормальном режиме.

Вот, собственно, и все. Очень просто, не правда ли? (Это я у своих гуру собезъянничал). Разумеется, некоторые непринципиальные детали были опущены. По сути, во многом то же самое выполняет сама ОС при исполнении DPC и APC процедур (по книге Хелен Кастер).

Чтобы не оставаться голословным, я написал маленькую программку (baby.exe) с небольшой библиотечкой (zzz.dll). В данном примере резидентный код просто пишет в файл. Использовать их очень просто. Скопируйте библиотеку в системную директорию system32 и запустите baby.exe. Если все прошло успешно, то в корневой директории диска C: появится файл 2_.2 с цифрами (если у вас уже есть такой файл (да вы батенька, маньяк!), то цифры будут дописываться в его конец). По прошествии времени (в зависимости от загруженности вашего компьютера) файл (по идее) будет медленно расти (если долго не будет, то удалите его, и он опять (наверное) появится).

В TaskManager никаких новых процессов или потоков не появится. (Библиотек тоже, но в TaskManager вы этого не увидите.) Да! Чуть не забыл, чтобы "убить резидента", перезапустите Explorer.exe.

Наконец, последнее. Программа не создана ни для каких практических целей. Это всего лишь демонстрация работы алгоритма и проба сил. В ней могут быть различного рода ошибки. Она может работать плохо или вообще не работать. Короче, "мы отклоняй претензий любой вид, любой форма, любой содержания". В конце концов, я не бог, не царь и не герой, то есть не Великий Рулез, не Microsoft и не гуру.

В заключении автор хотел бы выразить благодарность всем, кто помогал и поддерживал его. Я особенно благодарен книге А.В. Коберниченко "Недокументированные возможности Windows NT".

Если у вас есть вопросы, предложения, комментарии и т.д., то мой почтовый адрес tanaka@rambler.ru

Tanaka


Дополнение

Реакция читателей на мою статью оказалась для меня неожиданной.

Во-первых, я поражен благожелательностью читателей (и очень благодарен Вам за это), во-вторых, удивителен столь живой интерес к этой весьма отвлеченной теоретической теме, в-третьих, меня поняли не совсем так, как я рассчитывал. Поэтому, на мой взгляд, необходимы некоторые пояснения.

Что предлагаемый мною способ НЕ ДЕЛАЕТ:

  1. Не скрывает процесс.
  2. Не скрывает поток.
  3. Не запускает драйвер.

Процесс не создается, поэтому скрывать его отсутствие не нужно.

Поток создается, но, быстро отработав, нормально завершает работу, поэтому скрывать его тоже не нужно. (Кстати, может кто-нибудь, действительно, умеет скрывать поток без драйвера? Было бы очень интересно!)

Драйвер мне вообще не нужен.

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

Еще один вопрос ко всем. Как все-таки обнаружить подобного резидента? Или, как детектировать вызов APC и DPC процедур операционной системой?

Все хотят видеть исходники, но почему? Основную идею я описал, а техническое исполнение программы оставляет желать лучшего (не все рождаются программистами).

Но раз очень хочется, то пожалуйста (но я предупреждал!). По-моему, все, чтобы написать аналогичную прогу, описано.

Исходник exe помещаю полностью, но только в нем нет ничего интересного, да и написан он ужасным стилем. Все то же самое можно найти у Рихтера и гораздо лучше написанное.

Все, что делает exe - это загрузка библиотеки в Explorer.exe и запуск библиотечной функции zzzInit.

Исходник zzz.dll помещать полностью не буду, только основные куски. Главное - это функция my_switch, что переключает контексты.

Основные структуры:

typedef struct _THREAD_BASIC_INFORMATION
{
	BOOL ExitStatus;
	PVOID Teb;
	CLIENT_ID ClientID;
	DWORD AffinityMask;
	DWORD BasePriority;
	DWORD Priority;
} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;

 

Надеюсь, что мое предположение верно, и данная структура описывает волокно (fiber). Если кто-то знает больше, сообщите свои мысли, пожалуйста, особенно про E0C.


typedef struct _MYFIBER
{
	DWORD arg;
	DWORD Exceptions;
	DWORD StackBase;
	DWORD StackLimit;
	DWORD E0C; //????????????????????
	CONTEXT context;
} MYFIBER, *PMYFIBER;

 

Основная функция переключения контекста потока

1 аргумент - handle на поток, куда внедряется наш код

2 аргумент - указатель на предварительно созданный fiber

3 аргумент - указатель на исполняемый код

void my_switch(
	HANDLE hThread, //thread to be interrupted
	PVOID fib, //fib to switch to
	DWORD function) //function to process
{
	CONTEXT cont;
	BOOL bo;
	THREAD_BASIC_INFORMATION threadbuff;
	ULONG l;
	MYFIBER temp_fiber;

	memset(&cont,0x00,sizeof(CONTEXT));
	memset(&threadbuff,0x00,sizeof(THREAD_BASIC_INFORMATION));

	// Read context and stack of target stack
	cont.ContextFlags=CONTEXT_CONTROL;
	bo=GetThreadContext(hThread,&cont);
	bo=NtQueryInformationThread(hThread,0,&threadbuff,sizeof(THREAD_BASIC_INFORMATION),&l);

	//1. Save arg, Teb and context
	//arg
	memmove((BYTE *)&temp_fiber.arg,(BYTE *)fib,4);

	//Teb
	memmove((BYTE *)&temp_fiber.Exceptions,(BYTE *)threadbuff.Teb,4);
	memmove((BYTE *)&temp_fiber.StackBase,(BYTE *)threadbuff.Teb+4,4);
	memmove((BYTE *)&temp_fiber.StackLimit,(BYTE *)threadbuff.Teb+8,4);
	memmove((BYTE *)&temp_fiber.E0C,(BYTE *)threadbuff.Teb+0xe0c,4);

	//Context
	memmove((BYTE *)&temp_fiber.context,(BYTE *)&cont,sizeof(CONTEXT));

	//Помещаем в блок окружения (Teb) и контекст потока значения из созданного
	//и проиницилизированного волокна (fiber)

	//2. Change all

	memmove((BYTE *)threadbuff.Teb,(BYTE *)fib+4,4);
	memmove((BYTE *)threadbuff.Teb+4,(BYTE *)fib+8,4);
	memmove((BYTE *)threadbuff.Teb+8,(BYTE *)fib+12,4);
	memmove((BYTE *)threadbuff.Teb+0xe0c,(BYTE *)fib+16,4);
	memmove((BYTE *)threadbuff.Teb+20,(BYTE *)&fib,4); //pointer to saved fib
	memmove((BYTE *)&cont.Edi,(BYTE *)fib+0xb0,4);
	memmove((BYTE *)&cont.Esi,(BYTE *)fib+0xb4,4);
	memmove((BYTE *)&cont.Ebp,(BYTE *)fib+0xc8,4);
	memmove((BYTE *)&cont.Ebx,(BYTE *)fib+0xb8,4);
	memmove((BYTE *)&cont.Ecx,(BYTE *)fib+0xcc,4);
	memmove((BYTE *)&cont.Esp,(BYTE *)fib+0xd8,4);

	cont.Eip=(DWORD)function;

	bo=SetThreadContext(hThread,&cont);

	memmove((BYTE *)fib,(BYTE *)&temp_fiber,sizeof(MYFIBER));
}



Поток, внедряющий код в контекст заданного потока.

Аргумент в основном - это идентификатор потока, куда внедряется код.



DWORD WINAPI thread2(LPVOID arg2)
{
	ULONG i,l;
	PVOID fib1;
	BOOL bo;
	DWORD zero=0;
	HANDLE hThread=0;
	OBJECT_ATTRIBUTES ObjectAttributes;
	CLIENT_ID thread1;
	MYPAR * par3;
	MYARG * pmyarg;

 
	//Подготовка параметров (несущественно)

	par3=(MYPAR *)malloc(sizeof(MYPAR));
	pmyarg=(MYARG *)arg2;

	thread1.UniqueThread=(HANDLE)pmyarg->id1;
	thread1.UniqueProcess=(HANDLE)GetCurrentProcessId();
	(*par3).client_id.UniqueThread=(HANDLE)pmyarg->id2;
	(*par3).client_id.UniqueProcess=(HANDLE)GetCurrentProcessId();
	par3->fib=0;
	par3->st=0;

 
	memset(&ObjectAttributes,0x00,sizeof(OBJECT_ATTRIBUTES));

	//Открыть handle потока по его идентификатору.
	bo=NtOpenThread(&hThread,MAXIMUM_ALLOWED,&ObjectAttributes,&thread1);

	//Создать волокно
	fib1=CreateFiber(NULL,(LPFIBER_START_ROUTINE)f1,(void *)par3);

	i=SuspendThread(hThread);
	if(i==0xffffffff)
	{
		l=GetLastError();
	}
	//Переключить контекст заданного потока
	my_switch(hThread, fib1,(DWORD)f1);

	i=ResumeThread(hThread);
	//Очистить память
	CloseHandle(hThread);
	if(arg2!=0) free(arg2);
	return 1;

}


Вот, собственно, и ВСЕ.

Если этот хлам Вам понятен, и Вы извлекли из этих "исходников" что-то полезное, то Вы - Истинный Монстр программинга, хакинга, кракинга и бодибилдинга.

Если возникли вопросы, пишите tanaka@rambler.ru



обсудить  |  все отзывы (3)

[63625; 44; 8.27]




Rambler's Top100
Рейтинг@Mail.ru





  Copyright © 2001-2024 Dmitry Leonov   Page build time: 0 s   Design: Vadim Derkach