缓冲区溢出基础实践(一)——shellcode 与 ret2libc
最近结合软件安全课程上学习的理论知识和网络资料,对缓冲区溢出漏洞的简单原理和利用技巧进行了一定的了解。这里主要记录笔者通过简单的示例程序实现缓冲区溢出漏洞利用的步骤,按由简至繁的顺序,依次描述简单的 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
缓冲区溢出基础实践(一)——shellcode 与 ret2libc的更多相关文章
- 缓冲区溢出基础实践(二)——ROP 与 hijack GOT
3.ROP ROP 即 Return Oritented Programming ,其主要思想是在栈缓冲区溢出的基础上,通过程序和库函数中已有的小片段(gadgets)构造一组串联的指令序列,形成攻击 ...
- 逆向及BOF基础实践
逆向及BOF基础实践 20145316 许心远 一.缓冲区溢出基础知识 缓冲区溢出是一种非常普遍.非常危险的漏洞,在各种操作系统.应用软件中广泛存在.利用缓冲区溢出攻击,可以导致程序运行失败.系统宕机 ...
- 《网络对抗》 逆向及Bof基础实践
<网络对抗>-逆向及Bof基础实践 1 逆向及Bof基础实践说明 1.1 实践目标 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:main调用foo函数, ...
- 小白日记17:kali渗透测试之缓冲区溢出实例-windows,POP3,SLmail
缓冲区溢出实例 缓冲区溢出原理:http://www.cnblogs.com/fanzhidongyzby/archive/2013/08/10/3250405.html 空间存储了用户程序的函数栈帧 ...
- kali渗透测试之缓冲区溢出实例-windows,POP3,SLmail
kali渗透测试之缓冲区溢出实例-windows,POP3,SLmail 相关链接:https://www.bbsmax.com/A/xl569l20Jr/ http://4hou.win/wordp ...
- 缓冲区溢出实例(一)--Windows
一.基本概念 缓冲区溢出:当缓冲区边界限制不严格时,由于变量传入畸形数据或程序运行错误,导致缓冲区被填满从而覆盖了相邻内存区域的数据.可以修改内存数据,造成进程劫持,执行恶意代码,获取服务器控制权限等 ...
- Linux下缓冲区溢出攻击的原理及对策(转载)
前言 从逻辑上讲进程的堆栈是由多个堆栈帧构成的,其中每个堆栈帧都对应一个函数调用.当函数调用发生时,新的堆栈帧被压入堆栈:当函数返回时,相应的堆栈帧从堆栈中弹出.尽管堆栈帧结构的引入为在高级语言中实现 ...
- Linux下缓冲区溢出攻击的原理及对策
前言 从逻辑上讲进程的堆栈是由多个堆栈帧构成的,其中每个堆栈帧都对应一个函数调用.当函数调用发生时,新的堆栈 帧被压入堆栈:当函数返回时,相应的堆栈帧从堆栈中弹出.尽管堆栈帧结构的引入为在高级语言中实 ...
- 网络安全(超级详细)零基础带你一步一步走进缓冲区溢出漏洞和shellcode编写!
零基础带你走进缓冲区溢出,编写shellcode. 写在前面的话:本人是以一个零基础者角度来带着大家去理解缓冲区溢出漏洞,当然如果你是开发者更好. 注:如果有转载请注明出处!创作不易.谢谢合作. 0. ...
随机推荐
- ASP.NET MVC 学习笔记-3.面向对象设计原则
在设计程序.系统框架或者类时,最主要考虑的事情就是代码的可扩展性,而不是完成功能即可.因此,提倡使用面向对象设计的最佳实践和基本原则. 1. 单一职责原则(SRP:The Single R ...
- C# 小软件部分(二)
此次又新增了一些新的功能,直接接着上次的介绍吧 上次博客介绍地址:http://www.cnblogs.com/Liyuting/p/8540592.html 这次新增了三个功能,具体如下: 一.网 ...
- Runtime初识
什么是Runtime 我们写的代码在程序运行过程中都会被转化成runtime的C代码执行,例如[target doSomething];会被转化成objc_msgSend(target, @sel ...
- [C#]INI文件控制类
INI文件常用于保存各类设置或本地化文本,大概格式如下: [Section] key=value 然而.NET框架似乎并没有提供一个实用的工具来操作它,或许是因为MS想让我们都使用Settings类控 ...
- VMWare安装Mac OS X
原文作者 谷月K 2016.08.16 18:55 字数3473 随着iPhone.iPad.Mac等苹果产品越来越火爆,越来越多的初学者想要了解和尝试苹果平台,包括苹果操作系统Mac OS X.苹果 ...
- Java设计模式(三)单例模式
一.场景描述 在采集到仪器数据后,需要将数据发送到lims系统中,通过调用lims系统服务实现数据的上传. 在仪器数据采集组件中实现lims系统服务代理,该代理需要指定服务地址url,认证信息(用户名 ...
- EF CodeFirst 初识
随着EntityFramework的发展,原先的三种方式,{Code First ,Model First,Database First } CodeFirst基本摆脱了另外两种方式 成为了 最受欢 ...
- 转 mysqli 事务常用方法
原文:mysqli 事务常用方法 1. //打开(true)或关闭(false)本次数据库连接的自动命令提交事务模式 //参数如果设置为 FALSE,则表示关闭 auto-commit.如果设置为 T ...
- CSS - 伪类和伪元素的区别
伪类和伪元素皆独立于文档结构.它们获取元素的途径也不是基于id.class.属性这些基础的元素特征,而是在处于特殊状态的元素(伪类),或者是元素中特别的内容(伪元素).区别总结如下: CSS伪类 (P ...
- Ubuntu下彻底卸载wine
简介: wine是linux下模拟windows的一个东西,可以用来安装exe程序,但是对于wine的卸载确 实非常麻烦的,这里是彻底卸载wine的一个教程. ##首先卸载wine sudo apt- ...