不安全的strcat()所引出的缓冲区溢出

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-3objdump -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中直观地对比一下两段代码:
main注意到第二段代码中的第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即可。

猜你喜欢

转载自blog.csdn.net/u013213111/article/details/88397894
今日推荐