Представим ситуацию, когда ты вставил свой модуль М в адресное пространство некого процесса П. Это можно сделать множеством методом и об этом речи не пойдет. Представим для примера, что ты инжектировал М с помощью метода Рихтера, то есть комбинируя LoadLibrary с CreateRemoteThread. Так же не пойдет речи о том зачем ты это сделал. Представим, так же для примера, что ты это сделал для того, чтобы обеспечить невидимость выполняемого кода в списке процессов. Так же допустим, что ты сделал этот самый код, который в модуле М, заметным для администратора, то есть админ видит, что что-то идет не так в его системе – постоянное обращение к винту/сетевая
активность/ и_что_угодно_вплоть_до_messageboxa(…,”Админ – лопух!!!”,…). Что сделает опытный админ
(естественно, мы не рассматриваем случай, когда
приемлемо перезагрузить тачку и не волноваться. Допустим, что ты об этом позаботился и прописал каким-либо образом себя в автозагрузку)? Полезет в какой-нить
PETools (обожаю эту прогу :)) искать новые модули в таких процессах, как explorer. Если админ не лопух, то он сразу заметит подвох и просто выгрузит твой модуль из памяти
(есть проги, которые делают FreeLibrary в чужом процессе) или просто перезапустит explorer. В общем, для полной
конспирации надо укрыть свой модуль от глаз админа/
антивирус_майкера и вообще кого угодно. Для этого надо разобраться в том, как получают инфу о модулях такие приложения, как
PETools/OllyDBG (да-да, его Memory Map тоже обламывается :Е ). Обычно это делается с помощью снапшотов
(snapshots) или с помощью NtQuerySystemInformation. Я думаю, что ты знаешь что это такое, но все же
вот описание toolhelp функций, на всякий случай, а
здесь описание
NtQueryInformation.
При диссасемблирование, например, Module32FirstW бросается в глаза работа с регистром fs в режиме пользователя
(происходит call 77e77604, где идет оперирование с указанным выше регистром). Стоит пояснить, как используется регистр fs в 3м
(в 0м этот регистр используется по-иному) кольце Windows’ом. Дело в том, что регистр fs
указывает на Thread Enviroment Block, сокращенно TEB. Подробно описывать зачем/почему/когда и т.д. я не буду, ибо до меня об этом написали такие хорошие люди, как Шрайбер(«Незадокументированные возможности Windows 2000» - неплохая книга…), а недавно и volodya из HI-TECH в рассылке от wasm.ru, где он рассеил пару неточностей. Я дам вам не всю структуру, а только ту часть, которая нас интересует. А именно поле Ldr типа PPEB_LDR_DATA (Шрайбер ошибочно полагал, что там
(смещение от PEB=00C) находиться ProcessModuleInfo типа PPROCESS_MODULE_INFO) в Process Enviroment Block’e(PEB), указатель на который(PEB) можно найти в fs:[30h](он равен 7ffdf000). Почему 30h? Потому что 0h указывает на TEB, а в TEB по смещению 0x30 находиться PEB. Поэтому и fs:[30h].
struct _PEB_LDR_DATA {
/*000*/ unsigned long Length;
/*004*/ unsigned char Initialized;
/*008*/ void* SsHandle;
/*00C*/ struct _LIST_ENTRY InLoadOrderModuleList;
/*014*/ struct _LIST_ENTRY InMemoryOrderModuleList;
/*01C*/ struct _LIST_ENTRY InInitializationOrderModuleList;
};
Нас интересуют последние 3 поля, которые представляют собой входы в двусвязные списки
(очень часто используются виндой). Вот как определил _LIST_ENTRY Шрайбер:
typedef struct _LIST_ENTRY
{
/*000*/ struct _LIST_ENTRY *Flink;
/*004*/ struct _LIST_ENTRY *Blink;
/*008*/ }
LIST_ENTRY;
Причем в Flink указывает на следующий элемент, а Blink на предыдущий. После 004 может находиться все что угодно, а в нашем случае это
_LDR_DATA_TABLE_ENTRY:
struct _LDR_DATA_TABLE_ENTRY {
/*000*/ struct _LIST_ENTRY InLoadOrderLinks;
/*008*/ struct _LIST_ENTRY InMemoryOrderLinks;
/*010*/ struct _LIST_ENTRY InInitializationOrderLinks;
/*018*/ void* DllBase;
/*01c*/ void* EntryPoint;
/*020*/ unsigned long SizeOfImage;
/*024*/ struct _UNICODE_STRING FullDllName;
/*02c*/ struct _UNICODE_STRING BaseDllName;
/*034*/ unsigned long Flags;
/*038*/ unsigned short LoadCount;
/*03a*/ unsigned short TlsIndex;
/*03c*/ struct _LIST_ENTRY HashLinks;
/*03c*/ void* SectionPointer;
/*040*/ unsigned long CheckSum;
/*044*/ unsigned long TimeDateStamp;
/*044*/ void* LoadedImports;
};
Мы видим, что первые 3 элемента структуры – это _LIST_ENTRY, названия которых
соответствуют названиям различных списков. Очевидно, что это не что иное, как указатели на те же самые структуры, но разных списков! А если в название списка совпадает с тем, с которым мы сейчас работаем, то это просто *Flink и *Blink ;). Все это просто очень сильно облегчает нам работу. Итак, определимся, что мы будем делать:
- переходим к Peb.Ldr
- переходим к InLoadOrderModuleList (к примеру)
- сохраняем начальный элемент списка
- перечисляем InLoadOrderModuleList с помощью первых 8 байт, которые занимают *Flink и *Blink(определяем конец
списка засчет того, что Flink последнего элемента указывает на начало списка, а мы его сохранили ) - сравниваем имена BaseDllName с нужным модулем
- если в прошлом шаге сравнение показало, что это нужная нам запись, то удаляем ее из всех списков, иначе продолжаем...
Как ты заметил, для определения BaseDllName (ровно, как и FullDllName) используется структура UNICODE_STRING. Вот ее определение по Шрайберу:
typedef struct _UNICODE_STRING
{
/*000*/ USHORT Length;
/*002*/ USHORT MaximumLength;
/*004*/ PWSTR Buffer;
/*008*/ }
UNICODE_STRING;
Теперь обещанная особенность: заметил, что удаление из InInitializationOrderModuleList
закомментировано? Это потому, что этот список, судя по всему, интенсивно используется системой... Я не знаю для какой цели, но если, к примеру, удалить из списка главный модуль нашего приложения, то наше приложение незамедлительно выгрузиться БЕЗ каких-либо ошибок... Если же удалить user32.dll, то у нас будут проблемы с MessageBox’ом, к примеру. Он вызываться-то будет и его код будет получать управление
(то бишь модуль не выгружается, как мог бы подумать проницательный читатель), но будет где-то в глубине вызывать Access Violation... В тоже время удаление из этого списка ntdll.dll не вызовет никаких проблем... На самом деле это не проблема, ибо этот список не используется
(по крайней мере я не разу не видел...) отладочными функциями для получения списка модулей. В общем загадка, которую я, если будет время, в будущем попытаюсь решить. Если же у меня найдется единомышленник, то прошу написать мне на мыло...
Вернемся на землю. Пора рассмотреть пример. В примере я буду удалять модуль из user32.dll и смотреть, какая будет реакция у различных отладочных функций и программ. Вот код
(без вышеописанных функций, инклудов и директив):
.code
start:
main proc
local modname[10]:BYTE
local SnpSht :DWORD
local me :MODULEENTRY32
local IsItEnd :BOOL
; Удаляем модуль user32.dll из всех списков, кроме InInitializationOrderModuleList
mov modname[0], "u"
mov modname[1], "s"
mov modname[2], "e"
mov modname[3], "r"
mov modname[4], "3"
mov modname[5], "2"
mov modname[6], "."
mov modname[7], "d"
mov modname[8], "l"
mov modname[9], "l"
mov modname[10], 0
lea eax, modname
push eax
call DelModuleFromPEBNtA
; Пробуем снапшоты
invoke CreateToolhelp32Snapshot, TH32CS_SNAPMODULE, 0
mov SnpSht, eax
mov me.dwSize, sizeof MODULEENTRY32
invoke Module32First, SnpSht, addr me
mov IsItEnd, TRUE
.while IsItEnd!=FALSE
invoke MessageBox, 0, addr me.szModule, addr me.szModule, MB_OK
invoke Module32Next, SnpSht, addr me
mov IsItEnd, eax
.endw
invoke CloseHandle, SnpSht
mov eax, 0
invoke MessageBox, 0, SADD("Чтобы ты успел посмотреть в PeTools или любой другой проге..."), SADD("Check this"), MB_OK
invoke ExitProcess,0
main endp
Как сам видишь, снапшоты обламываются. Чтобы проверить
NtQuerySystemInformation не надо ничего писать, ибо за нас все давно написали ребята, которые делали PETools. Ты можешь удостовериться, что эта прога использует именно эту функцию просто поставив бряк на NtQuerySystemInformation в SoftIce’e и пронаблюдать как PETools ее вызывает. PETools обламывается. Модуля не видно => NtQuerySystemInformation тоже обламывается. Далее пробуем OllyDBG – неплохой отладчик режима пользователя. Он тоже, как и следовало ожидать, не показывает user32, причем ни в списке модулей, ни на карте памяти :). Единственный отладчик, выдержавший все испытания – SoftIce. Но это и не мудренно. Система должна ведь хранить список модулей еще и в структуре режима ядра
(я, к сожалению, ничего не знаю ни об этой структуре, ни о том, как SI ее получает, но я уверен, что google об этом знает все :)), иначе это была бы даже не дыра, а просто убийство, ибо как иначе система следила бы за тем, какие модули выгружать из памяти при выгрузке процесса? Хранить столь важные сведения в структуре режима пользователя было бы опасно с точки зрения целостности всей системы. В то же время если бы эти сведения не дублировались в PEB, то было бы проблематично получение этой инфы отладчикам режима пользователя... Поэтому данный метод не панацея от всех и вся. В то же время, админы не хранят у себя SoftIce, да и, вообще, отладчики режима ядра, ибо они очень часто вызывают нестабильные ситуации в системе, когда до bsod’а рукой подать. Зачем, если есть такие проги как PETools, которые получают бесконечное множество
наиполезнейшей информации из режима пользователя и в принципе не могут никак навредить системе... Про простых пользователей и говорить не приходиться :), а вот те, кто этим профессионально занимаются
(антивирус_майкеры ;)) без проблем смогут наткнуться на твой зловредный модуль. В общем, решать как всегда тебе ;).