hitb2017_1000levels(利用vsyscall滑动Bypass PIE)
hitb2017_1000levels(利用vsyscall滑动Bypass PIE)
查看防护措施
没开canary,开了PIE,应该能利用到栈溢出漏洞
本地调试
hint里面看着没什么东西,go里面两次输入然后回答问题
IDA静态分析
show_hint == 0 是放在bss段的,且开了PIE地址随机化,这里目前来看是没有什么办法去修改其为1,不过
不论show_hint为几,system的地址都会被写到rbp-0x110的位置
我们看一下go的逻辑
v4没有初始化,如果我们第一次输入的数大于0,才会使v4 = num,而且v4也是rbp-110h处的数据,由于这个函数和hint函数都是在主函数里依次调用的,它们的rbp是同一个,只是在不同时刻使用而已。那么,如果我们先执行一次hint,再进入这个函数,那么v4就会存储着system的地址
进入存在栈溢出漏洞的level函数看一下
rbp压栈,rsp抬高0x40此时的栈空间是这样的:
如果我们能把ret,rbp-0x120,rbp-0x118滑掉,那么不就执行system了吗?然而,system函数需要一个参数,并且x64使用寄存器传参,由于开了PIE我们泄露不了基地址也无法使用pop_rdi等gadgets,那么我们就不能用system了,可以考虑用one_gadget
大致思路
现在捋一捋思路
1.我们需要把rbp-0x110的位置内容改为one_gadget地址
2.我们需要滑动到rbp-0x110的位置
静态one_gadget我们直接用工具可以得到,这里可以选用0x45216,但是libc基地址如何得到?
我们再看一下go中的逻辑:
v5 = v4 + 我们的输入,且v4中保存的是system地址,那如果我们输入的是one_gadget - libc.sym[‘system’]是不是相当于 system_addr - libc.sym[‘system’] + one_gadget?这不就是one_gadget + libc_base么
那么现在想一下如何解决2,
有一处地址不管是否开启PIE都不会变:vsyscall
vsyscall
vsyscall是第一种也是最古老的一种用于加快系统调用的机制,工作原理十分简单,许多硬件上的操作都会被包装成内核函数,然后提供一个接口,供用户层代码调用,这个接口就是我们常用的int 0x80和syscall+调用号。
当通过这个接口来调用时,由于需要进入到内核去处理,因此为了保证数据的完整性,需要在进入内核之前把寄存器的状态保存好,然后进入到内核状态运行内核函数,当内核函数执行完的时候会将返回结果放到相应的寄存器和内存中,然后再对寄存器进行恢复,转换到用户层模式。
这一过程需要消耗一定的性能,对于某些经常被调用的系统函数来说,肯定会造成很大的内存浪费,因此,系统把几个常用的内核调用从内核中映射到用户层空间中,从而引入了vsyscall
在gdb 中使用命令 : dump binary memory file start_addr end_addr 将这段地址dump下来拖进IDA
三次系统调用分别是__NR_gettimeofday、__NRtime、_NR_getcpu
1 |
我们要利用的就是最后的那个retn,因为它会从栈顶弹出一个元素,就相当于esp下移了一个单位。
三次的vsyscall,相当于从这片区域滑到了Go函数的rbp-110处,这样,接下来就会执行one_gadget了
当我们直接调用vsyscall中的syscall时,会提示段错误,这是因为vsyscall执行时会进行检查,如果不是从函数开头执行的话就会出错
所以,我们可以直接利用的地址是0xffffffffff600000、0xffffffffff600400、 0xffffffffff600800
EXP:
1 | from pwn import * |
不过这里打远程的时候起初连的公司的WIFI,每次跑到700多就G了,最后连的自己的热点才成功getshell,但是getshell之后大概也就过了1秒就断开连接了,无奈又添加了两行自动ls,cat flag才成功

