首页 > 安全 > 系统安全 >

[译文改编版] smashing the stack for fun and profit part1

2012-10-31

introduction如syslog、splitvt、sendmail 8.7.5、linux的mount命令、xtlibrary等很多软件中都包含了缓冲区溢出(bo)这类问题本文就阐述了啥是bo,怎样利用bo攻击。阅读知识:汇编,虚拟内存、gdb的使用。本文的...

introduction

如syslog、splitvt、sendmail 8.7.5、linux的mount命令、xtlibrary等很多软件中都包含了缓冲区溢出(bo)这类问题

本文就阐述了啥是bo,怎样利用bo攻击。阅读知识:汇编,虚拟内存、gdb的使用。本文的使用环境为 x86+linux

首先定义下啥是buffer,buffer就是保存同一种数据类型若干实例的内存空间(还是没明白...)在c语言中,通常与字缓冲数组想联系(还是没明白...)

烦不了了,buffer就理解为一个内存块。

数组可以申明为静态或动态,静态变量在程序加载时,被分配到数据段中。动态的在程序运行时被加载在栈中

溢出就是指流出到顶、边缘、或者边界。本文所讨论的仅仅是动态buffer的,即所谓的基于堆栈的缓冲区溢出。

Process Memory Organization

为了了解堆栈缓冲区我们必须先学习下进程是怎样管理内存区域的

一个进程被划分为三个区域 文本、数据、堆栈区,我们主要关注的是 堆栈区,但还是瞄一下其他区

文本区:用于放置代码本身或只读数据,这个区域等同于 运行程序的文本部分,该区域一般都被标示成为只读,任何对该区域的写操作都会导致段错误

数据区:这里包含初始化与未初始化数据,静态变量就被存储在这里,该部分就是程序运行时的 data-bss部分...

/------------------\ 内存低地址
| |
| 文本 |
| |
|------------------|
| (已初始化) |
| 数据 |
| (未初始化) |
|------------------|
| |
| 堆栈 |
| |
\------------------/ 内存高地址

What Is A Stack?

堆栈是一种抽象的数据结构,他具有的属性就是后进先出LIFO,在堆栈上定义了PUSH和POP两个操作

Why Do We Use A Stack?

现代计算机需要高级语言的支持,何所谓高级语言,其最重要的组成是 函数。函数的引用可以改变程序的控制流(就是函数调用的改变)

但完成了本函数的执行体,函数将执行权返回给后面的程序(函数的调用语句后的执行指令)

说白了不就是调用栈的使用嘛,栈LIFO属性利于保存函数内部的局部变量、函数参数、返回值

The Stack Region

堆栈是一块连续的存储区域,寄存器中的sp指针指向栈顶,栈底一般是固定地址,整个栈的大小可以通过内核来动态改变。

CPU来实现指令的PUSH和POP操作

堆栈是由一系列的逻辑堆栈帧组成,这个鬼东西函数调用时被push,返回时被pop

好了,现在介绍什么是堆栈帧,堆栈帧包含了 函数入参,局部变量,以及恢复上级调用的堆栈帧,其中包含了上级调用的指令指针

根据系统不同,堆栈的实现方式也会有很大不同,本文介绍的stack是从高址到低址的增长,其中sp指针执行最后一个以用的的堆栈地址

在这里又引出了第二个指针bp,该指针用来表示本函数范围内局部变量(这个鬼bp就是用来指示这些局部变量的起始地址,注意啦,起始地址不包括sfp和ret)

bp、sp是基于这样的运行机制:

1被调函数首先将原始bp push,定义为 sfp(bp理解为函数栈帧的标志,so其也叫fp)

2然后将sp赋值于bp(可以想象,函数栈帧在内存间是连续的,每个函数栈帧可以看成由四个部分组成入参;ret;sfp;esp与ebp间的局部变量,见下图)

3 sp重置,扣掉局部变量的空间

好了到真正干活儿的时候了,研究真实的代码,我本地的环境是 ubuntu + gcc 4.4.3

example1.c:
------------------------------------------------------------------------------
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}

void main() {
function(1,2,3);
}
------------------------------------------------------------------------------
编译过程: gcc -S -o example1.s -fno-stack-protector example1.c

生成的汇编代码如下:

main:
pushl %ebp
movl %esp, %ebp
subl $12, %esp
movl $3, 8(%esp)
movl $2, 4(%esp)
movl $1, (%esp)

这三条与原文有些区别,但本质上就是push操作,不知为什么写成这样
call function
movl $0, %eax
leave
ret


