TEB/PEB (nie tylko) w WOW64 (cz. I)
• tech • 1575 słów • 8 minut czytania
Pogłębiając swoją wiedzę nad internalsami systemu Windows, moje badania przesunęły się ostatnio trochę w stronę WOW64. Jest to całkiem ciekawy fragment architektoniczny systemu, o którym kiedyś jeszcze mam nadzieję będę pisał, bo wypływa wiele ciekawych rzeczy w trakcie jego analizowania. Przy okazji tego tematu zahaczyłem o struktury bloków danych procesu PEB
i wątku TEB
, głównie w kontekście podsystemu WOW64. Uznałem, że okazja ta daje dobry moment, aby również przypomnieć sobie co nieco o tych podstawowych elementach systemu, które dostępne są z poziomu user-mode, mimo rezydowania w przestrzeni jądra.
Intro
W windowsie główne struktury związane z procesem i wątkami, jak można się spodziewać, znajdują się oczywiście w kernelu - EPROCESS
i KPROCESS
oraz adekwatne dla wątku - ETRHEAD
i KTRHEAD
. Mimo tego zawierają one w sobie (lub są powiązane) struktury przechowywane w przestrzeni adresowej użytkownika - PEB
(Process Environment Block) i TEB
(Thread Environment Block). Są to jedyne struktury przechowywane w pamięci dostępnej w trybie użytkownika, głównie (chyba nawet jedynie) z powodu zawierania danych dotyczących procesu, czy wątku, podlegających modyfikacjom właśnie przez kod pracujący w przestrzeni użytkownika.
Definicje tych struktur zawarte są w winternl.h
i nie do końca są oficjalnie w pełni udokumentowane (PEB i TEB na msdn). Ale pomimo tego, w sieci można znaleźć na ich temat dużo ciekawych informacji, chociażby na undocumented.ntinternals.net, czy (zapomniałem), gdzie wiele ich elementów zostało rozszyfrowanych. Nawet na Wikipedii coś się znajdzie (PEB, TEB).
Struktury te rodzą się w bólach, w funkcjach kernela, w procesie tworzenia i inicjalizacji nowego procesu lub wątku. Odpowiedzialne za to są funkcje MmCreatePeb
i MmCreateTeb
, które alokują za pomocą MiCreatePebOrTeb
pamięć w przestrzeni użytkownika, a następnie inicjalizują poszczególne pola wartościami początkowymi, bazując głównie na przekazanych wartościach w InitialPeb
/InitialTeb
. Niektóre pola mogą być później nadpisane w czasie ładowania obrazu PE. Scenariusz tworzenia nowego procesu lub wątku w systemie Windows, szczególnie w czeluściach kodu kernela jest bardzo ciekawy, może kiedyś do niego wrócę.
Zasadnicze pytanie brzmi: “jak się do nich dostać?”, a odpowiedź jest bardzo prosta: “bardzo łatwo!”. I to właśnie o tych prostych metodach i sposobach będzie w tej notatce.
Dostep w user-mode
Najbardziej popularny sposób dostępu do tych struktur możliwy jest przez wykorzystanie rejestrów segmentowych - FS
dla x86 i GS
dla x64, które to bezpośrednio wskazują na blok NT_TIB
, będący w rzeczywistości stałym, niezależnym od subsytemu nagłówkiem zawartym w TEB
:
struct NT_TIB {
PVOID ExceptionList;
PVOID StackBase;
PVOID StackLimit;
PVOID SubSystemTib;
PVOID FiberData;
PVOID ArbitraryUserPointer;
NT_TIB* Self;
};
Powszechnie przyjęło się nie używać dostępu do danych TEB
przez offset zerowy rejestru segmentowego, ale poprzez pobranie jego liniowego wskaźnika do samego siebie, zawartego w polu Self
w bloku NT_TIB
(FS:[0x18]
, GS:[0x30]
).
Bliższe spojrzenie na strukturę TEB
ukazuje ciekawe powiązanie mówiące o procesie do którego należy wątek - do struktury PEB
w polu ProcessEnvironmentBlock
. Zatem łatwo, za pomoca tego pola, można przejść z wątku do procesu, lub bezpośrednio przez odpowiedni offset rejestru segmentowego.
Funkcje dostepowe korzystające z tej wiedzy można byłoby zapisać w postaci prostych inline-ów:
inline PTEB GetCurrentTeb() {
#if defined(_M_IX86) // x86
__asm mov eax, fs:[0x18]
#elif defined _M_X64 // x64
__asm mov eax, gs:[0x30]
#else
#error "Unknown platform."
#endif
}
inline PPEB GetCurrentPeb() {
#if defined(_M_IX86) // x86
__asm {
mov eax, fs:[0x18]
mov eax, dword ptr [eax + 0x30]
}
#elif defined _M_X64 // x64
__asm {
mov eax, gs:[0x30]
mov eax, dword ptr [eax + 0x60]
}
#else
#error "Unknown platform."
#endif
}
Niestety wersja dla x64 nie zadziała na kompilatorze Microsoftu, wstawki asemblerowe nie są dostępne na platformach innych niż x86. Za to możemy pozbyć się ich ich jawnego używania i skorzystać z wbudowanych intrinsics, począwszy od wersji kompilatora 13012035:
inline PTEB GetCurrentTeb() {
#if defined _M_IX86 // x86
return (PTEB)__readfsdword(0x18);
#elif defined _M_X64 // x64
return (PTEB)__readgsqword(0x30);
#else
#error "Unknown platform."
#endif
}
inline PPEB GetCurrentPeb() {
#if defined _M_IX86 // x86
return (PPEB)__readfsdword(0x30);
#elif defined _M_X64 // x64
return (PPEB)__readgsqword(0x60);
#else
#error "Unknown platform."
#endif
}
Sam SKD Windowsa zawiera podobne do tych wyżej definicje, ale tylko dla bloku wątku pod nazwą NtCurrentTeb
, który można znaleźć w nagłówku winnt.h
. Próżno szukać tam podobnej funkcji dla bloku procesu.
W powyższej implementacji GetCurrentPeb
użyłem bezpośredniego offsetu do pola PEB, jak dla mnie jest to prosty i czytelny zapis, zawierający tylko jeden rozkaz. Zastanawiam się dlaczego nigdzie nie używa się offsetu w rejestrze segmentowym do pointera na PEB. O ile rozumiem powszechne omijanie szerokim łukiem pobierania TEB-a przez zerowy offset i używanie liniowego wskażnika do TEB
, to o tyle nie wiem czemu i nie widzę sensu, stosowania takiego rozwiazania, szczególnie dla PEB-a. Być może wynika to z faktu, że sam kompilator tak robi, a robić inaczej nie może, bo skoro jest tylko NtCurrentTeb
to wszyscy przez niego dostają się do danych procesu. A, że nie jest to dla kompilatora jawna i prosta arytmetyka na offsecie, którą można byłoby zoptymalizować, to otrzymujemy własnie taki wynikowy kod.
Oprócz wykorzystania rejestrów segmentowych, innym sposobem jest skorzystanie z API systemowego, korzystając z funkcji NtQueryInformationThread i NtQueryInformationProcess, pobierając bazowe informacje dla wątku/procesu, w strukturach *_BASIC_INFORMATION
możemy znaleźć interesujące nas dane - wskaźniki na TEB i PEB:
struct THREAD_BASIC_INFORMATION {
NTSTATUS ExitStatus;
PTEB TebBaseAddress;
CLIENT_ID ClientId;
ULONG AffinityMask;
LONG Priority;
LONG BasePriority;
};
struct PROCESS_BASIC_INFORMATION {
NTSTATUS ExitStatus;
PPEB PebBaseAddress;
ULONG_PTR AffinityMask;
KPRIORITY BasePriority;
ULONG_PTR UniqueProcessId;
ULONG_PTR InheritedFromUniqueProcessId;
};
Podobnie, gdy chcemy pobrać informacje nie dla bieżącego wątku lub procesu, ale dowolnego działającego w systemie, to funkcje te idealnie się nadają do tego celu, gdyż pobierają jako argument uchwyt do danego obiektu procesu lub wątku.
Dostęp w kernel-mode
W kernel-mode możemy dobrać się do struktur po odpowiednich polach w systemowych strukturach.
Dostępne są również odpowiednie nieudokumentowane funkcje:
PVOID PsGetThreadTeb(__in PETHREAD Thread)
{
return Thread->Tcb.Teb;
}
PPEB PsGetProcessPeb(__in PEPROCESS Process)
{
return Process->Peb;
}
Przed ich użyciem należy pamiętać o KeAttachProcess
lub KeStackAttachProcess
.
Standardowo, również dostepne jest znane z user-mode makro/funkcja NtCurrentTeb
, a nawet przed oczami przemknęło mi NtCurrentPeb
:
#if defined(RTL_USE_KERNEL_PEB_RTN) || defined(NTOS_KERNEL_RUNTIME)
#define NtCurrentPeb() (PsGetCurrentProcess ()->Peb)
#else
#define NtCurrentPeb() (NtCurrentTeb()->ProcessEnvironmentBlock)
#endif
Dziwi mnie czemu tego nie ma w standardowym SDK.
Zawartość i użycie
Wspomniałem na wstępie, że ogólna zawartość tych struktur jest nieudokumentowana i niepubliczna, chociaż powszechnie znana. Zawsze można wylistować bieżącą zawartość i ich skład wprost z załadowanych symboli w debugerze WinDbg za pomocą odpowiednich komend (mała ściągawka).
Oczywiście, jak każde nieudokumentowane elementy, a szczególnie gdy dotyczy to wewnętrznych elementów systemu, należy mieć na uwadze fakt, że dane oraz ich format, czy reprezentacja może ulec zmianie w każdej następnej wersji. To samo tyczy się PEB
/TEB
, struktury te ewoluują, więc należy unikać ich częstego i jawnego użytkowania, o ile jest to możliwe, bo czasami naprawdę dzięki nim można zrobić wiele ciekawych rzeczy.
Ewolucje i zmiany tych struktur na przestrzeni kilku wersji systemu Windows, począwszy od XP, zebrał i czytelnie zaprezentował ReWolf na swoim blogu we wpisie Evolution of Process Environment Block (PEB). Bardzo przydatne, gdy jednak musimy skorzystać z tych struktur i być jak najbardziej kompatybilni. Niestety zabrakło wersji dla TEB
.
Gdy jednak nie musimy sięgać łapami bezpośrednio do PEB
/TEB
, warto korzystać z funkcji systemowych, zapewniających kompatybilność z wszelkimi zmianami. Większość funkcji API przestrzeni użytkownika dotyczących procesu czy wątku, operuje bezpośrednio na tych danych - nawet łatwo można powiązać nazwy poszczególnych pól z odpowiednimi funkcjami WinAPI. Przekonać się o tym można zanurzając się w głąb ich implementacji.
Dla przykładu w kernel32
(kernelbase
) dla funkcji GetStartupInfo
znajdziemy taki fragment kodu:
mov eax, large fs:18h ; TEB
mov eax, [eax+30h] ; TEB->PEB
mov ecx, [eax+10h] ; PEB->ProcessParameters [PP]
mov eax, [ebp+lpStartupInfo] ; lpStartupInfo [SI]
mov dword ptr [eax], 44h ; SI->cb = 0x44
mov edx, [ecx+84h]
mov [eax+4], edx ; SI->lpReserved = PP->ShellInfo->Buffer
mov edx, [ecx+7Ch]
mov [eax+8], edx ; SI->lpDesktop = PP->DesktopInfo->Buffer
mov edx, [ecx+74h]
mov [eax+0Ch], edx ; SI->lpTitle = PP->WindowTitle->Buffer
mov edx, [ecx+4Ch]
mov [eax+10h], edx ; SI->dwX = PP->StartingX
mov edx, [ecx+50h]
mov [eax+14h], edx ; SI->dwY = PP->StartingY
Z moich komentarzach można zorientować się że funkcja ta wypełnia strukturę STARTUPINFO
danymi o procesie zaczerpniętymi wprost ze struktury RTL_USER_PROCESS_PARAMETERS
ulokowanej w PEB
.
Podobnie reversując i odtwarzając kod funkcji GetStdHandle
, ujrzymy szerokie wykorzystanie danych z bloku procesu, bez potrzeby sięgania do kernela:
HANDLE GetStdHandle(DWORD nStdHandle) {
HANDLE handle = INVALID_HANDLE_VALUE;
PPEB peb = NtCurrentTeb()->ProcessEnvironmentBlock;
if (nStdHandle == STD_ERROR_HANDLE) {
handle = peb->ProcessParameters->StandardError;
} else if (nStdHandle == STD_OUTPUT_HANDLE) {
if (peb->ProcessParameters->WindowFlags & 0x400)
return 0;
handle = peb->ProcessParameters->StandardOutput;
} else if (nStdHandle == STD_INPUT_HANDLE) {
if (peb->ProcessParameters->WindowFlags & 0x200)
return 0;
handle = peb->ProcessParameters->StandardInput
}
if (handle == INVALID_HANDLE_VALUE)
BaseSetLastNTError(STATUS_INVALID_HANDLE);
return handle;
}
To samo z TEB
, dla przykładu kilka prostych getterów, jak GetLastError
i GetCurrentThreadId
:
_GetLastError@0 proc near
mov eax, large fs:18h
mov eax, [eax+34h]
retn
_GetLastError@0 endp
_GetCurrentThreadId@0 proc near
mov eax, large fs:18h
mov eax, [eax+24h]
retn
_GetCurrentThreadId@0 endp
Magii za bardzo tutaj nie ma.
Outro
Struktury wątku i procesu, zawarte w przestrzeni użytkownika przechowują ciekawe informacje. Ich analiza i próby potencjalnego wykorzystania mogą się okazać ciekawą nauka o działaniu systemu. Wiedza ta również jest wykorzystywana nie tylko w dobrych intencjach, do osiągania różnych fajnych rzeczy, ale najczęściej bywa w tych niecnych. Większość malware i różnego innego paskudnego robactwa wykorzystuje różne tricki związane z tymi strukturami oraz danymi w nich zawartymi.
Mam nadzieje, że będę jeszcze wracał do tematu poszczególnych elementów owych struktur w niedalekiej przyszłości, bo naprawdę ciekawe rzeczy można w nich znaleźć, a jeszcze ciekawiej wykorzystać.
[updated 28 2014 o 01:39]
Przeredagowałem i uzupełniłem notatkę o kilka informacji, a fragmenty związane z WOW64 wzbogaciłem i wydzieliłem do osobnego wpisu, bo ich rozmiar znacznie się powiększył. Trochę to trwało, ale na pewno zwiększy to czytelność i przejrzystość, a zebrany materiał będzie ciekawy.
Komentarze (0)