参考资料1.https://www.cnblogs.com/0831j/p/9219243.html (实验、原理) 2.https://www.cnblogs.com/xidian-wws/p/10819073.html(getenvaddr那个的说明) 实验过程中的一些问题https://mp.csdn.net/postedit/89915014 遗留问题: 1.那些地址在gdb和直接运行的地址是不一样的,不清楚为什么,也没有找到规律 结论这些结论都在我的另一篇博客中有详细说明,为了阅读方便,放到一起三个程序实际运行的地址差异首先要关闭地址随机化
从上面的表格可以看出: (1)在shell中运行时,这几个程序地址的差异是它们程序名的二倍,这也是本次实验利用getenvaddr.c加上偏移获取retlib.c环境变量的方法,具体原因见这个博客的末尾:https://www.cnblogs.com/xidian-wws/p/10819073.html; (2)在gdb中不管是上面两种方法中的哪一种,同一程序获取到的地址都是一样的,但是不同程序获取到的地址偏移是它们程序名的一倍,这个原因尚没有找出来,希望知道的朋友告知; (3)retlib是漏洞程序,getenvaddr和expl是攻击程序,这两个攻击程序其实是可以合并的,我们的目的就是通过攻击程序猜漏洞程序环境变量的地址; (4)假设不用getenvaddr,只用expl这个攻击程序:可以通过在shell中运行expl程序(程序中会利用getenv函数获取程序变量地址),然后加上expl和retlib程序名偏移量的二倍来猜测retlib程序环境变量的地址;也可以通过在expl的gdb中获取到环境变量的地址后,加上他们程序名偏移量的一倍来获取retlib程序环境变量的地址(已验证); (5)假设是在gdb中获取到的地址,那么retlib只能在gdb中运行才能攻击成功,在shell中运行是不会攻击成功的,因为同一环境变量在shell中和在gdb中的地址是不一样的 (关于这个的解释,老师说因为gdb调试是静态的,shell运行是动态的,地址肯定不一样; 一个大神的解释是:gdb的调试环境会影响buf在内存中的位置,虽然关闭了ASLR,但这只能保证buf的地址在gdb的调试环境中不变,但当直接执行的时候,buf的位置会固定在别的地址上) 感觉大神的这个解释应该某一函数中的局部变量在gdb和直接运行时地不一样的原因,我现在也不是很懂 (6)对于(5),在gdb中攻击成功的话,获取的是普通用户的shell,不是root,现在我也不知道能不能获取到root以及如何获取到root 不同程序环境变量及地址差异
可以看到,这两个程序的环境变量地址是一样的,但是环境变量所指向的空间是不一样的,差了两个字节,自己也还没有想明白? 自己整了一份他们的内存比较,见自己的笔记。 同一程序不同参数地址的差异这个例子说明,对于同一个程序,找其环境变量的地址时,加不加那个参数都是一样的,只是环境变量的地址变量一点,但是环境变量所指向的 空间是一样的 |
实验原理(攻击)之前的攻击方法大量使用Shellcode,核心思想是修改EIP和注入Shellcode,在函数返回时跳到Shellcode去执行。 (防御)DEP(Data Execution Prevention,数据执行保护):将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。DEP 的主要作用是阻止数据页(如默认的堆页、各种堆栈页以及内存池页)执行代码。 (攻击)系统库函数通常是不受DEP保护的,所以通过将返回地址指向系统函数可以绕过DEP保护,所以可以通过调用系统函数system()获得shell。system函数是通过/bin/sh命令去执行一个用户执行命令或者脚本。因此,我们可以利用system来实现Shellcode的功能。 对于ret2libc,即return-to-libc(返回到系统库函数执行),攻击者能够通过缓冲区溢出改写返回地址为一个库函数的地址,并且将此库函数执行时的参数也重新写入栈中。这样当函数调用时获取的是攻击者设定好的参数值,并且结束后返回时就会返回到库函数而不是 main()。而此库函数实际上就帮助攻击者执行了其恶意行为。 构造恶意输入数据:借助缓冲区溢出手段,我们可以构造输入数据,使得其将返回地址 ret 的值覆盖,修改为系统中已存在的 system 函数的值,则函数返回时控制流会跳转至 system 函数,system 函数会将寄存器 %ebp 保存,之后其通过 %ebp + 8 获得所需的参数,而在函数返回时,其会认为位于保存的 %ebp 之后的栈顶数据为函数调用的返回地址。通过构造数据,可以将原函数调用的 ret 修改为目标函数地址,之后放置我们所需的返回地址(比如exit函数的地址),并根据正常的函数调用栈结构构造参数列表。 | |||
实验复现:复现流程按照该实现进行:https://www.cnblogs.com/0831j/p/9219243.html 环境:ubuntu12.04.5 64位 1.输入命令安装一些用于编译 32 位 C 程序的东西。 sudo apt-get update sudo apt-get install lib32z1 libc6-dev-i386 sudo apt-get install lib32readline-gplv2-dev 2.关闭地址随机化 sudo sysctl -w kernel.randomize_va_space=0 3.即使你能欺骗一个 Set-UID 程序调用一个 shell,也不能在这个 shell 中保持 root 权限,这个防护措施在/bin/bash 中实现。 linux 系统中,/bin/sh 实际是指向/bin/bash 或/bin/dash 的一个符号链接。为了重现这一防护措施被实现之前的情形,我们使用另一个 shell 程序(zsh)代替/bin/bash。下面的指令描述了如何设置 zsh 程序。 sudo apt-get install zsh sudo su cd /bin rm sh ln -s zsh sh exit 4.漏洞代码retlib.c sudo su gcc -m32 -g -z noexecstack -fno-stack-protector -o retlib retlib.c chmod u+s retlib exit 5.读取环境变量的程序 gcc -m32 -o getenvaddr getenvaddr.c export BIN_SH="/bin/sh" //这种是属于临时导入环境变量的方法,所以每次打开终端都要添加 echo $BIN_SH ./getenvaddr BIN_SH ./retlib 6.攻击程序expl.c gcc -m32 -g -o expl expl.c//编译 gdb -q ./exploit//调试 b 10//设置断点 run//运行到断点处 p system//获取system地址 p exit//获取exit地址 7.修改 expl.c 文件,填上刚才找到的内存地址 vim expl.c gcc -m32 -g -o expl expl.c 8.攻击 ./expl ./retlib | |||
实验代码
我们可以看到buffer距离ebp有20个字节,所以buffer距离ret有24个字节,所以我们将buffer[24](即system函数栈帧的ebp)填充为system地址,将buffer[32](即system函数栈帧的ebp+8)构造为/bin/sh的地址,这个地址作为system函数的输入;而buffer[28](即system函数栈帧的ebp+4)作为system函数的返回地址。 (1)buffer[28](即system函数栈帧的ebp+4)不做任何填充 (2)buffer[28](即system函数栈帧的ebp+4)填充为exit函数的地址 | |||
实验结果(1)buffer[28](即system函数栈帧的ebp+4)不做任何填充 (2)buffer[28](即system函数栈帧的ebp+4)填充为exit函数的地址 | |||
新知识: export BIN_SH="/bin/sh" 定义了一个BIN_SH变量,赋值为”/bin/sh”,并将其导出为环境变量 echo $BIN_SH 输出变量BIN_SH的值 头文件:#include <stdlib.h> 定义函数:char * getenv(const char *name); 函数说明:getenv()用来取得参数name 环境变量的内容. 参数name 为环境变量的名称, 如果该变量存在则会返回指向该内容的指针. 环境变量的格式为name=value. 返回值:执行成功则返回指向该内容的指针, 找不到符合的环境变量名称则返回NULL. http://c.biancheng.net/cpp/html/377.html C语言main函数参数说明: https://blog.csdn.net/xiaocainiaoshangxiao/article/details/13996459 Shell变量由全大写字母加下划线组成,有两种类型的Shell变量: 环境变量 环境变量可以从父进程传给子进程,因此Shell进程的环境变量可以从当前Shell进程传给fork出来的子进程。用printenv命令可以显示当前Shell进程的环境变量。 本地变量 只存在于当前Shell进程,用set命令可以显示当前Shell进程中定义的所有变量(包括本地变量和环境变量)和函数。 环境变量是任何进程都有的概念,而本地变量是Shell特有的概念。在Shell中,环境变量和本地变量的定义和用法相似。 在Shell中定义或赋值一个变量: $ VARNAME=value 注意等号两边都不能有空格,否则会被Shell解释成命令和命令行参数。 一个变量定义后仅存在于当前Shell进程,它是本地变量,用export命令可以把本地变量导出为环境变量,定义和导出环境变量通常可以一步完成: $ export VARNAME=value 也可以分两步完成: $ VARNAME=value $ export VARNAME 如果一个变量叫做VARNAME,用${VARNAME}可以表示它的值,在不引起歧义的情况下也可以用$VARNAME表示它的值。注意,在定义变量时不用$,取变量值时要用$。 参考:http://docs.linuxtone.org/ebooks/C&CPP/c/ch31s03.html linux下如何在终端上运行和安装可执行文件 linux下可执行文件,如果是在/bin、/sbin、/usr/bin或者/usr/sbin 里头,就直接敲文件名就可以运行.(注意环境变量PATH是否包含了路径) 如果是在其他文件夹里,比如说运行/root里头的 time文件 你就 cd /root 再 ./time。注意./与time之间没有空格。 ./表示当前目录下,../表示上一级目录,../../表示上上一级目录。 linux终端中,是用"sh"来运行文件,或者"./" 比如你在/usr/temp文件夹中有一个可执行文件"flash" 你可以在终端中输入 sh /usr/temp/flash 或者进入文件夹 cd /usr/temp 然后运行: ./flash 参考:https://blog.csdn.net/Se7enYea/article/details/8245672 lea对变量没有影响是取地址,对寄存器来说加[]时取值,第二操作数不加[]非法 mov对变量来说没有影响是取值,对寄存器来说是加[]时取地址,第二操作数不加[]是取值 LEA指令有两个操作数。左边是目的操作数,表示操作结果保存在此,该指令目的操作数只能是8个通用寄存器之一。逗号右边的是源操作数,该指令的源操作数只能是一个存储单元,表达存储单元有多种寻址方式。(看这个汇编代码感觉左右刚好反着呢,等自己看完汇编的书,再来研究研究) LEA指令的功能是将源操作数、即存储单元的有效地址(偏移地址)传送到目的操作数。 示例LEA BX,[BX+SI+0F54H]指令中,[BX+SI+0F54H]采用相对基址变址的寻址方式表达存储单元,它表示的存储单元的有效地址是:BX内容加SI内容加0F54H。这个结果被传送到BX中。 |