function:
pushl %ebp
movl %esp, %ebp
subl $16, %esp

字节没有对齐,原文是20
leave
ret

生成的function内存空间如下图所示


高潮到了,缓冲区溢出(其实现在可以认为是一个函数栈帧内对ret内容尝试去改写)

原文中的这个例子我们大概看下:无非就是用大字符串覆盖小字符串导致ret写坏

example2.c
------------------------------------------------------------------------------
void function(char *str) {
char buffer[16];
strcpy(buffer,str);
}

void main() {
char large_string[256];
int i;
for( i = 0; i < 255; i++)
large_string[i] = &#39;A&#39;;
function(large_string);
}
------------------------------------------------------------------------------

好下面改写我们的例子代码,如下

example3.c:
------------------------------------------------------------------------------
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
int *ret;
ret = buffer1 + 13;

/*

由于现行版本的gcc进行了优化,没搞明白进行了啥优化,但至少局部变量的定义顺序和字节对齐都发生了变化

对于这个例子,ebp-4 定义的变量为 ret ;ebp-9 由于没有字节对齐定义的变量为buffer1

13 = 5(buffer1)+4(ret)+4(sfp)

*/
(*ret) += 8;

/*

为什啥是+8相见后面的汇编

*/
}

void main() {
int x;
x = 0;
function(1,2,3);
x = 1; /*让这条语句不能执行*/
printf("%d\n",x);
}
------------------------------------------------------------------------------

以下是上面代码的汇编程序,仔细分析下

(gdb) disassemble main
Dump of assembler code for function main:
0x08048402 <+0>: push %ebp
0x08048403 <+1>: mov %esp,%ebp
0x08048405 <+3>: and $0xfffffff0,%esp
0x08048408 <+6>: sub $0x20,%esp
0x0804840b <+9>: movl $0x0,0x1c(%esp)
0x08048413 <+17>: movl $0x3,0x8(%esp)
0x0804841b <+25>: movl $0x2,0x4(%esp)
0x08048423 <+33>: movl $0x1,(%esp)
0x0804842a <+40>: call 0x80483e4 <function>

这个call占用了5个字节
0x0804842f <+45>: movl $0x1,0x1c(%esp)

这个movl占用了8个字节
0x08048437 <+53>: mov $0x8048520,%eax
0x0804843c <+58>: mov 0x1c(%esp),%edx
0x08048440 <+62>: mov %edx,0x4(%esp)
0x08048444 <+66>: mov %eax,(%esp)
0x08048447 <+69>: call 0x804831c <printf@plt>
0x0804844c <+74>: mov $0x0,%eax
0x08048451 <+79>: leave
0x08048452 <+80>: ret

(gdb) disassemble function
Dump of assembler code for function function:
0x080483e4 <+0>: push %ebp
0x080483e5 <+1>: mov %esp,%ebp
0x080483e7 <+3>: sub $0x20,%esp
0x080483ea <+6>: lea -0x9(%ebp),%eax

/* lea新语法 取偏移量的有效值 并赋给eax*/
0x080483ed <+9>: add $0xd,%eax
0x080483f0 <+12>: mov %eax,-0x4(%ebp)
0x080483f3 <+15>: mov -0x4(%ebp),%eax
0x080483f6 <+18>: mov (%eax),%eax
0x080483f8 <+20>: lea 0x8(%eax),%edx
0x080483fb <+23>: mov -0x4(%ebp),%eax
0x080483fe <+26>: mov %edx,(%eax)
0x08048400 <+28>: leave
0x08048401 <+29>: ret

上面的小trick仅仅是通过一个简单的溢出 来使得main的执行发生了改变

上面做的仅仅是一个溢出,没有啥实际危害,下面看到的是有破坏性的东西

shell code

理解为溢出所指向的程序,该程序可以获取shell或提升root

这里就提出了一般的缓冲区溢出攻击方式:将代码放置与我们将要溢出的buffer中,然后改写ret,使其指向这个buffer,详解看下图:


将buffer(现在是不是对buffer有了更深的认识,它就是一个数组,供你写穿用的)写入你的命令,然后改写ret,使其执行buffer,这就是b.o attack的精髓

下面分析的是怎样写一个shell code,即我buffer中应该填充什么

下回分解吧

角度:

对一个计算机系统的理解,不单是一段程序代码的分析,更重要的是对程序在系统中运行机制,所处运行环境的理解

丝滑:

看的没效果了 就放慢速度 放到最慢

文章的完成:建立本地获取root shell

相关文章
最新文章
热点推荐