Показать сообщение отдельно

  #2  
Старый 03.12.2006, 12:51
_Great_
Флудер
Регистрация: 27.12.2005
Сообщений: 2,372
Провел на форуме:
5339610

Репутация: 4360


Отправить сообщение для _Great_ с помощью ICQ
По умолчанию

Теперь мы можем вызвать эту функцию прямо из WinMain и узнать, что творится в стеке главного потока нашей программы. Но мы для наглядности напишем пару-тройку вызывающих друг друга процедур, чтобы проследить местоположение адресов возврата в стеке.
Код:
void f3()
{
	StackUnwind(50);
}

void f2()
{
	f3();
}

void f1()
{
	f2();
}
Мы решили проанализировать 50 адресов в стеке. Из WinMain мы вызываем функцию f1, она вызывает f2, f2, в свою очередь, вызывает f3, которая вызывает StackUnwind. Что ж, поставим точку входа в нашу программу на WinMain, чтобы нам не мешали всякие WinMainCRTStartup, и запустим ее.
Результат анализа стека показан ниже:
Цитата:
Unwinding stack by EBP=0x0013ff70
0x00401112 stackunwind!f3 + 0x000c
0x0040111f stackunwind!f2 + 0x0008
0x00401129 stackunwind!f1 + 0x0008
0x00401137 stackunwind!WinMain + 0x000c
0x7c90e64e ntdll!NtSetInformationThread + 0x000c
0x7c816fd4 kernel32!RegisterWaitForInputIdle + 0x0046
0x7c816fd7 kernel32!RegisterWaitForInputIdle + 0x0049
Found kernel32!BaseProcessStart (shown as 'kernel32!RegisterWaitForInputIdle + 0x0049'), stopping
По первым 3-4 строчкам видно, что WinMain вызвала f1, и по цепочке управление дошло до функции f3.
Но что за странные строчки после WinMain и до конца?
А все дело в том, что выполнение любого потока (кроме главного потока программы) начинается с функции BaseThreadStart, а главного потока программы - с BaseProcessStart. Она устанавливает некоторые параметры потоку, в том числе и истинную точку входа и передает управление на неё - в данном случае, на WinMain. Почему же не распозналась функция BaseProcessStart, а вместо нее идет RegisterWaitForInputIdle, которая, казалось бы, совсем не к месту? А все дело в причудливом расположении этих функций в kernel32.dll.
Сначала идет BaseProcessStart, в которой есть переход на 64С0 байт вперед, за ее пределы, потом идет RegisterWaitForInputIdle, а потом идет продолжение BaseProcessStart, куда она перепрыгивала с 64С0 байт назад.
Выглядит все это примерно так (листинг IDA Pro):
Код:
.text:7C810867 BaseProcessStart proc near              ; DATA XREF: sub_7C81059D+47D4o
.text:7C810867                 xor     ebp, ebp
.text:7C810869                 push    eax
.text:7C81086A                 push    0
.text:7C81086C                 jmp     __BaseProcessStartContinue
.text:7C81086C BaseProcessStart endp

... тут идет множество других функций, в том числе и RegisterWaitForInputIdle, которая идет последней ...

.text:7C816D06 ; int __fastcall RegisterWaitForInputIdle(int,int,int)
.text:7C816D06                 public RegisterWaitForInputIdle

а сюда управление попадает из BaseProcessStart. С легкой руки эту метку я обозвал __BaseProcessStartContinue

