本文仅供参考,作者本人是个小菜鸡第一次写博客,请口下留情。
一、前置工作

在希冀平台提供的远程桌面中下载系统文件,打开浏览器输入对应网址输入您的学号和邮箱号,得到系统文件压缩包,您可以选择将该文件(.tar)移动至mnt目录下的cgshare,再右键选择打开控制台输入tar -xvf targetxxxxxxx(xxxxxxx为你的标识符)就将该压缩包解压得到一个文件夹,之后你打开文件夹可以看到如上图中描述的几个文件,我们主要使用的是ctarget和rtarget文件以及cookie文件中的cookie字符,hex2raw每一题都会用到该工具(因为我们每一题都是攻击得生成攻击字符串)。
然后你可以右键打开控制台输入objdump -d ctarget > ctarget.txt
objdump -d rtarget > rtarget.txt来得到这两个文件的汇编文件
之后把他们复制到cgshare下然后点击下载远程桌面内的文件,就像图片这样:

这样主要是方便我们查看汇编代码,用自带的vim编辑器有点难操作,下载完之后我们就可以用自己电脑的记事本或WPF之内的文件来查看文件了。
接下来我就直接说解题思路了
二、解题过程
1.phase_1

注意:这一关以及后面的关卡都是以test函数为入口,test函数干的事情是调用getbuf函数并且输出一句提示这是正常输出的字符串告诉用户(挑战者)您攻击失败,因为我们的攻击是让返回地址不是test的地址而是touch1,touch2,touch3这样的函数地址,也就是调用了getbuf后不会再回到test了,也就不会输出上述的正常输出的字符串。
我们来看看getbuf函数:

原理:getbuf函数会规定一定大小的字符缓冲区也就是我们输入字符串的长度,然后调用一个不安全的gets函数来从用户(挑战者)那获取输入放在之前规定大小的字符缓冲区中,注意由于是局部变量,那这段空间是在栈上的。为什么说这是不安全的呢:因为gets函数不会检查用户输入的字符串是否超出之前规定的范围也就是说我们超出范围的话,我们输入的字符串除了把之前给我们的栈空间填满还会覆盖之前开辟的栈也就是调用函数的栈帧而调用函数的栈帧一般会包括返回地址(在调用函数帧栈最下方,与被调用函数的栈帧距离最近零距离)来告诉被调用函数执行完应该返回的地址是哪。这就出现了一个问题:我们被调用函数getbuf的栈由于gets函数的不安全性(不检查字符串长度)而用户输入字符串刚好超出了这个长度,这是原本的返回地址就会改变,这是就不会回到test函数了而是我们用户(挑战者)自己精心设计的返回地址去执行touch1之内的目标函数了。
第一关就是要我们先把分配给getbuf的栈铺满(输入可以全部是0),然后再加上touch1的地址来覆盖test的地址,来让getbuf函数执行完跳到touch1而不是test。我们需要的是touch1的地址和getbuf的栈空间大小

我这些是从记事本中打开ctarget.txt(上面有说过我下载到自己的电脑上了),使用记事本的查找功能查到这两个需求点了,touch1地址:40 17 b3,getbuf栈空间大小:0x28也就是40字节。可以画出栈的草图:

这时候就可以编写攻击文件了,我命名为touch1.txt:

新建一个touch1.txt文件然后vim编辑器打开,输入i进入嵌入模式也就是编辑模式:getbuf的40字节我用40字节的0填满了(当然你也可以填别的),两个十六进制对应一个字节哦。也就是00才是一个字节的0,输入8个00之后可以换行(enter)这样方便你数,我输入了5行的0也就是五八四十个字节的0之后输入touch1的地址来覆盖原本test的返回地址,注意这是小端模式(高地址对应高字节,低地址对应低字节,图中左上是低地址右下是高地址),也就是说右边是高地址所以我们的40(高字节)要写在最右边也就是写反的。因为我的touch1地址是40 17 b3我的输入就是这样了。之后输入完成后点击esc键再输入:wq 即编辑完成。
然后我们就要用到hex2raw工具(之后每关都需要用)来生成攻击字符串文件作为我们的真正输入了(电脑理解的输入,上面是我们理解的输入):
也就是我这样先转化再作为输入(例子)(之后每关都需要用)这两句:

第一关就结束了
2.phase_2
这一关比第一关难在你要往栈中注入攻击代码来实现传参以及返回到touch2函数的操作。

不过相信在我的讲解下你能很快明白
上面的touch1我们是不需要传参的所以我们没有写攻击代码,而touch2需要传参所以我们返回到touch2前我们需要让rdi(第一个参数寄存器) = 我们需要的值(查看图中touch2可知该值是cookie),所以这关我们需要的是:cookie的值,攻击代码(rdi = cookie,return touch2),以及我们攻击代码的地址。
我帮你捋一捋过程,这一关就不能像第一关那样用touch2的地址覆盖test的地址了,因为我们执行touch2之前要传正确的参,所以我们应该用攻击代码(你也可以理解为中间代码)的地址覆盖test的地址,所以我们执行完getbuf应该回到攻击代码那先赋值给参数而不是直接回到touch2。
栈分布:

