I Like to Watch – Hack-A-Sat CTF Challenge Solution


Dawid Golunski

On 23rd of May, Pentest took part in the Hack-A-Sat CTF challenge run by The United States Air Force in conjunction with the Defense Digital Service. As discussed in the previous insight piece, the CTF categories included:

– Astronomy, Astrophysics, Astrometry, Astrodynamics (AAAA)
– Satellite Bus
– Ground Segment
– Communication Systems
– Payload Modules
– Space and Things

Below we outline the solution to one of the CTF challenges within the Astronomy, ‘Astrophysics, Astrometry, Astrodynamics, AAAA’ category, specifically the challenge called ‘I Like to Watch’.

The overview

The brief provided by the Hack-A-Sat CTF challenge was to connect via netcat to an open port on the Internet and provide our Team’s ticket. This provided the following challenge:

We've captured data from a satellite that shows a flag located at the base of the Washington Monument. 
The image was taken on March 26th, 2020, at 21:52:07
The satellite we used was:
1 13337U 98067A   20087.38052801 -.00000452  00000-0  00000+0 0  9995
2 13337  51.6460  33.2488 0005270  61.9928  83.3154 15.48919755219337
Use a Google Earth Pro KML file to 'Link' to
and 'LookAt' that spot from where the satellite when it took the photo and get us that flag!

In addition to this, the Hack-A-Sat CTF provided an example KML file that looked like this

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
      <name>View Centered Placemark</name>
      <description>This is where the satellite was located when we saw it.</description>
      <LookAt id="ID">
        <!-- specific to LookAt -->
        <longitude>FILL ME IN</longitude>                <!-- kml:angle180 -->
        <latitude>FILL ME IN TOO</latitude>                  <!-- kml:angle90 -->
        <altitude>FILL ME IN AS WELL</altitude>                      <!-- double -->
        <heading>FILL IN THIS VALUE</heading>                <!-- kml:angle360 -->
        <tilt>FILL IN THIS VALUE TOO</tilt>                     <!-- kml:anglepos90 -->
        <range>FILL IN THIS VALUE ALSO</range>                     <!-- double -->
        <href>http://FILL ME IN:FILL ME IN/cgi-bin/HSCKML.py</href>

Firstly, what is KML?

When in doubt, Google:
KML (Keyhole Markup Language), is a file format used to display geographic data in an Earth browser such as Google Earth. You can create KML files to pinpoint locations, add image overlays, and expose rich data in new ways. KML is an international standard maintained by the Open Geospatial Consortium, Inc. (OGC).

Now, what is TLE?
Looking at the satellite information provided within the challenge description:

1 13337U 98067A   20087.38052801 -.00000452  00000-0  00000+0 0  9995
2 13337  51.6460  33.2488 0005270  61.9928  83.3154 15.48919755219337

We figured out it was written in TLE format. According to Wikipedia:
A two-line element set (TLE) is a data format encoding a list of orbital elements of an Earth-orbiting object for a given point in time, the epoch. Using suitable prediction formula, the state (position and velocity) at any point in the past or future can be estimated to some accuracy. The TLE data representation is specific to the simplified perturbations models (SGP, SGP4, SDP4, SGP8 and SDP8), so any algorithm using a TLE as a data source must implement one of the SGP models to correctly compute the state at a time of interest. TLEs can describe the trajectories only of Earth-orbiting objects.

The quest

The challenge was somewhat confusing as the description tag:

<description>This is where the satellite was located when we saw it.</description>

within the provided example KML file seemed to suggest that the KML “LookAt” coordinates should reflect the satellite’s location whereas the challenge description referred to the base of the Washington Monument as the spot to “LookAt” as well as the flag’s location.

Also, the KML description did not clarify where the observers were at when they saw the satellite:

– were they located at the base of the monument? 
– somewhere else on the Earth?
– The Moon…? 🙂

At first, using the KML reference, we built a valid KML file that included the known location of the Washington Monument:

The latitude of Washington Monument, Washington DC, USA is 38.889484, and the longitude is -77.035278.

We set the remaining settings (Altitude, Heading, Tilt, Range) to their default values or examples values taken from the KML LookAt documentation.

