最近结合软件安全课程上学习的理论知识和网络资料,对缓冲区溢出漏洞的简单原理和利用技巧进行了一定的了解。这里主要记录笔者通过简单的示例程序实现缓冲区溢出漏洞利用的步骤,按由简至繁的顺序,依次描述简单的 shellcode、ret2libc、ROP、Hijack GOT 等缓冲区溢出攻击技术的原理和步骤,以供总结和分享。为了保证缓冲区溢出实践能够顺利进行,需要对编译器选项和操作系统环境进行设置,可参见笔者博客使用Linux进行缓冲区溢出实验的配置记录。同时,针对使用 gdb 动态调试获得的程序局部变量地址较之程序直接运行时的地址可能存在差异的问题,可参见笔者博客针对 Linux 环境下 gdb 动态调试获取的局部变量地址与直接运行程序时不一致问题的解决方案

1.shellcode

  1.1 示例程序

  (1)示例程序如图所示,程序的主要功能为接受用户输入的字符串,之后显示“end of main”后结束运行。

 #include<stdio.h>
#define buf_size 64 void double_print()
{
char buf[ buf_size ]; gets( buf );
} int main( int argc , char *argv[] , char *envp[] )
{
double_print(); printf("end of main!\n");
}

hello.c

  (2)编译得到名为 hello 的可执行程序。

    gcc -m32 -g -fno-stack-protector -z execstack -o hello hello.c  //编译生成可执行文件hello

  运行结果如图所示

  

  注:在进行基础缓冲区溢出实验时,需对实验环境进行设置以去除某些编译器和操作系统设置的保护机制,包括通过 -fno-stack-protecotor 关闭 SSP 机制,-z execstack 使得栈可执行,关闭 ALSR 等,具体原理可以参见使用Linux进行缓冲区溢出实验的配置记录

  1.2 shellcode注入

  根据程序源码分析,这里的 double_print 函数结束后,会返回至 main 函数执行,并输出“end of main”字符串。在读取输入时,源程序并没有对输入数据长度进行检查,则可通过构造输入,将 double_print 的返回地址覆盖,改变源程序的执行流程。

  (1)构造所需的 shellcode,构造数据时需保证 shellcode 中不包含 '\n' 。因为 gets 会在读取到换行符或 EOF 后停止字符串的读取,从而造成字符串输入的截断。

  一个可行的 shellcode 如下左图所示,其对应的十六进制表示如右图所示,其作用为启动一个shell。( 来源:Shellcoding for Linux and Windows Tutorial )

  

  上述 shellcode 共有55个字节。

  (2)通过 gdb hello 启动 gdb ,通过 disas double_print 查看 hello 程序中 double_print 函数的反汇编指令,获得缓冲区数组 buf 的实际大小,可知 buf 数组的起始地址为 %ebp - 0x48,也就是说, buf 的起始地址距返回地址的长度为 0x48 + 4 = 76 bytes,也就是构造的 shellcode 应该为 shellcode + 填充 + 指向shellcode的地址 的格式,其中 shellcode + 填充 长度为 76 字节。

  

  (3)通过 gdb hello 动态调试 hello 程序。注意,使用 gdb 动态调试获得的程序的局部变量的地址与程序直接运行时的地址可能有所不同,解决方法见针对 Linux 环境下 gdb 动态调试获取的局部变量地址与直接运行程序时不一致问题的解决方案。这里笔者使用的方法是不传递环境变量数组给运行的 hello 程序,这样其通过 gdb 运行或直接运行时进程栈上的结构会保持一致。

    set exec-wrapper env -    //gdb 中设置不传递环境变量给调试程序