我的cookie值是:0x324b42ea(您们的可以去解压后的文件夹中的cookie.txt中查看,上面提到过)
touch2的地址:4017df(上文提到过,你去你的ctarget.txt汇编文件中用自己电脑的记事本或者其它工具查找touch2来得到地址)
而攻击代码我们可以想到应该是这样:
mov $0x324b42ea,%rdi
pushq $0x4017df
retq
这一段汇编代码的含义就是将cookie值赋给rdi,然后将touch2地址入栈当作返回地址,然后再返回到touch2。
攻击代码你需要新建一个文件命名为touch2.s(注意必须是.s结尾的),然后gcc -c touch2.s之后会生成touch2.o,之后就objdump -d touch2.o > touch2打开touch2就可以查看汇编指令对应的机器指令了。

我的例子:
之后查看touch2:
这就是攻击代码的机器指令。
之后我们就选择将攻击代码放哪了,我选择的是放在getbuf的栈顶那,因为方便得到攻击代码的地址也就是getbuf栈顶的地址。
我们还有一个需求点:攻击代码地址也就是getbuf栈顶地址:下图上面省略了gdb ctarget
可得getbuf栈顶地址为0x556743d8
所以需求点全有了后开始写touch2.txt:
之后像第一关那样./hex2raw <touch2.txt> touch22.txt 再./ctarget < touch22.txt就好了:

phase_3

第三题我们看看touch3的内容,其实也就是看hexmatch函数的内容,hexmatch函数开辟了一个字符缓冲区,大小为110字节,之后将字符指针s指针随机到cbuf+一个0到99的数的字节,也就是最少会留11个字节大小存这个字符数组,然后将传入的第一个参数也就是cookie的值(可以查看这个函数的汇编代码得到,也不难猜到这就是cookie)(第二个参数也就是我们传入的cookie对应的字符数组的首地址,这个字符串是我们自己到时候要输入的,也就是我们输入的答案里要包括的)(8位16进制数)改为8个字节长的字符串存到s的位置,也就是cookie的每一位数都会转成对应的ascii码值(十六进制表示),如图:

就比如我的cookie是0x32 4b 42 ea对应的ascii码也就是字符串值是(字符在计算机底层存的是其对应的ascii码值)33 32 34 62 34 32 65 61。
这比第二题难在我们第二题是直接传cookie值而这个函数需要传的是字符数组的首地址,也就是我们还需要在栈上构造一个字符数组也就是上面的33.。。。。。。。
上图是我们的栈变化:我们还是像第二题一样把攻击代码(中间代码)放在getbuf栈顶处,但是我们还需要考虑的是我们cookie字符串应该放在那里,通过我们图中的2后可发现原来getbuf的栈在执行完攻击代码后调用touch3和hexmatch等函数会覆盖原来的栈空间,也就是我们如果把cookie放在getbuf栈帧里面则有可能会被后来函数覆盖,而字符串首地址依旧指向原地,会造成访问权限不够或者访问的字符串不是我们要的,所以说你们猜到cookie字符串放哪了吗,当然是相对安全的原来的test函数的栈帧了,由于我们一直没返回test当然test的栈帧是一直没回收的也就是会维持相当长一段时间。所以我们就把cookie字符串放到如图的攻击代码返回地址的相邻的前面。
在第二个实验中可得getbuf的栈顶地址是0x556743d8,我们可以算出cookie字符串的首地址是这个加上getbuf的栈帧长度(0x28)再加上攻击代码返回地址的那段长度(0x8)也就是加上(0x30)0x55674408。
所以我们攻击代码汇编指令应该是:
movq $0x55674408,%rsi //这是把字符串首地址赋值给了第二个参数寄存器(hexmatch的第二个参数)
有些同学可能是rdi你们到时候错了就可以改成rdi试试
pushq $0x4018b3 //自己去查看ctarget.txt中touch3的地址,我的是4018b3
retq //返回到touch3函数
之后像我们第二题那样 将上述汇编指令写到一个新建的以.s结尾的文件中,再gcc -c touch3.s,他会生成一个touch3.o文件,再objdump -d touch3.o > touch3就可以查看汇编指令对应的机器代码了。(上文提到过就不多说了)如图:
自己打开touch3就可以看到机器指令了,跟第二个实验很像。
然后我们就编写touch3.txt文件了:

再
./hex2raw <touch3.txt> touch33.txt
./ctarget < touch33.txt即可(前面已经演示过了,就不放图了)
phase_4
第四关以及第五关就不能使用我们上面的注入代码办法来破坏返回地址了。原因如下:

