• XSS.stack #1 – первый литературный журнал от юзеров форума

Получение базы kernel32.dll

Great

CPU register
Пользователь
Регистрация
13.11.2005
Сообщения
1 622
Реакции
6
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)
Код:
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:
Код:
// SEH Handler
typedef struct _ERR
{
	_ERR* lpNext;
	DWORD lpHandler;
} ERR, *PERR;
Функция получения TEB:
Код:
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:[30] и берет оттуда двойное слово.
А что же там должно лежать? Ответ дает описание пользовательских структур - насчиная с 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 */
Подструктура TIB досталась ей в наследство от Windows 9x, а вот поле PPEB Peb нам очень интересно - к нему то и обращается наша функция по команде mov eax, fs:[eax+30]. Тут лежит адрес структуры PEB в памяти.
Смотрим, как устроена 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 */
Остальные поля не представляют для нас интереса. Функция обращается к полю со смещением 0c - Ldr (в некоторых других вариантах оно называется LoaderData, в том числе и я буду использовать такое определение PEB). Это адрес структуры PEB_LDR_DATA:
Код:
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
Код:
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, где и будет ее база.

Сокращенная реализация на Си выглядит предельно просто:
Код:
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 описана структура
Код:
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;
где ModuleList - два указателя вперед и назад, но в структуре еще есть и данные. Поэтому мы приводим указатель, полученный из Flink, к типу PLDR_MODULE.

Вот функция на С++, которая поможет лучше усвоить эти структуры - она проходит по всему списку модулей, выводя попутно на консоль его элементы (имя длл и база) и ищет 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 вместе и один из них подробно расписал ;)
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх