实验原网址:csapp.cs.cmu.edu/3e/labs.html
一、实验背景和要求
1.实验包含文件
2.任务:
1)对没有进行栈溢出攻击防御的程序“ctarget”使用“代码注入”的方式进行攻击,改变其行为。分别使其直接调用touch1函数;使其为带普通参数的touch2函数设置好参数,并调用;使其为带指针参数的touch3函数设置好参数,并调用;
2)对已经采用了”栈随机初始化“和“段锁定”两种防御方法的程序“rtarget”使用“面向返回”的方法进行攻击,改变其行为,使其像上面那样调用touch2函数和touch3函数。
二、实验过程
我们可以看到,程序调用了有漏洞的库函数Gets,Gets函数没有对缓冲区数组长度进行检查。如果字符串过长,将会使数组溢出。而这个数组是存储在程序栈上的,如果溢出的数据覆盖了原来存在上面的函数返回地址,则当函数返回时,程序将会跳到新的地方执行,程序的整个控制权就被窃取了。
这个实验的大个篇幅不是教我们进行防护,而是教我们怎么好好地利用这个漏洞,教我们怎么当一个“顽固”的攻击者。???
1.Phase1
我们从getbuf的汇编代码中可以看到,它在栈上申请了0x28个字节,将栈顶作为数组开头并传进了Gets库函数,所以,函数的返回地址存储在离数组开头40字节的地方。所以我们可以输入48字节的字符串,前40个字节随便输,最后8个字节是touch1函数的地址就行了。
48个字节应该是这样子:
利用工具hex2Raw转换成字符串:
2.Phase2
由于我们需要为touch2设置好参数,所以我们的字符串中需要包含代码,用来完成设置参数的功能。覆盖的地址要使用我们注入代码存储的地址。这个程序的栈地址是固定的,所以只要事先勘察一下就可以确定我们注入代码的绝对地址了。如果是在一个不太大的范围变化的话,还可以使用"滑雪橇"的办法,在代码中插入长长的nop指令使其滑向我们注入的代码。
第一行为设置参数。第二行重新设置返回地址,即touch2的地址。第三行为返回指令,将会跳到touch2里。
其机器代码为:
所以最后的输入字节是:
最终字符串:
3.Phase3
Phase3与Phase2原理基本一样:先覆盖返回地址,使程序返回到我们注入代码的开头,注入代码完成设置参数的功能,然后返回到目标函数touch3.不同的是,touch3函数调用的函数也使用了程序栈,为了让touch3的参数指针所指的字符串不被后面的函数覆盖,我们要把这个字符串放在比较远离栈顶的位置,地址尽量高一些.
注入的代码:
机器代码:
输入字节:
4.phase4
第二个程序“rtarget”分别使用了栈随机初始化和段锁定进行防御。使我们不知道注入的代码所存储的地址以及锁定了程序栈上的指令不能在cpu运行。但是,实验讲义很贴心地为我们介绍了一种针对这种情况的攻击方法:在栈上存储一系列地址,这些地址分别指向一系列程序片段,这些片段最后都包含有一个ret指令(0xc3),当一个片段里执行了ret指令时,便会返回到栈上所存储地址所指的地方,于是这一系列程序便可以串成一个链依次顺序执行。由于这些片段我们是选用“rtarget”原有的在代码段上的代码,所以他们的地址是固定的,而且代码段上的指令是不能被锁定的。但是这个方法我认为的缺点就是非常的繁琐,利用原有的代码想要拼凑出我们想要功能非常麻烦!
机器指令对照表:
讲义提示我们在start_farm和密度mid_farm之间可以找到想要的代码:
所以我们的攻击字符串的16进制表示可以为:
5.phase5
讲义说这个官方的做法用了8个程序片段,分布在start_farm和end_farm之间:
尼玛这么多我要拼到什么时候啊!
哈哈,这个我没做出来。
讲义说到这里我们已经获得了这个实验95%的分数了。剩下最后一个如果忙的话就可以不做了,真是说到我的心坎里去了,好就先不做了!????
总结:这个实验加深了我对程序运行的内存结构的理解。同时我也感觉到了这样一个现象:采用即使是简单的防御也可以让攻击的成本和难度陡升!