Авторские статьи Получение базы kernel32.dll

Discussion in 'Статьи' started by _Great_, 9 Dec 2006.

  1. _Great_

    _Great_ Elder - Старейшина

    Joined:
    27 Dec 2005
    Messages:
    2,032
    Likes Received:
    1,119
    Reputations:
    1,139
    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 вместе и один из них подробно расписал ;)
     
    #1 _Great_, 9 Dec 2006
    Last edited: 9 Dec 2006
    8 people like this.
  2. gevara

    gevara Elder - Старейшина

    Joined:
    29 Nov 2006
    Messages:
    47
    Likes Received:
    7
    Reputations:
    5
    Искал в инете описание PEB, вот наткнулся на твой топик. хочу сказать, надеяться на то, что LDR_MODULE of kernel32 в связном списке будет вторым не стоит. сам столкнулся с этой проблемой.