1 / 45

缓冲区溢出技术

缓冲区溢出技术. 堆栈. 从物理上讲,堆栈是就是一段连续分配的内存空间 静态全局变量是位于数据段并且在程序开始运行的时候被加载 动态的局部变量 则分配在堆栈里面 从操作上来讲,堆栈是一个先入后出的队列,其生长方向与内存的生长方向正好相反. 我们规定内存的生长方向为向上,则栈的生长方向为向下 压栈的操作 push = ESP-4 出栈的操作是 pop=ESP+4. 在一次函数调用中,堆栈中将被依次压入:  参数,返回地址, EBP 如果函数有局部变量,接下来,就在堆栈中开辟相应的空间以构造变量 函数执行结束,这些局部变量的内容将 被丢失。但是不被清除

chuck
Télécharger la présentation

缓冲区溢出技术

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 缓冲区溢出技术

  2. 堆栈

  3. 从物理上讲,堆栈是就是一段连续分配的内存空间从物理上讲,堆栈是就是一段连续分配的内存空间 • 静态全局变量是位于数据段并且在程序开始运行的时候被加载 • 动态的局部变量 则分配在堆栈里面 • 从操作上来讲,堆栈是一个先入后出的队列,其生长方向与内存的生长方向正好相反

  4. 我们规定内存的生长方向为向上,则栈的生长方向为向下我们规定内存的生长方向为向上,则栈的生长方向为向下 • 压栈的操作push=ESP-4 • 出栈的操作是pop=ESP+4

  5. 在一次函数调用中,堆栈中将被依次压入:  参数,返回地址,EBP • 如果函数有局部变量,接下来,就在堆栈中开辟相应的空间以构造变量 • 函数执行结束,这些局部变量的内容将 被丢失。但是不被清除 • 在函数返回的时候,弹出EBP,恢复堆栈到函数调用的地址,弹出返回地址到EIP以继续执行程序

  6. 在C语言程序中,参数的压栈顺序是反向的:比如func(a,b,c)。在参数入栈的时候,是先压c,再压b,最后压a在C语言程序中,参数的压栈顺序是反向的:比如func(a,b,c)。在参数入栈的时候,是先压c,再压b,最后压a • 在取参数的时候,由于栈的先入后出,先取栈顶的a,再取b,最后取c

  7. 运行时的堆栈分配

  8. 我们用gcc -S 来获得汇编语言输出,可以看到main函数的开头部分对应如下语句: pushl %ebp movl %esp,%ebp subl $8,%esp • 首先把EBP保存下来,然后EBP等于现在的ESP,这样EBP就可以用来访问本函数的 局部变量 • 之后ESP减8,就是堆栈向上增长8个字节,用来存放name[]数组。现在堆栈 的布局如下:

  9. 之后ESP减8,就是堆栈向上增长8个字节,用来存放name[]数组。现在堆栈 的布局如下:

  10. 由于我们输入的name字符串太长,name数组容纳不下,只好向内存顶部继续写‘A’由于我们输入的name字符串太长,name数组容纳不下,只好向内存顶部继续写‘A’ • 由于堆栈的生长方向与内存的生长方向相反,这些‘A’覆盖了堆栈的老的元素 • EBP,ret都已经被‘A’覆盖了 • 在main返回的时候,就会把‘ AAAA’ 的ASCII码:0x41414141作为返回地址,CPU会试图执行0x41414141处的指令,结果出现错误,这就是一次堆栈溢出

  11. Shellcode的编写

  12. Shellcode.c #include <stdio.h> void main() { char *name[2]; name[0] = "/bin/sh"; name[1] = NULL; execve(name[0], name, NULL); }

  13. execve( ) • execve函数将执行一个程序 • 程序的名字地址作为第一个参数 • 一个内容为该程序的argv[i](argv[n-1]=0)的指针数组作为第二个参数 • (char*) 0作为第三个参数

  14. execve的汇编代码 $ gcc -o shellcode -static shellcode.c $ gdb shellcode (gdb) disassemble __execve Dump of assembler code for function __execve:

  15. 如何精简? • 经过以上的分析,可以得到如下的精简指令算法: movl $execve的系统调用号,%eax movl "bin/sh\0"的地址,%ebx movl name数组的地址,%ecx movl name[n-1]的地址,%edx int $0x80 ;执行系统调用(execve)

  16. 问题 • 当execve执行成功后,程序shellcode就会退出,/bin/sh将作为子进程继续执行 • 如果execve执行失败,(比如没有/bin/sh这个文件),CPU就会继续执行后续的 指令,结果不知道跑到哪里去了 • 所以必须再执行一个exit()系统调用, 结束shellcode.c的执行

  17. exit(0)汇编代码

  18. exit(0) 的汇编代码精简 movl $0x1,%eax ;1号系统调用 movl 0,%ebx ;ebx为exit的参数0 int $0x80 ;引发系统调用

  19. execve + exit movl $execve的系统调用号, %eax movl “bin/sh\0”的地址, %ebx movl name数组的地址, %ecx movl name[n-1]的地址, %edx int $0x80 ;执行系统调用(execve) movl $0x1,%eax ;1号系统调用 movl 0,%ebx ;ebx为exit的参数0 int $0x80 ;执行系统调用(exit)

  20. 万事具备,还欠什么? • 字符串“/bin/sh” • name数组 • execve + exit 问题 • 每一次程序都是动态加载,字符串和name数组的地址都不是固定的 • 在shellcode中如何知道它们的地址呢?

  21. jmp + call

  22. 在large_string中填入buffer的地址

  23. 把shell代码放到large_string 的前面部分

  24. 将large_string拷贝到buffer中

  25. 造成溢出,使返回地址变为buffer,而buffer的内容为shell代码。这样当程序试从 strcpy() 中返回时,就会转而执行shell

  26. 如何利用别人的漏洞

  27. 利用别人的程序的堆栈溢出获得rootshell • 以一个有strcpy堆栈溢出漏洞的程序,利用前面说过的方法来得到shell 同样必须完成两件事 • 把自己的shellcode提供给对方,让对方可以访问shellcode • 修改对方的返回地址为shellcode的入口地址

  28. How • 必须知道strcpy(buffer,ourshellcode中,buffer的地址 • 因为当我们把shellcode提供给strcpy之后,buffer的开始地址就是shellcode的 开始地址 • 必须用这个地址来覆盖堆栈

  29. How • 对于操作系统而言,一个shell下的每一个程序的堆栈段开始地址都是相同的 • 可以写一个程序,获得运行时的堆栈起始地址,这样,我们就知道了目标程序堆栈的开始地址

  30. 下面这个函数,用eax返回当前程序的堆栈指针。(所有C函数的返回值都放在eax寄存器 里面) --------------------------------------------------------------- unsigned long get_sp(void) { __asm__("movl %esp,%eax"); } ---------------------------------------------------------------

  31. 猜? • 我们在知道了堆栈开始地址后,buffer相对于堆栈开始地址的偏移,是程序员自己写出来的程序决定的,我们不知道,只能靠猜测了 • 不过,一般的程序堆栈大约是几K 左右。所以,这个buffer与上面得到的堆栈地址,相差就在几K之间 • 显然猜地址这是一件很难的事情0-10K

  32. 前面我们用来覆盖堆栈的溢出字符串为: • 现在变为: 其中: • N为NOP.NOP指令意思是什么都不作,跳过一个CPU指令周期。在intel机器上,NOP指令的机器码为0x90 • S为shellcode • A为我们猜测的buffer的地址 这样,A猜大了也可以落在N上,并且最终会执行到 S

More Related