The seven medium challenges presented challenges across the Web Security, Fun, Network Security, Forensic, Crypto, and Reverse Engineering categories. While I’m not always a fan of cryptography challenges, both day 13 and 14 were fantastic, the former having me abuse a weak hash algorithm to bypass signing requirements, and the latter having me recover an encrypted file and key from a core dump. There’s also a Bash webserver with an unquoted variable, a PCAP with a flag in the TCP source ports, Jinja2 (Flask) template injection, steganography, and recovering the seed used for Python’s random function.
HV23.08
Challenge
HV23.08 SantaLabs bask
Categories:
WEB_SECURITY
Level:
medium
Author:
coderion
Ditch flask and complicated python. With SantaLabs bask, you can write interactive websites using good, old bash and even template your files by using dynamic scripting!
There’s a download and a container to spin up.
Enumeration
Website Enumeration
The website is a simple front page:
The “Home” link leads to /, and the “Login” link to /login, which presents a password field:
If I send a password, it shows a failure message:
Source
The source has a main Bash script (bask.sh), a Dockerfile, and two directories:
oxdf@hacky$lsbask.sh Dockerfile files templates
bask.sh defines a pipe, response, and a handleRequest function. Then it simply forever sends that pipe into nc and what nc gets to handleRequest:
#!/usr/bin/bash##################################### BASK v0.0.1 Alpha# Webserver in Bash##################################### Create the response FIFOrm-f response
mkfifo response
function handleRequest(){
...[snip]...
}echo'Listening on 0.0.0.0:3000...'# Serve requestswhile true;do
cat response | nc -lN 3000 | handleRequest
done
handleRequest does some parsing to get the HTTP verb and path, and then calls this switch statement to do routing:
STATUS_CODE=200
# Route to the response handler based on the REQUEST matchcase"$REQUEST"in### Static files"GET /files/styles.css")ADDITIONAL_HEADERS="Content-Type: text/css"RESPONSE=$(cat files/styles.css);;### Routes"GET /")RESPONSE=$(bash templates/index.sh);;"GET /login")RESPONSE=$(bash templates/get_login.sh);;"POST /login")RESPONSE=$(POST_PASSWORD=$INPUT_VALUE bash templates/post_login.sh);;"GET /admin")RESPONSE=$(COOKIES=$COOKIES bash templates/admin.sh);;### Default (404)*)STATUS_CODE=404
RESPONSE=$(bash templates/404.sh);;esac
For each route, it’s running another script and getting the output.
post_login.sh seems like a good place to look, as that’s where the password is handled:
The header and footer are run as other scripts. The interesting part is where the password sent in the request ($POST_PASSWORD) is compared to $ADMIN_PASSWORD. That must be assigned outside of the script. If they match, then the response has JavaScript that sets the cookie admin_cookie=$POST_PASSWORD. This would be a lot easier if it set it to $ADMIN_PASSWORD, but it doesn’t.
The other interesting file is admin.sh:
#!/bin/bash# Include header
bash templates/header.sh
# We only have one cookie so first value should be admin passwordFIRST_COOKIE=$(cut-d"="-f 2 <<<"$COOKIES")# Check if admin password is validif[["$FIRST_COOKIE"=="$ADMIN_PASSWORD"]];then
cat<<EOF
<main role="main" class="inner cover">
<h1 class="cover-heading">Admin Panel</h1>
<p>Your flag is: $FLAG</p>
</main>
EOF
else
cat<<EOF
<main role="main" class="inner cover">
<p>You are not authorized to view this page.</p>
<script>
document.cookie = "";
setTimeout(function() {
window.location.href = "/login";
}, 2000);
</script>
</main>
EOF
fi# Include footer
bash templates/footer.sh
If the cookie value matches $ADMIN_PASSWORD, then it gives the flag. Otherwise, it redirects to /login.
Glob
Half Success
Bash has issues when comparing variables without “” around them, as happens in the post_login.sh script:
if[[$ADMIN_PASSWORD==$POST_PASSWORD]];then
The issue is that if one of the variables has a wildcard (like *), then that will process. So sending the password * will match! If I send *, then I’ll briefly see:
Then:
Then a redirect to /login.
That’s because when the check in post_login.sh works, it sets my cookie to *:
When it goes to /admin, there the check is in quotes:
if[["$FIRST_COOKIE"=="$ADMIN_PASSWORD"]];then
So that fails, and redirects.
Brute Force
I can still learn from this. If I send a password of a*, if the password starts with “a”, then it will return the page with the /admin redirect. If it does not, it will say “Invalid username or password”. I can write a Python script to bruteforce the entire password this way:
I’m using a custom alphabet to remove some special characters that might not play nicely with the script / Bash.
I can’t use data={"password": f"{password}{c}*"} in requests, as it will encode the asterisk, and then the glob expansion will not happen. But this way does work.
Brute forcing on this server does also cause a bunch of crashes, which is why I try the same password over and over until I get 200 in the response.
Once I match the next character, I try logging in with the current password, using success there as the end state to break the overall loop.
This runs in about two minutes:
oxdf@hacky$time python brute.py
salami
real 1m42.754s
user 0m2.347s
sys 0m0.046s
The script will print the characters as it tries them to show progress.
Once I have the password, I can log in and get the flag:
Flag: HV23{gl0bb1ng_1n_b45h_1s_fun}
HV23.09
Challenge
HV23.09 Passage encryption
Categories:
FUN NETWORK_SECURITY
Level:
medium
Author:
dr_nick
Santa looked at the network logs of his machine and noticed that one of the elves browsed a weird website. He managed to get the pcap of it, and it seems as though there is some sensitive information in there?!
The download is a PCAP file:
oxdf@hacky$file secret_capture.pcapng
secret_capture.pcapng: pcapng capture file - version 1.0
PCAP Summary
In Wireshark, the Statistics > Endpoints popup shows the hosts and services that are in the PCAP:
There’s two hosts. 192.168.1.10 is the webserver, and 192.168.1.12 is the client making requests to it.
HTTP
Enumeration
The session starts with a request for /, which returns the HTML as well as 10 door images and some CSS (I can pull this out and reconstruct the page):
Each door is a link to /?door=X, where X is the numbers 0-9. With the filter http.request.method==GET, I’ll see the initial requests to load the page:
And then the user clicking on doors:
As the doors are clicked, they are added below the break. For example, after the 9th click:
After the last click, the returned page no longer offers doors:
False Flag
My initial thought is to look at the numbered entered via doors. I’ll use tshark to get the URIs:
I’ll use Python to quickly view that as hex and ASCII:
>>>x=2239869409783327317220697624099369>>>f"{x:x}"'6e6f20666c616720686572653a29'
>>>bytes.fromhex(f"{x:x}")b'no flag here:)'
It’s a troll. But it’s also a good sign that the input numbers aren’t the flag. Trying to make them decode to something else while still showing that message would be nearly impossible.
TCP
Source Ports
Going back to the TCP ports, I’ll notice something interesting:
72 is the decimal representation of “H”, and 86 is “V”. 50 is “2” and 51 is “3”. That looks like a flag!
Initial Fail
My initial reaction is to grab the last two characters of the source port and convert them to ASCII:
Where the flag starts, it goes 56772, 56786, 56750, 56751, 56823, then 56776. 56776 is becoming 76 -> “L”. That means 56823 is printing something unprintable. 23 is the ETB character. I want a “{“, which is 123. This is where it clicked for me. I shouldn’t be cutting off the last two characters, but rather subtracting 56700.
Instead of removing 80s with grep, I remove anything that starts with F after subtracting and converting to hex, which would be anything negative.
Flag: HV23{Lo0k1ng_for_port5_no7_do0r$}
HV23.10
Challenge
HV23.10 diy-jinja
Categories:
WEB_SECURITY
Level:
medium
Author:
coderion
We’ve heard you like to create your own forms. With SANTA (Secure and New Template Automation), you can upload your own jinja templates and have the convenience of HTML input fields to have your friends fill them out! Obviously 100% secure and even with anti-tampering protection!
There’s both a docker instance to spawn and a download of the source code.
Enumeration
Site
The site boasts of template automation:
It has a form asking for a Jinja template file. I think it wants me to give it the fields as well. In Jinja, variable are printed inside {{ }}, and control commands are inside {% %}. I’ll make a simple page:
this is a: {{ a }}
And upload it:
When I “Upload”, the resulting page is /form/{uuid}, and it asks for the “test variable”:
If I enter “0xdf” and click “Generate”, it shows the template filled in with that variable as “0xdf”:
Source
The source has an app.py, as well as a Dockerfile and a flag.txt:
oxdf@hacky$lsapp.py Dockerfile flag.txt
flag.txt is a fake flag. The Dockerfile shows the OS is Alpine and that the application and flag.txt are in /app:
FROM alpine:latest
RUN apk update
RUN apk add python3 py3-flask
COPY app.py flag.txt /app/
COPY form_template.html index.html /app/templates/
ENTRYPOINT ["python", "/app/app.py"]
app.py is a Flask web application that defines three routes. The first is the static index page:
# Prevent any injections
jinja_objects=re.findall(r"{{(.*?)}}",open(tmp_path).read())forobjinjinja_objects:ifnotre.match(r"^[a-z ]+$",obj):# An oopsie whoopsie happened
returnResponse(f"Upload failed for {tmp_path}. Injection detected.",status=400)
This is using re.findall to get all {{ }} blocks and makes sure that only lowercase characters and spaces are between them. This is supposed to allow variable names without allowing any more complex code.
Then it creates the form to offer rendering using the fields, redirecting to the next path:
# If file is injection-free, save it
os.rename(tmp_path,template_path)withopen(os.path.join(app.config["TEMPLATES_FOLDER"],f"{template_id}_form.html"),"w")asf:f.write(render_template("form_template.html",fields=fields,template_id=template_id))returnredirect(url_for("render_form",template_id=template_id))
The last route takes both GET and POST requests. The GET request returns a simple template with the id filled in:
@app.route("/form/<template_id>",methods=["GET","POST"])defrender_form(template_id):# On render
ifrequest.method=="POST":...[snip]...# User just wants to GET
returnrender_template(f"{template_id}_form.html",template_id=template_id)
If it’s a POST, then it emptys out the globals variable, loads the items passed in the form, and renders the uploaded template:
# Render the Jinja template with the provided data
template=secure_filename(template_id+".html")# Prevent hackers
app.jinja_env.globals={}# Set the parameters as globals
forvar_name,var_valueinrequest.form.items():app.jinja_env.globals[var_name]=var_value# Render the template
returnrender_template(template)
Solutions
It’s clear that I need a server-side template injection (SSTI) here. I’ll show X ways.
Control Block
It is possible to do a SSTI attack on Jinja without {{ }} (and even easier if I can use a simple block there with a variable to display). I first showed this on HTB Spider.
A simple test of this is to leak the available subclasses through the config variable with this template:
It works. I’ll search this for useful classes, and subprocess.Popen is there:
I’ll copy all of these into a text file, and replace “, “ with newline. It’s on line 453, which is 452 (because these lines are 1-indexed and the list is 0-indexed). I’ll verify this with the following payload:
An alternative way is to use {{ }} but break the regex. The easiest way to do that is to include a newline, as the . in re.findall(r"{{(.*?)}}", open(tmp_path).read()) doesn’t include newline, unless re.DOTALL is given as an option.
Many Steg tools (I usually use stegsolve.jar locally) will show the image with 8 “channels” for each color. This is effectively showing each bit as on or off for that color for that pixel. aperisolve.com has a nice display of this output. For the green channel, it looks like this, fairly normal:
The blue and red channels has oddities. Red only uses four bits, with the high bits always (dark is 0 here):
That means it’s values only go 0-115, and judging by the amount of black in the 4th bit, most are 0-7.
Blue uses 7 bits:
There’s also some patterns in the 6th and 7th bits (and maybe the 5th). Seven bits is interesting because that’s basically ASCII range.
Python Analysis
I’ll write a simple Python script to look at the pixel data:
Right aware the red column jumps out as PI (3.141592653…). That’s interesting.
Flag
The green values make up most of the picture. So if there’s data in the green, it would have to be like a least significant bit. It makes more sense to focus on the red and blue values.
The blue values are mostly ASCII, though not meaningfully. The red values are small. That means I can combine them and still get something ASCII-ish. I’ll try a few different methods, but XOR is what works:
oxdf@hacky$python solve.py
Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. HV23{pi_1s_n0t_r4nd0m}Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna let you down. Never gonna give you up. Never gonna give you up. Never gonna give you up. Never gonna give
Santa looked at the network logs of his machine and noticed that one of the elves browsed a weird website. He managed to get the pcap of it, and it seems as though there is some sensitive information in there?!
Solution
The previous challenge had the flag buried in some Rick Roll text. Closer inspection shows that the pattern of “up” and “down” is not everyother:
oxdf@hacky$python solve.py | grep-oP"(up|down)" | headup
down
up
up
down
up
up
up
up
down
If these are bits are I’m looking for ASCII, up would have to be 0, so I’ll try that, using sed to replace “up” with 0 and “down” with 1, then tr to remove newlines and perl to convert binary string to raw data:
To train his skills in cybersecurity, Grinch has played this year’s SHC qualifiers. He was inspired by the cryptography challenge unm0unt41n (can be found here) and thought he might play a funny prank on Santa. Grinch is a script kiddie and stole the malware idea and almost the whole code. Instead of using the original encryption malware from the challenge though, he improved it a bit so that no one can recover his secret!
Luckily, Santa had a backup of one of the images. Maybe this can help you find the secret and recover all of Santa’s lost data…?
The download has a couple directories and a malware.py file:
It seeds the random number generator with the flag, and then it uses that to loop over each file in memes, xoring each byte with the next output of random.getrandombites(32).
Break Mersenne
Background
The random number generating algorithm used by Python’s random module is Mersenne Twister (or mt19937). This algorithm is vulnerable to attack if at least 624 four-byte random values in a row are known. I actually performed this attack in the Snowbacll Fight terminal in the 2020 Sans Holiday Hack.
By giving me one pre-encrypted image, I can get the xor bytes used in it, and as long as the image is more than 624 * 4 = 2496 bytes, I’ll have enough to get the future “random” words, and then decrypt the other images.
The difference here vs other challenges like this is that rather than having the flag in one of the encrypted files, the flag is the seed.
RandCrack
RandCrack is a common tool for recovering the random stream. It won’t give the seed, but I can at least use it to validate that I’m on the right path.
I’ll read in the plaintext and encrypted file, and then create a key_stream by XORing their bytes. To get four byte words, I’ll use another loop and chunked. There’s almost certainly a better way to do this, but, it works.
Then I’ll create a RandCrack instance, and feed it the first 624 words from key_chunks. Now I can use that to get what the 625th word would have been:
oxdf@hacky$python solve.py
predicted number: 3500890921
actual number: 3500890921
It’s working.
Seed Recovery
I spent a long time while this was live reading CTF writeups trying to find someone who has recovered the seed value. There isn’t a ton of practical application for having the seed, as typically it’s a throw-away number like the current unix timestamp.
Of the things I found that didn’t help but were relevant:
untwister from Bishop fox. It’s quite old, and runs on Python2. I managed to get it running in a Docker container, but it never found the seed. I am writing this 14 hours after I started running it and it’s still looking.
This repo has tools that will be useful. Thismersenne.py file has a few things, but of interest is the function get_seeds_python_fast in the BreakerPy object. I’ll save the script to the same directory as my script and update my script to use it:
I’ll also need to add from mersenne import BreakerPy at the top.
This function tracks the time it takes to find the seeds as well:
oxdf@hacky$python solve.py
predicted number: 3500890921
actual number: 3500890921
time taken : 6.092571258544922
HV23{s33d_r3c0very_1s_34sy}
Flag: HV23{s33d_r3c0very_1s_34sy}
HV23.13
Challenge
HV23.13 Santa's Router
Categories:
CRYPTO
Level:
medium
Author:
Fabi_07
Santa came across a weird service that provides something with signatures of a firmware. He isn’t really comfortable with all that crypto stuff, can you help him with this?
Please note that the Hacking-Lab VPN is not necessary to solve the challenge (depends on your exploit).
Start the service, exploit it and get the flag!
The challenge comes with a spawnable Docker as well as a download.
Enumeration
Service
I’ll spawn the docker and it provides an IP and port (which is always 1337). On connecting, it offers a prompt:
oxdf@hacky$nc 152.96.15.2 1337
Welcome to Santa's secure router
santa@router:~$
The terminal has a very limited number of commands:
santa@router:~$lsCommand not found, use help to list all awailable commands.
santa@router:~$help
help - displays this menu
version - displays the current version of the firmware
update - updates the firmware with the provided signed zip file
exit - exit this shell
version shows the version and signature:
santa@router:~$version
Version 1.3.3.7, Signature: w3wH3CuQ7tgNFVNz70g0JsN6hpnJil8I7g5wAyl6sr5esqTtqnMlv1Ln9XTq6BY7r9ZhT67BKNphlIyjitnAMT8VrNcGxtQd4ualurog4f2eSElSezW2mKA7wbkXjx1Mqo16ljjQK2QBh59UMRZaPJbG1yE7QWcuEf21rEO8yLXy+hb04Q1Uw/kKYTbGJYo2I4WbKKayhWFpEQbyOn0TzGV8K/9Xdj1KZQGPQXbNsCktIkU8M7SoM4PaeEt2I8GvaYsXVRetE1Z9ijzs9BvftaiawI6UNVt9fMdxUA/bTwir43RgVfeRcvpCW8D8p1BhrjIfmLNfdOvYYeiyFhdHwA==
update prompts for a base64-encoded zip file and it’s signature:
santa@router:~$update
Please provide a base64 encoded zip file:
> test
Please provide a valide pkcs1_15 signature for the zip file:
> test
Signature is invalid
exit exits.
Source
The code has four files:
oxdf@hacky$unzip -l santas-router-source.zip
Archive: santas-router-source.zip
Length Date Time Name
--------- ---------- ----- ----
2840 2023-12-12 22:26 chall.py
199 2023-12-12 21:26 Dockerfile
179 2023-11-17 20:59 firmware.zip
15 2023-12-12 22:25 flag
--------- -------
3233 4 files
The flag is fake. The Dockerfile shows that it runs out of /app, and that it’s hosted with socat, which means each time I connect I get a different Python process.
FROM alpine:latest
RUN apk update
RUN apk add socat python3 py3-pycryptodome curl
WORKDIR /app
COPY firmware.zip chall.py flag .
ENTRYPOINT socat tcp-l:1337,reuseaddr,fork EXEC:"python3 chall.py"
It’s also running Apline Linux, so no Bash, but yes python3 and curl.
firmware.zip has a single file, start.sh:
oxdf@hacky$unzip -l firmware.zip
Archive: firmware.zip
Length Date Time Name
--------- ---------- ----- ----
29 2023-11-17 20:59 start.sh
--------- -------
29 1 file
start.sh just has a single line, echo "Nothing to see here...".
chall.py has the server. At the top, it generates an RSA key:
KEY=RSA.generate(2048)
Because this is generated on each run, it will change for each connection. Then it defines four functions, calculates the signature of the current firmware.zip, and prints the welcome message :
defhashFile(fileContent:bytes)->int:...[snip]...defverifySignature(fileContent:bytes,signatureEncoded:str)->bool:...[snip]...deffileSignature(fileContent:bytes):...[snip]...defverifyAndExtractZipFile(fileContentEncoded:str,signature:str):...[snip]...withopen('firmware.zip','rb')asf:SIGNATURE=fileSignature(f.read())print(f"\033[1mWelcome to Santa's secure router\033[0m\n")
Finally, it enters a while true loop offering the prompt and handling the commands matching what I observed above:
whileTrue:command=input(f"\x1b[32msanta@router\x1b[0m:\x1b[34m~\x1b[0m$ ")ifcommand.startswith('help'):print(f'''
help - displays this menu
version - displays the current version of the firmware
update - updates the firmware with the provided signed zip file
exit - exit this shell
''')elifcommand.startswith('version'):print(f'''
Version 1.3.3.7, Signature: {SIGNATURE.decode()}''')elifcommand.startswith('update'):zipFile=input('''Please provide a base64 encoded zip file:
> ''')signature=input('''Please provide a valide pkcs1_15 signature for the zip file:
> ''')verifyAndExtractZipFile(zipFile,signature)elifcommand.startswith('exit'):exit(0)else:print('''Command not found, use help to list all awailable commands.
'''
The interesting call is verifyAndExtractZipFile. This function base64 decodes the input, and then passes that to verifySignatre along with the given signature. If that passes, it extracts the files in the zip to ./www/root/, and calls the start.sh file as:
p=subprocess.Popen(['/bin/sh',filePath],stdout=subprocess.PIPE,stderr=subprocess.PIPE)print(f'Update exited with statuscode {p.wait()}')
The functions do basically what they say they will do. hashFile uses a custom hash algorithm to generate an eight-byte hash:
For each byte (0-7) in position j, it shifts it left by 8*j bits, creating an eight-byte value reversing the byte order. And then it XORs that with the hash value so far, and continues.
Strategy
The attack here is that it is very easy to append eigh bytes to any file and make the hash whatever I want. That means if I have a valid hash / signature pair, I can craft new files with the same hash and therefore the same signature.
Zip files are very tolerant to extra data on the end, so that part is easy.
Verify Known Good
Much of this strategy relies on my being able to have a known valid hash / signature pair for a file. I’ll try to submit the given firmware. I’ll base64 encode the given firmware.zip:
I’ll submit that to the challenge, after getting the signature so I can send that back as well. If the current firmware.zip on the docker is the same as the one I have, then this should work:
oxdf@hacky$nc 152.96.15.2 1337
Welcome to Santa's secure router
santa@router:~$version
Version 1.3.3.7, Signature: GJbEKuQ7QyeK7XYsALtN03ynPX/PbutKLCA99l35RsS7Fq1qdk/VKt9uB5tBqUk229cN6MnxbZO65KrojVJTQ5A+5iqomz4dNc6PTNIAOoqJ1PtqL3u1yqkaFl8gXu8eaKjnhbIYRpWhdf2c/DV/Xd1qyXD5iVzrk2dnlv5CHsvT10oe4lj878ycc7Yia4z9DM6vKiNLtB/MN7A7LHv/xFCFuXAeQDpjtKbPtMQU0FODibsJJCHlPErjgevZG73ZPFpMJa+g/p2Lc9zKAav0Vb60pCmjC8/K8Kr+rSFMcqAR/82OCS1SNuHE1VH1xH4Sq6QpMwpCAuv6Yy4B2z/ZKA==
santa@router:~$update
Please provide a base64 encoded zip file:
> UEsDBAoAAAAAAGOncVd9oXr5HQAAAB0AAAAIAAAAc3RhcnQuc2hlY2hvICJOb3RoaW5nIHRvIHNlZSBoZXJlLi4uIlBLAQIfAAoAAAAAAGOncVd9oXr5HQAAAB0AAAAIACQAAAAAAAAAIAAAAAAAAABzdGFydC5zaAoAIAAAAAAAAQAYAKiwYIWQGdoBqLBghZAZ2gH2AIP5iBnaAVBLBQYAAAAAAQABAFoAAABDAAAAAAA=
Please provide a valide pkcs1_15 signature for the zip file:
> GJbEKuQ7QyeK7XYsALtN03ynPX/PbutKLCA99l35RsS7Fq1qdk/VKt9uB5tBqUk229cN6MnxbZO65KrojVJTQ5A+5iqomz4dNc6PTNIAOoqJ1PtqL3u1yqkaFl8gXu8eaKjnhbIYRpWhdf2c/DV/Xd1qyXD5iVzrk2dnlv5CHsvT10oe4lj878ycc7Yia4z9DM6vKiNLtB/MN7A7LHv/xFCFuXAeQDpjtKbPtMQU0FODibsJJCHlPErjgevZG73ZPFpMJa+g/p2Lc9zKAav0Vb60pCmjC8/K8Kr+rSFMcqAR/82OCS1SNuHE1VH1xH4Sq6QpMwpCAuv6Yy4B2z/ZKA==
Update exited with statuscode 0
It seems it does. It ran the script and exited with statuscode 0.
Get Good Hash
I’ll add a print line to hashFile on my local copy:
I’ll connect to the router and get the current version signature. This signature changes because the key is changing, but the hash is still the same.
oxdf@hacky$nc 152.96.15.6 1337
Welcome to Santa's secure router
santa@router:~$version
Version 1.3.3.7, Signature: DBbKFTOakm7evaAfH2V9rANXhWxPg3KmNFxeHxIdM3DDRdI+wZdNU4STbmGQDE/YG/Wx8LLKkdqd9HTnLUUhr/BYvf07rZepyLlyS7RdioPbT0y0r4A353cYjNtFswltXXe8imca59QBSqcXXbJ0rH5oYpS6oufHy1Cp95RpoWjx5ST04yI7rCfQNaqZ1bvig2kijrH72zqAHhv5d3ASvaCE1sNLKFXjXE6cXi8hRUUrn7/USL7oM3BjtHAb7CnMCqgpg6K0Ff5DL51Njni2m9zrWgdbeGqeHwCwwuIlBwQ5CIDleJkhFbf8iUgOQnXoU4kglkL1ZVdbZYg+j8zJdw==
Now update, and it shows 223:
santa@router:~$update
Please provide a base64 encoded zip file:
> UEsDBAoAAAAAAJFgjVetBbK0FgAAABYAAAAIABwAc3RhcnQuc2hVVAkAAyLkeWUk5HlldXgLAAEEAAAAAATnAwAAIyEvYmluL2Jhc2gKCmV4aXQgMjIzClBLAQIeAwoAAAAAAJFgjVetBbK0FgAAABYAAAAIABgAAAAAAAEAAAD4gQAAAABzdGFydC5zaFVUBQADIuR5ZXV4CwABBAAAAAAE5wMAAFBLBQYAAAAAAQABAE4AAABYAAAAAAAAAAAABxS7g8zHQ1U=
Please provide a valide pkcs1_15 signature for the zip file:
> DBbKFTOakm7evaAfH2V9rANXhWxPg3KmNFxeHxIdM3DDRdI+wZdNU4STbmGQDE/YG/Wx8LLKkdqd9HTnLUUhr/BYvf07rZepyLlyS7RdioPbT0y0r4A353cYjNtFswltXXe8imca59QBSqcXXbJ0rH5oYpS6oufHy1Cp95RpoWjx5ST04yI7rCfQNaqZ1bvig2kijrH72zqAHhv5d3ASvaCE1sNLKFXjXE6cXi8hRUUrn7/USL7oM3BjtHAb7CnMCqgpg6K0Ff5DL51Njni2m9zrWgdbeGqeHwCwwuIlBwQ5CIDleJkhFbf8iUgOQnXoU4kglkL1ZVdbZYg+j8zJdw==
Update exited with statuscode 223
It ran my code!
Automate Generation / Upload
Because I’m going to be playing with all sorts of different payloads, I’ll automate the creation of a zip and uploading it with a Python script. I’ll use io so I don’t have to write files to disk in the process.
I’ll start by importing the necessary libraries and copying the hashFile function from chall.py
I’ll use BytesIO to create a file like object with the contents of my script, and another to be the zip file. Then zipfile will create the zip, and I can read the bytes and null pad it:
script_file=io.BytesIO(script.encode("utf-8"))zip_file=io.BytesIO()withzipfile.ZipFile(zip_file,'w',zipfile.ZIP_DEFLATED)asarchive:archive.writestr('start.sh',script_file.getvalue())zip_bytes=zip_file.getvalue()iflen(zip_bytes)%8!=0:zip_bytes=zip_bytes+b"\x00"*(8-(len(zip_bytes)%8))success(f"Generated zip file and padded to length {len(zip_bytes)}")
I’ll calculate the eight bytes to append to make the hash work, and append it:
oxdf@hacky$python solve.py
[+] Generated zip file and padded to length 144
[+] Opening connection to 152.96.15.2 on port 1337: Done
[+] Update exited with statuscode 223 ß
[*] Closed connection to 152.96.15.2 port 1337
Flag Strategy
There are several ways to get the flag from here. What I don’t get is standard out or standard error from the run process, only the exit code. So I’ll need to find another way to exfil the information and/or get a shell.
I’ll show three categories.
Connect Back [Method #1]
My original solve was to just get a reverse shell. I’ll download the Hacking Lab VPN repo and sudo start_openvpn.sh. This gives my system a tun0 IP that the container can talk to. I’ll update my script to send the flag over nc:
The author’s intended way to solve this is via exit code exfil. I already showed I could control that. I’ll just use that to read the flag byte by byte. I can get the length of the flag using wc:
oxdf@hacky$python solve.py
[+] Generated zip file and padded to length 160
[+] Opening connection to 152.96.15.2 on port 1337: Done
[+] Update exited with statuscode 44 ,
[*] Closed connection to 152.96.15.2 port 1337
ChatGPT suggested this line to get the nth byte of a file and get the ordinal value of it with od:
oxdf@hacky$python solve.py
[+] Generated zip file and padded to length 184
[+] Opening connection to 152.96.15.2 on port 1337: Done
[+] Update exited with statuscode 72 H
[*] Closed connection to 152.96.15.2 port 1337
I’ll update the script to include a character number as an arg:
oxdf@hacky$python solve.py 0
[+] Generated zip file and padded to length 184
[+] Opening connection to 152.96.15.2 on port 1337: Done
[+] Update exited with statuscode 72 H
[*] Closed connection to 152.96.15.2 port 1337
oxdf@hacky$python solve.py 1
[+] Generated zip file and padded to length 184
[+] Opening connection to 152.96.15.2 on port 1337: Done
[+] Update exited with statuscode 86 V
[*] Closed connection to 152.96.15.2 port 1337
oxdf@hacky$python solve.py 2
[+] Generated zip file and padded to length 184
[+] Opening connection to 152.96.15.2 on port 1337: Done
[+] Update exited with statuscode 50 2
[*] Closed connection to 152.96.15.2 port 1337
When I run this, the script listening on the socket is now just going to give me the output of flag:
oxdf@hacky$python solve.py
[+] Generated zip file and padded to length 200
[+] Opening connection to 152.96.15.2 on port 1337: Done
[+] Update exited with statuscode 0 \x00[*] Closed connection to 152.96.15.2 port 1337
oxdf@hacky$nc 152.96.15.2 1337
HV23{wait_x0r_is_not_a_secure_hash_function}
This does break my instance, so I’ll have to kill it and get a new one to try anything else.
oxdf@hacky$python solve.py
[+] Generated zip file and padded to length 200
[+] Opening connection to 152.96.15.2 on port 1337: Done
[+] Update exited with statuscode 0 \x00[*] Closed connection to 152.96.15.2 port 1337
oxdf@hacky$nc 152.96.15.2 1337
id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
pwd
/app
The discussion of this method did inspire one memory of the HackVent Discord to show this strategy, brute forcing the IP range of HackingLab dockers looking for any that spit the flag (hich I found quite amusing):
To keep today’s flag save, Santa encrypted it, but now the elf cannot figure out how to decrypt it. The tool just crashes all the time. Can you still recover the flag?
Files
Overview
The download is a zip containing two files:
oxdf@hacky$unzip -l crypto-dump.zip
Archive: crypto-dump.zip
Length Date Time Name
--------- ---------- ----- ----
6886 2023-11-08 17:58 coredump.zst
87840 2023-11-08 17:58 flagsave
--------- -------
94726 2 files
coredump.zst is a compressed archive itself:
oxdf@hacky$file coredump.zst
coredump.zst: gzip compressed data, was "dump", last modified: Wed Nov 8 16:58:09 2023, from Unix, original size modulo 2^32 196608
file shows that the original name was dump. 7z x coredump.zst will produce a file, dump.
dump
This is a 64-bit coredump file:
oxdf@hacky$file dump
dump: ELF 64-bit LSB core file, x86-64, version 1 (SYSV), SVR4-style, from 'flagsave ./flag.enc', real uid: 1000, effective uid: 1000, real gid: 100, effective gid: 100, execfn: '/etc/profiles/per-user/cryptelf/bin/flagsave', platform: 'x86_64'
file also shows that it was generated by running flagsave ./flag.enc from the /etc/profiles/per-user/cryptelf/bin/flagsave directory.
flagsave
flagsave is a 64-bit linux executable:
oxdf@hacky$file flagsave
flagsave: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
I’ll open the binary in Ghidra. A lot of the reversing involves reading the code and renaming variables to see how it works. All of the interesting code takes place in the main function.
It first opens key and checks the length of the file is 0x20 = 32 (by seek to the end, and then check the position in the file stream):
canary=*(long*)(in_FS_OFFSET+0x28);h_key=fopen64("./key","r");fseek(h_key,0,SEEK_END);len_key=ftell(h_key);if(len_key==0x20){...[snip]...else{enc_key=0xffffffff;}LAB_00401205:if(canary==*(long*)(in_FS_OFFSET+0x28)){returnenc_key;}/* WARNING: Subroutine does not return */__stack_chk_fail();}
If it isn’t 32 bytes, it exits. In my case above, running where no file name ./key exists, this is what leads to the crash.
Assuming the key is there and 32 bytes, it resets the pointer to read the key into memory:
Now it moves to the input file in argv[1] (first argument). It gets the length the same way as the key, uses malloc to get memory (on the heap), and reads the contents into that buffer:
Then it creates a buffer on the heap and uses the nettle_aes256_set_encrpyt_key function to initialize the key into that buffer with the bytes from the key file:
I had a hard time finding any documentation for nettle_ctr_crypt. It seems like it should take nettle_aes256_decrypt. I’ll notice it passes input_file_bytes and input_file_bytes + 0x10. I think the IV is stored at the front of the file and that’s the IV and then the encrypted content.
If argv[2] is “e”, then it calculates the length to be 0x10 more than the input bytes. It gets 0x10 bytes from /dev/random and uses that as the IV, which it passes to nettle_ctr_crypt:
I can open the core dump in gdb to see what memory was like at the crash, but before I do, I want to understand where things I care about are. If I can recover the input file and the key, I can decrypt that file and presumably get the flag.
Without Core
file showed that the dump was created running flagsave ./flag.enc. I’ll create a flag.enc, as well as a 32 byte key:
oxdf@hacky$echo"HV{this is not really the flag}"> flag.enc
oxdf@hacky$echo-n"AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD" | wc-c32
oxdf@hacky$echo-n"AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD"> key
I’ll load flagsave in gdb and run with the same arg as the dump file:
oxdf@hacky$gdb -q ./flagsave
Reading symbols from ./flagsave...
(No debugging symbols found in ./flagsave)
gdb-peda$r ./flag.enc
Starting program: /media/sf_CTFs/hackvent2023/day14/flagsave ./flag.enc
Program received signal SIGSEGV, Segmentation fault.
Unsurprisingly it crashes with a SIGSEGV. Because I’m running gdb with Peda, it also prints the registers and stack:
$rbx has the length of the encrypted flag. $r13 has the pointer to the buffer with the encrypted flag. And $r15 has the pointer to the buffer with the key.
Read dump
Now I’m ready to open the dump. I’ll exit gdb and reopen it with dump:
oxdf@hacky$gdb -q ./flagsave dump
Reading symbols from ./flagsave...
(No debugging symbols found in ./flagsave)
warning: Can't open file /nix/store/nnzp88khq8ygjjqdad8mjk3jkm94044p-flagsave-x86_64-unknown-linux-musl/bin/flagsave during file-backed mapping note processing
warning: exec file is newer than core file.
[New LWP 828]
warning: Section `.reg-xstate/828' in core file too small.
Core was generated by `flagsave ./flag.enc'.
Program terminated with signal SIGSEGV, Segmentation fault.
warning: Section `.reg-xstate/828' in core file too small.
#0 0x000000000040113a in main ()
gdb-peda$
p [register] will print the register value:
gdb-peda$ p $rbx$1 = 0x2b
So the encrypted content is 0x2b = 43 bytes long.
I’ll show the memory with x/[n]bx, where [n] is the number of bytes to show:
The easiest way to get the flag now that I have the key and encrypted text is to just save those to files and run the program. I’ll grab each buffer and throw it in vim, using Ctrl-v to select and delete each line up to the first byte values, and :%s/ 0x//g to get rid of the extra whitespace and “0x”, saving as flag.enc.hex. Then xxd will convert to raw bytes:
I’ll run flagsave (making sure to give it the “d” so it doesn’t crash), and the flag is in out:
oxdf@hacky$./flagsave flag.enc d
oxdf@hacky$cat out
HV23{17's_4ll_ri6h7_7h3r3}
Cyberchef
I have the encrypted bytes and the key, and I know it’s AES CTR mode. I can drop all of this in CyberChef:
I’ll need to take the first 16 bytes (or 32 hex characters) from the input as the IV, and the rest is simple.
pwntools
I didn’t know before this challenge (shoutout to Fabi_07 in the Hackvent Discord), but pwntools has a Coredump object that can parse and handle it. With that, I’ll write a simple Python script that solves the entire challenge:
frompwnimportCorefile,processcore=Corefile('./dump')# write ciphetext to flag.enc
enc_text_len=core.registers['rbx']enc_text_addr=core.registers['r13']enc_text=core.read(enc_text_addr,enc_text_len)withopen('flag.enc','wb')asf:f.write(enc_text)# write key to key
key_addr=core.registers['r15']key=core.read(key_addr,32)withopen('key','wb')asf:f.write(key)# get flag
p=process(['./flagsave','flag.enc','d'])p.wait()withopen('out','r')asf:print(f.read())
Running this returns the flag:
oxdf@hacky$python solve.py
[+] Parsing corefile...: Done
[*] '/media/sf_CTFs/hackvent2023/day14/dump'
Arch: amd64-64-little
RIP: 0x40113a
RSP: 0x7ffeef3dd660
Exe: '/nix/store/nnzp88khq8ygjjqdad8mjk3jkm94044p-flagsave-x86_64-unknown-linux-musl/bin/flagsave' (0x400000)
[+] Starting local process './flagsave': pid 14484
[*] Process './flagsave' stopped with exit code 0 (pid 14484)
HV23{17's_4ll_ri6h7_7h3r3}