We obtained the following settings:

<longitude>-77.035278</longitude>               <!-- kml:angle180 -->
<latitude>38.889484</latitude>                  <!-- kml:angle90 -->
<heading>0</heading>                <!-- kml:angle360 -->
<tilt>45</tilt>                     <!-- kml:anglepos90 -->

We also added the Link setting to link the KML server referenced in the challenge description:


After loading the KML file configured with the settings above into Google Earth Pro software we were able to see the monument:

But no signs of the flag except for the “Keep Looking…” text that was fetched from the linked server as we could see in the intercepted request/response:


GET /cgi-bin/HSCKML.py?BBOX=-77.05324143744043,38.88630815814003,-77.01731456255952,38.90964858140188;CAMERA=-77.03527799999998
,38.88959468960392,517.4299999999999,45,0;VIEW=60,48.874,756,595,1 HTTP/1.1 


HTTP/1.1 200 OK
Server: Apache/2.4.18 (Ubuntu)
Content-Type: application/vnd.google-earth.kml+xml
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<name>Keep Looking...</name>

Digging through the documentation we also found a timestamp setting that we set to the date provided in the challenge description:

<kml xmlns="http://www.opengis.net/kml/2.2"

However, this did not seem to make any difference and we moved on to decode the TLE data to try to obtain the satellite’s coordinates based on the provided TLE data set.

Satellite location

We found a TLE-tools library and used it to decode the provided data. The library only returned some basic info:

TLE(name='REDACT', norad='13337', classification='U', int_desig='98067A', epoch_year=2020, epoch_day=87.38052801, 
dn_o2=-4.52e-06, ddn_o6=0.0, bstar=0.0, set_num=999, inc=51.646, raan=33.2488, ecc=0.000527, argp=61.9928, M=83.3154,
n=15.48919755, rev_num=21933) 

To complete the challenge, we needed to get the exact satellite coordinates. 

Looking for available tools we came across a tle-calc.py script which promised to deliver satellite position (in ECEF coordinate system) based on sgp4 model at a specified time. When provided with the date mentioned in the challenge description (March 26th, 2020, at 21:52:07) and the TLE data set, the script returned the following coordinates:

2020,03,26,21,52,07.000000, 2029.72655181, 5206.47627522, 3857.06394627

Which should represent the X, Y, and Z coordinates in the ECEF coordinate system.

This should allow us to calculate longitude and latitude angles within the triangles shown in the image titled ‘ECEF coordinates in relation to latitude and longitude’ on Wikipedia ECEF page:

Calculating the triangle angles within the cuboid based on Pythagoras’ theorem we obtained:

λ – longitude – 68.70185059
α -latitude – 34.61439181

However, when using these coordinates and loading them into Google Earth Pro, we would be taken to a desert on the other side of the world:

Late into the night, half-asleep, we also entertained the idea of using the coordinates in Sky Mode of Google Earth Pro which did land us in space but apparently even further from the flag:

The Solution

After finally getting some sleep, we took a step back and verified that the calculated coordinates were about right with an online ECEF calculator.

Unsure whether the library we used was reliable, or the provided TLE data required a different SGP model to calculate correct coordinates we decided to look for another library.

We found skyfield which seemed to be much more powerful and reliable according to the github ratings which was a good sign.

After studying the library and its documentation, we came up with the following python script:

 —[./satcalc.py ]—

