CSAPPlab3 attacklab通关记录
本文记录了完成attacklab的全流程,编写的辅助信息均在GitHub。
一些重要注意事项:
- 小端序
- 使用-q启动ctarget/rtarget,因为是自学学生
ctarget
Level 1
test调用getbuf,读汇编发现栈上开辟0x28空间,将这些空间填满后再填入touch1的地址(8字节)就好了,通过反汇编获得touch1的第一条指令地址。注意地址要小端序。(信息按字节存储,每个字节内部顺序不变,字节之间逆序)
Level 2
和第一题的不同点在于调用touch2需要传递一个参数,而我们知道第一个参数放在rdi中,于是思路显然:
- 向栈中注入代码,代码的效果是将rdi赋值为cookie,然后ret。
- test -> getbuf,故getbuf返回,弹出test栈帧中最下方的返回值,覆盖这个返回值为注入代码的地址就调用了我们注入的代码
- 注入代码执行完赋值操作后,执行ret,再弹栈顶地址,将这个地址赋值为touch2的第一条指令的地址即可
但这里会出现栈指针rsp没有16字节对齐的bug。具体的科普可以看这个老哥的文章:(1 封私信 / 23 条消息) CTFer成长日记12:栈对齐—获取shell前的临门一脚 - 知乎,分析的很清晰了,其他人要么没发现错误,要么解释的不好。
最后的解决方案里需要在栈代码的地址和touch2的地址中间加一个ret的地址。
Level 3
和上一题的不同点在于,test -> getbuf -> touch3 -> hexmatch,hexmatch会使用栈空间,像上一题那样注入代码到缓冲区中的想法会被hexmatch覆盖。
既然往缓冲区注入会被覆盖,那就往更高的地方注入,在更高地址的栈空间为所欲为嘿嘿嘿。毕竟只要执行我们需要的函数就可以,对其他函数的栈区可以随意搞破坏。
1 | /* 0x5561dcc0 字符串内容 |
rtarget
Level 2
核心思想是利用现存代码构造自己的rop链。以及也许现存代码中没有恰好的指令,但是某些指令的一部分可以构成我们需要的指令。
需要把%rdi设为cookie值然后调用touch2。想直接找到 48 c7 c7 fa 97 b9 59 c3 mov $0x59b997fa, %rdi这么完美的指令比较难。根据提示可以用popq,先pop给一个寄存器,然后把这个寄存器的值复制到%rdi即可。在farm中浏览一下,发现rax符合要求。
构造结果如下:
1 | 00 00 00 00 00 00 00 00 00 00 |
进入这个rop链前,rsp为栈中第一个地址,即pop地址,pc指向原本的ret。执行ret,rsp指向栈中第二个地址,pc指向pop的地址。执行pop,rsp指向栈中第三个地址,pc指向ret。执行ret,rsp指向栈中第四个地址,pc指向mov的地址……
Level 3
与ctarget的Level3相比,不同点在于不能向栈上注入代码以及栈地址随机化了。目标是没变的,找个地方存储字符串,把字符串指针的值赋给%rdi,最后调用touch3。
首先考虑往哪放字符串,肯定还是栈上的高地址(低地址会被后续流程覆盖),在构造的所有rop地址更上面一个位置。也考虑过其他位置,但是其他地方写不进去呀,程序中也没有直接保存这个字符串。
随之而来的问题是如何找到字符串的地址。由于栈地址随机化,每次运行时栈的地址都不一样。但是相对地址是不变的。也就是说栈顶进入rop链的地址,和高处存储字符串的地址的差值,是固定的。获得最初的栈顶地址加上一个自己构造的偏移即是字符串地址。至于偏移量具体是多少,这由我们构造的rop链具体使用了多少栈空间来决定,先把所有gadgets拼好才能知道具体偏移量的值。
这里涉及到了加法操作,要么是addq要么是lea来完成,在farm中发现了唯一的lea指令!太伟大了!lea (%rdi, %rsi, 1), %rax,以这条指令为媒介,执行它之后需要 将%rax移动到%rdi中,执行它之前需要把初始栈顶地址以及偏移量放进%rdi %rsi中,具体对应关系还不确定。接下来就是搜寻合适的指令了,观察发现movq相比movl的指令编码只是多了一字节的前缀,所以搜索movl的更为快捷。最后构造的payload如下:
栈顶地址:rsp -> rax -> rdi(这条链初始数据来自rsp,可以从头往尾找)
偏移量:栈中 -> rax -> edx -> ecx -> esi,(这条链从头往尾找太繁琐了,可能性太多,确定偏移量放在rsi,所以从尾往头找)
1 | 00 00 00 00 00 00 00 00 00 00 |
后记
从栈对齐问题中学到,面对一个问题时,直接面向问题的本质而不是周围的细枝末节。一个continue直接到出错的指令上,然后针对这个指令想办法,比完整模拟一遍函数运行有效率的多。
从已知出发和从问题出发,也可以简化为从头出发和尾出发,解决问题的两个法宝还是发挥着重要作用。