env - hello的完整路径 //直接运行时,同样设置 hello 程序不从 shell 中继承环境变量。使用 hello 的完整路径是为了使 argv[0] 的内容保持一致

  在 double_print 函数中下断点,运行程序,查看缓冲数组 buf 的起始位置为 0xffffddf0,该地址即为用于覆盖原返回地址的新地址值。

  

  (4)构造完整的输入数据,数据格式为 shellcode( 55 bytes ) + 填充字节( 21 bytes ) + 新的返回地址( 4 bytes ,小端法存放 ),将上述十六进制形式的输入数据存放在名为 shellcode 的文件中。

  

  (5)通过以下指令执行注入过程。

    ./hex2raw < shellcode > shellcode.bin                //将 shellcode 文件中存放的数据转换为二进制数据,存放在shellcode.bin文件中
{ cat shellcode.bin ; cat - ; } | env - hello程序完整路径 //将输入注入至 hello 程序的缓冲区,这里使用{cat - ;}是为了保证输入流的持续
                                       //使得打开的 shell 不会随输入流的结束而结束

  其中 hex2raw 为 csapp 的 buffer lab 中提供的一个将十六进制表示的数据转换为二进制流的工具,可将之前构造的十六进制 shellcode 转换为二进制形式输入。使用 env - path_to_hello 的方式是为了保证调试状态下获得的局部变量地址与程序直接运行时的地址保持一致。

  运行结果如图所示:

  

  

  1.3 总结

  通过简单的构造注入数据,覆盖函数返回地址的方式,可以使用 shellcode 实现一定的效果。但在现实的操作系统环境下意图直接实现 shellcode 注入是十分困难的,在实验过程中进行了若干处的理想化处理:

  1.在编译过程中使用了特殊的编译选项,关闭了栈保护和栈不可执行的保护,从而使得能够 shellcode 中的代码能够直接执行;

  2.使用了一个不安全的 c 标准函数 gets,实际上在编译时编译器会警告最好不使用 gets 函数;

  进行基础的 shellcode 实验时,需要通过编译器和操作系统的设置取消众多已存在的保护手段以便实验的进行。而在后续的实验中,则会进一步的构造输入数据,从而使得构造的输入能够绕过某些安全机制的防护,达到攻击目的。

2.ret2libc

   ret2libc的核心思想是把函数的返回地址直接指向系统某个已存在的函数,而在栈上则构造所需的参数格式,这样函数在返回时会直跳转至系统函数执行,从而绕过 DEP 保护机制,实现攻击效果。一般可以选用 system、execve、mprotect 等函数作为指向目标。

  使用 ret2libc 技术主要需要两个方面的准备:(1)获得所需要的系统函数如 system 和参数字符串的地址;(2)构造栈上的数据,使得 system 函数能够正常执行;

  2.1 示例程序

  这里同样通过 shellcode 中使用的实例程序进行说明。使用 gcc -m32 -fno-stack-protector -g -o hello hello.c 生成可执行文件 hello,此时 hello 的栈数据是不可执行的。

    gcc -m32 -fno-stack-protector -g -o hello hello.c    //生成可执行文件 hello,其栈不可执行

  

  2.2 ret2libc注入过程

  典型的函数调用发生时的栈栈结构如下图所示,函数调用发生时,调用者将返回地址 ret 入栈,之后控制权转移至被调用函数。被调函数首先将 %ebp 的值保存入栈,随后即可进行其本身的函数操作,被调者可以通过 %ebp + 8 、 %ebp + 12 访问调用者压入栈中的参数的值。当函数调用返回时,被调用者会将保存的 %ebp 恢复,之后通过 ret 指令将返回地址 ret 的值赋值给 EIP,正常情况下,此时控制流跳转回调用者,由其对栈上的参数进行清除等操作。

  借助缓冲区溢出手段,我们可以构造输入数据,使得其将返回地址 ret 的值覆盖,修改为系统中已存在的 system 函数的值,则函数返回时控制流会跳转至 system 函数,system 函数会将寄存器 %ebp 保存,之后其通过 %ebp + 8 获得所需的参数,而在函数返回时,其会认为位于保存的 %ebp 之后的栈顶数据为函数调用的返回地址。通过构造数据,可以将原函数调用的 ret 修改为目标函数地址,之后放置我们所需的返回地址( 比如 exit 函数的地址),并根据正常的函数调用栈结构构造参数列表。

  

  可通过 gdb 动态调试获得构造输入所需要的地址。使用如下指令进行 gdb 调试。

    gdb hello    //启动 gdb
