本帖最后由 gclome 于 2020-3-25 13:08 编辑
本文转自:中南极光网安实验室
0.前言
pwn这个东西真是深奥,目前自己对于pwn的理解也刚刚处于微妙的入门阶段。想要系统全面地了解这方面,还是离不开CTF-wiki(https://ctf-wiki.github.io/ctf-wiki/pwn/readme-zh/)与针对细节问题的百度。
说到pwn题,初次接触的萌新应该都会从栈溢出这个方向开始。如果还不了解什么是pwn,那可以查看一下我们的往期推送。然而这个入门真的只是推荐了条路线吗……作者作为一个懒人型萌新,还是希望有更有操作感的入门。
1.你需要做的前期准备
首先,你需要有一个Linux系统的电脑或者一个linux系统的虚拟机。至于Linux镜像的选择的话,kali可能有更多的安全方向的预装软件,Ubuntu则更加主流,如果你遇到了操作系统层面的问题,Ubuntu系统可能更容易搜出解决方案。不过系统级别的问题还是重装系统更快,反正是虚拟机。 之后你需要安装pwntools pwndbg。这里给出指令: - sudo apt install python
- sudo apt install python-pip
- sudo apt install git
- pip install ROPgadget
- pip install pwntools
- git clone https://github.com/pwndbg/pwndbg
- cd pwndbg/
- ./setup.sh
复制代码
安装完成后建议重启一下虚拟机。
2.栈溢出攻击是什么?
如果学过数据结构或者跟汇编打过交道,想必对栈都并不陌生。程序有程序段,数据有数据段,缓冲区也有缓冲区的栈段。众所周知,栈的大小是有限的,但是有时候数据的输出可能是无限的。当读入数据时没有限制,或者限制大小比栈段大小要大时,都会导致缓冲区的溢出。
我们能利用缓冲区溢出做什么呢?先来解释一下栈帧的结构。
这张图建议从下往上看,你写入的数据会从rsp指向的地方往上依次排列,学过汇编的小伙伴也知道在执行函数返回的ret 指令时会从栈帧中弹出父函数的地址,并且程序跳转到该地址。而我们就可以利用这一步弹出,通过缓冲区溢出来实现将程序转移到一个我们希望它去的地址。
而做pwn题攻击的终极目标是什么呢?是取得系统的权限,即让攻击者能够执行一些指令。那我们需要控制程序跳转到哪里才能达成目的呢?针对初级的题目而言,主要是以下四种形式:
ret2text:控制程序执行程序本身的代码,比如程序中已经有了system函数,我们就可以通过执行system("/bin/sh") 或system(“sh”)实现。
ret2shellcode:先写入一段能够获取shell的汇编代码,然后让程序跳到rsp指向shellcode的开头,来执行该段汇编代码。但实现该攻击需要shellcode所在的区域有课执行权限。
ret2syscall:调用Linux的系统中断int 0x80实现。如果不知道linux有哪些系统中断可以查阅这个网址。
ret2libc:控制函数执行libc中的函数,返回一个函数的具体位置,诸如靠这样获得system函数的地址。
这几个方法需要的前置条件各有不同,不过使用起来的复杂程度大致还是递增的,有兴趣深入了解的可以参考CTF-Wiki,并且多百度一下。
3.原理知道了,提供的工具如何使用?
这里以ctf-wiki上给出的ret2text例子来深入讲解一下虽然别人CTF-wiki已经介绍了攻击步骤了,这里还是稍微说一下思路。
拿到程序的第一步,一定是确定一个做题的方向。首先使用checksec看一下最基本的文件信息。
我们对checksec显示的内容做一个大致的解释:
Arch 程序使用的架构
canary 栈保护,原理是用一个标记位放在栈中,用来防止缓冲区溢出攻击
NX(DEP) 设置栈段不可执行,enable时开启保护,不能执行代码
PIE(ASLR) 内存地址随机化。三种情况:
0 - 表示关闭进程地址空间随机化。
1 - 表示将mmap的基址,stack和vdso页面随机化。
2 - 表示在1的基础上增加栈(heap)的随机化。
RELRO partial RELRO说明对got表有读写权限,FULL RELRO 则无法修改got表
所以该程序只启用了栈段不可执行的保护,我们不能在栈段放入sehllcode进行运行,而其他的攻击手段都可以使用。知道了大概的方向之后就可以用IDA查看源码了。
另外如果想使用IDA远程linux调试32位程序时会需要libstdc++.so.6的库文件。ubuntu环境下安装该库:apt-get install lib32stdc++6。如何使用远程调试可参考博客(https://blog.csdn.net/lacoucou/article/details/71079552)。
可以看出来这个程序不仅有system函数,并且输入system的参数都替我们准备好了,否则通常情况下是需要自己写入或寻找其他地方的字符的。在动态调试时可以看到这个的地址:
然后我们观察一下利用栈溢出漏洞的输入函数:
左侧eax寄存器的值是给gets作为输入数据的起点,而ebp代表的是当前的栈底。最后程序进行的步骤是leave,ret所以我们构造的填充用的输入应该有(eax与ebp间的插值)+leave时赋给ebp的字节数,即0x80+4个字节。
在平常构造跳转地址的时候还要注意,若是32位程序,是将参数放在栈中的,可以直接利用栈溢出进行修改;但是64位程序的函数前6个参数,则是分别存储在RDI, RSI, RDX, RCX, R8, R9这六个寄存器当中的。那么如何改变寄存器的值呢?我们可以使用ROPgadget工具来实现。
当然至于ROPgadget更进一步的使用就需要自己去百度了。
4. 接下来就是最后一步了!
当你成功找到了栈溢出攻击的方法,最后一步就是构造脚本了。我们使用pwntools这个现成的包来构造攻击脚本。pwntools包的使用分为以下几步:
1. 构造context。设定程序执行的环境与程序体系结构。常见的体系结构即i386 与amd64
2.建立连接。与本地文件进行交互时使用process("filename"),与远程交互时使用remote(ip="",port="")
3. 构造一个输入。这个就是具体题目具体分析了。基本套路就是前面的缓冲区与寄存器部分可以随意填充,而只需要保证跳转地址是你需要的跳转地址即可。注意跳转地址应使用p64()或p32()包裹。
4. 输入进去并进行交互。注意交互时如果没有正确实现提权是不会有结果的,可以用ls指令或echo "something"指令试一下。
运行结果:
这样就是做一个Pwn题的大概流程了,各位有没有觉得有趣呢?有趣的话可以看看入门路线进行更系统全面的学习。
|