TEB和PEB学习记录(一)

TEB 和 PEB学习记录——基础篇

概述

注意!该文章讲的所有结构都是32位,且由Windows定义,Windows版本为W10。寻址方式和结构体具体元素与64位会有不同!同时请注意这些结构体在Windows不同版本下的差异!

TEB的全称为Thread Environment Block,该结构用于存储当前进程的相关信息,我们可以通过TEB得到PEB。PEB的全称为 Process Environment Block ,该结构用于存储当前进程的相关信息。TEB和PEB都是Windows定义的两个结构体,对于TEB可以通过FS段寄存器得到它的首地址,由于TEB结构体中有个元素存储了PEB结构体的首地址,所以可以通过ImageBase + offset的形式得到PEB结构体的首地址。

目前我们只关注几个较为重要的部分:

FS:[0x00] : Current SEH Frame
FS:[0x18] : TEB (Thread Environment Block)
FS:[0x20] : PID
FS:[0x24] : TID
FS:[0x30] : PEB (Process Environment Block)
FS:[0x34] : Last Error Value

SEH

SEH的全称是Structured Exception Handling,这是Windows操作系统的一种异常化处理机制。为了实现这种机制,Windows定义了一个结构体,以链表的形式实现了它,TEB结构体中的第一个元素就是指向该结构体的指针。

struct EXCEPTION_REGISTRATION
{
    EXCEPTION_REGISTRATION*     prev;
    ExceptionHandler*           handler;//a pointer to a function to deal with the exception
};
typedef EXCEPTION_DISPOSITION (CDECL ExceptionHandler)(EXCEPTION_RECORD* ExceptionRecord, EXCEPTION_REGISTRATION* EstablisherFrame, CONTEXT* ContextRecord, DISPATCHER_CONTEXT* DispatcherContext); //same as EXCEPTION_ROUTINE and _except_handler

注意:x64不使用这种异常处理机制。

PEB

PEB结构link:struct PEB (nirsoft.net)

因为是初级阶段的学习,所以目前只讨论PEB中比较常见的几个成员:

0x002 BYTE BeingDebugged;
0x008 void* ImageBaseAddress;
0x00C _PEB_LDR_DATA* Ldr;
0x018 void* ProcessHeap
0x064 DWORD NumberOfProcessors;
0x068 DWORD NtGlobalFlag;

  • BeingDebugged:用来检查当前程序是否正在被调试。
  • ImageBaseAddress:创建该进程的程序在该进程中的加载基地址。
    _PEB_LDR_DATA Ldr:这个结构体提供了载入该进程的dll的相关信息。
typedef struct _PEB_LDR_DATA
{
     DWORD          Length;                             //0x00
     BYTE           Initialized[4];                     //0x04
     void*          SsHandle;                           //0x08
     LIST_ENTRY     InLoadOrderModuleList;              //0x0C
     LIST_ENTRY     InMemoryOrderModuleList;            //0x14
     LIST_ENTRY     InInitializationOrderModuleList;    //0x1C
     void*          EntryInProgress;                    //0x24
} PEB_LDR_DATA;

