计算机系统——buflab

代码链接:pan.baidu.com/s/1FLGjvTg61wVWz1sOUzrmvw
提取码:i8x8

实验题目:

buflab

实验目的:

详细了解IA-32调用约定和堆栈组织、它涉及对lab目录中的可执行文件bufbomb应用一系列缓冲区溢出攻击。
这个实验中你会体验到第一次使用操作系统和网络服务下开发安全缺陷的方法,我们的目的是帮助你理解程序的运行时所发生的内容,以及理解这种安全缺陷环境,从而帮助你避免在写代码时误入歧途。

实验环境:

个人PC,Linux发行版本,gdb 工具
实验内容及操作步骤:

一、 准备过程

1、将burflab-handout.tar.gz复制到linux系统中,解压压缩包得到bufbomb、hex2raw、makecookie三个文件;
在这里插入图片描述

2、阅读README、buflab-writeup.pdf等实验相关材料,通过各种方式了解实验的相关内容和过程;
——这个实验室的各个阶段需要每个学生有一个稍微不同的解决方案。正确的解决方案将基于您的用户标识。cookie是一个由8个十六进制数字组成的字符串,它(很可能)是用户id唯一的。

3、结合 buflab-writeup 中的介绍,分析一下上述三个文件的作用:
在这里插入图片描述
bufbomb 是要攻击的文件;
makecookie 是基于个人 id 生成的身份数据;
hex2raw 文件协助进行字符串间的转化;

4、确定我的userid为我的学号201808010205,并以此进行实验,按说明中的方法,生成自己的cookie:
在这里插入图片描述
5、bufbomb程序从标准输入读取字符串。它使用下面定义的函数getbuf执行此操作
在这里插入图片描述
函数Gets类似于标准库函数Gets,它从标准输入(以’\n’或文件结尾结尾)读取字符串,并将其(连同空终止符)存储在指定的目标。在这段代码中,目标是一个数组buf,它有足够的空间容纳32个字符。获取从输入流中获取字符串,并将其存储到目标地址(在本例中为buf)。

实验基础(核心原理):
Gets()无法确定buf是否足够大以存储整个输入。它只是复制整个输入字符串,可能超出了在目标位置分配的存储的边界。当错误信息出现时,缓冲区溢出导致程序被破坏,而这也是本次实验的基本原理;

6、实验中需要的几个控制语句:
-u userid,使用这个语句是要确保不同的人使用不同的 ID 做题,并攻击不同的地址。
-h 用于打印这几个操作的内容,
-n 用于 Level4 关卡,
-s 用于提交你的解决方案到服务器中

7、我们可以把自己写好的 exploit 文件写成 txt 格式,并应用到 bufbomb 中去;
在这里插入图片描述
(来自buflab-writeup.pdf文件)

8、注意事项:
Exploit文件中在任何位置都不能包含 0x0A,因为这是 ASCII 码的换行编码,如果写了这个语句,Gets 会认为你想终止字符串
HEX2RAW 期望输入的是空格隔开的两位 16 进制数,如果你想创建一个值为 0 的,就要输入00,另外,注意小端法输入

二、 分析过程

1、Level 0

题目要求为:当 test 函数在调用 getbuf 函数时,本来这个程序会按照惯例返回 test 函数,但是我们要做的就是当 getbuf 函数执行结束时,返回到 smoke 函数中去。
实现方法:在getbuf读取字符串buf时输入一个过长的字符串,把getbuf 函数的返回地址覆盖掉,改成我们想要的 smoke 函数的地址,那么就可以在 getbuf 函数运行结束时返回到 smoke 函数中去。
接下来反汇编bufbomb来查看我们需要输入的字符串
在这里插入图片描述
根据反汇编考虑 getbuf 的栈结构:
在这里插入图片描述
输入字符串buf的存放位置为$epb-0x28,这里从buf的起始地址到返回地址总共有48个字节,前44个字节的内容无关重要,但最后4个字节我们要填入smoke函数的起始地址以覆盖旧的ebp;

反汇编smoke函数:
在这里插入图片描述
得到其起始地址0x080448e0a;
但是如果在这里输入 0a,会被Gets 函数判断为换行符\n,从而结束字符串输入,导致这个地址错误,所以最后肯定不能输入0a;

