PWN堆ORW
-
四月份有一场新能源比赛,比赛后向别人要到了
PWN
的附件,这个比赛只有一题PWN
考的是常规的堆题,考的是堆
与ORW
。第一次接触堆
与ORW
的结合的题型。所以就认真做了一下。 -
堆与ORW相结合的题目,与栈溢出的
ORW
最后的套路基本一样,都是要构造ROP
链,或者写shellcode
。- 那么问题来了栈溢出类型的ORW很好写ROP链,因为可以劫持返回地址,也可以进行栈迁移构造ROP链。
- 而堆与ORW相结合的这类题型,构造ROP链就成了一个问题,因为我们不知道栈地址,劫持返回地址还比较困难。
- 但是堆与ORW相结合的题型基本上还是构造ROP链或者是利用栈迁移构造ROP链。
- 在原来的栈上构造ROP链之前,我们还需要泄露栈地址(利用
__envrion
全局变量)以及任意地址写 - 如果使用栈迁移的构造ROP链,我们就需要使用magic gadgets进行栈迁移写ROP。而这个
magic gadgets
出现在setcontext函数中 - 有的题目可能还可以构造SROP链。
- 还有的题目需要利用IO_FILE
- 在原来的栈上构造ROP链之前,我们还需要泄露栈地址(利用
-
以上就是堆与ORW相结合题目的利用思路,接下来先介绍一下前俩个利用思路中的
__envrion全局变量
和setcontext函数中magic gadgets
envrion和setcontext
envrion全局变量
- 注意
envrion
是一个全局变量,并不是一个函数,我在做题的时候一开始误将envrion
看成是一个函数,劫持了__free_hook()
为该全局变量的地址,导致在exp利用的时候出现了问题。之后才反应过来这时一个全局变量 envrion
全局变量是在glibc
中,它保存了当前进程的环境变量列表,而环境变量列表会在一开始执行程序时被压入栈中。而envrion
这个全局变量是一个指针,指向的就是环境变量列表
所在的栈地址。envrion
这个全局变量一般是这样定义的:
1 | extern char **environ; |
- 我们的环境变量列表一般如下图所示:
1 | "PATH=/usr/bin:/bin" |
- 接下来我们使用一个示例程序,通过调试查看一下
environ
这个值。
1 |
|
- 接下来我们gdb动态调试看看,先查看栈上的这个位置。
- 然后我们再来看
printf()
函数的第二个参数,就会发现第二个参数输出的就是0x7fffcbcf4fc8
这个栈地址
- 此时我们利用堆溢出、UAF漏洞、堆风水实现任意地址泄露,我们就可以泄露出栈上的地址,之后我们再利用任意地址写,就可以在栈上构造ROP链了还可以调用mprotect函数然后向指定地址写入shellcode进行ORW操作。
setcontext函数
-
现在也来了解一下setcontext函数中的magic_gadgets。接下来简单介绍一下这个函数。
-
setcontext
是glibc
中提供的一个上下文切换函数,属于 POSIX 的用户级上下文(User Context) API,主要用于用户空间实现协程、线程切换、信号恢复、非局部跳转等功能。注意:这个与SROP非常像 -
这个函数做个简单了解就行,我们主要使用的就是这个函数里面的一小段汇编,也就是我们所说的
magic_gadgets
。setcontext
这个函数的magic_gadgets
主要以glibc2.27
、glibc2.29
、glibc2.31
这三个版本为分界线。 -
我们先来说
glibc2.27
及glibc2.27
之前的版本,在执行这个函数的时候,我们会发现有进行一次syscall 0xE
的系统调用,之后就是将[rdi+0xA0]
位置一系列的值都给寄存器。这就相当于我们在用户层面使用SROP
(真正的SROP调用是在内核层面)。- 此时我们就可以控制RSP、RBP这俩个指针。这样我们就可以直接进行栈迁移操作。
- 并且由于是将
[rdi+0x]
的值赋值给寄存器,也就是rdi
寄存器指向的内存地址中的数据,这时我们就可以配合着__free_hook
进行使用,在堆上布置寄存器的值
- 我们再来说明一下
glibc2.27
以后的版本,这个是glibc2.29
的版本,我们发现glibc2.29
的这个版本setcontext
中的magic gadget
原本的rdi
被替换成了rdx
,此时我们就不能使用__free_hook
直接对这个程序进行控制
- 这个时候我们就需要用上其他的
gadget
,这时我们先使用__free_hook
将程序执行流执行到下面的gadget
,之后再调用setcontext
- 接下来查看
glibc2.31
的magic gadget
,我们发现这个magic gadget
由原来的setcontext+53
变成了setcontext+61
- 并且原来的
glibc2.29
的gadget
已经删除了只剩一个,如下图所示
利用envrion
- 接下来我们就利用新能源网络CTF的这题,简单介绍一下利用
envrion
泄露栈地址进行orw
的操作。
cfc_level_1
- 由于这是在本地打的,但是我使用
glibc-all-in-one
找不到对应的glibc2.31
,只能用其他版本的glibc2.31
,所以偏移会有所不同。
- 先运行一下这个程序,看看程序的大致逻辑。一开始运行的时候,我们会发现是一个堆菜单。但是在这个菜单中并没有
show
这个选项,即将内容输出。
1.Add
选项如下,这个程序add
堆块的size
大小必须为32
。
2.Edit
这个选项比较平常
3.Free
这个选项运行来看也比较平常
- 然后再查看一下程序的保护机制。这时我们发现
RELRO
没开,也就是说我们的got
表是可以写的。并且程序的PIE
是没有开启的。
- 接下来我们就逆向一下这个程序,先来查看一下这个程序的
main
函数。这个逆向的过程就不再一步一步逆了。直接给出逆向好的一些函数名称。- 发现该程序一开始就已经开启了沙箱
- 之后就是这个程序的堆菜单的主要运行逻辑。
- 我们还注意到,程序中并没有输出堆块内容的相关函数,但是有出现一个
my_write()
这个函数,这个函数就是用来输出一些内容的。
- 先来看看沙箱的禁用情况。发现只是把
execve
给禁用掉。ORW
相关的一个都没被禁用
- 接下来查看
add()
函数:发现程序可以申请0x20
个堆块,申请的每个堆块地址都会被放在idx
这个全局数组中
- 我们查看一下
idx
这个全局数组,发现实际上这个全局数组只能存储4
个堆块地址,由于后面的程序段也是可以写的,所以这边存在数组越界(但是并没有什么大作用)
- 接下来我们查看一下
edit()
这个函数- 选择一个索引小于等于4的堆块
- 向堆块写入数据,但是并不存在堆溢出
- 最后我们查看
delete()
函数,这个时候发现我们只能释放索引小于等于4的堆块。并且注意到这里存在UAF
漏洞。还注意到题目给的是glibc2.31
的版本。
cfc_level_2
- 由于这个是
glibc2.31
版本的堆,并且我们只能申请到tcache_bin
范围内的堆块。所以很明确,这题就是tcachebin_attack
。
cfc_exp
- 最终的
exp
如下:
1 | from pwn import * |
利用magic gadget
glibc2.27及以下
glibc2.29
glibc2.31
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 iyheart的博客!