TEB/PEB (nie tylko) w WOW64 (cz. II)

tech • 2087 słów • 10 minut czytania

Dosyć długo to trwało, ale wreszcie dokończyłem przeredagowanie ostatniej notatki o strukturach PEB/TEB w Windowsie i dokończyć tą aktualną, bardziej ukierunkowaną na WOW64. Bo tematem zainteresowałem się właśnie z tego powodu, szukając prostego i zarazem przenośnego (niezależnego od wersji systemu) sposobu dobrania się do tych struktur. Nawet o tym wspominałem w prologu poprzedniego wpisu.

W ramach krótkiego wstępu…

WOW64, czyli Windows 32-bit On Windows 64-bit (WOW64), jest warstwą emulacyjną umożliwiającą uruchomienie 32-bitowych aplikacji w środowisku 64-bitowym. W wielkim skrócie, warstwa ta gnieździ się pomiędzy 32-bitową aplikacją a natywnym API (ntdll), zawiera 32-bitowe wersje bibliotek systemowych i translatuje wywołania z 32-bitowego ntdll do natywnego 64-bitowego, i odwrotnie.

Jak można się domyślić, procesy WOW-owe zawierają obie wersje 32- i 64- bitowe bloków procesu i wątku. Od strony systemu są to natywne wersje 64-bitowe, a dla subsystemu WOW64 - jego 32-bitowe odpowiedniki, które w większości są kopią wersji natywnych (z małymi wyjątkami). Problemem może być dotarcie do interesującej nas w danej chwili wersji tych struktur. O ile pobranie natywnej wersji przebiega w standardowy sposób, o tyle sprawa z wersją WOW-ową się nieco komplikuje.

Na początek powinienem uściślić pojęcie natywnej wersji w aktualnym kontekście, choć może to być trochę kłopotliwe. Bo należy rozróżnić to, czy natywne struktury będą się odnosić do wersji systemu/platformy, czy do kodu aplikacji. Intuicja podpowiada, że powinien to być ten pierwszy przypadek, bo system jest natywny, a WOW to tylko emulacja. Choć z drugiej strony, patrząc od kodu aplikacji, dla niej natywna wersja to ta przeznaczona dla niej, odpowiadająca jej natywnemu kodowi. Myślę, że druga definicja w moim dalszym rozważaniu będzie bardziej odpowiednia, ale będę starał się jawnie odwoływać do wersji 32/64-bitowej.

Dostęp do wersji natywnych, czyli spod systemu do wersji 64-bitowej (wskaże dla systemu proces mimo wszystko jest 64-bitowy), a także dostęp do 32-bitowej wersji wprost z 32-bitowego kodu uruchamianego pod WOW-em, przebiega standardowo, dostępnymi metodami jakie przedstawiłem w poprzednim poście. Tutaj nie ma się nad czym zastanawiać.

Ciekawe jest dobranie się do wersji WOW-owej. Mianem tym będę określał wersję nie natywną, czyli dla 64-bitowego procesu systemu będzie to wersja 32-bitowa, a dla kodu 32-bitowego uruchomionego w warstwie emulacji - wersja 64-bitowa. Tutaj musimy zajrzeć nieco pod maskę systemu.

Wykorzystanie korelacji

W sieci można znaleźć informacje, o ułożeniu poszczególnych bloków w pamięci. Większość z tych informacji mówi jasno, że 32-bitowy TEB zawsze leży w odległości 2 stron (0x2000) za 64-bitową wersją (systemową), a 32-bitowy PEB 1 stronę (0x1000) przed wersją systemową (64-bitową).

Jako potwierdzenie tego, wystarczy zerknąć na adresy tych struktur wydedukowane przez WinDbg, za pomocą polecenia !wow64exts.info:

0:000> !wow64exts.info

PEB32: 0x7efde000
PEB64: 0x7efdf000

Wow64 information for current thread:

TEB32: 0x7efdd000
TEB64: 0x7efdb000