解决方法:选择smoke起始地址的下一条地址即可,因为我们只需要执行这个函数;
这个方法在 writeup 中也有所介绍:Note that your exploit string may also corrupt parts of the stack not directlyrelated to this stage, but this will not cause a problem, since smoke causes the program to exit directly.
因此,level 0的答案就应该可以是(前44个字节字符不是0a即可):
在这里插入图片描述
测试结果:
在这里插入图片描述
结果正确,level 0 完成;
通过level 0,大致上明白了这个实验的基本原理;

2、level 1

类似于 level 0,任务就是让 test 函数返回到 fizz,而不是 test,然而,这次需要要传入自己的 cookie 作为参数。
程序不会真的调用 fizz,而是仅仅执行它,这句话说明了要在堆栈哪个地方去放置cookie。
在这里插入图片描述
fizz函数的C代码如上,我们要使得val参数为自己的cookie才算实现level 1;

首先反汇编fizz函数:
在这里插入图片描述
通过fizz的反汇编可以看出fizz的value参数的地址为%ebp+8,而fizz函数的首地址为0x08048daf
在进入函数时%esp的位置在返回地址处,进入后在返回地址+4的地方;
在这里插入图片描述
因为进入函数后有个push %ebp的操作,因此此时%esp又回到了gethub返回地址处,fizz的%ebp就会在gethub返回地址处,因此参数在返回地址+8的位置;并且因为是执行fizz函数而不是call fizz函数,所以栈不会自动push return address,即参数应该放在旧%ebp+12的位置;
结果就出来了,在Level 0的基础上修改return address使其指向fizz函数,再继续向上修改栈中的值,使其传递所特定的参数(cookie)给fizz函数.
结果为:
在这里插入图片描述
测试结果:
在这里插入图片描述
测试正确,level 1 成功;
那么如果我们跳过fizz函数第一条push指令呢?
这时参数的位置就会比上面的方法距离入口地址还要远4字节,即答案为:
在这里插入图片描述
在这里插入图片描述
测试同样正确,也就是说这道题有多种答案;

3、level 2

在这里插入图片描述
类似于 0 和 1 关,你的任务是使 bufbomb 的执行过程中,执行 bang而非回到 test,在这之前,你需要定义全局变量 global_value 为你的 cookie 值;

1)、可以使用 gdb 得到所需信息来组织你的代码,在 getbuf 中设置断点并运行,设置参数例如全局变量的地址和缓冲区的位置;
2 )用手写字节编码很复杂也容易带来错误,通过编写包含指令和数据的代码,你可以使用工
具做所有的工作, 我们可以使用 objdump 或者 gcc 将自己的代码生成可执行的机器码;
3)不要用 jmp 或者 call 指令,可以使用 ret 指令;

需要写汇编代码,完成相应需求,再通过 gcc 转成机器语言作为密码,再看看buflab-writeup.pdf最后给出的一个实例,明确一下使用方法:
示例代码:
在这里插入图片描述
使用 gcc 汇编、反汇编得到的机器码:
在这里插入图片描述
所以这个汇编代码的机器码就是:
在这里插入图片描述
这个机器码再通过 hex2raw 生成一个输入字符串传给 bufbomb 就可以完成任务;

此时可以开始level 2的解题:
首先需要找到全局变量的地址,因此反汇编bang函数:
在这里插入图片描述
找到bang的起始地址0x08048d52

和c代码比较,可以看到这个函数先把 0x804d10c 地址处的值传给 eax 寄存器,并和0x804d104 处的值做比较,看一下这两个地址处的值:
在这里插入图片描述
因此得知全局变量global_value的地址为0x804d10c;

现在可以开始写汇编代码:
要完成的任务有两个,一是修改变量值,二是使用 ret 语句完成对 bang 函数的调用,前一个操作很简单,就是mov 指令传值,后一个操作结合 ret 操作的作用(将栈顶单元出栈并赋给 eip 以实现转移),需要先把 bang 函数的入口地址压栈,再 ret,即可实现bang的调用
汇编代码如下:
在这里插入图片描述
转换成机器码为:
在这里插入图片描述
在这里插入图片描述
即为c7 85 0c d1 04 08 d3 10 ee 49 68 52 8d 04 08 c3;

这串机器码的使用方法:
将机器码写在buf的头部,getbuf执行完后的返回地址改成buf的首地址,这样当在getbuf中调用ret返回时程序会跳转到buf处执行上面汇编代码的指令,出现ret时会跳转到bang函数中执行。

