Работа с PEB и TEB

    PEB — структура процесса в windows, заполняется загрузчиком на этапе создания процесса, которая содержит информацию о окружении, загруженных модулях (LDR_DATA), базовой информации по текущему модулю и другие критичные данные необходимые для функционирования процесса. Многие системные api windows, получающие информацию о модулях (библиотеках) в процессе, вызывают ReadProcessMemory для считывания информации из PEB нужного процесса.

    TEB — структура которая используется для хранения информации о потоках в текущем процессе, каждый поток имеет свой TEB. Wow64 процессы в Windows имеют два Process Environment Blocks и два Thread Environment Blocks. TEB создается функцией MmCreateTeb, PEB создается функцией MmCreatePeb, если интересен процесс создания, то можно посмотреть исходники ReactOS, или взять WinDBG и исследовать самостоятельно. Перед собой была поставлена цель в изменении PEB чужого процесса.

    TEB имеет следующий вид:

    typedef struct _CLIENT_ID {
        DWORD UniqueProcess;
        DWORD UniqueThread;
    } CLIENT_ID, *PCLIENT_ID;
    typedef struct _THREAD_BASIC_INFORMATION {
    typedef PVOID KPRIORITY;
    NTSTATUS ExitStatus; PVOID TebBaseAddress; CLIENT_ID ClientId; KAFFINITY AffinityMask; KPRIORITY Priority; KPRIORITY BasePriority;
    
    } THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;
    


    [TEB+0] Указатель на первый SEH на стэке.
    [TEB+4] Указатель на конец области памяти, выделенных на стеке.
    [TEB+8] Указатель на начало области памяти выделенных на стеке, для контроля исключений переполнения стека.
    [TEB+18] Адрес текущей TEB.
    [TEB+30] Адрес PEB.
    

    Для получения TEB конкретного потока можно воспользоваться NtQueryInformationThread.

    #include <Windows.h>
    #include <stdio.h>
    #pragma comment(lib,"ntdll.lib")
    typedef struct _CLIENT_ID {
        DWORD UniqueProcess;
        DWORD UniqueThread;
    } CLIENT_ID, *PCLIENT_ID;
    typedef struct _THREAD_BASIC_INFORMATION {
    typedef PVOID KPRIORITY;
    NTSTATUS ExitStatus; PVOID TebBaseAddress; CLIENT_ID ClientId; KAFFINITY AffinityMask; KPRIORITY Priority; KPRIORITY BasePriority;
    
    } THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;
    typedef   enum   _THREADINFOCLASS
    {
        ThreadBasicInformation,
        ThreadTimes,
        ThreadPriority,
        ThreadBasePriority,
        ThreadAffinityMask,
        ThreadImpersonationToken,
        ThreadDescriptorTableEntry,
        ThreadEnableAlignmentFaultFixup,
        ThreadEventPair_Reusable,
        ThreadQuerySetWin32StartAddress,
        ThreadZeroTlsCell,
        ThreadPerformanceCount,
        ThreadAmILastThread,
        ThreadIdealProcessor,
        ThreadPriorityBoost,
        ThreadSetTlsArrayAddress,
        ThreadIsIoPending,
        ThreadHideFromDebugger,
        ThreadBreakOnTermination,
        MaxThreadInfoClass
    }   THREADINFOCLASS;
    THREADINFOCLASS   ThreadInformationClass;
      extern "C"
      {
      NTSTATUS WINAPI NtQueryInformationThread(
      _In_       HANDLE ThreadHandle,
      _In_       THREADINFOCLASS ThreadInformationClass,
      _Inout_    PVOID ThreadInformation,
      _In_       ULONG ThreadInformationLength,
      _Out_opt_  PULONG ReturnLength
    );
    }
    THREAD_BASIC_INFORMATION ThreadInfo;
    DWORD ntstatus = NtQueryInformationThread(
    	        GetCurrentThread(), // хэндл на поток
    			ThreadBasicInformation,
    			&ThreadInfo, //ThreadInfo.TebBaseAddress содержит адрес теба для указанного потока.
    			sizeof(THREAD_BASIC_INFORMATION),
    			0
    			);
    // Если нужен teb только своего потока, использовать  __readfsdword(0x18)  в 32 бит или __readgsqword(0x30) в х64.
    

    на MSDN PEB описывается следующим образом для 32 битного процесса:

    typedef struct _PEB {
      BYTE                          Reserved1[2];
      BYTE                          BeingDebugged;
      BYTE                          Reserved2[1];
      PVOID                         Reserved3[2];
      PPEB_LDR_DATA                 Ldr;
      PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
      BYTE                          Reserved4[104];
      PVOID                         Reserved5[52];
      PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
      BYTE                          Reserved6[128];
      PVOID                         Reserved7[1];
      ULONG                         SessionId;
    } PEB, *PPEB;
    

    и для 64 битного:

    typedef struct _PEB {
        BYTE Reserved1[2];
        BYTE BeingDebugged;
        BYTE Reserved2[21];
        PPEB_LDR_DATA LoaderData;
        PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
        BYTE Reserved3[520];
        PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
        BYTE Reserved4[136];
        ULONG SessionId;
    } PEB;
    

    в моем проекте используется следующая структура для 32 и 64 бит
    //автор структуры - http://blog.rewolf.pl/blog/?p=573
    #pragma pack(push)
    #pragma pack(1)
    template <class T>
    struct LIST_ENTRY_T
    {
    	T Flink;
    	T Blink;
    };
     
    template <class T>
    struct UNICODE_STRING_T
    {
    	union
    	{
    		struct
    		{
    			WORD Length;
    			WORD MaximumLength;
    		};
    		T dummy;
    	};
    	T _Buffer;
    };
     
    template <class T, class NGF, int A>
    struct _PEB_T
    {
    	union
    	{
    		struct
    		{
    			BYTE InheritedAddressSpace;
    			BYTE ReadImageFileExecOptions;
    			BYTE BeingDebugged;
    			BYTE _SYSTEM_DEPENDENT_01;
    		};
    		T dummy01;
    	};
    	T Mutant;
    	T ImageBaseAddress;
    	T Ldr;
    	T ProcessParameters;
    	T SubSystemData;
    	T ProcessHeap;
    	T FastPebLock;
    	T _SYSTEM_DEPENDENT_02;
    	T _SYSTEM_DEPENDENT_03;
    	T _SYSTEM_DEPENDENT_04;
    	union
    	{
    		T KernelCallbackTable;
    		T UserSharedInfoPtr;
    	};
    	DWORD SystemReserved;
    	DWORD _SYSTEM_DEPENDENT_05;
    	T _SYSTEM_DEPENDENT_06;
    	T TlsExpansionCounter;
    	T TlsBitmap;
    	DWORD TlsBitmapBits[2];
    	T ReadOnlySharedMemoryBase;
    	T _SYSTEM_DEPENDENT_07;
    	T ReadOnlyStaticServerData;
    	T AnsiCodePageData;
    	T OemCodePageData;
    	T UnicodeCaseTableData;
    	DWORD NumberOfProcessors;
    	union
    	{
    		DWORD NtGlobalFlag;
    		NGF dummy02;
    	};
    	LARGE_INTEGER CriticalSectionTimeout;
    	T HeapSegmentReserve;
    	T HeapSegmentCommit;
    	T HeapDeCommitTotalFreeThreshold;
    	T HeapDeCommitFreeBlockThreshold;
    	DWORD NumberOfHeaps;
    	DWORD MaximumNumberOfHeaps;
    	T ProcessHeaps;
    	T GdiSharedHandleTable;
    	T ProcessStarterHelper;
    	T GdiDCAttributeList;
    	T LoaderLock;
    	DWORD OSMajorVersion;
    	DWORD OSMinorVersion;
    	WORD OSBuildNumber;
    	WORD OSCSDVersion;
    	DWORD OSPlatformId;
    	DWORD ImageSubsystem;
    	DWORD ImageSubsystemMajorVersion;
    	T ImageSubsystemMinorVersion;
    	union
    	{
    		T ImageProcessAffinityMask;
    		T ActiveProcessAffinityMask;
    	};
    	T GdiHandleBuffer[A];
    	T PostProcessInitRoutine;
    	T TlsExpansionBitmap;
    	DWORD TlsExpansionBitmapBits[32];
    	T SessionId;
    	ULARGE_INTEGER AppCompatFlags;
    	ULARGE_INTEGER AppCompatFlagsUser;
    	T pShimData;
    	T AppCompatInfo;
    	UNICODE_STRING_T<T> CSDVersion;
    	T ActivationContextData;
    	T ProcessAssemblyStorageMap;
    	T SystemDefaultActivationContextData;
    	T SystemAssemblyStorageMap;
    	T MinimumStackCommit;
    };
     
    typedef _PEB_T<DWORD, DWORD64, 34> PEB32;
    typedef _PEB_T<DWORD64, DWORD, 30> PEB64;
    
    #pragma pack(pop)
    


    PEB можно получить следующим образом:

    // воспользуемся  intrinsics функциями, так как в 12 студии инлайн асм для х64 компиляции отсутствует.
    // адрес PEB - константа для всех процессов в системе.
    #if defined _M_IX86
    int offset = 0x30;
    DWORD peb __readfsdword(PEB) //mov eax, fs:[0x30]
    #elif defined _M_X64
    //На 64 битных windows сегментный регистр GS хранит указатель на PEB в GS:[0x60]
    int offset = 0x60;
    DWORD64 peb =__readgsqword(PEB); //mov rax, gs:[0x60]
    

    Получение базы kernel32 и адрес GetProcAddress:

    //х64, проверено, работать будет начиная с xp x64 sp2 до последней win 8.
    typedef FARPROC (WINAPI * GetProcAddress_t) (HMODULE, const char *);
    struct LDR_MODULE
      {
       LIST_ENTRY e[3];
       HMODULE    base;
       void      *entry;
       UINT       size;
       UNICODE_STRING dllPath;
       UNICODE_STRING dllname;
      };
       int offset = 0x60;
       int ModuleList = 0x18;
       int ModuleListFlink = 0x18;
       int KernelBaseAddr = 0x10;
    
       INT_PTR peb    =__readgsqword(offset);
       INT_PTR mdllist=*(INT_PTR*)(peb+ ModuleList);
       INT_PTR mlink  =*(INT_PTR*)(mdllist+ ModuleListFlink);
       INT_PTR krnbase=*(INT_PTR*)(mlink+ KernelBaseAddr);
    
       LDR_MODULE *mdl=(LDR_MODULE*)mlink;
       do 
       {
          mdl=(LDR_MODULE*)mdl->e[0].Flink;
    
          if(mdl->base!=NULL)
            {
             if(!lstrcmpiW(mdl->dllname.Buffer,L"kernel32.dll")) //сравниваем имя библиотеки в буфере с необходимым
               {
                break;
               }
            }
       } while (mlink!=(INT_PTR)mdl);
    
    	kernel32base = (HMODULE)mdl->base;
    	ULONG_PTR base = (ULONG_PTR) kernel32base;
    	IMAGE_NT_HEADERS * pe = PIMAGE_NT_HEADERS(base + PIMAGE_DOS_HEADER(base)->e_lfanew);
    	IMAGE_EXPORT_DIRECTORY * exportDir = PIMAGE_EXPORT_DIRECTORY(base + pe->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    	DWORD * namePtr = (DWORD *) (base + exportDir->AddressOfNames); // Адрес имен функций.
    	WORD * ordPtr = (WORD *) (base + exportDir->AddressOfNameOrdinals); //Адрес имени для функции.
    	for(;_stricmp((const char *) (base +*namePtr), "GetProcAddress"); ++namePtr, ++ordPtr);
    	DWORD funcRVA = *(DWORD *) (base + exportDir->AddressOfFunctions + *ordPtr * 4);
    
    	auto myGetProcAddress = (GetProcAddress_t) (base + funcRVA); //получили адрес GetProcAddress.
    

    Базовый адрес PEB для определенного процесса получаем так:

    typedef enum _PROCESSINFOCLASS {
    	ProcessBasicInformation = 0
    } PROCESSINFOCLASS;
        status = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(PROCESS_BASIC_INFORMATION), &dwLength);
        
        if(status != 0x0)
        {
            printf("NtQueryInformationProcess Error  0x%x\n", status);
    		exit(EXIT_FAILURE);
        }
        
        printf("PEB address : 0x%x\n", pbi.PebBaseAddress);
    

    Интересное наблюдение, что если немного «испортить» LDR_DATA, такие api функции как GetModuleHandleEx и EnumProcessModules, QueryFullProcessImageName не будут выдавать нужный результат, так как они вызывают ReadProcessMemory для чтения PEB. Кода много, поэтому манипуляции с PEB (чтение из процесса, изменение и запись) оформлены в виде простого тестового класса, который можно найти тут.
    • +16
    • 24.6k
    • 4
    Support the author
    Share post

    Comments 4

      +5
      Всем привет от разработчиков ReactOS +)
        0
        Есть прекрасная книга Свена Шрайбера "Недокументированные возможности windows 2000". Возможно, она немного устарела, но может быть полезна начинающим исследователям.
          0
          Ещё можно поизучать Windows Research Kernel (WRK).

          Он содержит исходники executive (ntoskrnl.exe). По временной шкале это ядро примерно соответствует Win XP / Windows Server 2003.

          Найти его можно в пиринговых сетях.

          Ещё можно найти утёкшие исходники Windows 2000, кроме собственно ядра там есть почти весь User-space, включая калькулятор, regedit и даже CPL-ки для настроек системы.
            0
            Получение
            TEB
            через
            NTQueryInformationThread
            не будет работать на x64 системе. Потому что структура
            THREAD_BASIC_INFORMATION
            содержит поля типа
            KPRIORITY
            , которые на моей Win7 x64 имеют размер 4 байта, тогда как их
            typedef PVOID KPRIORITY;
            выше, приравнивает их к указателям (то есть 8 байт на моей системе).
            Не могу ручаться за то, что на других системах размер
            KPRIORITY
            не изменился, но он точно не может быть равен размеру
            PVOID
            на Windows 7.

            Only users with full accounts can post comments. Log in, please.