typedef struct _LIST_ENTRY {
   struct _LIST_ENTRY *Flink;  //指向下一个节点
   struct _LIST_ENTRY *Blink;  //指向前一个节点
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

typedef struct _LDR_DATA_TABLE_ENTRY {
    PVOID Reserved1[2];
    LIST_ENTRY InMemoryOrderLinks;
    PVOID Reserved2[2];
    PVOID DllBase;
    PVOID EntryPoint;
    PVOID Reserved3;
    UNICODE_STRING FullDllName;
    BYTE Reserved4[8];
    PVOID Reserved5[3];
    union {
        ULONG CheckSum;
        PVOID Reserved6;
    };
    ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

_PEB_LDR_DATA 结构体中,InLoadOrderModuleListInMemoryOrderModuleListInInitializationOrderModuleList 这三个变量都是 LIST_ENTRY 类型的,它们都是头节点,代表了三个双向链表,这些链表分别按照模块的加载顺序、内存顺序和初始化顺序组织,其中每个节点都包含了模块的信息。双向链表中的每一个元素都指向一个_LDR_DATA_TABLE_ENTRY结构,这个结构里面存储着具体的导入的dll的相关信息。

  • ProcessHeap:a flag under PEB that shows if the first heap memory space of the process was created in debug mode. It can be accessed at offset 0x18 (at PEB).这个是从网上一篇文章里面看到的,很容易能看懂这句话是什么意思,但是为什么这个成员的作用是这个,有待实验证明或者官方文档证明。
  • NtGlobalFlag:This field is 0x0 in normal cases and is set to 0x70 when debugging. That is, FLG_HEAP_ENABLE_TAIL_CHECK (0x10), FLG_HEAP_ENABLE_FREE_CHECK (0x20), and FLG_HEAP_VALIDATE_PARAMETERS (0x40) are set. Note that if you attach a running process, it does not change.先看着,链接我待会给在下面。这个成员和上面那个成员一样都与调试有关。
  • NumberOfProcessors:表示当前可用处理器的数量。

详细讲一下这个结构体_PEB_LDR_DATA

这个结构体关联了好几个结构体,它们的用处只有一个——用来描述导入该进程的exe/dll的信息,这个元素十分重要!需要完全弄明白!

typedef struct _PEB_LDR_DATA
{
     DWORD          Length;                             //0x00
     BYTE           Initialized[4];                     //0x04
     void*          SsHandle;                           //0x08
     LIST_ENTRY     InLoadOrderModuleList;              //0x0C
     LIST_ENTRY     InMemoryOrderModuleList;            //0x14
     LIST_ENTRY     InInitializationOrderModuleList;    //0x1C
     void*          EntryInProgress;                    //0x24
} PEB_LDR_DATA;

typedef struct _LIST_ENTRY {
   struct _LIST_ENTRY *Flink;  //指向下一个节点
   struct _LIST_ENTRY *Blink;  //指向前一个节点
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

typedef struct _LDR_DATA_TABLE_ENTRY {
    PVOID Reserved1[2];
    LIST_ENTRY InMemoryOrderLinks;
    PVOID Reserved2[2];
    PVOID DllBase;
    PVOID EntryPoint;
    PVOID Reserved3;
    UNICODE_STRING FullDllName;
    BYTE Reserved4[8];
    PVOID Reserved5[3];
    union {
        ULONG CheckSum;
        PVOID Reserved6;
    };
    ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
_PEB_LDR_DATA
typedef struct _PEB_LDR_DATA
{
     DWORD          Length;                             //0x00
     BYTE           Initialized[4];                     //0x04
     void*          SsHandle;                           //0x08
     LIST_ENTRY     InLoadOrderModuleList;              //0x0C
     LIST_ENTRY     InMemoryOrderModuleList;            //0x14
     LIST_ENTRY     InInitializationOrderModuleList;    //0x1C
     void*          EntryInProgress;                    //0x24
} PEB_LDR_DATA;

这个结构体用来记录导入进程的用户使用的模块(这里的模块,可以理解为一些被使用到的dll)。它有三个元素分别为三个双向链表的头节点,这三个双向链表结构都为_LDR_DATA_TABLE_ENTRY,下面是这三个元素的定义:

LIST_ENTRY     InLoadOrderModuleList;              //0x0C
LIST_ENTRY     InMemoryOrderModuleList;            //0x14
LIST_ENTRY     InInitializationOrderModuleList;    //0x1C
LIST_ENTRY

存着两个指针,如果作为头节点,那么我们只考虑第二个指针。无论考虑哪一个元素,当有下一个和上一个节点时,它们指向的结构体为_LDR_DATA_TABLE_ENTRY,这个结构体,存储着导入dll的真正信息。

typedef struct _LIST_ENTRY {
   struct _LIST_ENTRY *Flink;  //指向下一个节点
   struct _LIST_ENTRY *Blink;  //指向前一个节点
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
_LDR_DATA_TABLE_ENTRY

这个结构体存储着导入的dll的具体信息,同时它是双向链表中的一个节点,通过LIST_ENTRY存储着上一节点和下一节点的指针。通过一个UNICODE_STRING存储着这个dll的名字。

typedef struct _LDR_DATA_TABLE_ENTRY {
    PVOID Reserved1[2];
    LIST_ENTRY InMemoryOrderLinks;
    PVOID Reserved2[2];
    PVOID DllBase;
    PVOID EntryPoint;
    PVOID Reserved3;
    UNICODE_STRING FullDllName;
    BYTE Reserved4[8];
    PVOID Reserved5[3];
    union {
        ULONG CheckSum;
        PVOID Reserved6;
    };
    ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
_UNICODE_STRING

这是一个用来描述字符串信息的结构体,结构如下:

typedef struct _UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
实验

通过vs2022调试时的反汇编定位PEB,然后找到导入该进程中的一个dll的名字。

#include<Windows.h>

int main()
{
	HANDLE test = GetModuleHandle(NULL);
	return 0;
}

在第五行下个断点,开始调试,得到反汇编信息。

	HANDLE test = GetModuleHandle(NULL);
00481795 8B F4                mov         esi,esp  
00481797 6A 00                push        0  
00481799 FF 15 00 B0 48 00    call        dword ptr [__imp__GetModuleHandleW@4 (048B000h)] 

F11步进第四行

771AC7F0 8B FF                mov         edi,edi  
771AC7F2 55                   push        ebp  
771AC7F3 8B EC                mov         ebp,esp  
771AC7F5 5D                   pop         ebp  
771AC7F6 FF 25 A0 13 21 77    jmp         dword ptr ds:[772113A0h] 

F11步进jmp的这一行,进入GetModuleHandle函数内部

7682EC90 8B FF                mov         edi,edi  
7682EC92 55                   push        ebp  
7682EC93 8B EC                mov         ebp,esp  
7682EC95 83 EC 0C             sub         esp,0Ch  
7682EC98 83 7D 08 00          cmp         dword ptr [ebp+8],0  
7682EC9C 74 2E                je          7682ECCC  
7682EC9E 56                   push        esi  
7682EC9F FF 75 08             push        dword ptr [ebp+8]  
7682ECA2 8D 45 F4             lea         eax,[ebp-0Ch]  
7682ECA5 50                   push        eax  
7682ECA6 FF 15 44 30 94 76    call        dword ptr ds:[76943044h]  
7682ECAC 8D 45 FC             lea         eax,[ebp-4]  
7682ECAF 33 F6                xor         esi,esi  
7682ECB1 50                   push        eax  
7682ECB2 8D 45 F4             lea         eax,[ebp-0Ch]  
7682ECB5 50                   push        eax  
7682ECB6 56                   push        esi  
7682ECB7 56                   push        esi  
7682ECB8 FF 15 3C 34 94 76    call        dword ptr ds:[7694343Ch]  
7682ECBE 85 C0                test        eax,eax  
7682ECC0 78 15                js          7682ECD7  
7682ECC2 8B 75 FC             mov         esi,dword ptr [ebp-4]  
7682ECC5 8B C6                mov         eax,esi  
7682ECC7 5E                   pop         esi  
7682ECC8 C9                   leave  
7682ECC9 C2 04 00             ret         4  
7682ECCC 64 A1 30 00 00 00    mov         eax,dword ptr fs:[00000030h]  ;请注意这里
7682ECD2 8B 40 08             mov         eax,dword ptr [eax+8]  
7682ECD5 EB F1                jmp         7682ECC8  
7682ECD7 8B C8                mov         ecx,eax  
7682ECD9 E8 F9 18 00 00       call        768305D7  
7682ECDE EB E5                jmp         7682ECC5  

注意标记处,在32位程序中,FS段寄存器存储了TEB的首地址,TEB首地址加上0x30得到的地址处的信息就是PEB的首地址,所以标记处的代码执行后,eax中存储了PEB的首地址。故,得到PEB。

0x0086F000  00 00 01 04 ff ff ff ff  ........
0x0086F008  00 00 47 00 00 db e7 77  ..G..??w
0x0086F010  90 69 ea 00 00 00 00 00  ?i?.....
0x0086F018  00 00 ea 00 c0 d8 e7 77  ..?.???w
0x0086F020  00 00 00 00 00 00 00 00  ........
0x0086F028  01 00 00 00 00 00 00 00  ........

其中0x0086F000为PEB首地址,由于我们需要得到的结构体指针在偏移0xc处,就不将PEB在内存中所有的结构给copy过来了。

注意这里的文本编码方式为ANSI不是Unicode。提醒一下,虽然不重要。

接下来我们通过addr + offset的方式得到_PEB_LDR_DATA结构体地址0x77e7db00,并且跟进到该地址。

0x77E7DB00  30 00 00 00 01 00 00 00  0.......
0x77E7DB08  00 00 00 00 28 29 ea 00  ....()?.
0x77E7DB10  e0 a2 ea 00 30 29 ea 00  ???.0)?.
0x77E7DB18  e8 a2 ea 00 40 28 ea 00  ???.@(?.

我们目前只关注上文中提到的三个LIST_ENTRY元素,所以并未把该结构体中所有的信息copy过来。虽然说有三个链表,但是它们的区别只是链接的顺序不同,我们随便找一个头节点跟进去。找offset:0x14的这个头节点。

0x77E7DB14  30 29 ea 00 e8 a2 ea 00

低位的四字节是上一节点指针,高位四字节是下一节点指针,由于是头节点,所以我们跟进下一节点指针指向的地址。找到_LDR_DATA_TABLE_ENTRY结构体。

0x00EAA2E8  14 db e7 77 90 a1 ea 00  .??w???.
0x00EAA2F0  98 a1 ea 00 a0 8d ea 00  ???.???.
0x00EAA2F8  00 00 b9 7a 10 bc c1 7a  ..?z.??z
0x00EAA300  00 50 1a 00 42 00 44 00  .P..B.D.
0x00EAA308  c8 a6 ea 00 1a 00 1c 00  ???.....
0x00EAA310  f0 a6 ea 00 ec a2 08 00  ???.??..
0x00EAA318  06 00 00 00 a8 d9 e7 77  ....???w
0x00EAA320  a8 d9 e7 77 a9 c1 aa 61  ???w???a

我们的目的是为了得到,这个dll的名字,所以需要关注偏移量0x24和0x2c。

start addr + 0x24得到_UNICODE_STRING结构体:

0x00EAA30C  1a 00 1c 00 f0 a6 ea 00  ....???.

高位的四位才是这个字符串的地址,我们跟进去。

0x00EAA6F0  75 00 63 00 72 00 74 00  ucrt
0x00EAA6F8  62 00 61 00 73 00 65 00  base
0x00EAA700  64 00 2e 00 64 00 6c 00  d.dl
0x00EAA708  6c 00 00 00 65 00 64 00  l.ed

得到字符串ucrtbased.dll

问题:这个问题我现在没有解决,实际上偏移0x2c处有个元素,这个元素定义:UNICODE_STRING BaseDllName;,但是我发现当我找到这个元素的时候,它的数据如下:

0x00EAA314  ec a2 08 00 06 00 00 00  ꋬ...

这就很离谱,高位四字节应该是一个指向字符串的指针,但是这个明显不是。问题还未解决…

补充

NtCurrentTeb一个有意思的API

PVOID A = (PVOID)NtCurrentTeb();

可以通过这段代码得到当前进程的TEB的首地址,该函数被定义在Windows.h中。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值