CTF Circle - Hack-A-Sat 2021 Qualifier CTF
"groundead" Challenge Writeup
Written by sen (@sarahemm)

###############
### Summary ###

The 'groundead' challenge provided a file named 'challenge' and a server and
port number, with no other information. Upon connecting to the port it said
"ground station online" but nothing further, and there wasn't any indication as
to what you were supposed to be attempting.


############################
### Tools/Infrastructure ###

Our team has a couple servers we use as a launching point for CTF work, so my
work was done on these. I ended up using Ghidra for the reversing part of the
challenge, and Ruby to write the solver.


###########################
### Phase 1 - Discovery ###

To start off, I ran 'file' on the challenge file provided, which showed it was
an ELF binary. I disassembled it using objdump just to get a rough overview of
what I was dealing with, which showed symbols with mangled names like
_Z23getSatellitePacketBytesPv. This usually means C++, but I hadn't done any
reversing of C++ before this point, and had barely even worked with it at all
in the forward direction.

A bit of googling showed that Ghidra seemed to be a reasonable tool to use for
C++ reversing work, and it had been on my todo list to learn to use for awhile
anyways. I got that installed and ready to use (literally just unzipping it
and ./ghidraRun, easier than I'd expected!), then turned to the server port
to see what information I could get from there.

Connecting to the port gave a message saying the ground station was ready,
with ASCII art of a ground station. Entering any information generated a
message saying "That sequence of hex characters did not work. Try again."
Running the provided binary, it gave the same messages and output, so the
binary seemed to be a copy of what was running on the challenge server.


###########################
### Phase 2 - Reversing ###

At this point it seemed that the challenge wanted hex digits, and I had a copy
of the challenge binary to reverse. Loading it into Ghidra, I figured out how
to get it disassembled and decompiled.

Looking through the different functions in the binary, I decided to focus on
processSatellitePacketBytes as, after quickly looking through it, it seemed
to reference both the error message I was getting about the hex digits as
well as the flag. I would end up regretting focusing so narrowly so quickly,
but that's how it goes sometimes when you're under time pressure.

I looked at both the decompilation and the disassembly, and decided to work
off of the decompilation as much as possible. With plain C it's easy enough
to work off the disassembly, but I found that harder with C++ and this seemed
more likely to get results in a quicker time. I began working from the top of
the function down, renaming variables as I figured out what they were for, or
at least started to suspect what they were for. As I was doing that I found
references to things like APID, indicating this was looking for CCSDS format
packets, a standard protocol for space applications. In addition, there was a
reference to what looked like command 0x08 which would request emergency mode
and emit the flag.

case 8:
  puts("EMERGENCY_MODE: SPACECRAFT IS IN EMERGENCY_MODE");
  puts("You made it!\nHere\'s your flag:");
  puts(flag);
  exit(0);

After thinking this through a bit more, I realized that the challenge was
pretending to be a ground station rather than a satellite itself, so it
would likely be expecting a satellite to send telemetry rather than commands.
I worked up a quick tool to generate CCSDS headers, but every attempt I made
at sending data to the service resulted in the same error message about hex
characters not working. I'd hit a point where I was feeling stuck in the
reversing, so started working up from the bottom of the function, reversing
out a bunch of debug output that helped me further narrow down which variable
was used for what. This got me a more complete understanding of how everything
worked, but didn't seem to get me any closer to solving the specific error I
was running into. I did find some code that validated various header fields,
which would become important later on. At this point though even with these
fields set appropriately it wasn't getting me anywhere.

if (((fieldPVN == 0) && (fieldPktType == 0)) &&
  ((fieldAPID != 0x7ff || (fieldSecHdr != 1)))) {
  // sets 'header is okay' flag


##########################
### Phase 3 - Debugger ###

I'd spent several hours getting to this point, and was feeling fairly stuck.
Trying a debugger seemed a reasonable next step, but using debuggers on
stripped binaries isn't something I have a lot of experience with. I spent
half an hour trying to get useful information out of gdb before deciding that
I should step away from the challenge for a bit and try to get a fresh
perspective.

I took my dogs out for a walk and hoped some new ideas came to me while I was
out. While I was out, it occurred to me that the error message I was getting
might be generated from somewhere else in the binary, even though the text was
identical. Even though my packet matched all the "rules" I could find in the
processSatellitePacketBytes function, it could be another function entirely
that was rejecting my packets with the same error message.


################################
### Phase 4 - More Reversing ###

I decided to go back to more familiar tools. Since much of my background is
in embedded systems where  a lot of debugging gets done via "printf debugging"
and setting pins high or low depending on what areas of code are executing,
I thought about how I could use these methods to figure out what was going on
rather than using an interactive debugger.

Looking through all the other functions, I found getSatellitePacketBytes was
referencing the same error message I was getting. Ghidra keeps the cursor
synced between the decompilation and disassembly windows, so putting my cursor
on the "That sequence of hex characters..." string in the decompilation then
flipping back to the disassembly showed me instructions that loaded the
address of the string then executed the << C++ operator to add it to a buffer.

001036b2 48 8d 35      LEA   RSI,[s_That_sequence_of_hex_characters_d_0010b...
         57 80 00 00
001036b9 48 8d 3d      LEA   param_1,[std::cout]
         c0 c9 00 00
001036c0 e8 5b ee      CALL  <EXTERNAL>::std::operator<<
         ff ff

This indicated that the instruction that referred to the string should be at
0x36B2, so changing the parameter should accomplish what I wanted. A minute
with a hex editor changing the byte at 0x36B6 from 0x80 to 0x81 changed the
location of the text it was retrieving to give the error, so that I'd be able
to tell if the error I was getting was coming from the getSatellitePacketBytes
function or from processSatellitePacketBytes. A quick test of the modified
binary showed that now I got the "EMERGENCY MODE" message if the
getSatellitePacketBytes function was rejecting my input, and the "hex
characters" message if it was processSatellitePacketBytes.

Now that I knew for sure what piece of code was rejecting my input, I started
reversing the getSatellitePacketBytes function. I had found previously that
the processSatellitePacketBytes function was reading characters out of a
queue, but I hadn't thought much about where characters in the queue came
from. This function was reading characters from the console and pushing them
into the queue, and must have been doing some preliminary filtering as well.

After a brief reversing session on this new function, I found a reference to
0x07 I thought looked promising. I tried throwing just a whole bunch of 07
at the challenge binary, and it got past the first check! The data was then
passed to the processSatellitePacketBytes routine which output a bunch of
debug information, then rejected the packet since it was just 07 over and
over and not valid CCSDS.

I reversed out the area around the 0x07 in the function a bit more, and it
seemed like it only cared if the 7th byte in a packet was 0x07, the other bytes
could be any values. The header is 6 bytes, which means the first data byte
needed to be 0x07 to pass the check. I modified my test data to send 0x07 after
sending the header, and it resulted in a "Handling test telemetry message"!
There was no obvious way to get the flag though since the only status value
I was allowed to send was 0x07 (test telemetry) and not 0x08
(emergency mode/flag), but it seemed likely that I was just one step away from
the flag now.


#########################
### Phase 5 - Solving ###

I played around a bit more with various patterns of digits, trying to send
multiple bytes rather than one or add padding in various places, but didn't
have any success as the "reader" function restricted the value of the 7th
byte, but the "process" function only cared about the 7th byte (as long as
the header passed its checks).

Looking through the "reader" function a bit more, I noticed the number
0x1ACFFC1D in the code, which seemed significant. I had skipped over it
initially thinking it was referring to an address or something, but googling
this number brought up the same CCSDS documentation I'd been referencing which
seemed unlikely to be cooincidental. This series of bytes is used between each
frame of certain variants of CCSDS (called the Attached Sync Marker, or ASM),
which made me think that maybe I could use it to pad the data I was sending.
If both the "reader" and "processor" would ignore it for actual data parsing,
it might shift my bytes around enough that the check for 0x07 would be
checking a byte in the header rather than data. The header bytes have more
flexibility in this case, so I could probably set one to 0x07 if that ended
up being required to satisfy the check.

I modified the header I was sending to prefix it with 0x1ACFFC1D, then worked
out that the 7th byte in my data was now in the "sequence number" field
of the header. This field is ignored by the processing function, so seemed
like I would be able to set it to 0x07 to satisfy that check without
affecting anything in the processing function. I changed this byte to be
0x07, and changed the data to 0x08 now that it shouldn't be blocked anymore.
Putting this data into the challenge server finally resulted in the flag!

I ended up sending the following (without the spaces). The first group is
the ASM, the second group is the CCSDS TM header (ending in 01 meaning one
byte of data follows), and the third group is the data following the header.

1acffc1d 0a2307000001 08


##################################
### Lessons Learned/Reinforced ###

- As with many CTF challenges, stepping away for a bit is always a good idea
  whenever I get stuck, it usually results in new ideas that are often helpful
  in getting me un-stuck.

- Sometimes it's essential to start working with a new tool mid-CTF (Ghidra
  in this case), but sometimes it serves as too much of a distraction and
  wastes too much time when under the deadlines of a CTF (gdb in this case).
  I need to make sure to evaluate my use of new tools after a bit of time
  working with them, to make sure I'm not wasting time when I could be using
  tools I'm already familiar with instead.

- Focusing too narrowly too soon caused me to waste some time in this one, I
  should have looked over all the functions in the challenge binary first
  rather than just looking at processSatellitePacketBytes.