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) Code: push ss:[esp] ; удваиваем в стеке адрес возврата в kernel32.BaseProcessStart, чтобы дать его как аргумент к GetBase call GetBase ; получаем базу ; теперь в EAX искомая база 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: Code: // SEH Handler typedef struct _ERR { _ERR* lpNext; DWORD lpHandler; } ERR, *PERR; Функция получения TEB: Code: TEB * GetTEB() { __asm mov eax, fs:[18h]; #pragma warning(disable:4035) } Ну а код для получения базы будет таким: Code: 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. Code: 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]. Этот способ мы и рассмотрим детально, т.к. он представляет особый интерес и затраты на поиск базы у него существенно ниже. В исходниках вирей можно найти следующий код для получения базы, реализующий этот способ: Code: 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:[30] и берет оттуда двойное слово. А что же там должно лежать? Ответ дает описание пользовательских структур - насчиная с FS:[0] располагается структура TEB, в которой есть указатель на PEB, а в ней есть указатель на структуры загрузчика. В хидерах Windows Platform SDK структуры не документированы и большинство их полей обозваны обычно как BYTE Reserved[A]; Поэтому мы обратимся к хидерам React OS TEB имеет много полей, и все они нам не понадобятся. Нас интересует эта часть структуры: Code: 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 */ Подструктура TIB досталась ей в наследство от Windows 9x, а вот поле PPEB Peb нам очень интересно - к нему то и обращается наша функция по команде mov eax, fs:[eax+30]. Тут лежит адрес структуры PEB в памяти. Смотрим, как устроена PEB: Code: 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 */ Остальные поля не представляют для нас интереса. Функция обращается к полю со смещением 0c - Ldr (в некоторых других вариантах оно называется LoaderData, в том числе и я буду использовать такое определение PEB). Это адрес структуры PEB_LDR_DATA: Code: 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; В ней есть три кольцевых двусвязанных списка структур LDR_MODULE (иногда они называются LDR_DATA_TABLE_ENTRY): InLoadOrderModuleList - список модулей в порядке загрузки InMemoryOrderModuleList - список модулей в порядке расположения в памяти InInitializationOrderModuleList - список модулей в порядке инициализации. Он-то нам и нужен, т.к. первые два элемента в нем ntdll и kernel32 Code: 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; Поле BaseAddress нас как раз интересует. В списке первой будет стоять ntdll.dll, а за ней будет kernel32.dll Поэтому мы получаем голову списка через PEB_LDR_DATA.InInitializationOrderModuleList.Flink и делаем еще раз переход по Flink в следующему элементу списка. Преобразовав указатель к типу LDR_MODULE*, мы получим указатель структуру описания kernel32.dll, где и будет ее база. Сокращенная реализация на Си выглядит предельно просто: Code: void* GetKernel32Base() { TEB* teb; _asm { mov eax, fs:[18h] mov teb, eax } return ((PLDR_MODULE)teb->Peb->LoaderData->InInitializationOrderModuleList.Flink->Flink)->BaseAddress; } Расскажу еще немного про устройство двусвязанных списков в Windows. В файле winnt.h описана структура Code: typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY; Это как бы абстрактное звено списка, т.к. структура содержит только указатели вперед и назад и никаких данных. Список модулей на самом деле содержит структуры Code: typedef struct _LDR_MODULE { LIST_ENTRY ModuleList; где ModuleList - два указателя вперед и назад, но в структуре еще есть и данные. Поэтому мы приводим указатель, полученный из Flink, к типу PLDR_MODULE. Вот функция на С++, которая поможет лучше усвоить эти структуры - она проходит по всему списку модулей, выводя попутно на консоль его элементы (имя длл и база) и ищет kernel32.dll по имени (хоть она и всегда будет второй). Code: 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 вместе и один из них подробно расписал
Искал в инете описание PEB, вот наткнулся на твой топик. хочу сказать, надеяться на то, что LDR_MODULE of kernel32 в связном списке будет вторым не стоит. сам столкнулся с этой проблемой.