使用 gdb 调试,在 getbuf 函数中设置断点,查询 buf 的首地址:
在这里插入图片描述
从getbuf函数的反汇编中可以看出%eax中的值即为buf的首地址;
在这里插入图片描述
将断点设置在call Gets处,这里的%eax中的值为我们需要的值;
在这里插入图片描述
以我的userid为-u指令的参数来运行程序,找到%eax中的值为0x55683ba8;
即buf的首地址为0x55683ba8;

因此buf的内容应该是:
在这里插入图片描述
测试结果:
在这里插入图片描述
运行正确,level 2解决;
当然,汇编程序的编写不仅仅是这一种,还有其他的可行方案,比如汇编代码不使用ret,即只完成全局变量的赋值,进入bang函数的操作留在汇编程序段结束ret回gethub时通过gethub的返回地址的前四个字节设置为bang的起始地址同样可以实现level 2;

4、level 3

之前的实验都是破坏了栈的状态而跳转到另一个程序中执行并退出,这个实验要求程序要正常返回到test执行,并且改变返回值为cookie的值,且不能破坏test函数的栈状态。
任务:
1)在栈上编写机器码
2)开始处设置返回指针
3)撤销对堆栈状态的破坏
注意事项:
1) 使用 gdb 获得你需要构造的代码的信息,设置断点并运行,以确定参数如保存的返回地址。
2)同 level2,写汇编代码并转成机器码

同样可以用上一个实验的方式,但我们在执行时要把破坏的栈恢复过来,并直接返回到test ,在getbuf返回地址中填充buf的首地址,当getbuf函数碰到返回指令ret时,将跳转到buf处执行,此时esp寄存器指向getbuf返回地址的高一个字节;

先看test的c代码:
在这里插入图片描述
返回值val变为cookie时会提示Boom!,可以借此判断是否实现将返回值val变为cookie且是否正确的返回到test中;

解题思路:

要求 1:修改返回值——函数的返回值是存在 eax 寄存器中的,如果想修改返回值,只要把 eax修改了即可;
要求 2:不破坏栈结构—— 有两方面的要求:
旧的 ebp 不被改写,能够将旧 ebp 保护下来并继续执行下去;
返回地址不出错,即执行完 getbuf 后继续执行 test 函数里的下面几句。

首先反汇编test查看调用getbuf后的下一条指令:
在这里插入图片描述
下一条指令的地址为:0x08048e50;
接着断点调试,查看getbuf旧ebp的值
在这里插入图片描述
查看到旧ebp的值为:0x55683c00

此时开始编写汇编程序:
有两种方法:
第一种是在这个代码里不对 ebp 作操作,而在我们最后填入 getbuf 的字符串中修改 ebp ;
第二种是在这个代码里把 ebp 改为原 ebp,在我们最后填入 getbuf 的字符串中随意填 ebp ;

1) 第一种方法(汇编程序仅改变返回值):

在这里插入图片描述
机器码为:
在这里插入图片描述
即机器码为b8 d3 10 ee 49 68 50 8e 04 08 c3
buf接受数据应该是:
在这里插入图片描述
测试结果:
在这里插入图片描述
第一种方法正确;

2) 第二种方法(汇编程序返回值、ebp均改变)

exploit汇编程序:
在这里插入图片描述
转为机器码:
在这里插入图片描述
即机器码为b8 d3 10 ee 49 bd 00 3c 68 55 68 50 8e 04 08 c3
这时buf接受数据应该是:
在这里插入图片描述
测试结果:
在这里插入图片描述
两种方法均满足条件;

5、level 4

相关介绍:

Getbuf 函数调用过程中,我们安放了使堆栈稳定的因素,因此 getbuf 堆栈框架在运行中趋于稳定,这使得你有机会写攻击代码来通晓 buf 的始地址,如果你试着在一个普通的程序中使用这个攻击代码,你会发现它有时能用,但其他情况下会出现分裂错误。
这一关里,我们要与原来背向而行,使堆栈的位置比平时更加不稳定。
当你使用-n 运行 bufbomb 时,它就会进入“硝基模式”,程序不调用 getbuf,而是调用 getbufn;
在这里插入图片描述
这个函数和 getbuf 类似,但是它限制了缓冲区大小为 512 个字符,你将会需要这个额外空间来创建可信的攻击程序,调用 getbufn 的代码第一次在堆栈上分配一个随机大小的存储空间,所以如果你在 getbufn 两次连续调用过程中对 ebp 采样的话,你会发现他们相差 240;
另外,在硝基模式下运行时,bufbomb 要求你提供你的字符串共 5 次,而且他也会执行 5 次,每次都有不同的堆栈偏移,你的 exploit 必须使它每次都返回你的cookie。

