什么是溢出?
简单的说,溢出就是越界,跑到不属于自己的领地里去了...如:
overflow.c:
main()
{
int a[10] = {0};
int i;
for (i = -10; i < 20; i++)
printf("%d ",a[i]);
}
由于C的自由书写风格,编译系统并不对数据边界进行检测,
导致有意无意的出现溢出。
复杂点的例子:
overflowC.c:
#include <stdio.h>
#include <stdlib.h>
int
main (int argc, char *argv[])
{
int value = 5;
char buffer_one[8],buffer_two[8];
strcpy(buffer_one,"one");
strcpy(buffer_two,"two");
printf("[BEFORE] buffer_two is at %p, contains %s\n",buffer_two, buffer_two);
printf("[BEFORE] buffer_one is at %p, contains %s\n",buffer_one, buffer_one);
printf("[BEFORE] value is at %p, contains %d(0x%08x)\n",&value, value, value);
printf("[STRCPY] copying %d bytes into buffer_two\n",strlen(argv[1]));
strcpy(buffer_two, argv[1]);
printf("[AFTER] buffer_two is at %p, contains %s\n",buffer_two, buffer_two);
printf("[AFTER] buffer_one is at %p, contains %s\n",buffer_one, buffer_one);
printf("[AFTER] value is at %p, contains %d(0x%08x)\n",&value, value, value);
return 0;
}
一次运行结果:
root@linux-t0jw:~/桌面> ./overflow 1234567890
[BEFORE] buffer_two is at 0xbfccc5bc, contains two
[BEFORE] buffer_one is at 0xbfccc5c4, contains one
[BEFORE] value is at 0xbfccc5cc, contains 5(0x00000005)
[STRCPY] copying 10 bytes into buffer_two
[AFTER] buffer_two is at 0xbfccc5bc, contains 1234567890
[AFTER] buffer_one is at 0xbfccc5c4, contains 90
[AFTER] value is at 0xbfccc5cc, contains 5(0x00000005)
分析:buffer_two 和 buffer_one相差0xc4-0xbc=8个单元,
而复制10个字节,导致8个字节的缓冲区不足,溢出到了buffer_one中...
再次运行:
root@linux-t0jw:~/桌面> ./overflow AAAAAAAAAAAAAAAAAA
[BEFORE] buffer_two is at 0xbf9b60bc, contains two
[BEFORE] buffer_one is at 0xbf9b60c4, contains one
[BEFORE] value is at 0xbf9b60cc, contains 5(0x00000005)
[STRCPY] copying 18 bytes into buffer_two
[AFTER] buffer_two is at 0xbf9b60bc, contains AAAAAAAAAAAAAAAAAA
[AFTER] buffer_one is at 0xbf9b60c4, contains AAAAAAAAAA
[AFTER] value is at 0xbf9b60cc, contains 16705(0x00004141)
分析:增加参数长度,会发现甚至溢出到了value(0x41是A的ASCII值)...
再次运行:
root@linux-t0jw:~/桌面> ./overflow AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
[BEFORE] buffer_two is at 0xbff6c3ec, contains two
[BEFORE] buffer_one is at 0xbff6c3f4, contains one
[BEFORE] value is at 0xbff6c3fc, contains 5(0x00000005)
[STRCPY] copying 91 bytes into buffer_two
[AFTER] buffer_two is at 0xbff6c3ec, contains AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
[AFTER] buffer_one is at 0xbff6c3f4, contains AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
[AFTER] value is at 0xbff6c3fc, contains 1094795585(0x41414141)
段错误
root@linux-t0jw:~/桌面> echo $?
139
最终,会引发一个段错误...
最经典的例子:
overflowclassic:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int check_password(char *password)
{
int flag = 0;
char password_buffer[16];
strcpy(password_buffer, password);
if (strcmp(password_buffer, "brillig") == 0)
flag = 1;
if (strcmp(password_buffer, "outgrabe") == 0)
flag = 1;
return flag;
}
int
main (int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "Usage %s <password>\n", argv[0]);
exit(1);
}
if (check_password(argv[1])) {
printf("----------\n");
printf("Access Granted.\n");
printf("----------\n");
} else {
printf("\nAccess Denied.\n");
}
return 0;
}
运行结果:
root@linux-t0jw:~/桌面> ./overflowclassic brillig
----------
Access Granted.
----------
root@linux-t0jw:~/桌面> ./overflowclassic okok
Access Denied.
root@linux-t0jw:~/桌面> ./overflowclassic AAAAAAAAAAAAAAAAAAAAAAA
----------
Access Granted.
----------
加入调试信息,重新编译运行:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int check_password(char *password)
{
int flag = 0;
char password_buffer[16];
strcpy(password_buffer, password);
printf("flag is at %p contains %d \n",&flag, flag);
printf("buffer is at %p contains %s \n",password_buffer, password_buffer);
if (strcmp(password_buffer, "brillig") == 0)
flag = 1;
if (strcmp(password_buffer, "outgrabe") == 0)
flag = 1;
return flag;
}
int
main (int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "Usage %s <password>\n", argv[0]);
exit(1);
}
if (check_password(argv[1])) {
printf("----------\n");
printf("Access Granted.\n");
printf("----------\n");
} else {
printf("\nAccess Denied.\n");
}
return 0;
}
root@linux-t0jw:~/桌面> gcc -Wall overflow.c -o overflowclassic
root@linux-t0jw:~/桌面> ./overflowclassic AAAAAAAAAAAAAAAAAAAAAAA
flag is at 0xbfa3895c contains 1094795585
buffer is at 0xbfa3894c contains AAAAAAAAAAAAAAAAAAAAAAA
----------
Access Granted.
----------
可知:flag和password_buffer相差16的字节,最后flag可怜被溢出的数据覆盖了...
使用以下调试过程可以更清晰地看出:
root@linux-t0jw:~/桌面> gcc -Wall -g overflow.c -o overflowclassic
root@linux-t0jw:~/桌面> gdb -q ./overflowclassic
Reading symbols from /home/ly/桌面/overflowclassic...done.
(gdb) list 1
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4
5 int check_password(char *password)
6 {
7 int flag = 0;
8 char password_buffer[16];
9
10 strcpy(password_buffer, password);
(gdb)
11
12 printf("flag is at %p contains %d \n",&flag, flag);
13 printf("buffer is at %p contains %s \n",password_buffer, password_buffer);
14 if (strcmp(password_buffer, "brillig") == 0)
15 flag = 1;
16 if (strcmp(password_buffer, "outgrabe") == 0)
17 flag = 1;
18 return flag;
19 }
20
(gdb) break 10
Breakpoint 1 at 0x8048551: file overflow.c, line 10.
(gdb) break 18
Breakpoint 2 at 0x80485d5: file overflow.c, line 18.
(gdb) run AAAAAAAAAAAAAAAAAAAAAAA
Starting program: /home/ly/桌面/overflowclassic AAAAAAAAAAAAAAAAAAAAAAA
Missing separate debuginfo for /lib/ld-linux.so.2
Try: zypper install -C "debuginfo(build-id)=b6b00f5560b849cf9fac5e6efb9f403c21f508dd"
Missing separate debuginfo for /lib/libc.so.6
Try: zypper install -C "debuginfo(build-id)=6478c346f66a284b77eb5ca82ab8f2f4f9561600"
Breakpoint 1, check_password (password=0xbffff3a4 'A' <repeats 23 times>)
at overflow.c:10
10 strcpy(password_buffer, password);
(gdb) x/s password_buffer
0xbffff07c: "\370\203\004\b\310\360\377\277\364\237\004\b\002"
(gdb) x/x &flag
0xbffff08c: 0x00
(gdb) print 0xbffff08c - 0xbffff07c
$1 = 16
(gdb) x/16xw password_buffer
0xbffff07c: 0x080483f8 0xbffff0c8 0x08049ff4 0x00000002
0xbffff08c: 0x00000000 0x08048490 0x00000000 0xbffff0b8
0xbffff09c: 0x08048624 0xbffff3a4 0xb7e78b45 0x08048679
0xbffff0ac: 0xb7fb2ff4 0x08048670 0x00000000 0x00000000
(gdb) continue
Continuing.
flag is at 0xbffff08c contains 1094795585
buffer is at 0xbffff07c contains AAAAAAAAAAAAAAAAAAAAAAA
Breakpoint 2, check_password (password=0xbffff3a4 'A' <repeats 23 times>)
at overflow.c:18
18 return flag;
(gdb) x/16xw password_buffer
0xbffff07c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff08c: 0x41414141 0x00414141 0x00000000 0xbffff0b8
0xbffff09c: 0x08048624 0xbffff3a4 0xb7e78b45 0x08048679
0xbffff0ac: 0xb7fb2ff4 0x08048670 0x00000000 0x00000000
(gdb) x/x &flag
0xbffff08c: 0x41414141
(gdb) x/dw &flag
0xbffff08c: 1094795585
(gdb)
最后显示,flag的值被覆盖了...
如果对以上代码改动一下:
以反序定义password_buffer和flag
reverse.c:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int check_password(char *password)
{
char password_buffer[16];
int flag = 0;
strcpy(password_buffer, password);
printf("flag is at %p contains %d \n",&flag, flag);
printf("buffer is at %p contains %s \n",password_buffer, password_buffer);
if (strcmp(password_buffer, "brillig") == 0)
flag = 1;
if (strcmp(password_buffer, "outgrabe") == 0)
flag = 1;
return flag;
}
int
main (int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "Usage %s <password>\n", argv[0]);
exit(1);
}
if (check_password(argv[1])) {
printf("----------\n");
printf("Access Granted.\n");
printf("----------\n");
} else {
printf("\nAccess Denied.\n");
}
return 0;
}
运行结果:
root@linux-t0jw:~/桌面> ./overflowclassic AAAAAAAAAAAAAAAAAAAAAAA111111111111111111111111111111
flag is at 0xbfb9057c contains 0
buffer is at 0xbfb90580 contains AAAAAAAAAAAAAAAAAAAAAAA111111111111111111111111111111
段错误
会发现flag 位于password_buffer之前(堆栈是由高到低扩展),从而无法被覆盖掉...
但是会溢出到返回地址ret的单元里,从而导致段错误
通过gdb反汇编调试可以得到更多的内容。
PS:
对以上验证密码程序可以使用strncpy等安全的函数,避免使用gets,strcpy等不安全的函数,此外,对数组边界的检查也要格外小心。
修改如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int check_password(char *password)
{
char password_buffer[16];
int flag = 1;
strncpy(password_buffer,password,15); //strncpy()
printf("flag is at %p contains %d \n",&flag, flag);
printf("buffer is at %p contains %s \n",password_buffer, password_buffer);
if (strcmp(password_buffer, "brillig") == 0)
flag = 0;
if (strcmp(password_buffer, "outgrabe") == 0)
flag = 0;
return flag;
}
int
main (int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "Usage %s <password>\n", argv[0]);
exit(1);
}
if (check_password(argv[1]) == 0) {
printf("----------\n");
printf("Access Granted.\n");
printf("----------\n");
} else {
printf("\nAccess Denied.\n");
}
return 0;
}
简单的说,溢出就是越界,跑到不属于自己的领地里去了...如:
overflow.c:
main()
{
int a[10] = {0};
int i;
for (i = -10; i < 20; i++)
printf("%d ",a[i]);
}
由于C的自由书写风格,编译系统并不对数据边界进行检测,
导致有意无意的出现溢出。
复杂点的例子:
overflowC.c:
#include <stdio.h>
#include <stdlib.h>
int
main (int argc, char *argv[])
{
int value = 5;
char buffer_one[8],buffer_two[8];
strcpy(buffer_one,"one");
strcpy(buffer_two,"two");
printf("[BEFORE] buffer_two is at %p, contains %s\n",buffer_two, buffer_two);
printf("[BEFORE] buffer_one is at %p, contains %s\n",buffer_one, buffer_one);
printf("[BEFORE] value is at %p, contains %d(0x%08x)\n",&value, value, value);
printf("[STRCPY] copying %d bytes into buffer_two\n",strlen(argv[1]));
strcpy(buffer_two, argv[1]);
printf("[AFTER] buffer_two is at %p, contains %s\n",buffer_two, buffer_two);
printf("[AFTER] buffer_one is at %p, contains %s\n",buffer_one, buffer_one);
printf("[AFTER] value is at %p, contains %d(0x%08x)\n",&value, value, value);
return 0;
}
一次运行结果:
root@linux-t0jw:~/桌面> ./overflow 1234567890
[BEFORE] buffer_two is at 0xbfccc5bc, contains two
[BEFORE] buffer_one is at 0xbfccc5c4, contains one
[BEFORE] value is at 0xbfccc5cc, contains 5(0x00000005)
[STRCPY] copying 10 bytes into buffer_two
[AFTER] buffer_two is at 0xbfccc5bc, contains 1234567890
[AFTER] buffer_one is at 0xbfccc5c4, contains 90
[AFTER] value is at 0xbfccc5cc, contains 5(0x00000005)
分析:buffer_two 和 buffer_one相差0xc4-0xbc=8个单元,
而复制10个字节,导致8个字节的缓冲区不足,溢出到了buffer_one中...
再次运行:
root@linux-t0jw:~/桌面> ./overflow AAAAAAAAAAAAAAAAAA
[BEFORE] buffer_two is at 0xbf9b60bc, contains two
[BEFORE] buffer_one is at 0xbf9b60c4, contains one
[BEFORE] value is at 0xbf9b60cc, contains 5(0x00000005)
[STRCPY] copying 18 bytes into buffer_two
[AFTER] buffer_two is at 0xbf9b60bc, contains AAAAAAAAAAAAAAAAAA
[AFTER] buffer_one is at 0xbf9b60c4, contains AAAAAAAAAA
[AFTER] value is at 0xbf9b60cc, contains 16705(0x00004141)
分析:增加参数长度,会发现甚至溢出到了value(0x41是A的ASCII值)...
再次运行:
root@linux-t0jw:~/桌面> ./overflow AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
[BEFORE] buffer_two is at 0xbff6c3ec, contains two
[BEFORE] buffer_one is at 0xbff6c3f4, contains one
[BEFORE] value is at 0xbff6c3fc, contains 5(0x00000005)
[STRCPY] copying 91 bytes into buffer_two
[AFTER] buffer_two is at 0xbff6c3ec, contains AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
[AFTER] buffer_one is at 0xbff6c3f4, contains AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
[AFTER] value is at 0xbff6c3fc, contains 1094795585(0x41414141)
段错误
root@linux-t0jw:~/桌面> echo $?
139
最终,会引发一个段错误...
最经典的例子:
overflowclassic:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int check_password(char *password)
{
int flag = 0;
char password_buffer[16];
strcpy(password_buffer, password);
if (strcmp(password_buffer, "brillig") == 0)
flag = 1;
if (strcmp(password_buffer, "outgrabe") == 0)
flag = 1;
return flag;
}
int
main (int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "Usage %s <password>\n", argv[0]);
exit(1);
}
if (check_password(argv[1])) {
printf("----------\n");
printf("Access Granted.\n");
printf("----------\n");
} else {
printf("\nAccess Denied.\n");
}
return 0;
}
运行结果:
root@linux-t0jw:~/桌面> ./overflowclassic brillig
----------
Access Granted.
----------
root@linux-t0jw:~/桌面> ./overflowclassic okok
Access Denied.
root@linux-t0jw:~/桌面> ./overflowclassic AAAAAAAAAAAAAAAAAAAAAAA
----------
Access Granted.
----------
加入调试信息,重新编译运行:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int check_password(char *password)
{
int flag = 0;
char password_buffer[16];
strcpy(password_buffer, password);
printf("flag is at %p contains %d \n",&flag, flag);
printf("buffer is at %p contains %s \n",password_buffer, password_buffer);
if (strcmp(password_buffer, "brillig") == 0)
flag = 1;
if (strcmp(password_buffer, "outgrabe") == 0)
flag = 1;
return flag;
}
int
main (int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "Usage %s <password>\n", argv[0]);
exit(1);
}
if (check_password(argv[1])) {
printf("----------\n");
printf("Access Granted.\n");
printf("----------\n");
} else {
printf("\nAccess Denied.\n");
}
return 0;
}
root@linux-t0jw:~/桌面> gcc -Wall overflow.c -o overflowclassic
root@linux-t0jw:~/桌面> ./overflowclassic AAAAAAAAAAAAAAAAAAAAAAA
flag is at 0xbfa3895c contains 1094795585
buffer is at 0xbfa3894c contains AAAAAAAAAAAAAAAAAAAAAAA
----------
Access Granted.
----------
可知:flag和password_buffer相差16的字节,最后flag可怜被溢出的数据覆盖了...
使用以下调试过程可以更清晰地看出:
root@linux-t0jw:~/桌面> gcc -Wall -g overflow.c -o overflowclassic
root@linux-t0jw:~/桌面> gdb -q ./overflowclassic
Reading symbols from /home/ly/桌面/overflowclassic...done.
(gdb) list 1
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4
5 int check_password(char *password)
6 {
7 int flag = 0;
8 char password_buffer[16];
9
10 strcpy(password_buffer, password);
(gdb)
11
12 printf("flag is at %p contains %d \n",&flag, flag);
13 printf("buffer is at %p contains %s \n",password_buffer, password_buffer);
14 if (strcmp(password_buffer, "brillig") == 0)
15 flag = 1;
16 if (strcmp(password_buffer, "outgrabe") == 0)
17 flag = 1;
18 return flag;
19 }
20
(gdb) break 10
Breakpoint 1 at 0x8048551: file overflow.c, line 10.
(gdb) break 18
Breakpoint 2 at 0x80485d5: file overflow.c, line 18.
(gdb) run AAAAAAAAAAAAAAAAAAAAAAA
Starting program: /home/ly/桌面/overflowclassic AAAAAAAAAAAAAAAAAAAAAAA
Missing separate debuginfo for /lib/ld-linux.so.2
Try: zypper install -C "debuginfo(build-id)=b6b00f5560b849cf9fac5e6efb9f403c21f508dd"
Missing separate debuginfo for /lib/libc.so.6
Try: zypper install -C "debuginfo(build-id)=6478c346f66a284b77eb5ca82ab8f2f4f9561600"
Breakpoint 1, check_password (password=0xbffff3a4 'A' <repeats 23 times>)
at overflow.c:10
10 strcpy(password_buffer, password);
(gdb) x/s password_buffer
0xbffff07c: "\370\203\004\b\310\360\377\277\364\237\004\b\002"
(gdb) x/x &flag
0xbffff08c: 0x00
(gdb) print 0xbffff08c - 0xbffff07c
$1 = 16
(gdb) x/16xw password_buffer
0xbffff07c: 0x080483f8 0xbffff0c8 0x08049ff4 0x00000002
0xbffff08c: 0x00000000 0x08048490 0x00000000 0xbffff0b8
0xbffff09c: 0x08048624 0xbffff3a4 0xb7e78b45 0x08048679
0xbffff0ac: 0xb7fb2ff4 0x08048670 0x00000000 0x00000000
(gdb) continue
Continuing.
flag is at 0xbffff08c contains 1094795585
buffer is at 0xbffff07c contains AAAAAAAAAAAAAAAAAAAAAAA
Breakpoint 2, check_password (password=0xbffff3a4 'A' <repeats 23 times>)
at overflow.c:18
18 return flag;
(gdb) x/16xw password_buffer
0xbffff07c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff08c: 0x41414141 0x00414141 0x00000000 0xbffff0b8
0xbffff09c: 0x08048624 0xbffff3a4 0xb7e78b45 0x08048679
0xbffff0ac: 0xb7fb2ff4 0x08048670 0x00000000 0x00000000
(gdb) x/x &flag
0xbffff08c: 0x41414141
(gdb) x/dw &flag
0xbffff08c: 1094795585
(gdb)
最后显示,flag的值被覆盖了...
如果对以上代码改动一下:
以反序定义password_buffer和flag
reverse.c:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int check_password(char *password)
{
char password_buffer[16];
int flag = 0;
strcpy(password_buffer, password);
printf("flag is at %p contains %d \n",&flag, flag);
printf("buffer is at %p contains %s \n",password_buffer, password_buffer);
if (strcmp(password_buffer, "brillig") == 0)
flag = 1;
if (strcmp(password_buffer, "outgrabe") == 0)
flag = 1;
return flag;
}
int
main (int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "Usage %s <password>\n", argv[0]);
exit(1);
}
if (check_password(argv[1])) {
printf("----------\n");
printf("Access Granted.\n");
printf("----------\n");
} else {
printf("\nAccess Denied.\n");
}
return 0;
}
运行结果:
root@linux-t0jw:~/桌面> ./overflowclassic AAAAAAAAAAAAAAAAAAAAAAA111111111111111111111111111111
flag is at 0xbfb9057c contains 0
buffer is at 0xbfb90580 contains AAAAAAAAAAAAAAAAAAAAAAA111111111111111111111111111111
段错误
会发现flag 位于password_buffer之前(堆栈是由高到低扩展),从而无法被覆盖掉...
但是会溢出到返回地址ret的单元里,从而导致段错误
通过gdb反汇编调试可以得到更多的内容。
PS:
对以上验证密码程序可以使用strncpy等安全的函数,避免使用gets,strcpy等不安全的函数,此外,对数组边界的检查也要格外小心。
修改如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int check_password(char *password)
{
char password_buffer[16];
int flag = 1;
strncpy(password_buffer,password,15); //strncpy()
printf("flag is at %p contains %d \n",&flag, flag);
printf("buffer is at %p contains %s \n",password_buffer, password_buffer);
if (strcmp(password_buffer, "brillig") == 0)
flag = 0;
if (strcmp(password_buffer, "outgrabe") == 0)
flag = 0;
return flag;
}
int
main (int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "Usage %s <password>\n", argv[0]);
exit(1);
}
if (check_password(argv[1]) == 0) {
printf("----------\n");
printf("Access Granted.\n");
printf("----------\n");
} else {
printf("\nAccess Denied.\n");
}
return 0;
}