做The C Programming Language中的习题5-3时,程序跑出了个 stack smashing detected,于是探究一下。
最初出现stack smashing detected的代码是这样的:
#include <stdio.h>
void strcats(char *s, char *t);
int main()
{
char s1[] = "Hello, ";
char t1[] = "world!";
strcats(s1, s2);
return 0;
}
void strcats(char *s, char *t)
{
while (*s)
s++;
while (*s++ = *t++);
}
调试发现,当char s1[] = "H, "; char t1[] = "w!"
时就会出现溢出。
将程序中s1和t1改为char s1[] = "H, "; char t1[] = "w!"
。
禁用gcc的堆栈保护gcc -fno-stack-protector -g ex5-3.c -o ex5-3
,objdump -d ./ex5-3
得到汇编代码如下:
080483db <main>:
80483db: 8d 4c 24 04 lea 0x4(%esp),%ecx
80483df: 83 e4 f0 and $0xfffffff0,%esp
80483e2: ff 71 fc pushl -0x4(%ecx)
80483e5: 55 push %ebp
80483e6: 89 e5 mov %esp,%ebp
80483e8: 51 push %ecx
80483e9: 83 ec 14 sub $0x14,%esp
80483ec: c7 45 f4 48 2c 20 00 movl $0x202c48,-0xc(%ebp)
80483f3: 66 c7 45 f1 77 21 movw $0x2177,-0xf(%ebp)
80483f9: c6 45 f3 00 movb $0x0,-0xd(%ebp)
80483fd: 83 ec 08 sub $0x8,%esp
8048400: 8d 45 f1 lea -0xf(%ebp),%eax
8048403: 50 push %eax
8048404: 8d 45 f4 lea -0xc(%ebp),%eax
8048407: 50 push %eax
8048408: e8 10 00 00 00 call 804841d <strcats>
804840d: 83 c4 10 add $0x10,%esp
8048410: b8 00 00 00 00 mov $0x0,%eax
8048415: 8b 4d fc mov -0x4(%ebp),%ecx
8048418: c9 leave
8048419: 8d 61 fc lea -0x4(%ecx),%esp
804841c: c3 ret
再开启gcc堆栈保护gcc -fstack-protector-all -g ex5-3.c -o ex5-3
,得到汇编代码:
0804843b <main>:
804843b: 8d 4c 24 04 lea 0x4(%esp),%ecx
804843f: 83 e4 f0 and $0xfffffff0,%esp
8048442: ff 71 fc pushl -0x4(%ecx)
8048445: 55 push %ebp
8048446: 89 e5 mov %esp,%ebp
8048448: 51 push %ecx
8048449: 83 ec 14 sub $0x14,%esp
804844c: 65 a1 14 00 00 00 mov %gs:0x14,%eax #!!!!!!
8048452: 89 45 f4 mov %eax,-0xc(%ebp) #!!!!!!
8048455: 31 c0 xor %eax,%eax
8048457: c7 45 f0 48 2c 20 00 movl $0x202c48,-0x10(%ebp)
804845e: 66 c7 45 ed 77 21 movw $0x2177,-0x13(%ebp)
8048464: c6 45 ef 00 movb $0x0,-0x11(%ebp)
8048468: 83 ec 08 sub $0x8,%esp
804846b: 8d 45 ed lea -0x13(%ebp),%eax
804846e: 50 push %eax
804846f: 8d 45 f0 lea -0x10(%ebp),%eax
8048472: 50 push %eax
8048473: e8 21 00 00 00 call 8048499 <strcats>
8048478: 83 c4 10 add $0x10,%esp
804847b: b8 00 00 00 00 mov $0x0,%eax
8048480: 8b 55 f4 mov -0xc(%ebp),%edx #!!!!!
8048483: 65 33 15 14 00 00 00 xor %gs:0x14,%edx #!!!!!
804848a: 74 05 je 8048491 <main+0x56> #!!!!!
804848c: e8 7f fe ff ff call 8048310 <__stack_chk_fail@plt>#!
8048491: 8b 4d fc mov -0x4(%ebp),%ecx
8048494: c9 leave
8048495: 8d 61 fc lea -0x4(%ecx),%esp
8048498: c3 ret
在vi中直观地对比一下两段代码:
注意到第二段代码中的第9-11行,是第一段代码中没有的:
804844c: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8048452: 89 45 f4 mov %eax,-0xc(%ebp)
8048455: 31 c0 xor %eax,%eax
以及第23-26行:
8048480: 8b 55 f4 mov -0xc(%ebp),%edx
8048483: 65 33 15 14 00 00 00 xor %gs:0x14,%edx
804848a: 74 05 je 8048491 <main+0x56>
804848c: e8 7f fe ff ff call 8048310 <__stack_chk_fail@plt>
来自 Canary绕过之__stack_chk_fail劫持这篇文章的解释:
在函数开始时,会取gs:0x14处的值,并放在%ebp-0xc的地方(mov %gs:0x14,%eax, mov %eax,-0xc(%ebp)),在程序结束时,会将该值取出,并与gs:0x14的值进行抑或(mov -0xc(%ebp),%eax,xor %gs:0x14,%eax),如果抑或的结果为0,说明canary未被修改,程序会正常结束,反之如果抑或结果不为0,说明canary已经被非法修改,存在攻击行为,此时程序流程会走到__stack_chk_fail,从而终止程序。
至于为什么canary取gs:0x14处的值,可以参考这篇:
GCC栈溢出保护
下面来分析一下开启了堆栈保护时main函数的栈,0xbfffef58是ebp,而%ebp-0xc的地方即0xbfffef4c就是放canary的地方,从epb至canary中间这段是用来保护ebp吗?继续往下看,0xbfffef48是s1,数组长度是4,也就是占据了0xbfffef48至0xbfffef4b之间的内存,同理t1所占的内存应该是0xbfffef45至0xbfffef47,esp的位置则在0xbfffef40:
Breakpoint 1, main () at ex5-3.c:11
11 strcats(s1, t1);
(gdb) info r ebp
ebp 0xbfffef58 0xbfffef58
(gdb) info r esp
esp 0xbfffef40 0xbfffef40
(gdb) p &s1
$1 = (char (*)[4]) 0xbfffef48
(gdb) p &t1
$2 = (char (*)[3]) 0xbfffef45
假如禁用堆栈保护是什么样呢?ebp和esp的位置没有改变,而s1则“上移”了4个内存单元,占据了0xbfffef4c至0xbfffef4f的位置,t1也随之“上移”,占据0xbfffef49至0xbfffef4b的位置。所以canary只占据4个内存单元?用来干啥呢?ebp和canary之间的内存又放了啥?@-@
Breakpoint 1, main () at ex5-3.c:11
11 strcats(s1, t1);
(gdb) info r ebp
ebp 0xbfffef58 0xbfffef58
(gdb) info r esp
esp 0xbfffef40 0xbfffef40
(gdb) p &s1
$1 = (char (*)[4]) 0xbfffef4c
(gdb) p &t1
$2 = (char (*)[3]) 0xbfffef49
开启堆栈保护,在调用strcats函数前,内存的值是这样的:从0xbfffef45至0xbfffef4b依次是77(‘w’),21(’!’),00(’\0’),48(‘H’),2c(’,’),20(空格),00(’\0’),与t1和s1对应。canary的值,即0xbfffef4c为0x00。
Breakpoint 1, main () at ex5-3.c:11
11 strcats(s1, t1);
(gdb) x/20xb $sp
0xbfffef40: 0x01 0x00 0x00 0x00 0x04 0x77 0x21 0x00
0xbfffef48: 0x48 0x2c 0x20 0x00 0x00 0xa3 0xce 0xe2
0xbfffef50: 0xdc 0x83 0xfb 0xb7
调用了strcats函数后,0xbfffef4b至0xbfffef4d明显被修改,依次变为77(‘w’),21(’!’)和00(’\0’)。canary的值变了,因此就会有 stack smashing detected 。
Breakpoint 2, main () at ex5-3.c:13
13 return 0;
(gdb) x/20xb $sp
0xbfffef40: 0x01 0x00 0x00 0x00 0x04 0x77 0x21 0x00
0xbfffef48: 0x48 0x2c 0x20 0x77 0x21 0x00 0xce 0xe2
0xbfffef50: 0xdc 0x83 0xfb 0xb7
多次测试发现,s1和t1中的字符串取不同的值时不一定会 出现stack smashing detected ,调出内存来看canary确实没有变,因为存放s1和t1的内存恰好不会导致canary改变,至于s1和t1放在哪里由谁决定,暂时放过这个问题吧。
P.S.还有几个使用gdb的小技巧:
1.disas/s
,加上/s
这个参数可以吧源代码和汇编代码一起打印出来。
2.b *main
在main函数的入口处(0x0804843b <+0>: lea 0x4(%esp),%ecx
)设置断点,如果想在某条汇编指令处设置断点,例如0x0804844c <+17>: mov %gs:0x14,%eax
处,就用b *main+17
即可。