Article: Получение базы kernel32.dll
Author: Great
Date: 09.12.2006
Note: В этой мини-статье я опишу устройство структур TEB,PEB и способы получения базы kernel32.dll. Предполагается знание читателем языков программирования C/C++, Assembler
/***********************************************************************************************/
Многие задачи системного мирного и не очень программирования сталкиваются напрямую с задачей получения адреса базы kernel32.dll, ведь в ней находится большинство апишек, а точнее переходников к native api из ntdll.dll, которые в свою очередь обращаются к сервисам ядра из ntoskrnl.exe, нужных для функционирования программы. Если у тебя PE-файл, то проблем нет - дописал kernel32.dll в импорт и загрузчик сам ее подгрузит и даже адреса апишек выставит (биндинг не в счет). А вот если ты пишешь шеллкод или (и того хуже =)) вирь, то тут уже придется туго без kernel'а.
Для нахождения заветной базы существует много способов. Я вспомню три из них:
1) разбор стека только что запустившегося потока (для этого нужно вирю внедрить свой код в самое начало, либо поставить EntryPoint на свой код) - в стеке будет адрес возврата в kernel32.dll (в цепочку функции BaseProcessStart)
2) анализ последнего SEH-обработчика, который опять же указывает куда-то в kernel32.dll
Кратко опишу реализацию метода. В TIB есть поле ExceptionList с линейным односвязанным списоком обработчиков SEH. Последний обработчик обязательно укажет в kernel32.dll, т.к. если никто исключение не перехватит, то управление пойдет в этот обработчик и он выдаст до боли знакомое окно "someprog.exe has encountered a problem and needs to close. We are sorry for the inconvenience."
Элементы списка - структуры ERR:
Функция получения TEB:
Ну а код для получения базы будет таким:
В этих двух случаях дальше пишется небольшая функция GetBase, которая берет адрес где-то в kernel32.dll, листает страницы назад, пока не наткнется на MZ и PE-заголовки, что будет означать, что она, наконец, дошла до базового адреса загрузки kernel32.dll.
Третий способ основан на том факте, что подавляющее большинство программ импортируют kernel32.dll и ее базу можно найти в списке модулей текущего процесса, который можно взять из Process Environment Block (PEB), адрес которого, в свою очередь, можно узнать из структуры Thread Environment Block (TEB), расположенной по адресу FS:[0]. Этот способ мы и рассмотрим детально, т.к. он представляет особый интерес и затраты на поиск базы у него существенно ниже.
В исходниках вирей можно найти следующий код для получения базы, реализующий этот способ:
С первого взгляда даже непонятно, что он делает. Если присмотреться, можно обнаружить, что сперва он обращается по адресу FS:[30] и берет оттуда двойное слово.
А что же там должно лежать? Ответ дает описание пользовательских структур - насчиная с FS:[0] располагается структура TEB, в которой есть указатель на PEB, а в ней есть указатель на структуры загрузчика.
В хидерах Windows Platform SDK структуры не документированы и большинство их полей обозваны обычно как BYTE Reserved[A];
Поэтому мы обратимся к хидерам React OS
TEB имеет много полей, и все они нам не понадобятся. Нас интересует эта часть структуры:
Подструктура TIB досталась ей в наследство от Windows 9x, а вот поле PPEB Peb нам очень интересно - к нему то и обращается наша функция по команде mov eax, fs:[eax+30]. Тут лежит адрес структуры PEB в памяти.
Смотрим, как устроена PEB:
Остальные поля не представляют для нас интереса. Функция обращается к полю со смещением 0c - Ldr (в некоторых других вариантах оно называется LoaderData, в том числе и я буду использовать такое определение PEB). Это адрес структуры PEB_LDR_DATA:
В ней есть три кольцевых двусвязанных списка структур LDR_MODULE (иногда они называются LDR_DATA_TABLE_ENTRY):
InLoadOrderModuleList - список модулей в порядке загрузки
InMemoryOrderModuleList - список модулей в порядке расположения в памяти
InInitializationOrderModuleList - список модулей в порядке инициализации. Он-то нам и нужен, т.к. первые два элемента в нем ntdll и kernel32
Поле BaseAddress нас как раз интересует.
В списке первой будет стоять ntdll.dll, а за ней будет kernel32.dll
Поэтому мы получаем голову списка через PEB_LDR_DATA.InInitializationOrderModuleList.Flink и делаем еще раз переход по Flink в следующему элементу списка.
Преобразовав указатель к типу LDR_MODULE*, мы получим указатель структуру описания kernel32.dll, где и будет ее база.
Сокращенная реализация на Си выглядит предельно просто:
Расскажу еще немного про устройство двусвязанных списков в Windows.
В файле winnt.h описана структура
Это как бы абстрактное звено списка, т.к. структура содержит только указатели вперед и назад и никаких данных.
Список модулей на самом деле содержит структуры
где ModuleList - два указателя вперед и назад, но в структуре еще есть и данные. Поэтому мы приводим указатель, полученный из Flink, к типу PLDR_MODULE.
Вот функция на С++, которая поможет лучше усвоить эти структуры - она проходит по всему списку модулей, выводя попутно на консоль его элементы (имя длл и база) и ищет kernel32.dll по имени (хоть она и всегда будет второй).
Скачать все описания структур: structures.h
NT_TIB входит в стандартные виндовые хидеры
*********************
Я собрал несколько способов получения базы kernel32.dll вместе и один из них подробно расписал
Author: Great
Date: 09.12.2006
Note: В этой мини-статье я опишу устройство структур TEB,PEB и способы получения базы kernel32.dll. Предполагается знание читателем языков программирования C/C++, Assembler
/***********************************************************************************************/
Многие задачи системного мирного и не очень программирования сталкиваются напрямую с задачей получения адреса базы kernel32.dll, ведь в ней находится большинство апишек, а точнее переходников к native api из ntdll.dll, которые в свою очередь обращаются к сервисам ядра из ntoskrnl.exe, нужных для функционирования программы. Если у тебя PE-файл, то проблем нет - дописал kernel32.dll в импорт и загрузчик сам ее подгрузит и даже адреса апишек выставит (биндинг не в счет). А вот если ты пишешь шеллкод или (и того хуже =)) вирь, то тут уже придется туго без kernel'а.
Для нахождения заветной базы существует много способов. Я вспомню три из них:
1) разбор стека только что запустившегося потока (для этого нужно вирю внедрить свой код в самое начало, либо поставить EntryPoint на свой код) - в стеке будет адрес возврата в kernel32.dll (в цепочку функции BaseProcessStart)
Код:
push ss:[esp]; удваиваем в стеке адрес возврата в kernel32.BaseProcessStart, чтобы дать его как аргумент к GetBase
call GetBase; получаем базу
; теперь в EAX искомая база
Кратко опишу реализацию метода. В TIB есть поле ExceptionList с линейным односвязанным списоком обработчиков SEH. Последний обработчик обязательно укажет в kernel32.dll, т.к. если никто исключение не перехватит, то управление пойдет в этот обработчик и он выдаст до боли знакомое окно "someprog.exe has encountered a problem and needs to close. We are sorry for the inconvenience."
Элементы списка - структуры ERR:
Код:
// SEH Handler
typedef struct _ERR
{
_ERR* lpNext;
DWORD lpHandler;
} ERR, *PERR;
Код:
TEB * GetTEB()
{
__asm mov eax, fs:[18h];
#pragma warning(disable:4035)
}
Код:
ERR* err = (ERR*)GetTEB()->Tib.ExceptionList; // Получаем список SEH
while(err->lpNext!=(ERR*)-1) // ищем последний элемент списка
err = err->lpNext;
DWORD dwKernel32Base = GetBase( (void*)err->lpHandler ); // Получаем базу его обработчика - базу kernel32.dll
В этих двух случаях дальше пишется небольшая функция GetBase, которая берет адрес где-то в kernel32.dll, листает страницы назад, пока не наткнется на MZ и PE-заголовки, что будет означать, что она, наконец, дошла до базового адреса загрузки kernel32.dll.
Код:
void* GetBase(DWORD lpAddr)
{
for(lpAddr &= 0xFFFF0000 /* гранулярность выделения памяти */;; lpAddr -= 0x10000 /* листаем назад страницами */)
{
if( ( ((IMAGE_DOS_HEADER*)lpAddr)->e_magic == 0x5a4d ) // MZ
&& ( ((IMAGE_NT_HEADERS*) ((DWORD)lpAddr + ((IMAGE_DOS_HEADER*)lpAddr)->e_lfanew) )->Signature == 0x00004550 ) // PE
)
return (void*)lpAddr; // это заветная база
}
// Ни фига =(
return NULL;
}
Третий способ основан на том факте, что подавляющее большинство программ импортируют kernel32.dll и ее базу можно найти в списке модулей текущего процесса, который можно взять из Process Environment Block (PEB), адрес которого, в свою очередь, можно узнать из структуры Thread Environment Block (TEB), расположенной по адресу FS:[0]. Этот способ мы и рассмотрим детально, т.к. он представляет особый интерес и затраты на поиск базы у него существенно ниже.
В исходниках вирей можно найти следующий код для получения базы, реализующий этот способ:
Код:
void* asmGetKernelBase()
{_asm{
xor eax, eax
mov eax, fs:[eax+30h]
test eax, eax
js win9x
push esi
mov eax, [eax+0Ch]
mov esi, [eax+1Ch]
lodsd
mov eax, [eax+8]
pop esi
leave
ret
win9x:
mov eax, [eax+34h]
add eax, 7Ch
mov eax, [eax+3Ch]
}}
А что же там должно лежать? Ответ дает описание пользовательских структур - насчиная с FS:[0] располагается структура TEB, в которой есть указатель на PEB, а в ней есть указатель на структуры загрузчика.
В хидерах Windows Platform SDK структуры не документированы и большинство их полей обозваны обычно как BYTE Reserved[A];
Поэтому мы обратимся к хидерам React OS
TEB имеет много полей, и все они нам не понадобятся. Нас интересует эта часть структуры:
Код:
typedef struct _TEB
{
NT_TIB Tib; /* 00h */
PVOID EnvironmentPointer; /* 1Ch */
DWORD Cid[2]; /* 20h */
PVOID ActiveRpcInfo; /* 28h */
PVOID ThreadLocalStoragePointer; /* 2Ch */
PPEB Peb; /* 30h */
ULONG LastErrorValue; /* 34h */
Смотрим, как устроена PEB:
Код:
typedef struct _PEB
{
UCHAR InheritedAddressSpace; /* 00h */
UCHAR ReadImageFileExecOptions; /* 01h */
UCHAR BeingDebugged; /* 02h */
BOOLEAN SpareBool; /* 03h */
HANDLE Mutant; /* 04h */
PVOID ImageBaseAddress; /* 08h */
PPEB_LDR_DATA Ldr; /* 0Ch */
Код:
typedef struct _PEB_LDR_DATA
{
ULONG Length;
BOOLEAN Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
InLoadOrderModuleList - список модулей в порядке загрузки
InMemoryOrderModuleList - список модулей в порядке расположения в памяти
InInitializationOrderModuleList - список модулей в порядке инициализации. Он-то нам и нужен, т.к. первые два элемента в нем ntdll и kernel32
Код:
typedef struct _LDR_MODULE
{
LIST_ENTRY ModuleList;
PVOID BaseAddress;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;
} LDR_MODULE, *PLDR_MODULE;
В списке первой будет стоять ntdll.dll, а за ней будет kernel32.dll
Поэтому мы получаем голову списка через PEB_LDR_DATA.InInitializationOrderModuleList.Flink и делаем еще раз переход по Flink в следующему элементу списка.
Преобразовав указатель к типу LDR_MODULE*, мы получим указатель структуру описания kernel32.dll, где и будет ее база.
Сокращенная реализация на Си выглядит предельно просто:
Код:
void* GetKernel32Base()
{
TEB* teb;
_asm
{
mov eax, fs:[18h]
mov teb, eax
}
return ((PLDR_MODULE)teb->Peb->LoaderData->InInitializationOrderModuleList.Flink->Flink)->BaseAddress;
}
В файле winnt.h описана структура
Код:
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
Список модулей на самом деле содержит структуры
Код:
typedef struct _LDR_MODULE
{
LIST_ENTRY ModuleList;
Вот функция на С++, которая поможет лучше усвоить эти структуры - она проходит по всему списку модулей, выводя попутно на консоль его элементы (имя длл и база) и ищет kernel32.dll по имени (хоть она и всегда будет второй).
Код:
void* ListModules()
{
// Get TEB
TEB* teb = GetTEB();
// Get PEB
PEB* peb = teb->Peb;
// Get PEB_LDR_DATA
PPEB_LDR_DATA pldr = peb->LoaderData;
// Get double-linked list of the modules
// View in right direction
PLDR_MODULE pentry;
pentry = (PLDR_MODULE) pldr->InInitializationOrderModuleList.Flink;
VOID* dwBase = 0;
char buffer[1024];
// Walk the list
do
{
// Print the name
WideCharToMultiByte(CP_ACP, 0, pentry->BaseDllName.Buffer, -1, buffer, sizeof(buffer), 0, 0);
printf("%s\t[0x%08x]\n", buffer, pentry->BaseAddress);
// kernel32.dll found
if(lstrcmpi(buffer, "kernel32.dll")==0)
dwBase = pentry->BaseAddress;
// Next
pentry = (PLDR_MODULE)pentry->ModuleList.Flink;
}
while(pentry->ModuleList.Flink != pldr->InInitializationOrderModuleList.Flink);
return dwBase;
}
Скачать все описания структур: structures.h
NT_TIB входит в стандартные виндовые хидеры
*********************
Я собрал несколько способов получения базы kernel32.dll вместе и один из них подробно расписал