set exec-wrapper env -u COLUMNS -u LINES -u _ //设置忽略某些环境变量,这里没有选择直接忽略所有的环境变量,因为在后续过程中使用了环境变量SHELL的内容
r       //开始运行程序

  设置完成后即可对 hello 程序进行动态调试,获得的运行时局部变量的地址与通过命令 env -u _ /home/yh/sc/hello 直接运行 hello 程序时保持一致。

  (1)通过 p system 和 p exit 命令获得进程中已存在的系统函数 system 和 exit 的地址;

  

  (2)通过程序的环境变量 SHELL 获得参数字符串"/bin/bash"的地址,通过 gdb 的 x 命令查看环境变量字符串。

  可知环境变量字符串的首地址为 0xffffd05c。

  

  通过 gdb 的 x 命令查看得到环境变量 SHELL 的起始地址为 0xffffd356,则字符串 /bin/bash 的起始地址为 0xffffd35c.

  

  (3) 通过上述获得的信息,即可构造输入数据,为 填充数据( 76字节 ) + system函数地址( 4字节,小端法 ) + exit函数地址( 4字节,小端法 ) + 参数字符串地址( 4字节,小端法)。构造好的数据格式如图所示。

  

  (4) 通过构造的数据对目标程序 hello 进行注入。

    ./hex2raw < shellcode_ret2libc > shellcode.bin           //将 shellcode_ret2libc 中的数据转化为二进制形式并保存在文件 shellcode.bin 中
{ cat shellcode.bin ; cat - ; } | env -u _ hello程序完整路径 //对 hello 程序进行注入

  运行结果如图所示,可以看到在栈不可执行的环境下成功打开了一个 shell。

  

  2.3 总结

  相对而言,ret2libc 方法的实践效果较之简单的 shellcode 注入的方法要更好,其可以成功的绕过栈不可执行的保护,使用系统中已均在的函数实现攻击效果。但 ret2libc 方法的实践有一定的局限性,其主要通过栈上数据进行参数构造,这对于以寄存器传参的 x86_64 体系是无效的。同时,与 shellcode 一样,ret2libc 方法也没有克服 SSP 机制和 ALSR 机制,想要在实际环境中使用还需要进一步的处理。

参考资料:

  1.Shellcoding for Linux and Windows Tutorial

  2.Smashing The Stack For Fun And Profit

  3.使用ret2libc攻击方法绕过数据执行保护

