Програми-невидимки

Tanaka, HackZone

Мене завжди цікавило питання про програми-невидимках. Уявіть, є резидентний код, який виконує якісь дії на вашому комп'ютері, а виявити його ви не можете. Обурливо, не правда чи що? А тому мати таку програму мені дуже хотілося.


В Інтернеті ця тема також широко обговорювалася, обговорюється і, думаю, буде обговорюватися. Серед величезної кількості думок, суджень та іншого словесного сміття, мені попався вердикт одного гуру, який проголошував: "Ви не можете приховати процес від функції 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

Доповнення

Реакція читачів на мою статтю виявилася для мене несподіваною.


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

Що пропонований мною спосіб НЕ РОБИТЬ:


  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 +0 xe0c, 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

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*