我来解释一下:栈位置随机化,我们之前不是获取了getbuf的栈顶地址(我们攻击代码的位置)吗,每次运行栈位置变为随机化了我们在栈上写的随机代码位置就不确定了就不能直接拿某次运行时候的栈位置作为返回地址了。同时,栈中注入的代码也不可执行,也就是说我们即使在栈上注入了攻击代码并且成功找到了他的位置,这段攻击代码由于在栈上还是不能执行。也就算是双重保护了。
我们就有另一种攻击方式,你不准我注入代码那我就用现成的代码呗:利用现有的汇编代码(位于一定范围之间,其中有多个小函数)对应的机器码(也就是我们汇编代码前面的那些十六进制数)经过一点变化,然后多个这样的机器码经过ret到下一个我们改编过的机器码的地址连成一段程序链,一步一步实现我们第二题第三题那样的攻击代码的效果。这些小小的程序段叫gadget(后文就这么称呼了),由于有多个ret,这种攻击方式也叫做ROP(面向返回编程)。这种方法是逆推的,根据答案来猜过程。
我来举例这种变化是什么:看下图:

上图第一张图是一些我们第四题第五题可以用到的汇编指令和其对应的机器码,我们可以看到48 89 c7是A中第一行的最后一列movq %rax,%rdi。假如我们攻击代码需要传递rax中的值给rdi,接下来我们看上面第二张图我们找到了范围内的一个函数(查找 48 89 c7)其中有这段机器码,那我们可以将前一段gadget的返回地址设为400f15+3也就是400f18他到时候跳到该地址就执行48 89 c7 c3这四个指令,也就是传递rax中的值给rdi并且返回到下一个gadget的地址(在栈上每一个gadget的地址是相连的从而保证每个gadget执行完后能到下一个gadget的地址,使得他们能形成一段连续完整的程序链)。
接下来我就直接说第四关了

我们的gadget farm(选取范围)在rtarget.txt(和第一二三关的ctarget.txt不是一个文件)的start_farm和end_farm函数之间。
我们先来想想攻击代码:只能有两个gadget
这关还是调用touch2也就是传的参数是cookie(一个参数),我们上面的关于gadget的例子里面不是有个movq %rax,%rdi(把rax中的值给rdi),刚好我们需要传的参数就位于rdi中,为了方便我们就用这段gadget作为我们第二个也就是最后一个target。所以我们逆推可以推出前一个gadget应该让rax中的值是cookie值。而我们的可以利用汇编函数的表中没有movq 立即数,寄存器,但有popq,就是把栈顶元素出栈给寄存器。所以我们可以想到前一个gadget是popq %rax。可以画出
栈的分布图:

之后我们就去找这些汇编指令的机器指令分别是58 c3和48 89 c7 c3这两条,然后我们就用记事本查找这两条指令的位置(事先可以从rtarget.txt先提取出start_farm和end_farm之间的代码段作为一个新文件来查找)


我的查找如上图:90是nop空指令的机器码(可以查看上面汇编指令对应的机器码那张图),没有任何作用(除了使得程序计数器加1),所以58和c3中间隔很多个90是没关系的。我们可以得出我们要的两个gadget地址:40 19 64和40 19 56之后就可以编写我们的攻击文件touch4.txt了
我的cookie值是0x32 4b 42 ea
touch2地址是40 17 df(你们自己去看看rtarget里面touch2地址)

之后./hex2raw <touch4.txt> touch44.txt再./ratrget < touch44.txt即可
phase_5
这关就要求调用touch3,传进去的是我们cookie字符串的地址,而我们cookie字符串是在栈上栈位置又一直在变,所以我们是这样得出cookie字符数组首地址的:将栈顶地址加上偏移量然后赋值给一个寄存器然后再挪到我们目标寄存器rdi中。
我的rtarget中可以找到这样的lea,所以到时候我再调用一个movq rax , rdi就可以将字符串首地址传给我们hexmatch中进行比较了。
之后我们就要考虑怎么让rdi存栈顶地址,rsi存偏移量
下文的找中间寄存器是看上文中汇编指令对应的机器指令图某一列的机器指令是否在范围内中有
rdi存栈顶地址可以:movq rsp,rdi但是你去查找这个汇编对应的机器码可能是没有的,所以我们可能要一个中间变量来传递这个值,你需要逆推得出来,即什么寄存器(寄存器1)可以传递给rdi(查表rdi那列),而后rsp能不能传给这个寄存器(寄存器1)。这个过程我是找一个中间变量就行了,我的中间变量是rax,所以我的两条budget是movq rsp rax 然后 movq rax rdi。
rsi存偏移量:这个我那有两个中间寄存器,即a->d需要a->b->c->d,你还是需要逆推,找什么寄存器(寄存器1)能传到esi(为什么是esi呢,因为e对应的指令和r相比少了个48,更容易找到对应的budget),然后找什么寄存器(寄存器2)能传到寄存器1,然后一般都是先把偏移量挪到rax中,因为第四关这样用过,即popq rax然后自己手写偏移量,所以最后找eax是不是能传到寄存器2。
由于每个人可能找到的中间寄存器不同我就只讲原理了
注意:90和汇编指令对应的机器代码d表里面都是空指令,可以插在c3(ret)和我们功能代码之间
最后是我的栈分布:
这是我的touch5.txt和过程:



然后./hex2raw <touch5.txt> touch55.txt 再./rtarget < touch55.txt就好了
完结了,谢谢大家看到这里



1001

被折叠的 条评论
为什么被折叠?