.text:7C816D2C ; START OF FUNCTION CHUNK FOR BaseProcessStart
.text:7C816D2C
.text:7C816D2C __BaseProcessStartContinue:             ; CODE XREF: BaseProcessStart+5j
.text:7C816D2C                 push    0Ch
.text:7C816D2E                 push    offset dword_7C816D58
.text:7C816D33                 call    sub_7C8024CB
.text:7C816D38                 and     dword ptr [ebp-4], 0
.text:7C816D3C                 push    4
.text:7C816D3E                 lea     eax, [ebp+8]
.text:7C816D41                 push    eax
.text:7C816D42                 push    9
.text:7C816D44                 push    0FFFFFFFEh
.text:7C816D46                 call    ds:NtSetInformationThread
.text:7C816D4C                 call    dword ptr [ebp+8]
.text:7C816D4F                 push    eax             ; dwExitCode
.text:7C816D50
.text:7C816D50 loc_7C816D50:                           ; CODE XREF: .text:7C843635j
.text:7C816D50                 call    ExitThread
Сначала управление попало в BaseProcessStart, процессор перепрыгнул на __BaseProcessStartContinue и произошел вызов NtSetInformationThread.
Смещение 0x0046 относительно RegisterWaitForInputIdle - это адрес инструкции
Код:
.text:7C816D4C                 call    dword ptr [ebp+8]
а смещение 0x0049 - адрес
Код:
.text:7C816D4F                 push    eax             ; dwExitCode
И все тайное становится явным
Адрес возврата в ntdll!NtSetInformationThread появляется просто по причине того, что в ntdll содержатся функции-переходники для Native API.
Как раз NtSetInformationThread является одним из таких переходников. Смещение 0x000c относительно NtSetInformationThread - это адрес инструкции после call, затолкнутый процессором в стек при выполнении инструкции call.
Код:
.text:7C90E642                 public ZwSetInformationThread
.text:7C90E642 ZwSetInformationThread proc near        ; CODE XREF: RtlImpersonateSelf+71p
.text:7C90E642                                         ; sub_7C92764E+79DCp ...
.text:7C90E642                 mov     eax, 0E5h       ; NtSetInformationThread
.text:7C90E647                 mov     edx, 7FFE0300h
.text:7C90E64C                 call    dword ptr [edx]
.text:7C90E64E                 retn    10h
.text:7C90E64E ZwSetInformationThread endp
Кроме того, что мы рассмотрели, как анализировать стек текущего процесса, мы еще и узнали, как происходит старт нового процесса.
Попробуем теперь распотрошить стек любого другого процесса.
Что же нам потребуется - найдем первый попавшийся поток этого процесса, приостановим, снимем его контекст, снимем дамп его стека и возобновим поток.
Потом будем производить анализ стека точно так же, как и для текущего процесса.

Поскольку я использую Microsoft Visual C++ 6.0 и ленюсь обновить свой давно устаревший SDK , а функция OpenThread отсутствовала в ранних версиях Windows, мне пришлось написать оболочку для динамического вызова OpenThread:
Код:
HANDLE WINAPI OpenThread(
  DWORD dwDesiredAccess,  // access right
  BOOL bInheritHandle,    // handle inheritance option
  DWORD dwThreadId        // thread identifier
)
{
	typedef HANDLE (WINAPI *func)(DWORD,BOOL,DWORD);
	func f = (func)GetProcAddress(GetModuleHandle("kernel32.dll"), "OpenThread");
	if(!f)
		ExitProcess(MessageBox(0, "Cannot find 'OpenThread' entry point in the 'kernel32.dll'", 0, MB_ICONERROR));
	return f(dwDesiredAccess, bInheritHandle, dwThreadId);
}
Теперь напишем функцию для анализа стека процесса. Ее заголовок будет таким:
BOOL StackRemoteUnwind(DWORD dwProcessId, DWORD dwThreadId, DWORD nSize=10, BOOL bSkipUnknownModules = TRUE)
dwProcessId - ID процесса
dwThreadId - ID потока
nSize - сколько адресов анализировать
bSkipUnknownModules - пропускать ли адреса, чьи модули не известны.

Для начала открываем дескрипторы процесса и потока:
Код:
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, dwProcessId);
	if(!hProcess)
		return MessageBox(0,"Cannot open process", "Stack unwind", MB_ICONSTOP)?0:0;

	HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, 0, dwThreadId);
	if(!hThread)
		return MessageBox(0,"Cannot open thread", "Stack unwind", MB_ICONSTOP)?0:0;
