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
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}