2020 Hack-A-Sat DEFCON Space Security Challenge CTF Qualifiers 2020 - Part 1

Preface

This year the US Air Force teamed up with DoD's Digital Service and DEFCON's Aerospace village to build a CTF - the first of it's kind - to hack satellites.

I joined with some colleagues and their CTF team to get our numbers around half a dozen. We missed the top ten by ~50 points, which was disappointing, but shows the quality of the teams we were up against and that the tournament attracted.

Since joining a Bay Area tech company, I haven't found much time or mental space for CTFs or bug bounties. But aerospace has a special place in my heart, and with numerous commercial launch companies massively driving down the costs to get satellites up, understanding the frameworks, communication protocols, physics, tools, history etc. is going to be super important.

Before I get to the goods, a hat tip to the USAF & DoD for getting behind this. Satellites are going to become increasingly important in our day-to-day lives, and investing in bringing the security community in like this is awesome to see. I know how hard it can be to get buy-in from executives at large, bureaucratic organizations around public hacking initiatives, particularly when those organizations are PR and risk sensitive, so it's doubly impressive. To the folks at DEFCON, the Aerospace Village, DoD and USAF, thanks from the bottom of my heart for putting this together.


Challenges
I Like To Watch
The first noteworthy challenge involved placing a camera in Google Earth at the correct position of an orbiting satellite, and observing the Washington Monument in DC.

Upon connecting to the challenge service, you're given the following output:
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:53:14
The satellite we used was:

REDACT
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 http://redacted/cgi-bin/HSCKML.py
and 'LookAt' that spot from where the satellite when it took the photo and get us that flag!

We're given a Two Line Element (TLE), and we've also been provided with a KML Template:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Folder>
<name>HackASatCompetition</name>
<visibility>0</visibility>
<open>0</open>
<description>HackASatComp1</description>
<NetworkLink>
<name>View Centered Placemark</name>
<visibility>0</visibility>
<open>0</open>
<description>This is where the satellite was located when we saw it.</description>
<refreshVisibility>0</refreshVisibility>
<flyToView>0</flyToView>
<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 -->
<altitudeMode>clampToGround</altitudeMode>
</LookAt>
<Link>
<href>http://FILL ME IN:FILL ME IN/cgi-bin/HSCKML.py</href>
<refreshInterval>1</refreshInterval>
<viewRefreshMode>onStop</viewRefreshMode>
<viewRefreshTime>1</viewRefreshTime>
<viewFormat>BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth];
                CAMERA=[lookatLon],[lookatLat],[lookatRange],[lookatTilt],[lookatHeading];
                VIEW=[horizFov],[vertFov],[horizPixels],[vertPixels],[terrainEnabled]
        </viewFormat>
</Link>
</NetworkLink>
</Folder>
</kml>


The first challenge is going to be working out where the satellite was / is on the provided dates. I'd had some experience with tracking SpaceX satellites, so the first tool I reached for was gpredict. It'll do exactly what we want - lets us load in the co-ordinates of the DC monument, let us load the TLE of the satellite and .... oh shit the TLE doesn't load, and there's no error.

Reading the wiki page, we find out the last character is a mod 10 checksum.

doing the math:
>>> count = 0
>>> foo = '1 13337U 98067A   20087.38052801 -.00000452  00000-0  00000+0 0  999'
>>> for c in foo:
...     if c.isnumeric():
...             count += int(c)
...
>>> count
130
'-' characters count as +1 to the checksum. No mention of what to do if it wraps, but eh, we're at 2. Do the same for line two, we end up with a correct TLE:
REDACT
1 13337U 98067A   20087.38052801 -.00000452  00000-0  00000+0 0  9992
2 13337  51.6460  33.2488 0005270  61.9928  83.3154 15.48919755219334

Now we can import it into gpredict after setting up our Washington Monument, and use the time controller feature to go back to when the photo was taken to get the position of the satellite.



GPredict gives us the altitude of the satellite, long/lat its range from us, its azimuth and elevation from us. However, we don't want to be centered on us, we want the opposite - to look from the satellite down. So we just take the inverse using the whole "the interior angle on one side is the exterior angle if you draw a box" concept...

(left the decimals out but you get the idea. also yes thats mspaint).

Applying the same concept for elevation, and knowing we're at 50.23 deg elevation, we can look down with 90-(ground elevation angle).

Plugging in the range and altitude, our solution looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Folder>
<name>HackASatCompetition</name>
<visibility>0</visibility>
<open>0</open>
<description>HackASatComp1</description>
<NetworkLink>
<name>View Centered Placemark</name>
<visibility>1</visibility>
<open>1</open>
<description>This is where the satellite was located when we saw it.</description>
<refreshVisibility>1</refreshVisibility>
<flyToView>1</flyToView>
<LookAt id="HSCKML.py">
<!-- specific to LookAt -->
<longitude>-77.035204</longitude> <!-- kml:angle180 -->
<latitude>38.889429</latitude> <!-- kml:angle90 -->
<altitude>419000.0</altitude> <!-- double -->
<heading>63.8</heading> <!-- kml:angle360 -->
<tilt>39.7</tilt> <!-- kml:anglepos90 -->
<range>534000.0</range> <!-- double -->
<altitudeMode>clampToGround</altitudeMode>
</LookAt>
<Link>
<href>http://redacted/cgi-bin/HSCKML.py</href>
<refreshInterval>1</refreshInterval>
<viewRefreshMode>onStop</viewRefreshMode>
<viewRefreshTime>1</viewRefreshTime>
<viewFormat>BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth];
CAMERA=[lookatLon],[lookatLat],[lookatRange],[lookatTilt],[lookatHeading];
                    VIEW=[horizFov],[vertFov],[horizPixels],[vertPixels],[terrainEnabled]
        </viewFormat>
</Link>
</NetworkLink>
</Folder>
</kml>

And when we feed that to Google Earth , we look down on Washington in the way we'd expect it to look based on where the satellite was:
At this point the flag popped up as a Point of Interest, as the metadata service (HSCKML.py) returned this as an object we could see.

Mistakes made / lessons learned
I burned the better part of half an hour wondering why this TLE wasn't being loaded into gpredict- a team mate was the one to realize the checksums were bad (I'm not sure if he worked this out by reading the spec or importing it with another library).
Gpredict almost silently ignores TLEs with bad checksums:

It makes it fairly clear it didn't load anything new, but no hint that it might be because of a bad checksum. Nothing on stderr either, where I was suspecting it might dump an error should it have a problem. Without reading their source code, I suspect that it's because the import TLE functionality requests a directory, not an individual TLE file, and as such, it's in a position to ingest a lot of garbage, and warning the user about each garbage entry would be a pain in the ass.

That's not to say it was a completely bad idea to use gpredict in this case. As I'll bring up in later challenges, other libraries for dealing with TLEs introduce ambiguity around date parsing which is extremely hard to debug without something like gpredict's GUI representation of where the satellite is. The satellite prediction libraries are extremely powerful, but without immediate feedback, can be annoying.

More to come.


Comments

  1. This comment has been removed by the author.

    ReplyDelete
  2. Followed you and it's not working, it's driving me nuts, tha HSCKML.py is not showing up eventhoug the rest seems to be correct. THats my solution.. Why is it not working?
    https://pastebin.com/G6yigym3

    ReplyDelete
    Replies
    1. Did you end up getting it working? Your angles are a fair way off from what my solution was (albeit do you have a very different TLE?)

      Delete

Post a Comment

Popular posts from this blog

Man-in-the-middling SSL / TLS on Windows

2021 Hack-A-Sat DEFCON Space Security Challenge CTF Qualifiers Writeup - Linky