Skip to main content
  1. Posts/

Braeker CTF 2024 Writeups pt. 1

·835 words·4 mins
Lacroix Raphaël (Chepycou)
Author
Lacroix Raphaël (Chepycou)
I’m Raphaël LACROIX, a French computer scientist developping various applications in my free time ranging from definitely useless to somewhat usefull. I also do quite a lot of Capture the flag and cybersecurity challenges.
Table of Contents
2024 Braeker CTF - This article is part of a series.
Part 1: This Article

Hey there 👋, in this blog post I will be going over a few challenges from the Braeker CTF

Microservices
#

OOB network shenanigans

This challenge was quite interesting, every bit of the flag was being sent using another port that we had to guess in a “higher or lower” game. It was fun experimenting a bit more than I’m used to with scappy which I recommend anyone who’s interested in network cybersecurity to master a bit !

from scapy.all import *
load_layer("http")
from Crypto.Util.number import bytes_to_long, long_to_bytes
import random


def dropTheFlag(flag):

    # Flag falls into bits
    pieces = bytes_to_long(flag.encode())
    pieces = bin(pieces)
    pieces = pieces[2:]
    pieces = [int(x) for x in pieces]

    # Bits get scattered about
    d = {}
    for i,x in enumerate(pieces):
        d[i]=x
    l = list(d.items())
    random.shuffle(l)
    pieces = dict(l)

    return pieces


# It was right here
flag = "brck{not_the_flag}"

# Oh dang I dropped the flag
pieces = dropTheFlag(flag)

# Let's pick it back up
pickMapping = {}
for i,v in enumerate(pieces):
    pickMapping[i] = (v, pieces[v])


# Neat TCP things
FIN = 0x01
SYN = 0x02
PSH = 0x08
ACK = 0x10

# Server IP for filter
serverIP = "server_ip"

# Totally valid HTTP response
body = b"HTTP/1.1 404 OK\r\nConnection: close\r\nContent-Type: text/html\r\n\r\nKeep guessing!"


def processWebRequest(payload, dport):
    
    # Secret and piece of flag
    secret, b = pickMapping[dport-1000]

    # Extract guess from request
    filterString = b'GET /guess?guess='
    guess = 0
    try:
        guess = payload[len(filterString)+2:payload.find(' ', len(filterString)+2)]
        guess = int(guess)
    except:
        guess = 0

    # Return based on guess
    body_ret = body + b' guess = ' + str(guess).encode() + b'\r\n'

    if guess > secret:
        return 1, body_ret
    elif guess == secret:
        return 2+b, body_ret
    else:
        return 0, body_ret


def packet_callback(pkt):

    # User packet
    ip = pkt[IP]
    tcp = pkt[TCP]
    payload = str(bytes(tcp.payload))

    # Init response packet
    ip_response = IP(src=ip.dst, dst=ip.src)

    # No response by default
    pkt_resp = False

    # ACK some SYNS
    if tcp.flags == SYN:

        syn_ack_tcp = TCP(sport=tcp.dport, dport=tcp.sport, flags="SA", ack=tcp.seq + 1, seq=1337)
        pkt_resp = ip_response / syn_ack_tcp
    
    # Respond to the PSH
    elif tcp.flags & PSH == PSH:

        if len(payload) > 10:
            ret, lbody = processWebRequest(payload, tcp.dport)
        
        # Reply OOB
        pkt_resp = (ip_response / 
                    TCP(sport=tcp.dport, dport=tcp.sport, flags="PAF", seq=1338, ack=tcp.seq+len(tcp.payload)) / 
                    Raw(lbody))
        send(pkt_resp)
        pkt_resp = (ip_response / 
                    TCP(sport=tcp.dport, dport=tcp.sport, flags="PAF", seq=7331+ret, ack=tcp.seq+len(tcp.payload)) / 
                    Raw(lbody))

    # ACK them FINs
    elif tcp.flags & FIN == FIN:
        pkt_resp = ip_response / TCP(sport=tcp.dport, dport=tcp.sport, flags="RA", seq=tcp.ack, ack=tcp.seq+len(tcp.payload))

    # Respond if applicable
    if pkt_resp:
        send(pkt_resp)


# Filter packets for required ports
def packet_filter(pkt):
    return (TCP in pkt and
            pkt[IP].dst == serverIP and
            pkt[TCP].dport >= 1000 and pkt[TCP].dport <= 1000+len(pickMapping)-1)


# Spin up hundreds of webservices just like in the cloud
sniff(filter="tcp", prn=packet_callback, lfilter=packet_filter)

’e’
#

weird stuff with numbers and how they’re handled

In this challenge you meet a not-so-friendly robot apparently called epsilon that can count very well, and your objective is to get him to fail counting 3 times in a row

alt text

First challenge
#

The first challenge’s code is as follows :

bool flow_start() {

	// Get user input
	float a = get_user_input("Number that is equal to two: ");

	// Can't be two
	if (a <= 2)
		return false;

	// Check if equal to 2
	return (unsigned short)a == 2;
}

This is an example of Type conversion and the risks of it. Here we can input the maximum value of an unsigned short + 2 which by overflowing will be truncated to 2 like for instance 65538

Second challenge
#

here is the code for the second challenge :

bool round_2() {

	float total = 0;

	// Sum these numbers to 0.9
	for (int i = 0; i < 9; i++)
		total += 0.1;

	// Add user input
	total += get_user_input("Number to add to 0.9 to make 1: ");

	// Check if equal to one
	return total == 1.0;
}

If we add 0.099999880791 we are in floating point hell territory and the rounding makes it actually be 1.

Step 3
#

bool level_3() {

	float total = 0;

	unsigned int *seed;
	vector<float> n_arr;

	// Random seed
	seed = (unsigned int *)getauxval(AT_RANDOM);
	srand(*seed);

	// Add user input
	add_user_input(&n_arr, "Number to add to array to equal zero: ");

	// Add many random integers
	for (int i = 0; i < 1024 * (8 + rand() % 1024); i++)
		n_arr.push_back((rand() % 1024) + 1);

	// Add user input
	add_user_input(&n_arr, "Number to add to array to equal zero: ");

	// Get sum
	for (int i = 0; i < n_arr.size(); i++)
		total += n_arr[i];

	// Check if equal to zero
	return total == 0;
}

The most interesting of the three if we enter values to test we see that it’s impossible to guess how much to remove to reach 0 exactly. And then we realize that we can leverage the fact that adding to MAX_FLOAT values such as the ones added line 17 will not change it.

Therefore, we can cap the value to a big value (I picked 340282000000000014192072600942972764160.0), let random small number fill it without impact and then remove the -340282000000000014192072600942972764160.0

brck{Th3_3pS1l0n_w0rkS_In_M15t3riOuS_W4yS}

2024 Braeker CTF - This article is part of a series.
Part 1: This Article