This weekend I played in CrewCTF with Babbage Patch Kids. As always, the first thing I did when the CTF started was check the misc category. I found this challenge which appeared to involve classification of random data. Perfectly in my wheelhouse.
We connect to the server and we are given a bunch of data points. It seems to dump previous data for classifying aircrafts as friendly or enemy and radar warning receiver (RWR) data as missile or not. We're asked to classify new data points. One interesting note is that each datapoint for the aircraft data is a 10 dimensional vector, while the RWR data is just 3 dimensional.
At first I went down a rabbit hole of looking into how radar data works. I made the assumption that there would be some intricacy that would lead to a solution. After about an hour though, I had a thought: why not just let a neural network learn the data?
Now, in hindsight, I probably could've plotted the data to see what I was dealing with. It turned out that the aircraft data was just clusters of friendlies and enemies, easily solvable with K-nearest neighbors; while the RWR data was just two concentric rings of points, solvable by calculating distance to the center of the rings.
Thankfully, neural networks are quite effective for learning unknown patterns in data, so my method worked just fine. I used a 3 layer neural network with ReLU activations and a sigmoid output for the aircraft classification. My RWR model was similar, though I used 5 layers instead. I trained on the prior data given, which wasn't a ton of data, but it worked fine.
Solve script is below. It's pretty ugly, but it got the job done quickly. Quickly enough for first blood. I guess that's one benefit of not looking at the data and instead just throwing a neural network at the problem.
import torch
from torch import nn
from pwn import *
aircraft_model = nn.Sequential(
nn.Linear(10, 128),
nn.ReLU(),
nn.Linear(128, 256),
nn.ReLU(),
nn.Linear(256, 1),
nn.Sigmoid(),
).double()
aircraft_criterion = nn.BCELoss()
aircraft_opt = torch.optim.SGD(aircraft_model.parameters(), lr=0.01)
with open("./data.csv", "r") as f:
aircraft = [x.strip().split(",") for x in f.readlines()]
aircraft = [
(
torch.DoubleTensor([float(y) for y in x[:-1]]),
torch.DoubleTensor([1]) if x[-1] == "FRIENDLY" else torch.DoubleTensor([0]),
)
for x in aircraft
]
for _ in range(10):
for a in aircraft:
pred = aircraft_model(a[0])
loss = aircraft_criterion(pred, a[1])
loss.backward()
aircraft_opt.step()
aircraft_opt.zero_grad()
r = remote("fighterjet.chal.crewc.tf", 40001)
r.recvuntil("DUMPING PRIOR RWR DATA")
rwrs = [x.split(",") for x in r.recvuntil("SPIKE! ").decode().split("\n")[1:-1]]
rwrs = [
(
torch.DoubleTensor([float(y) for y in x[:-1]]),
torch.DoubleTensor([1])
if x[-1] == "GROUND RADAR LOCK"
else torch.DoubleTensor([0]),
)
for x in rwrs
]
rwr_model = nn.Sequential(
nn.Linear(3, 128),
nn.ReLU(),
nn.Linear(128, 256),
nn.ReLU(),
nn.Linear(256, 512),
nn.ReLU(),
nn.Linear(512, 256),
nn.ReLU(),
nn.Linear(256, 1),
nn.Sigmoid(),
).double()
rwr_criterion = nn.BCELoss()
rwr_opt = torch.optim.SGD(rwr_model.parameters(), lr=0.01)
for _ in range(150):
for rwr in rwrs:
pred = rwr_model(rwr[0])
loss = rwr_criterion(pred, rwr[1])
loss.backward()
rwr_opt.step()
rwr_opt.zero_grad()
context.log_level = "debug"
outcome = rwr_model(
torch.DoubleTensor(
[float(x) for x in r.recvline().decode().strip().split(",")]
)
)
print(float(outcome[0]))
r.sendline("Y" if float(outcome[0]) < 0.5 else "N")
bogey = False
while True:
l = r.recvline().decode().strip()
if l == "BOGEY!":
bogey = True
elif "SPIKE! " in l:
outcome = rwr_model(
torch.DoubleTensor([float(x) for x in l.split("SPIKE! ")[1].split(",")])
)
print(float(outcome[0]))
r.sendline("Y" if float(outcome[0]) < 0.5 else "N")
elif bogey:
bogey = False
outcome = aircraft_model(torch.DoubleTensor([float(x) for x in l.split(", ")]))
print(float(outcome[0]))
r.sendline("Y" if float(outcome[0]) >= 0.5 else "N")
r.interactive()