32 bit, StackBase   : 0xf0000
        StackLimit  : 0xdf000
        Deallocation: 0xb0000

64 bit, StackBase   : 0x18fd20
        StackLimit  : 0x18c000
        Deallocation: 0x150000

Na tym również jasno widać, że istnieją również dwie wersja stosu, co wydaje się logiczne, ale do tego może wrócimy kiedyś indziej.

Innym potwierdzeniem słuszności tych informacji może być zerkniecie do kodu biblioteki systemowej w wersji dla WOW64, gdzie dokładnie widać do jakich danych dobiera się system, w zależności od procesu. Taką krótką analizę można znaleźć na blogu redplait w notatce teb32 of wow64 process.

Znając rozmieszczenie danych w pamięci, z łatwością można zaimplementować funkcje pobierające wybrane dane, które będą zwracały wskaźniki na bloki WOW-owe (przeciwne do natywnych wersji):

inline PTEB GetWowCurrentTeb() {
	const int offset = 0x2000;
#if defined _M_IX86		// x86
	return (PTEB)((ULONG_PTR)GetCurrentTeb() - offset);
#elif defined _M_X64	// x64
	return (PTEB)((ULONG_PTR))GetCurrentTeb() + offset);
#else
	#error "Unknown platform."
#endif
}

inline PPEB GetWowCurrentPeb() {
	const int offset = 0x1000;
#if defined _M_IX86		// x86
	return (PPEB)((ULONG_PTR)GetCurrentPeb() + offset);
#elif defined _M_X64	// x64
	return (PPEB)((ULONG_PTR)GetCurrentPeb() - offset);
#else
	#error "Unknown platform."
#endif
}

Niestety jak to bywa, w pewnym momencie nowa wersja systemu musi wprowadzić zmiany, od wersji Windows 8, rozlokowanie danych w pamięci uległo małej modyfikacji:

0:000> !wow64exts.info
PEB32: 0x7f086000
PEB64: 0x7f088000

Wow64 information for current thread:

TEB32: 0x7f08f000
TEB64: 0x7f08d000

.restart /f

0:000> !wow64exts.info
PEB32: 0x7f39f000
PEB64: 0x7f39a000

Wow64 information for current thread:

TEB32: 0x7f39e000
TEB64: 0x7f39c000

Korelacja miedzy TEB-ami nadal pozostaje taka sama, zmieniła się natomiast lokacja struktur PEB. Przy każdym uruchomieniu jest inna, losowa, więc nie da się znaleźć żadnej deterministycznej metody jej wyznaczania.

Wygląda na to, że zmieniła się ścieżka wykonywania kodu, i zależnie od okoliczności przydział pamięci następuje w inny sposób. Możliwe jest również to, że włączono losowy przydział adresów (nie mogę przypomnieć sobie nazwy) i dla bezpieczeństwa każda alokacja otrzymuje niedeterministyczną wartość adresu z wolnej przestrzenia adresowej. Ciekawe, ale obecnie nie mam czasu, aby zajrzeć głębiej pod maskę systemu, aby się dowiedzieć co tak naprawdę się wydarzyło.

Mimo to nadal możemy bez problemu polegać na korelacji między TEB-ami, ciągle obowiązuje ten sam offset. Myślę, że nie powinno się to szybko zmienić, szczególnie, że w WRK (Windows Research Kernel) można znaleźć takie oto makra:

//
// Macro to round to the nearest page size
//

#define WOW64_ROUND_TO_PAGES(Size) \
	(((ULONG_PTR)(Size) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))

//
// Get the 32-bit TEB without doing a memory reference.
//

#define WOW64_GET_TEB32_SAFE(teb64) \
	((PTEB32) ((ULONGLONG)teb64 + WOW64_ROUND_TO_PAGES (sizeof (TEB))))

#define WOW64_GET_TEB32(teb64) \
	WOW64_GET_TEB32_SAFE(teb64)

A dla PEB-a możemy zmodyfikować kod w bardziej przenośną implementację - używającą pola ProcessEnvironmentBlock z TEB-a.

