【深入理解计算机系统】CSAPP Attack Lab实验,ctarget
零碎记事
前些天画画的时候,数位板出了点问题,笔隔着板子还有段距离,就有了压感,导致到处误触,换笔芯也没用,很是头疼。后来问了问客服,他们家技术人员问我我这边的当地温度几度,我在手机上看了看天气,答,10度。嗯......亲,建议热一热板子再试试。我半信半疑,拿吹风筒热了热,然后用笔试划了几下,好家伙,还真是温度问题,问题解决了。哎,这什么原理啊,挺好奇的,忍不住追问了下,据说是低过15度后板子灵敏度会开始变高。啊,恍然大悟。
南国的冬天总是来得很晚,但其寒气盘踞在这里时,又是格外令人痛苦。
刚热的板子没过一会,温度又低了下去,又得拿吹风筒吹吹,但终归只是权宜之计,往复几次后,我接受了现实,在南方,这种室温下就是没法用板子。唉,羡慕北方人啊,明明冬天比我们冷,但是暖气开起来就是比我们舒服。想起一首歌,马顿的《南山南》,觉得这段非常应景:
你在南方的艳阳里,大雪纷飞
我在北方的寒夜里四季如春~~
说起来,为什么南方人不开暖气呢,尤其是广东这带,明明那么有钱,却也不开暖气。后来跟同学们聊天的时候提起这个问题,大家也不是很清楚为什么会这样,上网查了查资料,才发现是因为南方的城市在当初规划时并没考虑到多少集中供暖的问题,真要搞起来,成本会很高,加上南方建筑的透风性都比较好,就算供了暖,屋子也留不住暖气,所以啊,南方人人下人,就别想着暖气啦!
这个月底,气温会骤降到1度,尽管御寒的衣服已经提前准备了一套一套的,竟还仍有些心虚。1度啊,如果天气预测有偏差,温度再往下掉掉的话,我能见到雪吗?在老家读书时那里是大陆的最南端,南国中的南国,从小到大还没经历过低于5度的天气,自然也没见过雪,加上看过的很多作品对雪景都有着很美好的描写,我是对看雪十分憧憬的,想着如果实在是见不到雪,那么总有一天就是自己独自一人都要坐飞机去北方看一看,看一眼,足矣。
一天晚上回来宿舍,舍友桌子上多了个南极人热水袋,我借来试着用来暖手,这一试,哎,心动了,不得不说这玩意是真的暖,以前听别人说热水袋不安全还是什么的,就没碰过这东西,现在试了下,确实挺香的,而且,暖板子神器啊。马上当天晚上我也给自己在网上买了个,顺便也给父母买个暖脚类型的,家居天天只能穿拖鞋,不太好顶月底的严寒,虽然平时似乎大家也不在意,但是暗暗中每次穿上冻拖鞋时的痛苦还是懂得都懂。
写下这篇文章时已多个DDL缠身,大数据人的冬天,也不过是冬天。
实验简介
这个实验要求你结合attacklab.pdf这个文档,加上一些gdb调试,用冲出缓冲区的方法解开三道谜题。
本篇文章只讲解ctarget的三道如何解开。
注意事项
ctarget在执行时默认发送成绩,因为我们连不上其服务器,所以需要加参数-q,即不发送成绩,不然程序都没法跑。
hex2raw是用来把16进制的东西转化成字符串的。用法是./hex2raw < <inputFile>。可以看看第一关我怎么用,也可以自己看官方文档。
如果打算只是来看看了解这实验是干嘛的,之后自己独立完成的话,请确保你了解以下两条命令,不然没法解决touch2以后的问题
命令1:gcc -c <code.s>
命令2:objdump -d <code.o>
不会的话建议看完第一关和第二关的讲解,第三关再尝试自己完全思考解决。
事先准备
解压target1.tar,反编译里面的ctarget,得到ctarget.asm,建议把这文件搬到windows上来,方便调试。
了解getbuf函数
这是官方文档展示出来的getbuf()函数,其中Gets()是不安全的,有缓冲器溢出漏洞,我们总是通过这个函数进行输入,调整溢出的内容来达到我们成功攻击三个touch的任务。
我们分析其汇编代码
看778那行,sub $0x28,%rsp,这是一个开栈操作,开了0x28,也就是40个字节大小的栈,我们的输入从栈顶开始输入,所以如果我们输入的内容超过了40个字节,就会发生缓冲区溢出。
Touch1(Level1)
我们只需要成功进入touch1()函数即可过关。
这里需要利用缓冲区漏洞,输入超过了40字节后,就会开始覆盖开栈以前的内容,而比较近的4个字节的内容是getbuf()函数的返回地址,我们要用touch1()的地址覆盖掉原来正确的返回地址。
查看touch1()函数对应的汇编代码,找到函数的地址是0x4017c0,所以我们随便输入40个字符后,还要再输入0x00,0x40,0x17和0xc0这4个ASCII码所对应的字符以覆盖返回地址成touch1()的地址,这样当getbuf()的retq执行后,程序会返回到touch1来。然而,我们无法输入0x00这种字符,所以官方给了我们字符串转换器,也就是hex2raw程序,我们只需要把需要的字符的ASCII码的十六进制打到文件里,之后用这个程序转成字符串就行,输入就用管道输入或者直接文件输入。
我编写了一个名为hex.txt的文件,里面是我的攻击字符串的16进制码,注意小端机器是低地址存低位。
用hex2raw转成字符串,然后用管道输入,通过
下面演示用文件输入的方法,文件输入用参数-i
Touch2(Level2)
我们的任务是把cookie作为val参数传进来,这样(val == cookie)这个条件就为真,才能通关。cookie的值,存在cookie.txt文件中,大家可以自行查看。
val是函数的第一个参数,因此其参数的存储位置是寄存器%rdi,我们要做的事情就是,在跳到touch2()时,其%rdi中的值是cookie。
怎么跳到touch2()时,顺便修改%rdi中的值呢?像touch1()那样只是单纯考虑到用retq跳到touch2()是不行的,你这样改不到%rdi。
那怎么办呢?接下来,是整个实验最阴间的部分,我也是看了下别人的博客,不看真的是打死都想不到。
先说明一点,虽然我们可以分析汇编,但是本质上,机器运行的还是机器码,什么意思呢?就拿getbuf()函数的汇编代码为例
实际上机器执行add $0x28,%rsp时,是在执行机器码48 83 c4 28,而机器在执行retq时,实际上是在执行机器码c3。
这能给我们带来什么启发?
想一想,如果你输入getbuf()里面是48 83 c4 28 c3,然后覆盖返回地址成栈顶的地址,也就是说返回后,cpu从栈顶处读指令,这会发生什么?
没错,由于栈顶是我们输入的48 83 c4 28 c3,所以机器会当指令来读,接下来就会执行add $0x28,%rsp和retq。
顺着这个思路,我们可以找到解决touch2()的一丝线索。
我们输入能把cookie的值复制到%rdi中的机器码,附加个能返回到touch2()的机器码,之后覆写初始返回地址到栈顶即可。
看不懂也没关系,看看我下边的操作就懂了。
0x4017ec是我touch2()的地址
0x59b997fa是我的cookie值。
新建个InjectCode.s,里面写上我的注入代码
第一行很好理解,为什么第二第三行结合起来就能返回到地址0x4017ec呢?其实很简单,因为函数执行前调用函数者就是在push自己的地址进栈,Call完函数之后就能返回到自己这里,我们模仿这步而已。
现在就是要把这些汇编代码转成机器码,用
gcc -c InjectCode.s
然后会生成目标代码InjectCode.o,我们再反汇编这个代码就能看到机器码了,用
objdump -d InjectCode.o > InjectCode.txt
打开InjectCode.txt,我们就能看到机器码了
之后就是把机器码输入到栈顶,40个字符的后面覆盖的地址是覆盖成栈顶的地址,我这里用gdb查看到getbuf时的栈顶地址是0x5561dc78,你们的可能不一样,自行查看。
这里新建了个hex2.txt,输入攻击字符串的十六进制
输入到ctarget,过了
如果不过,红圈那里就会是FAIL,自己再接再厉吧。
Touch3(Level3)
如果前面两个Touch的题解有好好看的话,那么这个应该也是可以完全独立解决的,从这里开始并没有什么新操作。
这一关是要把cookie的字符串形式作为参数输入函数。大家可以看看hexmatch函数,里面有个随机,这个随机导致我们不能直接用s的地址来直接作为函数参数,所以我们得老实自己构造cookie字符串的十六进制,然后把首地址给%rdi即可。
0x4018fa是我这里touch3()函数的地址
我cookie的字符串的16进制形式是
0x35 0x39 0x62 0x39 0x39 0x37 0x66 0x61
如果我把他放到了栈首地址往下偏移0x10处,我的getbuff栈地址是0x5561dc78,很容易计算得到我构造的字符串的首地址就是0x5561dc78+0x10 = 0x5561dc88,我把这个地址复制到%rdi,汇编代码就这么写
得到的机器代码是
写成16进制的攻击字符串就是
然而,攻击失败
其实思路没有问题,只是忽略了一个细节,就是由于touch3()函数里面有对栈的操作,栈里面内容可能会被改变,直接gdb给大家演示看更快
我在一进入touch3()函数那里就设了个断点,就是图中红点标记处
碰到断点后,查看getbuff()栈顶附近的值,发现里面确实是我们输进去的数据没错
我在hexmatch()函数里又设了个断点,是在进行字符串比较的前一句汇编代码处打的,红点处就是
跑到这里后,我们再查看getbuff()栈顶附近的值
发现里面的值变得几乎面目全非了。
怎么办呢?
其实我们把cookie字符串放到不会改变的地方就行了,对比下,发现0x5561dcb8那里就没变化,一直都是0x00000000,我们可以试着放在那里
改地址的话,其实只需要对机器码做个小调整,就是上面小红圈处从88改成b8就行,还有下面大家可以看到我放了好几个cookie串,打叉处是我曾经试错失败的地方,最后打勾处是我最后成功的地方,因为是覆写,所以我留着之前的也不影响,就当做是记录了。
最后,成功