from skyfield.api import EarthSatellite, Topos, load
ts = load.timescale()
# Time provided the challenge description
t = ts.utc(2020, 3, 26, 21, 52, 7)
line1 = '1 13337U 98067A   20087.38052801 -.00000452  00000-0  00000+0 0  9995'
line2 = '2 13337  51.6460  33.2488 0005270  61.9928  83.3154 15.48919755219337'
washington_monument = Topos(latitude_degrees=38.889484, longitude_degrees=-77.035278)
# Satellite object
satellite = EarthSatellite(line1, line2, name='REDACT')
# Geocentric location (GCRS coordinate system) at the specified time (March 26th, 2020, at 21:52:07)
geometry = satellite.at(t)
print("\nGCRS coordinates of the satellite (on March 26th, 2020, at 21:52:07) as X, Y, Z: ")
# Geographic point beneath satellite
subpoint = geometry.subpoint()
latitude = subpoint.latitude
longitude = subpoint.longitude
elevation = subpoint.elevation
print("\nGeographic point beneath satellite: \nlatitude: %s   
longitude: %s   elevation: %s" % (latitude.degrees, longitude.degrees, elevation.m))
# Topocentric
# Where will the satellite be relative to Washington Monument
difference = satellite - washington_monument
topocentric = difference.at(t)
# Altitude and azimuth in the sky
alt, az, distance = topocentric.altaz()
if alt.degrees > 0:
    print('The Satellite is above the horizon')
print("\nAltitude: %d , Azimuth: %d , Distance: %dm from Washington Monument" % (alt.degrees, az.degrees, int(distance.m)))

The script returned the following data:

$ ./satcalc.py
EarthSatellite 'REDACT' number=13337 epoch=2020-03-27T09:07:58Z
GCRS coordinates of the satellite (on March 26th, 2020, at 21:52:07) as X, Y, Z: 
[2060.71403219 5197.2148698  3853.10441927]
Geographic point beneath satellite: 
latitude: 34.783388135766145   longitude: -84.12402545866135   elevation: 418778.0017887158
The Satellite is above the horizon
Altitude: 23 , Azimuth: 236 , Distance: 906237m from Washington Monument

The X,Y,Z coordinates were different which was to be expected as the new library operated on a different coordinate system – Geocentric Celestial Reference System (GCRS), instead of the system used by the previously used library (ECEF). However we expected the geographic coordinates longitude and latitude to be the same, but as it turned out, the longitude was completely different.

We then set the KML settings of tilt, heading, range to the corresponding altitude, azimuth and distance values calculated with our script:

<longitude>-84.12402545866135</longitude>                <!-- kml:angle180 -->
<latitude>34.783388135766145</latitude>                  <!-- kml:angle90 -->
<heading>236</heading>                <!-- kml:angle360 -->
<tilt>23</tilt>                     <!-- kml:anglepos90 -->

This looked promising as it took us closer to the East coast of the USA, but no Washington in sight:

We tried switching the coordinates back to the Washington Monument coordinates:

<longitude>-77.035278</longitude>               <!-- kml:angle180 -->
<latitude>38.889484</latitude>                  <!-- kml:angle90 -->
<heading>236</heading>                <!-- kml:angle360 -->
<tilt>23</tilt>                     <!-- kml:anglepos90 -->

This took us to Washington but still no sign of the flag, still ‘Keep looking’.

Not giving up, we tried to further adjust settings. The LookAt documentation featured the following illustration for the tilt setting:

Together with the description:


Angle between the direction of the LookAt position and the normal to the surface of the earth. (See diagram below.) Values range from 0 to 90 degrees. Values for <tilt> cannot be negative. A <tilt> value of 0 degrees indicates viewing from directly above. A <tilt> value of 90 degrees indicates viewing along the horizon.

As the maximum angle was 90 degrees, the alternative would be to set tilt to 67 as:


We implemented the change into the KML file. It gave us a different angle. However, still no luck with the flag:

We then also looked at modifying the heading setting. The documentation contained another illustration for this setting:


Direction (that is, North, South, East, West), in degrees. Default=0 (North). (See diagram below.) Values range from 0 to 360 degrees.


Similarly, we then tried modifying the heading setting by subtracting the value we previously calculated: 236 (as azimuth) from 360 degrees which resulted in = 124 degrees. This unfortunately did not work either. Then we tried subtracting 180 angle which gave 56 degrees (236 – 180). 

that the final settings were:

<LookAt id="ID">
       <longitude>-77.035278</longitude>               <!-- kml:angle180 -->
      <latitude>38.889484</latitude>                  <!-- kml:angle90 -->
      <heading>56</heading>                <!-- kml:angle360 -->
      <tilt>67</tilt>                     <!-- kml:anglepos90 -->

This finally did the trick and the flag was returned at last:

So, after travelling all across the world and space, we finally got there. Next job, apply to NASA as a rocket scientist!

How can we support you?

Contact our team today to find out how we can help support your organization.