最近结合软件安全课程上学习的理论知识和网络资料,对缓冲区溢出漏洞的简单原理和利用技巧进行了一定的了解。这里主要记录笔者通过简单的示例程序实现缓冲区溢出漏洞利用的步骤,按由简至繁的顺序,依次描述简单的 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. C# 中 NPOI 库读写 Excel 文件的方法【摘】

    原作:淡水网志 NPOI 是开源的 POI 项目的.NET版,可以用来读写Excel,Word,PPT文件.在处理Excel文件上,NPOI 可以同时兼容 xls 和 xlsx.官网提供了一份 Exa ...

  2. vb.net的String类型和Bytes转换(C#也适用)

    1.Bytes---->StringSystem.Text.Encoding.Unicode.GetString(bytes, 0, bytes.Length) 2.String----> ...

  3. 通向全栈之路——(3)node环境搭建

    1:更新系统 sudo apt-get update2:安装相关软件 sudo apt-get install vim openssl build-essential libssl-dev wget ...

  4. HashMap概述及其三种遍历方式

    一.HashMap概述: 1.HashMap是一个散列表,它存储的是键值对(key-value)映射: 2.HashMap继承AbstractMap,实现了Map,Cloneable,Serializ ...

  5. JS实现继承的几种方式以及优缺点(转载)

    前言 JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一.那么如何在JS中实现继承呢?让我们拭目以待. JS继承的实现方式 既然要实现继承,那么首先我们得有一个父类,代码如下: // 定义一个 ...

  6. Cannot perform conversion to XML from legacy HTML:

    错误信息:Cannot perform conversion to XML from legacy HTML: The nekoHTML library is not in classpath. ne ...

  7. Django之WSGI浅谈

    一.什么是Web框架 框架,即framework,特指为解决一个开放性问题而设计的具有一定约束性的支撑结构,使用框架可以帮你快速开发特定的系统. 浏览器与服务器之间发起HTTP请求: 1.浏览器发送一 ...

  8. CSS选择器之伪类选择器(伪元素)

    selection [CSS4]应用于文档中被用户高亮的部分(比如使用鼠标或其他选择设备选中的部分).(IE8及以下不支持)(火狐-moz-selection) first-line 选择每个 < ...

  9. opencv3.2.0图像对比度与亮度调整

    ##名称:图像对象度与对比度调整(由轨迹条分别控制对比度和亮度值) ##平台:QT5.7.1+opencv3.2.0 ##时间:2017年12月13日 /***********建立QT控制台程序*** ...

  10. mac os idea的快捷键

    全局搜索:shift+command+f 搜索类:command+o 光标向前向后移动:command+option+(左/右) 删除一行: command+delete