「0day」从TEB和PEB(线程环境块和进程环境块)到动态获取shellcode所需API函数的地址

《0day安全:软件漏洞分析技术(第2版)》学习路线……

Posted by 许大仙's Litt1ε Bl0g on July 24, 2018

一、介绍

首先介绍PEB和TEB概念: PEB(Process Environment Block,进程环境块)存放进程信息,每个进程都有自己的PEB信息。位于用户地址空间。

TEB(Thread Environment Block,线程环境块)系统在此TEB中保存频繁使用的线程相关的数据。位于用户地址空间,在比 PEB 所在地址低的地方。进程中的每个线程都有自己的一个TEB

TEB线程环境块是一个结构体,结构体中包含进程中运行线程的各种信息,每个线程都对应一个TEB结构体。

不同OS中TEB结构的形态略微不同。

二、定义

结构体中有非常多的成员,其中用户模式调试中起着重要作用的成员有两个:

+0 NtTib : _NT_TIB
...
+0X30 ProcessEnvironmentBlock : Ptr32_PEB
  1. 偏移0x30处的ProcessEnvironmentBlock是指向PEB(进程环境块)结构体的指针

  2. 偏移0处的_NT_TIB结构体内容如下

     typedef struct _NT_TIB {
          struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
          PVOID StackBase;
          PVOID StackLimit;
          PVOID SubSystemTib;
          union {
           PVOID FiberData;
           DWORD Version;
          };
          PVOID ArbitraryUserPointer;
          struct _NT_TIB *Self;
     };
    
    • ExceptionList成员指向_EXCEPTION_REGISTRATION_RECORD结构体组成的链表,用于Windows OS的SEH(异常处理机制)

    • Self成员是结构体的自引用指针[FS:0 偏移0x18后就是_NT_TIB结构体中的self成员,指向这个线程自己的TEB]

三、访问方法

内核模式下可以直接查看内核的内存,但是用户模式下由于没有相关权限,所以只能通过OS提供的API来访问。

比如: Ntdll.NtCurrentTeb():用来返回当前线程的TEB结构体的地址 函数体:

mov eax, DWORD PTR FS:[18]

RETN

四、FS段寄存器

FS段寄存器所指定的内存段可以获得很多系统关键信息,主要是和进程、线程相关的信息。也就是说,FS可以用来指示当前线程的TEB结构体。

具体方法为 FS寄存器指示SDT[段描述符表SDT,一种是全局段描述符表(GDT),另一种是局部段描述符表(LDT), 对应的寄存器分别为GDTR和LDTR]的索引index,其中存放着TEB结构体的偏移地址。 SDT位于内核内存区域,基地址存放在GDTR(全局描述符表寄存器)中。 因此GDTR+FS就可以得到TEB结构体的指针了[也就是FS是选择子,GDTR提供基址。最后GDTR+FS得到的地址就是TEB结构体的指针位置。就像系统段描述符和进程的LDT表获取的方法一样]

从而可知,FS寄存器可以定位TEB和PEB的位置:

  • FS:[0x18] = FS:0
  • (+0x18处就是self指针)
  • FS:[0x30] = &PEB
  • FS:[0] = &SEH

五、动态获取shellcode所需API函数的地址

所有的win_32程序都会加载ntdll.dll和kerner32.dll这两个最基础的动态链接库。如果想要在win_32平台下定位kernel32.dll中的API地址

1、首先通过段选择字FS在内存中找到当前的线程环境快TEB。

2、线程环境快偏移位置为0x30的地方存放着指向进程环境块PEB的指针。

3、进程环境块中偏移位置为0x0C的地方存放着指向PEB_LDR_DATA结构体的指针,其中,存放着已经被进程装在的动态链接库的信息。

4、PEB_LDR_DATA结构体偏移位置为0x1C的地方存放着指向模块初始化链表的头指针InInitizationOrderModuleList.

5、模块初始化链表InInitizationOrderModuleList中按顺序存放着PE装入运行时初始化模块信息,第一个链表结点是ntdll.dll,第二个链表结点就是kernel32.dll。

6、找到属于kernel32.dll的结点后,在其基础上再偏移0x08就是kernel32.dll在内存中的加载基地址。

7、从kernel32.dll的加载基址算起,偏移0x3C的地方就是其PE头。

8、PE头偏移0x78的地方存放着指向函数导出表的指针。

9、导出表0x1C处的指针指向存储导出函数偏移地址(RVA)的列表->导出表偏移0x20处的指针指向存储导出函数函数名的列表->函数的RVA地址和名字按照顺序存放在上述两个列表中,我们可以在名称列表中定位到所需的函数是第几个,然后在地址列表中找到对应的RVA—>获得RVA后,再加上前面已经得到的动态链接库的加载基址,就获得了所需API此刻在内存中的虚拟地址