缓冲区溢出基础实践(一)——shellcode 与 ret2libc的更多相关文章

  1. 缓冲区溢出基础实践(二)——ROP 与 hijack GOT

    3.ROP ROP 即 Return Oritented Programming ,其主要思想是在栈缓冲区溢出的基础上,通过程序和库函数中已有的小片段(gadgets)构造一组串联的指令序列,形成攻击 ...

  2. 逆向及BOF基础实践

    逆向及BOF基础实践 20145316 许心远 一.缓冲区溢出基础知识 缓冲区溢出是一种非常普遍.非常危险的漏洞,在各种操作系统.应用软件中广泛存在.利用缓冲区溢出攻击,可以导致程序运行失败.系统宕机 ...

  3. 《网络对抗》 逆向及Bof基础实践

    <网络对抗>-逆向及Bof基础实践 1 逆向及Bof基础实践说明 1.1 实践目标 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:main调用foo函数, ...

  4. 小白日记17:kali渗透测试之缓冲区溢出实例-windows,POP3,SLmail

    缓冲区溢出实例 缓冲区溢出原理:http://www.cnblogs.com/fanzhidongyzby/archive/2013/08/10/3250405.html 空间存储了用户程序的函数栈帧 ...

  5. kali渗透测试之缓冲区溢出实例-windows,POP3,SLmail

    kali渗透测试之缓冲区溢出实例-windows,POP3,SLmail 相关链接:https://www.bbsmax.com/A/xl569l20Jr/ http://4hou.win/wordp ...

  6. 缓冲区溢出实例(一)--Windows

    一.基本概念 缓冲区溢出:当缓冲区边界限制不严格时,由于变量传入畸形数据或程序运行错误,导致缓冲区被填满从而覆盖了相邻内存区域的数据.可以修改内存数据,造成进程劫持,执行恶意代码,获取服务器控制权限等 ...

  7. Linux下缓冲区溢出攻击的原理及对策(转载)

    前言 从逻辑上讲进程的堆栈是由多个堆栈帧构成的,其中每个堆栈帧都对应一个函数调用.当函数调用发生时,新的堆栈帧被压入堆栈:当函数返回时,相应的堆栈帧从堆栈中弹出.尽管堆栈帧结构的引入为在高级语言中实现 ...

  8. Linux下缓冲区溢出攻击的原理及对策

    前言 从逻辑上讲进程的堆栈是由多个堆栈帧构成的,其中每个堆栈帧都对应一个函数调用.当函数调用发生时,新的堆栈 帧被压入堆栈:当函数返回时,相应的堆栈帧从堆栈中弹出.尽管堆栈帧结构的引入为在高级语言中实 ...

  9. 网络安全(超级详细)零基础带你一步一步走进缓冲区溢出漏洞和shellcode编写!

    零基础带你走进缓冲区溢出,编写shellcode. 写在前面的话:本人是以一个零基础者角度来带着大家去理解缓冲区溢出漏洞,当然如果你是开发者更好. 注:如果有转载请注明出处!创作不易.谢谢合作. 0. ...

随机推荐

  1. MySQL死锁检测和回滚

    最近碰到“TOO DEEP OR LONG SEARCH IN THE LOCK TABLE WAITS-FOR GRAPH, WE WILL ROLL BACK FOLLOWING TRANSACT ...

  2. wpf中应该使用c#四种定时器中的DispatcherTimer

    c#中有四种定时器 1:System.Threading.Timer 使用: private System.Threading.Timer timerClose; timerClose = new S ...

  3. SpringBoot+JPA+cache入门

    在pom.xml中加入如下依赖 <dependency> <groupId>org.springframework.boot</groupId> <artif ...

  4. [源码] 定义String s="abcd", 求长度

    一般会答: s.length() 看源码是如何实现的: /** * Returns the length of this string. * The length is equal to the nu ...

  5. Android DiskLruCache完全解析,硬盘缓存的最佳方案

    Android DiskLruCache完全解析,硬盘缓存的最佳方案 概述   记得在很早之前,我有写过一篇文章Android高效加载大图.多图解决方案,有效避免程序OOM,这篇文章是翻译自Andro ...

  6. php 多次导入文件导致 Cannot redeclare class

    定义了类A b.php中导入了A c.php中导入了b.php c.php中导入A会报错:

  7. QT的信号和槽机制简介

    信号与槽作为QT的核心机制在QT编程中有着广泛的应用,本文介绍了信号与槽的一些基本概念.元对象工具以及在实际使用过程中应注意的一些问题. QT是一个跨平台的C++ GUI应用构架,它提供了丰富的窗口部 ...

  8. 使用Python生成基础验证码教程

    pillow是Python平台事实上的图像处理标准库.PIL功能非常强大,但API却非常简单易用. 所以我们使用它在环境里做图像的处理. 第一步 下载pillow #运行命令 pip install ...

  9. jquery3.0移除了.load()方法

    参考链接:新版jquery去掉load事件了吗? 今天也遇到了这个问题,查了一下文档,确实从3.0开始移除了load.unload.error事件方法.不过依然可以用on方法绑定这些事件. Break ...

  10. WPF ListView 分组 Grouping

    在Resource里定义数据源和分组字段: <CollectionViewSource x:Key="listData" Source="{Binding Cate ...