Inną opcją jest skorzystanie z dostępnych funkcji API tych jawnych oraz nieudokumentowanych, albo znaleźć inne sztuczki, a takie jeszcze istnieją, o czym dalej.

PEB/TEB64 z procesu WOW64 (32-bit)

Chcąc dobrać się do systemowego 64-bitowego bloku danych wprost z kodu aplikacji 32-bitowej uruchomionej w warstwie emulacyjnej WOW64, można skorzystać z oficjalnie dostępnych w WinAPI funkcji. Wraz w WOW-em pojawiło się kilka specjalnych wersji funkcji przeznaczonych do tego celu - interakcji z natywnym podsystemem spoza warstwy WOW64. Można je rozpoznać po podsłowie Wow64 w nazwie, np. funkcje NtWow64*, RtlWow64*. Dostępne są tylko w 32-bitowych wersjach bibliotek systemowych używanych przez WOW64.

Adres do 64-bitowego systemowego bloku procesu można pobrać używając funkcji NtWow64QueryInformationProcess64:

NTSTATUS WINAPI NtWow64QueryInformationProcess64(
  _In_       HANDLE ProcessHandle,
  _In_       PROCESSINFOCLASS ProcessInformationClass,
  _Out_      PVOID ProcessInformation,
  _In_       ULONG ProcessInformationLength,
  _Out_opt_  PULONG ReturnLength
);

Jest to nic innego jak WOW-owy wrapper na natywny 64-bitowy syscall NtQueryInformationProcess, a ten jak już wiadomo w strukturze PROCESS_BASIC_INFORMATION w polu PebBaseAddress dostarczy adres na poszukiwany obszar pamięci.

Niestety nie istnieje WOW64-owa wersja odpowiadająca podobnemu działaniu w kontekście wątku, ani żadne inne wywołanie API.

Za to grzebiąc w źródłach i kodach systemowych można znaleźć inne sposoby na zdobycie adresu do 64-bitowej struktury TEB. WOW64 (i nie tylko) w różnych, nie używanych (w danym kontekście) polach i fragmentach (tutaj PEB/TEB) wrzuca inne dane. Można się zdziwić co zawiera pole GdiBatchCount w TEB procesu 32-bitowego w WOW-ie – jest to adres do wersji 64-bitowej!

Świadczyć o tym mogą znalezione fragmenty kodu w WRK:

#define NtCurrentTeb64()	((PTEB64)((PTEB32)NtCurrentTeb())->GdiBatchCount)
#define NtCurrentPeb64()	((PPEB64)NtCurrentTeb64()->ProcessEnvironmentBlock)

Aby się przekonać, że to wciąż jest prawda, wystarczy odpalić debuggera:

// !wow64exts.info
TEB32: 0x7efda000
TEB64: <strong>0x7efd8000</strong>

// dt -r _TEB 7efda000
TEB32: 
...
	+0xf6c WinSockData				: (null) 
	+0xf70 GdiBatchCount			: <strong>0x7efd8000</strong>
	+0xf74 CurrentIdealProcessor	: _PROCESSOR_NUMBER
...

albo pogrzebać w kodzie systemowych dll-ek. ReWolf analizując działanie GetSystemFileCacheSize trafił na podobny ślad potwierdzający te doniesienia…

PEB/TEB32 z procesu natywnego (64-bit)

W drugą stronę, z natywnego procesu systemowego dostęp do bloków danych procesu i wątku również jest prosty, wystarczy tylko wykorzystać odpowiednią funkcje z API. A jest nią znane już NtQueryInformationProcess. Posiada ona możliwość pobrania specyficznych danych dla procesu uruchomionego pod WOW64. Mowa tutaj o info klasy typu ProcessWow64Information.

Dokumentacja nie mówi jasno wprost o tym, że dostaniemy adres, pod którym leży PEB32:

When the ProcessInformationClass parameter is ProcessWow64Information, the buffer pointed to by the ProcessInformation parameter should be large enough to hold a ULONG_PTR. If this value is nonzero, the process is running in a WOW64 environment; otherwise, if the value is equal to zero, the process is not running in a WOW64 environment.

