获取TEB和PEB的四种方法
文章目录
TEB和PEB结构: TEB and PEB x64 and TEB and PEB x86
两种获取当前进程的TEB和PEB两种跨进程获取PEB,前两种方法可以获得当前进程的TEB和PEB,后两种方法是获得指定进程的PEB。第四种方法其实在x86体系下也可以获得TEB,同理x64肯定也行,但是暂时用不着,我也没去试试。
方法一:__readgsqword() 和 __readfsdword()
前置知识
__readgsqword(x)
用于获取相对GS段偏移量为x处的八字节大小的值。同理__readfsdword(x)
用于获取相对FS段开始偏移量为x处的四字节大小的值。
根据实验可知:
- 在x64架构环境下,
GS + 30h
处存储的是Teb结构体的基地址,GS + 60h
处存储的是Peb结构体的基地址。 - 在x86架构环境下,
FS + 18h
处存储的是Teb结构体的基地址,FS + 30h
处存储的是Peb结构体的基地址。
实现
语法:
/*
* 读GS
*/
unsigned char __readgsbyte(
unsigned long Offset
);
unsigned short __readgsword(
unsigned long Offset
);
unsigned long __readgsdword(
unsigned long Offset
);
unsigned __int64 __readgsqword(
unsigned long Offset
);
/*
* 读FS
*/
unsigned char __readfsbyte(
unsigned long Offset
);
unsigned short __readfsword(
unsigned long Offset
);
unsigned long __readfsdword(
unsigned long Offset
);
unsigned __int64 __readfsqword(
unsigned long Offset
);
具体实现与测试
#include <iostream>
#include <windows.h>
using namespace std;
void* GetTebAddress()
{
#ifdef _WIN64
return (void*)__readgsqword(0x30);
#else
return (void*)__readfsdword(0x18);
#endif
}
void* GetPebAddress()
{
#ifdef _WIN64
return (void*)__readgsqword(0x60);
#else
return (void*)__readfsdword(0x30);
#endif
}
void test()
{
//to get the image base
HANDLE imagebase = GetModuleHandle(NULL);
if (imagebase != NULL) std::cout <<"imagebase:0x" << hex << imagebase << endl;
#ifdef _WIN64
//
//
/*x64 Architecture*/
//
//
//get the address of teb and peb
void* teb = GetTebAddress();
void* peb = GetPebAddress();
std::cout << "teb address:0x" << hex << (size_t)teb << endl;
std::cout << "peb address:0x" << hex << (size_t)peb << endl;
//get peb by teb to test if the return value of `GetTebAddress()` is the address of teb
size_t pebByteb = *(size_t*)((size_t)teb + 0x60);
if (pebByteb == (size_t)peb)
{
std::cout << "Yes the GetTebAddress works corresctly" << endl;
std::cout << "pebByTeb:0x" << hex << pebByteb << endl;
}
else
{
std::cout << "No the of GetTebAddress works incorrectly" << endl;
std::cout << "pebByTeb:0x" << hex << pebByteb << endl;
}
//get imagebase by peb test if the `GetPebAddress()` works correctly
size_t imagebaseBypeb = *(size_t*)((size_t)peb + 0x10);
if (imagebaseBypeb == (size_t)imagebase)
{
std::cout << "Yes the GetPebAddress works correctly" << endl;
std::cout << "imagebaseBypeb:0x" << hex << imagebaseBypeb << endl;
}
else
{
std::cout << "No the GetPebAddress works incorrectly" << endl;
std::cout << "imagebaseBypeb:0x" << hex << imagebaseBypeb << endl;
}
#else
//
//
/*x86 Architecture*/
//
//
//get the address of teb and peb
void* teb = GetTebAddress();
void* peb = GetPebAddress();
std::cout << "teb address:0x" << hex << teb << endl;
std::cout << "peb address:0x" << hex << peb << endl;
//get peb by teb to test if the return value of `GetTebAddress()` is the address of teb
DWORD32 pebByteb = *(DWORD32*)((DWORD32)teb + 0x30);
if (pebByteb == (size_t)peb)
{
std::cout << "Yes the return value of GetTebAddress is corresct" << endl;
std::cout << "pebByTeb:0x" << hex << pebByteb << endl;
}
else
{
std::cout << "No the return value of GetTebAddress is incorrect" << endl;
std::cout << "pebByTeb:0x" << hex << pebByteb << endl;
}
//get imagebase by peb test if the `GetPebAddress()` works correctly
DWORD32 imagebaseBypeb = *(DWORD32*)((DWORD32)peb + 0x8);
if (imagebaseBypeb == (DWORD32)imagebase)
{
std::cout << "Yes the GetPebAddress works correctly" << endl;
std::cout << "imagebaseBypeb:0x" << hex << imagebaseBypeb << endl;
}
else
{
std::cout << "No the GetPebAddress works incorrectly" << endl;
std::cout << "imagebaseBypeb:0x" << hex << imagebaseBypeb << endl;
}
#endif
}
int main()
{
test();
return 0;
}
运行结果:
x86
x64
单词打错了,不想改了,请各位谅解【悲】
解释
GetModduleHandle(NULL)
返回值为创建当前进程的可执行文件载入内存的基地址,由Teb结构可知starting address of teb + 60h(x64)/30h(x86)
这个地址存储了Peb结构体的基地址。由Peb结构体可知starting address of peb + 10h(x64)/8h(x86)
这个地址存储的值是image base
。通过这些特性就可以通过APIGetModuleHandle()
来测试得到的Teb和Peb的地址是否准确。
方法二:NtCurrentTeb()获取当前进程的Teb
前置知识
NtCurrentTeb()
用于返回当前线程的线程环境块(TEB)指针。
- 在x64架构环境下,
address of TEB + 60h
处存储的是PEB结构体的基地址。 - 在x86架构环境下,
address of TEB + 30h
处存储的是PEB结构体的基地址。
实现
语法
_TEB * NtCurrentTeb();
具体实现与测试
#include <iostream>
#include <windows.h>
using namespace std;
void testfor_2()
{
//to get the image base
HANDLE imageBase = GetModuleHandle(NULL);
if (imageBase != NULL) std::cout << "imageBase:0x" << hex << imageBase << endl;
#ifdef _WIN64
//
//
/*x64 Architecture*/
//
//
//use API NtCurrentTeb() to get the TEB of current thread
size_t TebAddr = (size_t)NtCurrentTeb();
//to get the PEB
size_t PebAddr = *(size_t*)(TebAddr + 0x60);
//to get the image base
size_t imageBase_byAPI = *(size_t*)(PebAddr + 0x10);
//test the imagebase I get
if (imageBase_byAPI == (size_t)imageBase)
{
std::cout << "Yes the NtCurrentTeb works correctly\n";
std::cout << "imageBase_byAPI:0x" << hex << imageBase_byAPI << endl;
}
else
{
std::cout << "No the NtCurrentTeb works incorrectly\n";
std::cout << "imageBase_byAPI:0x" << hex << imageBase_byAPI << endl;
}
#else
//
//
/*x86 Architecture*/
//
//
//use API NtCurrentTeb() to get the TEB of current thread
DWORD32 TebAddr = (DWORD32)NtCurrentTeb();
//to get the PEB
DWORD32 PebAddr = *(DWORD32*)(TebAddr + 0x30);
//to get the image base
DWORD32 imageBase_byAPI = *(DWORD32*)(PebAddr + 0x8);
//test the imagebase I get
if (imageBase_byAPI == (DWORD32)imageBase)
{
std::cout << "Yes the NtCurrentTeb works correctly\n";
std::cout << "imageBase_byAPI:0x" << hex << imageBase_byAPI << endl;
}
else
{
std::cout << "No the NtCurrentTeb works incorrectly\n";
std::cout << "imageBase_byAPI:0x" << hex << imageBase_byAPI << endl;
}
#endif
}
int main()
{
testfor_2();
return 0;
}
x86
x64
解释
TEB与PEB的关系在第一种方法中已经讲了,可以通过TEB获得指向PEB的指针,再通过PEB获得程序的image base
。请记住,这个方法将用于之后的方法准确性验证中。
方法三:NtQueryInformationProcess()
前置知识
- 该函数在
ntdll.dll
中,所以需要先用LoadLibrary
把这个dll导进来。 - 这个函数用于获取指定进程的信息,可以用于获取某一个指定进程的信息,讲这些信息放到一个结构体中。
以下是存储指定进程信息的结构体
typedef struct _PROCESS_BASIC_INFORMATION {
NTSTATUS ExitStatus;
PPEB PebBaseAddress;
ULONG_PTR AffinityMask;
KPRIORITY BasePriority;
ULONG_PTR UniqueProcessId;
ULONG_PTR InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION;
可以惊讶地发现,第二个元素是该进程地PEB,而address of PEB + 0x10(x64)/0x8(x86)
是image base
。
补充:这种方式只能得到PEB,没有办法得到TEB,有些情况下该方法适用。
注意:在以下的实验中,我会先创建一个悬挂进程,然后去得到该悬挂进程的PEB。
实现
语法
__kernel_entry NTSTATUS NtQueryInformationProcess(
[in] HANDLE ProcessHandle,
[in] PROCESSINFOCLASS ProcessInformationClass,
[out] PVOID ProcessInformation,
[in] ULONG ProcessInformationLength,
[out, optional] PULONG ReturnLength
);
具体实现
#include <windows.h>
#include<iostream>
#include<winternl.h>
using namespace std;
typedef NTSTATUS(WINAPI *fnNtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
void testfor_3()
{
#ifdef DEBUG
size_t susProImageBase;
#else
DWORD32 susProImageBase;
#endif
//create a suspended process by program2.exe
//the program2.exe is just for test don't care what it is
TCHAR appName[] = TEXT("program2.exe");
PROCESS_INFORMATION pi;
STARTUPINFO si;
ZeroMemory(&pi, sizeof(pi));
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(STARTUPINFO);
bool sucess = CreateProcess(
appName,
NULL,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
&pi
);
//if false return
if (sucess == FALSE) {
printf("failed to create new process\n");
return;
}
//load the ntdll.dll into the current process.
HMODULE hNtdll = LoadLibrary(L"ntdll.dll");
if (hNtdll == NULL) {
printf("Error:fail to connect ntdll.dll\n");
return;
}
//get the target function NtQueryInformationProcess
fnNtQueryInformationProcess fNtQueryInformationProcess = (fnNtQueryInformationProcess)GetProcAddress(hNtdll, "NtQueryInformationProcess");
//if fail, return
if (fNtQueryInformationProcess == NULL) {
printf("Error:we can not get the function named ZwUnmapViewOfSection or fNtQueryInformationProcess\n");
return;
}
//get the PEB of the suspended process
PROCESS_BASIC_INFORMATION info{ 0 };
NTSTATUS stat = fNtQueryInformationProcess(pi.hProcess, ProcessBasicInformation, &info, sizeof(info), NULL);
}
解释
这个需要和第四种方法进行同步测试,相互验证以确保这个确实能够得到指定进程的PEB。所以具体的验证和解释将在下一种方法的代码中。已知第三种方法是肯定能获取这个悬挂了进程的PEB的,于是读者可以通过第三种方法去获取PEB以测试第四种方法获得的PEB地址是否准确。
方法四:通过GetThreadContext()和CONTEXT获得PEB
前置知识
说明:这种方法也可以跨进程获取TEB和PEB(甚至可以说是这四种方法中最高明的一种方法),下面的测试会创建一个进程并且获取它的PEB(不写TEB的主要原因是我困了)。
- 通过API
GetThreadContext()
获取指定线程的context,context中Ebx(x86)/Rdx(x64)
中存储了PEB的地址。
实现
语法
BOOL GetThreadContext(
[in] HANDLE hThread,
[in, out] LPCONTEXT lpContext
);
//
//x64
//
typedef struct _CONTEXT {
DWORD64 P1Home;
DWORD64 P2Home;
DWORD64 P3Home;
DWORD64 P4Home;
DWORD64 P5Home;
DWORD64 P6Home;
DWORD ContextFlags;
DWORD MxCsr;
WORD SegCs;
WORD SegDs;
WORD SegEs;
WORD SegFs;
WORD SegGs;
WORD SegSs;
DWORD EFlags;
DWORD64 Dr0;
DWORD64 Dr1;
DWORD64 Dr2;
DWORD64 Dr3;
DWORD64 Dr6;
DWORD64 Dr7;
DWORD64 Rax;
DWORD64 Rcx;
DWORD64 Rdx;
DWORD64 Rbx;
DWORD64 Rsp;
DWORD64 Rbp;
DWORD64 Rsi;
DWORD64 Rdi;
DWORD64 R8;
DWORD64 R9;
DWORD64 R10;
DWORD64 R11;
DWORD64 R12;
DWORD64 R13;
DWORD64 R14;
DWORD64 R15;
DWORD64 Rip;
union {
XMM_SAVE_AREA32 FltSave;
NEON128 Q[16];
ULONGLONG D[32];
struct {
M128A Header[2];
M128A Legacy[8];
M128A Xmm0;
M128A Xmm1;
M128A Xmm2;
M128A Xmm3;
M128A Xmm4;
M128A Xmm5;
M128A Xmm6;
M128A Xmm7;
M128A Xmm8;
M128A Xmm9;
M128A Xmm10;
M128A Xmm11;
M128A Xmm12;
M128A Xmm13;
M128A Xmm14;
M128A Xmm15;
} DUMMYSTRUCTNAME;
DWORD S[32];
} DUMMYUNIONNAME;
M128A VectorRegister[26];
DWORD64 VectorControl;
DWORD64 DebugControl;
DWORD64 LastBranchToRip;
DWORD64 LastBranchFromRip;
DWORD64 LastExceptionToRip;
DWORD64 LastExceptionFromRip;
} CONTEXT, *PCONTEXT;
//
//x86
//
typedef struct _CONTEXT {
DWORD ContextFlags;
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
FLOATING_SAVE_AREA FloatSave;
DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;
DWORD Ebp;
DWORD Eip;
DWORD SegCs; // MUST BE SANITIZED
DWORD EFlags; // MUST BE SANITIZED
DWORD Esp;
DWORD SegSs;
BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;
注意:在这里我们只需要关注Rdx
和Ebx
。
具体代码实现以及测试
代码实现
#include <windows.h>
#include<iostream>
#include<winternl.h>
using namespace std;
void testfor_4()
{
#ifdef _WIN64
#define PEBpointer Rdx
#else
#define PEBpointer Ebx
#endif
//create a suspended process by program2.exe
//the program2.exe is just for test don't care what it is
TCHAR appName[] = TEXT("program2.exe");
PROCESS_INFORMATION pi;
STARTUPINFO si;
ZeroMemory(&pi, sizeof(pi));
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(STARTUPINFO);
bool sucess = CreateProcess(
appName,
NULL,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
&pi
);
//if false return
if (sucess == FALSE) {
printf("failed to create new process\n");
return;
}
// Get the context of the suspended process
CONTEXT context;
context.ContextFlags = CONTEXT_FULL;
GetThreadContext(pi.hThread, &context);
//close the handle
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
int main()
{
testfor_4();
return 0;
}
测试
注意:在测试的时候用来创建悬挂进程的程序,需要使用x86程序和x64程序两类程序测试。
#include <windows.h>
#include<iostream>
#include<winternl.h>
using namespace std;
void testfor_4()
{
#ifdef _WIN64
#define PEBpointer Rdx
#define format size_t
#else
#define PEBpointer Ebx
#define format DWORD32
#endif
//create a suspended process by program2.exe
//the program2.exe is just for test don't care what it is
TCHAR appName[] = TEXT("program2.exe");
PROCESS_INFORMATION pi;
STARTUPINFO si;
ZeroMemory(&pi, sizeof(pi));
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(STARTUPINFO);
bool sucess = CreateProcess(
appName,
NULL,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
&pi
);
//if false return
if (sucess == FALSE) {
printf("failed to create new process\n");
return;
}
HMODULE hNtdll = LoadLibrary(L"ntdll.dll");
if (hNtdll == NULL) {
printf("Error:fail to connect ntdll.dll\n");
return;
}
//get the target function NtQueryInformationProcess
fnNtQueryInformationProcess fNtQueryInformationProcess = (fnNtQueryInformationProcess)GetProcAddress(hNtdll, "NtQueryInformationProcess");
//if fail, return
if (fNtQueryInformationProcess == NULL) {
printf("Error:we can not get the function named ZwUnmapViewOfSection or fNtQueryInformationProcess\n");
return;
}
//get the PEB of the suspended process
PROCESS_BASIC_INFORMATION info{ 0 };
NTSTATUS stat = fNtQueryInformationProcess(pi.hProcess, ProcessBasicInformation, &info, sizeof(info), NULL);
// Get the context of the suspended process
CONTEXT context;
context.ContextFlags = CONTEXT_FULL;
GetThreadContext(pi.hThread, &context);
if (context.PEBpointer == (format)info.PebBaseAddress)
{
cout << "it works\n";
cout << "PEBpointer:0x" << hex << context.PEBpointer << endl;
cout << "info.PebBaseAddress:0x" << hex << context.PEBpointer << endl;
}
else
{
cout << "it doesn't work\n";
cout << "PEBpointer:0x" << hex << context.PEBpointer << endl;
cout << "info.PebBaseAddress:0x" << hex << info.PebBaseAddress << endl;
}
//close the handle
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
int main()
{
testfor_4();
return 0;
}
x86
x64
解释
首先,用CreateProcess()
创建了一个进程,然后通过GetThreadContext()
得到了指定进程的CONTEXT
然后通过结构体中的相关值获得了指定进程的PEB
地址。通过方法三和方法四的相互验证可以得出一个相对准确的结论——这两种方法都能获取指定进程的PEB。
总结
留下来了一个问题:如何获取指定线程的TEB,这个目前只知道32位如何获得,64位的估计也是通过方法四更容易,但是TEB的地址存在CONTEXT的哪个元素里面我还没去找(太困了)。
以此记录我这段时间学到的四种获取TEB和PEB的方式,其实有很多细节可以深究但是目前没有时间深究,先挖个坑,到时候有时间了填上去。