首页
社区
课程
招聘
[原创]PEB结构:获取模块kernel32基址技术及原理分析
2021-3-26 14:42 19795

[原创]PEB结构:获取模块kernel32基址技术及原理分析

2021-3-26 14:42
19795

0.前言

在学习《恶意代码分析实战》第19章shellcode分析时,提到了一个根据PEB结构搜索kernel32基址的方法。书中描述的很晦涩难懂,于是花了点时间详细了解了一下这个技术。整理成笔记方便日后查找。

1.技术背景

Windows是一个强大的操作系统。为了方便开发者,微软将进程中的每个线程都设置了一个独立的结构数据。这个结构体内存储着当前线程大量的信息。这个结构被称为TEB(线程环境块)。通过TEB结构内的成员属性向下扩展,可以得到很多线程信息。这其中还包含大量的未公开数据。

 

TEB结构的其中一个成员为PEB(进程环境块),顾名思义,这个结构中存储着整个进程的信息。通过对PEB中成员的向下扩展可以找到一个存储着该进程所有模块数据的链表。

2.参考资料

本文共涉及两个资料:

  1. 神图:image-20210326095430084

  2. VergiliusProject结构体查询网站:(下文简称VP)

    https://www.vergiliusproject.com/kernels

  3. 微软未公开数据:(本文没有用到,但是收藏起来以后会用到,包含API,结构体等信息)

    http://undocumented.ntinternals.net/

3.模块遍历-理论知识

对模块的遍历是从PEB结构开始的,因此我们需要获取PEB结构指针。PEB的结构指针存储在TEB中。TEB结构指针存储在fs寄存器中。那么如何在内存中定位TEB结构的位置呢?有三种方法:

  1. 查看OD等调试器的寄存器窗口,fs段寄存器的后面会接着TEB结构指针。直接在内存窗口跳过去即可,如下:

    image-20210326103016090

  2. 通过fs的值拆分成段选择子,通过GDT表查找段描述符,得到一个3环的调用门....#*#%$()#.....

    显然这种方式很笨重,而且对于像我这种没学过内核的彩笔来说根本不现实,更别说后面落实代码了。

  3. 第三种是最方便的,fs:[0x18]存储着TEB结构指针,fs:[0x30]存储着PEB结构指针。

3.1 fs:[0x30] fs:[0x18]的来历

网上很多文章都会说:fs:[0x30]是PEB fs:[0x18]是TEB,虽然确实方便,但我们还是要看下原理。

 

目前我们已经知道了fs就是TEB结构指针,但是不巧,在OD中尝试直接跳转到fs处,OD无法直接找到TEB。这是因为fs只存储了段选择子,而通过段选择子找到的东西才是真正的TEB结构指针(内核知识)。如下图:

 

image-20210326103955352

 

这时我们回到VP上,看看TEB结构的具体成员:

这里注意下,系统为64时,VP首页kernels记得选X64,但是当你在64位系统上运行32位程序时,我们要查询的TEB结构为_TEB32

切记不要32位程序去查64位的TEB,会迷失自己。

 

image-20210326104524136

 

image-20210326104603616

 

可以看到TEB结构中第一个成员是另一个结构体TIB(线程信息块),它占了1C个字节,我们点击黄色的结构体名可以直接跳到TIB结构处,看看它的成员。

 

image-20210326105010855

 

可以看到,偏移0x18处有个Self成员,它存储着这个TIB结构的首地址。也就是SEH指针

 

回到TEB结构,我们知道TIB是TEB中的第一个成员,那么可以理解为TIB的首地址就是TEB的首地址。所以TIB的0x18偏移等于TEB的0x18偏移,TIB的Self成员同时也指向TEB首地址

 

至此,我们得出了一个结论,pTEB->0x18 == *pTEB

 

还记得上文说的fs就是TEB指针吗,带入公式,得到:

 

fs:[0x18] == TEB

 

同理,我们看到TEB结构0x30偏移处为PEB结构(图上没缩写,我淦),那么同理:

 

pTEB->0x30 == fs:[0x30] == PEB

 

SEH:pTEB->0 == fs:[0] == SEH链

 