Затем аккуратно останавливаем поток, снимаем всю инфу, которая нужна (контекст, дамп стека) и возобновляем выполнение потока
Код:
	CONTEXT ctx = {CONTEXT_FULL};
	SuspendThread(hThread); // приостанавливаем
	if(!GetThreadContext(hThread, &ctx) || !ctx.Eip) // получаем контекст
	{
		ResumeThread(hThread);
		return MessageBox(0, "Cannot get thread context", "Stack unwind", MB_ICONSTOP)?0:0;
	}
	DWORD stackptr = ctx.Esp; // снимаем стек по адресу в ESP того потока
	char stack[10240];
	DWORD* lpdwStack = (DWORD*)stack; // делаем указатель на массив DWORD'ов для удобного анализа адресов
	DWORD read=0;
	if(!ReadProcessMemory(hProcess, (LPVOID)stackptr, stack, nSize*4, &read)) // снимаем дапм стека
	{
		ResumeThread(hThread);
		return MessageBox(0, "Cannot read process memory", "Stack unwind", MB_ICONSTOP)?0:0;
	}
	ResumeThread(hThread); // не забываем возобновить поток :)
Далее производим точно такие же действия, что и для текущего процесса:
Код:
	char buffer[10240] = "";
	wsprintf(buffer+lstrlen(buffer),"Unwinding stack at address 0x%08x\n", stackptr);

	char buf[1024];
	for(DWORD i=0;i<nSize;i++)
	{
		GetModuleAndFunctionNameByAddress(hProcess, lpdwStack[i], buf, 1024);
		if(!strncmp(buf, "unknown", 7) && bSkipUnknownModules)
			continue;
		wsprintf(buffer+lstrlen(buffer), "0x%08x %s\n", lpdwStack[i], buf);
	}

	MessageBox(0, buffer, "Stack unwind information", MB_ICONINFORMATION);

  // не забываем закрыть дескрипторы потока и процесса
	CloseHandle(hThread);
	CloseHandle(hProcess);
	return 1;
Для нахождения процесса по имени его образа не помешала бы функция, выполняющая эту операцию:
Код:
/* GetPIDbyName()
 *
 * Описание
 *		Получает идентификатор процесса по его имени
 *
 * Параметры
 *	szProcessName		имя процесса
 *
 * Возвращаемое значение
 *  PID процесса
 */
DWORD GetPIDbyName(LPTSTR szProcessName)
{
	HANDLE hSnapshot;
	PROCESSENTRY32 pe = {sizeof(pe)};

	hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (hSnapshot == INVALID_HANDLE_VALUE)
		return 0; 

	if (!Process32First(hSnapshot, &pe))
		return 0;

	do
		if(!lstrcmpi(pe.szExeFile,szProcessName)) 
			return pe.th32ProcessID;
	while (Process32Next(hSnapshot, &pe)); 
	return 0;
}
Теперь все готово для анализа стека потока другого процесса.
Код:
	// Создаем снимок всех потоков системы
	HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
	if(hSnapShot == INVALID_HANDLE_VALUE)
		return MessageBox(0, "CreateToolhelp32Snapshot() failed", "Thread snapshot", MB_ICONERROR)?0:0;

	THREADENTRY32 te = {sizeof(te)};
	if(!Thread32First(hSnapShot, &te))
		return AlternateMessageBox("No threads", "Thread snapshot")?0:0;

	DWORD pid=GetPIDbyName("explorer.exe");
	// Крутим список всех потоков
	do
	{
		// Раскручиваем стек первому попавшемуся потоку нашего процесса
		if(te.th32OwnerProcessID == pid)
		{
			StackRemoteUnwind(te.th32OwnerProcessID, te.th32ThreadID, 200, TRUE);
			break;
		}
	}
	while(Thread32Next(hSnapShot, &te));
Приведенный код находит первый поток процесса explorer.exe и анализирует его стек.
Вообще то, на этом стоит и закончить и без того затянувшееся повествование об устройстве потоков
Замечу только, что в сорце к статье вместо MessageBox для вывода информации используется самописная функция AlternateMessageBox, которая выводит диалоговое окно с текстовым полем для удобства копирования текста из него. Выход производится по нажатию любой клавиши. Код этой функции, если сильно повезет, ты найдешь в сорсе к статье, и я думаю, что знакомые с программированием окон на Win32 API, без труда его разберут.
На сим откланяюсь, удачного компилирования

(С) Great, 2006.

Source: http://cribble.by.ru/data/stackunwind.cpp

Последний раз редактировалось _Great_; 03.12.2006 в 13:06.. Причина: сорцы забыл))