ale skoro zwracany jest typ mogący pomieścić adres, i tylko dla procesów WOW64 jest to wartość niezerowa, to znak, że trzeba to zbadać.

Jak prześledzimy źródła to dojdziemy do wniosku, że zwracana jest wartość pola Wow64Process struktury PROCESS. A ona przechowuje dokładnie to co nas interesuje. Żeby nie zarzucać za dużo kodu binarnego, można to potwierdzić za pomocą WinDbg listując zawartość struktury PROCESS:

[Mam mały problem z dostępem do debugera kernela pod Windows 7, więc na razie nie mogę nic tutaj pokazać. Jak tylko będę miał okazję to wrzucę potrzebne fragmenty z jego outputu…]

Pole to jest wypełniane adresem do zaalokowanej pamięci przez MiCreatePebOrTeb na potrzeby PEB32 w funkcji MiInitializeWowPeb, która inicjalizuje dla procesów WOW64-owych dane w MmCreatePeb.

Historycznie wypada mi wspomnieć, że przed Windows 7, Wow64Process w rzeczywistości nie był bezpośrednim wskaźnikiem na PEB32 tylko prowadził do danych zawartych w strukturze WOW64_PROCESS, która zawierała nieco więcej informacji o procesie w kontekście subsystemu WOW64:

typedef struct _WOW64_PROCESS {
	PVOID Wow64;
#if defined(_IA64_)
	FAST_MUTEX AlternateTableLock;
	PULONG AltPermBitmap;
	ULONG AltFlags;
#endif
} WOW64_PROCESS, *PWOW64_PROCESS;

Adres do bloku środowiska procesu zawierało pole Wow64.

W kernel-mode do pobierania tej wartości z PROCESS można użyć następujących funkcji:

#if defined (_WIN64)
	#define PS_GET_WOW64_PROCESS(Process) ((Process)->Wow64Process)
#else
	#define PS_GET_WOW64_PROCESS(Process) ((Process), ((PWOW64_PROCESS)NULL))
#endif

PVOID PsGetProcessWow64Process(__in PEPROCESS Process)
{
	return PS_GET_WOW64_PROCESS (Process);
}

PVOID PsGetCurrentProcessWow64Process(VOID)
{
	return PS_GET_WOW64_PROCESS (PsGetCurrentProcess());
}

Funkcje te w każdej wersji zwracały wartość wskaźnika zapisanego w polu Wow64Process, niezależnie od tego, czy zawarty adres wskazywał na WOW64_PROCESS, czy bezpośrednio na PEB32.

Podobnie jak w przypadku dostępu do TEB64 w aplikacji 32-bitowej w WOW64, tutaj również istnieją pewne tricki i nieudokumentowane miejsca, gdzie kernel wrzuca adres do WOW-owego bloku wątku.

Jak wiadomo, w 64-bitowy kod nie ma obsługi wyjątków stack-based, zatem w polu ExceptionList przechowywany jest adres do TEB32, jeśli proces jest WOW-owy, lub 0, gdy jest to proces natywny 64-bitowy.

Mechanizm ten jest używany przez jądro, o czym mogą świadczyć następujące fragmenty kodu w WRK:

//
// Update the first qword in the 64-bit TEB.  The 32-bit rdteb instruction
// reads the TEB32 pointer value directly from this field.
//

#define WOW64_SET_TEB32(teb64, teb32) \
	(teb64)->NtTib.ExceptionList = (struct _EXCEPTION_REGISTRATION_RECORD *)(teb32);

#define WOW64_TEB32_POINTER_ADDRESS(teb64) \
	(PVOID)&((teb64)->NtTib.ExceptionList)

Potwierdzić to można za pomocą WinDbg lub innego debuggera:

// !wow64exts.info
TEB32: <strong>0x7efda000</strong>
TEB64: 0x7efd8000

