关于栈溢出的研究

本文最后更新于:2023年11月8日 中午

关于栈溢出的研究

最近入坑pwn,学习了很多偏底层的知识点,在这里试着总结(CV)一下。

0x00 关于栈溢出

1. call&ret

说到栈就不得不提到程序调用,说到程序调用就不得不提到call和ret,call指令的操作是调用一个进程,指示处理器从新的内存地址开始执行而最终又通过ret来使处理服务回到调用方的进程上。

这个过程从底层角度来说,就是call的时候将返回地址压入堆栈,然后再把被调用的指令地址放入EIP(指令寄存器),当被调用部分完成并return时,就会从堆栈区把返回地址弹回到指令指针寄存器。

函数调用栈在内存中从高地址向低地址生长,所以栈顶指针ESP在压栈时需要–,退栈时++

2. 关于函数调用

函数状态主要会涉及到三个寄存器(ESP, EBP, EIP)

  • ESP 存储函数调用栈的栈顶地址,在压栈退栈时发生变化
  • EBP 存储当前函数状态的基地址,在函数运行时不变,用来索引确定函数参数或局部变量的位置
  • EIP 存储即将执行的指令的地址

3. 函数调用过程细节

  1. 将被调用函数参数压栈

  1. 将被调用函数的返回地址压栈

  1. ESP压栈,并将当前栈顶地址传到EBP

  1. 将被调用函数的局部变量压栈

  1. 将被调用函数的局部变量弹出

  1. 将调用方的EBP弹出,并存入EBP

  1. 将被调用函数的返回地址弹出,并存入EIP

而根据栈溢出不同的修改覆盖方式,又存在以下方法

  • shellcode 修改返回地址指向溢出数据的一段指令
  • ret2libc 修改返回地址指向内存中已有的函数
  • ROP 修改返回地址指向内存中已有的一段指令
  • hijack GOT 修改被调用函数的地址,指向另一个函数

0x01 栈溢出的防御手段

根据checksec的保护机制检查总结如下:

  • RELRO 有Partial RELRO和FULL RELRO,如果开启FULL RELRO,意味着我们无法修改got表,设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,以此来减少对GOT表的攻击
  • Stack 如果栈中开启了Canary found,那么久不能直接用溢出的方法覆盖栈中的返回地址,而且要通过改写指针与局部变量、leak canary、overwrite canary的方法绕过
  • NX 开启后栈中的数据没有执行权限,所以覆盖的call esp,jmp esp,亦或是直接shellcode的方法都不能使用

0x02 ROP的研究

ret2syscall

通过执行gadgets改变寄存器的值然后控制程序执行系统调用,系统调用的方法就是shellcode,而改变寄存器值的方法如下

  • 32位程序

    1. 将EAX寄存器的值设置为0xb EAX=0xb
    2. 将EBX寄存器的值设置为”/bin/sh”字符串的地址:EBX=&(“/bin/sh”)
    3. 将ECX和EDX寄存器的值设置为0: ECX=EDX=0
  • 64位程序

    1. 将RAX寄存器的值设为0x3b RAX=0x3b
    2. 将RDI寄存器的值设置为”bin/sh”字符串地址 RDI=&(“/bin/sh”)
    3. 将RSI和RDC寄存器的值都设为0 RSI=RDX=0

ret2libc

控制函数执行libc中的函数,通常是返回到某个函数的plt处或者函数的实际地址,如果这样做我们就需要知道system函数的地址,且需要找到”/bin/sh”字符串的地址,而system函数的执行结构如下图所示,所以在得到两个地址后中间参数任意填充即可

system_call

这里需要特别提到如何在栈中正确的布置函数和参数的位置

  • 在32位的程序中,栈里面一个函数地址后面紧跟着的就是程序执行之后的返回地址,然后再依次存放函数的参数
  • 在64位的程序中也会遵循同样的规则,但是函数的参数将会先放在寄存器RDI,RSI,RDX,RCX,R8,R9等当中,直到第7个参数才会放入栈中

ret2_dl

_dl_runtime_resolve

当第一次调用某一个函数时会调用本函数,并执行如下过程:

_dl_runtime_resolve(link_map_obj, reloc_index)

  1. 首先用link_map访问.dynamic, 分别取出.dynstr .dynsym .rel.plt的地址
  2. .rel.plt + 参数reloc_index,求出当前函数的重定位表项Els32_Rel指针,记作rel
  3. rel->r_info >> 8=n作为.dynsym的下标,求出当前函数的符号表项Elf32_Sym的指针,记作sym
  4. .dynstr + sym->st_name得出符号名字字符串指针
  5. 在动态链接库查找这个函数的地址,并且把地址赋给*rel->r_offset,即GOT表
  6. 调用函数

攻击原理

  1. 伪造字符串表,使得符号对应的名称被我们控制
  2. 伪造符号表,指向伪造的字符串表
  3. 伪造重定位表项,使得重定位表项所指的符号指向伪造的符号表
  4. 控制reloc_index的大小,将重定位表项指向伪造的重定位表
  5. 给定_dl_runtime_resolve函数的参数link_map_obj和reloc_index

攻击成功的原因主要有二:

  1. _dl_runtime_resolve函数不会检查对应的符号是否越界,只会根据参数link_map和reloc_index来执行
  2. _dl_runtime_resolve函数的对链接的函数依赖于给定的字符串

attack_method

具体计算方法:

  • n=fake_rel_plt_addr - .rel.plt_addr
  • r_info=((fake_dynsym_addr)/ 0x10)<<8 + 0x7
  • name_offset=fake_dynstr_addr - .dynstr_addr

关于栈溢出的研究
https://www.0error.net/2022/09/30289.html
作者
Jiajun Chen
发布于
2022年9月23日
许可协议