任务要求:getbufn 返回你的 cookie 到 test 中,保存任何的破坏状态,把正确的堆栈位置压栈,并且执行 ret 操作来真正的返回 testn;
buflab-writeup.pdf提供了一些建议:使用nop指令(编码0x90)

解释:一种常见的把戏就是在实际的攻击代码中插入一段很长的 nop 指令,只要攻击者能猜中这段程序中的某个地址,程序就会经过这个序列,到达攻击代码。

解决思路

针对这个函数 getbufn,ebp 随机变化,栈上地址不固定,要求写的跳转地址是固定的,可以在大范围内都写上 nop 代码,即这段地址内的代码都会滑到这段 nop 之后的攻击代码上。
由于要保证五次运行都能顺利执行有效机器代码,跳转地址位于有效机器代码入口地址之前的 nop 机器指令填充区。

开始解题:

首先反汇编getbufn以观察buf的首地址:
在这里插入图片描述
buf的首地址为%ebp-0x208,也就是说buf达到返回地址的大小有520字节,考虑这个函数中,testn 的 ebp 随每次输入都随机变化,但是栈底 esp 的位置却不变,可以通过 esp 和 ebp 的关系来找出这个关系,从而进行攻击。
接着设置断点并且用-n指令运行,查看变化五次%bdp的值:
在这里插入图片描述
找出最大的 ebp 值 0x55683c00,再减去 0x208,即为五次栈偏移中buf的最大的起始地址为:0x556839F8;
将有效机器代码置于该地址之前,并将其它所有字符都用作 nop 指令,此时所有 buf 地址的写入都能满足条件;

反汇编testn:
在这里插入图片描述
getbufn的返回地址为0x08048ce2;
%esp+0x24+0x4为旧ebp的值,因为%esp是不变的,因此可以根据%esp+0x28来找到正确的%ebp的值;
因此可以写出汇编代码:
在这里插入图片描述
修改%eax返回值,修改旧%ebp,exploit结束后返回到testn中;
转为机器码:
在这里插入图片描述
即:b8 d3 10 ee 49 8d 6c 24 28 ff 35 e2 8c 04 08 c3

考虑 buf 部分共有 520+4(旧 ebp)+4(返回地址)共 528 个字节,我们这个代码里要做的就是填入三部分:nop 操作、攻击代码、和跳转地址。
先考虑后面的部分,在原函数的返回地址处我们肯定要用 buf 的最大首地址代替,是最后 4字节,然后紧跟着它之前的是攻击代码,共 15字节,剩下的 528-4-15=509 字节全用 nop填满
在这里插入图片描述
因此,填入buf的字符串应该为(很多90未显示):

测试结果:
在这里插入图片描述
测试正确,level4解决;

实验结果及分析:

level 0:
在这里插入图片描述
level 1:
在这里插入图片描述
level 2:
在这里插入图片描述
level 3:
在这里插入图片描述
level 4:
在这里插入图片描述
buflab的五个level全部完成,结果如上图,完整的解决了整个实验;

收获与体会:

通过这次实验,对于Linux系统的一些操作命令有了一些了解和掌握,学习了如何使用gdb 这个强大的工具进行调试,以及加深了对于汇编语言的熟悉。
这次的实验比之上一个bomblab实验要简单一些,主要用到的原理只有一条——缓存溢出攻击,四个level都围绕着这个核心在展开,因此较为简单;
这次实验中用到的指令都是实验自带的,我们只需要会应用即可,实验目的主要还是放在了对堆栈的结构的理解和应用上,溢出攻击这一实验本身也非常有趣;
本学期的四个课程实验到此就基本上结束了,收获颇多,尤其是bomblab与buflab,很有趣的展示了反汇编的灵活使用,从中也可以看出反汇编工具的强大和学习的必要性;
通过这四门实验我也更加理解了学习计算机系统、底层操作的魅力和实用性,引领我们初步认识了计算机系统,为今后的学习打下了坚实的基础。

  • 11
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值