6.1.16 pwn HITBCTF2017 1000levels

下载文件

题目复现

$ file 1000levels
1000levels: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=d0381dfa29216ed7d765936155bbaa3f9501283a, not stripped
$ checksec -f 1000levels
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Partial RELRO   No canary found   NX enabled    PIE enabled     No RPATH   No RUNPATH   No      0               6       1000levels
$ strings libc-2.23.so | grep "GNU C"
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu9) stable release version 2.23, by Roland McGrath et al.
Compiled by GNU CC version 5.4.0 20160609.

关闭了 Canary,开启 NX 和 PIE。于是猜测可能是栈溢出,但需要绕过 ASLR。not stripped 可以说是很开心了。

玩一下:

$ ./1000levels
Welcome to 1000levels, it's much more diffcult than before.
1. Go
2. Hint
3. Give up
Choice:
1
How many levels?
0
Coward
Any more?
1
Let's go!'
====================================================
Level 1
Question: 0 * 0 = ? Answer:0
Great job! You finished 1 levels in 1 seconds

Go 的功能看起来就是让你先输入一个数,然后再输入一个数,两个数相加作为 levels,然后让你做算术。

但是很奇怪的是,如果你使用了 Hint 功能,然后第一个数输入了 0 的时候,无论第二个数是多少,仿佛都会出现无限多的 levels:

所以应该重点关注一下 Hint 功能。

题目解析

程序比较简单,基本上只有 Go 和 Hint 两个功能。

hint

先来看 hint:

可以看到 system() 的地址被复制到栈上(local_110h),然后对全局变量 show_hint 进行判断,如果为 0,打印字符串 “NO PWN NO FUN”,否则打印 system() 的地址。

为了绕过 ASLR,我们需要信息泄漏,如果能够修改 show_hint,那我们就可以得到 system() 的地址。但是 show_hint 放在 .bss 段上,程序开启了 PIE,地址随机无法修改。

go

继续看 go:

可以看到第一个数 num1 被读到 local_120h,如果大于 0,num1 被复制到 local_110h,然后读取第二个数 num2 到 local_120h,将两个数相加再存到 local_110h。但是如果 num1 小于等于 0,程序会直接执行读取 num2 到 local_120h 的操作,然后读取 local_110h 的数值作为 num1,将两数相加。整个过程都没有对 local_110h 进行初始化,程序似乎默认了 local_110h 的值是 0,然而事实并非如此。回想一下 hint 操作,放置 system 的地址正是 local_110h(两个函数的rbp相同)。这是一个内存未初始化造成的漏洞。

接下来,根据两数相加的和,程序有三条路径,如果和小于 0,程序返回到开始菜单;如果和大于 0 且小于 1000,进入游戏;如果和大于 1000,则将其设置为最大值 1000,进入游戏。

然后来看游戏函数 sym.level_int()

可以看到 read() 函数有一个很明显的栈溢出漏洞,local_30h 并没有 0x400 这么大的空间。由于游戏是递归的,所以我们需要答对前 999 道题,在最后一题时溢出,构造 ROP。

漏洞利用

总结一下,程序存在两个漏洞:

  • hint 函数将 system 放到栈上,而 go 函数在使用该地址时未进行初始化

  • level 函数存在栈溢出

关于利用的问题也有两个:

  • 虽然 system 被放到了栈上,但我们不能设置其参数

  • 程序开启了 PIE,但没有可以进行信息泄漏的漏洞

对于第一个问题,我们有不需要参数的 one-gadget 可以用,通过将输入的第二个数设置为偏移,即可通过程序的计算将 system 修改为 one-gadget。

这里我们选择 0x4526a 地址上的 one-gadget。

第二个问题,在随机化的情况下怎么找到可用的 ret gadget?这时候可以利用 vsyscall,这是一个固定的地址。(参考章节4.15)

但我们必须跳到 vsyscall 的开头,而不能直接跳到 ret,这是内核决定的。

最后一次的 payload 和调试结果如下:

三次 return 之后,就会跳到 one-gadget 上去。

Bingo!!!

exploit

完整的 exp 如下:

参考资料

Last updated

Was this helpful?