Bonjour à tous 👋, dans cet article de blog, je vais passer en revue quelques défis du Braeker CTF.
Microservices #
Manigances de réseau OOB
Ce défi était assez intéressant, chaque bit
du drapeau était envoyé en utilisant un autre port que nous devions deviner dans un jeu « plus ou moins ». C’était amusant d’expérimenter un peu plus que ce à quoi je suis habitué avec scappy
que je recommande à tous ceux qui s’intéressent à la cybersécurité des réseaux de maîtriser un peu !
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’ #
Bidouilles bizarres de nombres et représentation de nombres
Dans ce défi, vous rencontrez un robot pas très sympathique, apparemment appelé epsilon, qui sait très bien compter et votre objectif est de le faire échouer à compter 3 fois de suite.
Première étape #
Le code du premier défi est le suivant :
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;
}
Voici un exemple de Conversion de type et de ses risques. Ici, nous pouvons saisir la valeur maximale d’un unsigned short
+ 2 qui, en débordant, sera tronqué à 2 comme par exemple 65538
.
Deuxième étape #
Voici le code pour le deuxième défi :
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;
}
Si nous ajoutons 0.099999880791
(bienvenue en [floating point hell] territory) (https://youtu.be/jxi0ETwDvws?t=135) et l’arrondi fait qu’il s’agit en fait de 1.
Troisième étape #
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;
}
Le plus intéressant des trois défis : Si nous entrons des valeurs à tester, nous voyons qu’il est impossible de deviner combien il faut enlever pour atteindre 0 exactement. On se rend alors compte que l’on peut tirer parti du fait qu’ajouter à MAX_FLOAT des valeurs telles que celles ajoutées ligne 17 ne le changera pas.
Par conséquent, nous pouvons plafonner la valeur à une grande valeur (j’ai choisi 340282000000000014192072600942972764160.0
), laisser les petits nombres aléatoires la remplir sans impact et ensuite supprimer la grande valeur ajoutée avec un -340282000000000014192072600942972764160.0
brck{Th3_3pS1l0n_w0rkS_In_M15t3riOuS_W4yS}