===========================================================================

 

知道了fs:[0x30]的来历那就好办了,我们继续模块遍历的学习。VP上看下PEB结构(我这里直接看X86下的PEB了,不然PEB32里面没有缩写,不能直接跳过去很麻烦):

 

image-20210326105759321

 

image-20210326110204730

 

可以看到PEB偏移为C处存储着LDR指针,它指向一个_PEB_LDR_DATA结构,我们点进去看看:

 

image-20210326110515918

 

结构中提供了三个链表,链表内的节点都是一样的,只是排序不同。由于我们要寻找kernel32的基址,所以我们选择第三个InInitializationOrderModuleList,这样kernel32的链表节点会比较靠前。其实选其他两个也一样,就是要找久一点。我们看下这个链表入口结构_LIST_ENTRY的信息:

 

image-20210326110744971

 

可以看到这个结构有两个成员,第一个成员Flink指向下一个节点,Blink指向上一个节点。所以这是一个双向链表。接下来的概念很重要:

 

当我们从_PEB_LDR_DATA结构中取到InInitializationOrderModuleList结构时,这个结构中的Flink指向真正的模块链表,这个真正的链表的每个成员都是一个LDR_DATA_TABLE_ENTRY结构。

 

之前的_PEB_LDR_DATA只是一个入口这个结构只有一个,它不是链表节点,真正的链表节点结构如下图:

 

image-20210326111443725

_LDR_DATA_TABLE_ENTRY结构中的 _LIST_ENTRY 结构对应下一个 _LDR_DATA_TABLE_ENTRY 节点中的 _LIST_ENTRY 结构。

如:第一个 _LDR_DATA_TABLE_ENTRY 结构中的 InInitializationOrderModuleList 中的 Flink 指向的是第二个 _LDR_DATA_TABLE_ENTRY 结构中 InInitializationOrderModuleList 的首地址。而不是另外两个 _LIST_ENTRY 结构。

第一个 _LDR_DATA_TABLE_ENTRY 结构中的 Blink 指向 PEB_LDR_DATA 中对应成员的 Blink

最后一个 _LDR_DATA_TABLE_ENTRY 结构中的 Flink 指向 PEB_LDR_DATA 中对应的成员的 Flink

PEB_LDR_DATA 结构中的 Blink 指向最后一个 _LDR_DATA_TABLE_ENTRY 中对应成员的 Blink

PEB_LDR_DATA 结构中的 Flink 指向第一个 _LDR_DATA_TABLE_ENTRY 中对应的成员的 Flink

 

看不懂上面这段话? 我们转换成图:

 

image-20210326135732359

 

宏观的体现为下图:

 

image-20210326140642056

 

可以看到这是一个以PEB_LDR_DATA为起点的一个闭合环形双向链表。

 

每个_LDR_DATA_TABLE_ENTRY节点结构中偏移为0x30处的成员为dllName,偏移为0x18处的成员为DllBase

 

通过遍历链表,比较dllName字符串内容可以找到目标模块的所属节点。

 

通过节点成员DllBase可以定位该模块的DOS头起始处。

 

通过对PE结构的解析可以搜索导出表,从而可以取到指定的导出函数地址。

4.模块遍历 - 实战演练

理论讲的差不多了,下面我们在OD中实战演练一下,手动寻找kernel32的基地址。

 

打开OD,随便载入一个PE文件。在内存窗口按Ctrl+G跳转到fs:[0x30]处。PEB的首地址为0x7EFDE000

 

image-20210326141506780

 

对照VP上的结构体信息,我们知道PEB偏移0xC处为成员LDR。继续跳转到LDR结构首地址0x7DF70200

 

image-20210326142252722

MD 属性名搞错了,不是list 是link, 懒得改了

 

三个成员任选一个,我们这里使用InInitializationOrderModuleList成员,Flink代表第一个节点,我们跳转到0x5E2590,由于我们是从InInitializationOrderModuleList中的Flink跳过来的,因此我们向上拉一点,从0x005E2580处开始为_LDR_DATA_TABLE_ENTRY结构

 

image-20210326143239523

 