// dt -r _TEB 0x7efd8000
TEB64: 
...
	+0x000 NtTib            : _NT_TIB
		+0x000 ExceptionList    : <strong>0x00000000`7efda000</strong> _EXCEPTION_REGISTRATION_RECORD
			+0x000 Next             : 0x01fa0000`ffffffff _EXCEPTION_REGISTRATION_RECORD
...

Słowem zakończenia…

Jak widać, istnieje wiele dróg prowadzących do celu. Dużo ciekawych nieudokumentowanych rzeczy można znaleźć w internalsach systemu Windows. Niektóre do tej pory znane sposoby na zlokalizowanie odpowiednich struktur w procesach WOW64 uległy zmianie - mam tutaj na mysli rozmieszczenie w pamięci struktur PEB. Ukazuje to jeden istotny mankament - poleganie na nieudokumentowanych elementach prędzej czy później może przynieść problemy. Ale z drugiej strony, większość tych mechanizmów jest powszechnie znana i używana, wiec wybór pomiędzy używaniem tych elementów a innych sposobów, jak systemowe API (o ile istnieje), zależy ściśle od potrzeb i możliwości jakie chce się osiągnąć.

Wywołanie funkcji API zawsze pociąga jakiś narzut, a szybki dostęp bezpośrednio do poszczególnych pól struktur, w niektórych sytuacjach ma bardzo istotne znaczenie. Poza tym patrząc na przeprowadzone “śledztwo”, mogę rzec, że użycie tych samych mechanizmów, jakie używa jądro powinno być bezpieczne. Na pewno do czasu, aż kolejna wersja systemu nie wprowadzi zmian.

Do zmian i problemów z tego wynikłych zawsze można się jakoś przygotować. Jednym ze sposób może być badanie, czy to na czym bazujemy nadal jest dostępne lub spełnia określone wymagania. Można porównać zwracane wartości różnych metod, a gdy to zwróci wynik negatywny, zasygnalizować występujący problem reakcja typu “Unsuportded version”. Da to wyraźny sygnał, że cos zostało zmienione. Na pewno jest to lepsze niż błądzenie po nie swojej pamięci i rzucenie Access Violation przez aplikację.

U mnie obecnie faworytem jest skorzystanie z wiedzy w jakich polach sam kernel trzyma dane, szczególnie te, które dostępne są w przestrzeni adresowej użytkownika. Korzystając z tych tajnych informacji można zaimplementować funkcje w sposób podobny do poniższego zapisu:

inline PTEB GetWowCurrentTeb() {
#if defined _M_IX86		// x86
	// return TEB64
	return (PTEB)GetCurrentTeb()->GdiBatchCount;
#elif defined _M_X64	// x64
	// return TEB32
	return (PTEB)GetCurrentTeb()->NtTib.ExceptionList;
#else
	#error "Unknown platform."
#endif
}

inline PPEB GetWowCurrentPeb() {
	// return PEB32 or PEB64
	return GetWowCurrentTeb()->ProcessEnvironmentBlock;
}

Wykorzystanie poszczególnych pól różnych struktur do innego celu niż zostały przywidziane, jest dosyć częsta praktyką, nie tylko w WOW64. Pozwala to zminimalizować użycie pamięci, szczególnie, że większość istniejących pól w różnych kontekstach nie jest używana. A skoro nie jest używana, to nie ma sensu dodawać nowych i zwiększać użycie pamięci, bo czasem w pewnych sytuacjach ma to ogormne znaczenie. Podobnie pozwala to uchronić się przed załamaniem kompatybilności.

Z punktu widzenia deweloperskiego, może to zaciemni kod, ale dobre tego zaimplementowanie, nie powinno stwarzać takiej iluzji.

BTW. WOW64 trzyma jeszcze masę ciekawych informacji w innych polach i slotach TLS (Thread Local Storage), może kiedyś o tym napiszę, jak znajdę coś szczególnie interesującego.

Komentarze (0)

Dodaj komentarz

/dozwolony markdown/

/nie zostanie opublikowany/