我们看到BaseDllName并不是我们想要的kernel32.dll,因此我们继续去看下一个节点。

 

继续跳转到Flink(0x5E29B8)处

 

image-20210326143550283

 

可以看到此时的Blink已经指向了第一个节点的Blink,dllname依然不是kernel32.dll

 

我们再次跳转到Flink(0x5E28D0)处

 

image-20210326143702703

 

可以看到,此时我们找到了kernel32.dll的字符串。偏移为0x18(0x5E28D8)处的DllBase为0x7DD60000

 

image-20210326143849197

 

通过对比内存窗口的模块基址,我们发现是一致的。说明我们成功找到了kernel32.dll的基址。

 

剩下的就是通过汇编代码的方式来完成这一套逻辑,这样就可以应用在shellcode的编写中了。

5.小结

本文主要记录一下各个结构体的寻找方法,还有FS寄存器的使用。整体下来如果理解了的话是非常简单的。最主要的还是理解FS:[0x30]的原理,不要死记硬背。


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞19
打赏
分享
最新回复 (14)
雪    币: 7960
活跃值: (4228)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
sunsjw 1 2021-3-27 16:22
2
0
学习了!
雪    币: 10536
活跃值: (3931)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jgs 2021-3-27 17:54
3
0
收藏,学习
雪    币: 310
活跃值: (1887)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
niuzuoquan 2021-3-28 09:52
4
0
mark
雪    币: 29
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
杜若 2021-3-28 11:12
5
0
感谢,理解了
雪    币: 8746
活跃值: (5130)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
我的小拇指啊 2021-3-31 13:42
6
0

学习学习!

最后于 2021-3-31 13:45 被我的小拇指啊编辑 ,原因: 。。。眼瞎没看到楼主已经贴出来了
雪    币: 296
活跃值: (510)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Abelly 2021-3-31 13:59
7
0
mark
雪    币: 16428
活跃值: (59345)
能力值: (RANK:125 )
在线值:
发帖
回帖
粉丝
Editor 2021-3-31 14:02
8
0
感谢分享~
雪    币: 50
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wx_大可爱 2021-7-25 12:56
9
0
太强了,get到了
雪    币: 50
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wx_大可爱 2021-7-26 14:48
10
0
三个成员任选一个,我们这里使用InInitializationOrderModuleList成员,Flink代表第一个节点,我们跳转到0x5E2590,由于我们是从InInitializationOrderModuleList中的Flink跳过来的,因此我们向上拉一点,从0x005E2580处开始为_LDR_DATA_TABLE_ENTRY结构

解释:由于InInitializationOrderModuleList是_LDR_DATA_TABLE_ENTRY的第三个成员,偏移是0x10,前三个结构:
     LIST_ENTRY InLoadOrderLinks;                     0x0
     LIST_ENTRY InMemoryOrderLinks;               0x8
     LIST_ENTRY InInitializationOrderLinks;         0x10
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wx_翟... 2021-8-6 17:21
11
0
雪    币: 507
活跃值: (4373)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
glopen 2022-3-22 00:04
12
0
用vc2010里的结构体不一样:
typedef struct _PEB_LDR_DATA {
  BYTE       Reserved1[8];
  PVOID      Reserved2[3];
  LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

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;
雪    币: 507
活跃值: (4373)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
glopen 2022-3-22 00:07
13
0
vs2010下,只有选择InLoadOrderLinks才正确,远第二第三个,解不出来,或者必须网上回滚8或者16个字节才可以解出来正确的基址
雪    币: 507
活跃值: (4373)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
glopen 2022-3-22 00:08
14
0
wx_大可爱 三个成员任选一个,我们这里使用InInitializationOrderModuleList成员,Flink代表第一个节点,我们跳转到0x5E2590,由于我们是从InInitializationOr ...
vs2010下,只有选择InLoadOrderLinks才正确,远第二第三个,解不出来,或者必须网上回滚8或者16个字节才可以解出来正确的基址。

这一点查表网络,都没有讲
雪    币: 995
活跃值: (1513)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
WMBa0 2023-1-26 17:17
15
0
只能一句:牛逼
游客
登录 | 注册 方可回帖
返回