Tags: space octal reversing history
Rating:
# Summary
The '1201 Alarm' challenge listed a server and port number only, with no other files or resources. Upon connecting to the port, it gave a message that the pi constant had accidentally been modified in the core rope of an Apollo Guidance Computer, and listed a port to connect your DSKY to. The challenge was to locate the modified constant, and enter the value of it into the console.
# Tools/Infrastructure
Our team has a couple servers we use as a launching point for CTF work, and these have TigerVNC set up so that we can connect in using a VNC client to run X apps in situations like this one where we need a GUI.
The challenge listed that we should be thankful for Virtual AGC, and when connecting to the server using netcat it gave an IP and port to connect your DSKY to, so I got that installed before starting.
No other tooling was required, but since this CTF required submitting a token before connecting to a challenge, I used a simple Ruby script that submitted the token, retrieved the IP and port number for the DSKY connection, then launched yaDSKY with that connection setup. This made the iterative process much less painful than having to manually launch everything every 5 minutes after the connection timed out.
# Preface
A few terms that will get used throughout this writeup, before we start. The AGC was the Apollo Guidance Computer, which was used for guidance and navigation guidance during the Apollo missions. The DSKY (DiSplay/KeYboard, usually pronounced "disk-ee") is the user interface module for this system, whereas the AGC is the computer itself. The ROM for the AGC uses core rope which is a system where wires are wound through or around magnetic cores to encode information. References to core rope throughout this writeup can be read as 'ROM', as in this system the two terms are essentially interchangable.
Being a very early computer system, the AGC has some conventions that are unusual to those used to more modern computers. Words are 15-bits wide, stored in hardware as 16 bits plus a parity bit, for example, and most numbers are represented in octal rather than hexadecimal like we're more used to now. All numbers in this writeup will be in octal unless otherwise specified.
# Phase 1 - Discovery
To start off, I connected to the provided IP/port with netcat to see what the challenge was in the first place. It explained that the core rope in an Apollo AGC had "had a tangle" and the pi constant was stored in it incorrectly. It listed an IP and port to connect your DSKY to.
I knew vaguely how the AGC/DSKY worked, but didn't know any specific key sequences or anything beyond that it used "verbs" and "nouns" as a command system. The server I connected to initially mentioned it was using the Comanche 055 software (which was used for the Command Module during Apollo 11), so I googled this and found a quick reference on the main Virtual AGC page which, while not quite for the same revision and code this was based on, I figured would be a good starting point.
I connected yaDSKY from the Virtual AGC package to the IP and port given, and keyed in the sequence for a Lamp Test (VERB 35 ENTER). The proper sequence of lamps began to light and flash, confirming communication between the server and my DSKY was working fine and that the instructions I had found were at least somewhat applicable to Comanche 055.
Now that I had the infrastructure set up and some basic documentation about what I needed to do, I keyed in what should have been the Examine Core Rope command to see if it gave reasonable-looking output (which would help confirm that this command was also valid on the platform I was on). Keying in VERB 27 NOUN 02 12345 ENTER, the R1 display lit up with 5 octal digits which is exactly what should happen when you request a core rope word be examined.
# Phase 2 - Plan A
I had connectivity and a very basic understanding of the commands I'd need to use, so moved on to trying to understand which words in the core rope I needed to examine. These would be the ones where pi was stored, but I didn't know where in memory this was. Pi is actually stored as PI/16 on the AGC, due to the specifics of how non-integer numbers are stored internally.
Looking through the Virtual AGC software package, I found that the Comanche 055 source code was all included. Grepping through this source showed that PI/16 was in the TIME_OF_FREE_FALL module, stored as type 2DEC. I didn't understand anything about how floating point was represented on this platform and since it's from so early in computing, things about it don't fit with how we're used to them working these days. Some research on the Virtual AGC site showed that 2DEC is double-precision floating point, stored as two 15-bit words in the core rope.
It felt like it should be pretty straightforward at this point, just look through the code to see where that is stored, and ask the AGC/DSKY for the values at that location! It turned out to be somewhat less straightforward, as it often does in CTFs. The constant was in a section of the source code that started with:
BANK 32
SETLOC TOF-FF1
BANK
From reading the documentation, my understanding is that this moves the point that instructions are being assembled to next around a few times. The first instruction means "move to the first unused location in bank 32", then "move to location TOF-FF1", then "move to the first unused location in the current bank". Why do three moves in a row with nothing between them? The first line on its own made sense to me, and the last two together make sense to me, but all 3 together didn't. I'm sure there's a good reason for it, but I'm still not sure what that is.
# Phase 2.5 - Plan B
I decided that I'd spent enough time trying to figure it out from that angle, and it was time to switch to a different strategy. The first phase of building the Comanche 055 system is that the toolchain turns the directory full of .agc files into a single ~83,000 line .lst file, with everything contained in it for the whole system. Looking through this file I found the PI/16 constant with some extra information added in, which I assumed was probably related to addressing:
060455,000703: 27,3355 06220 37553 PI/16 2DEC 3.141592653 B-4
PI/16 was the constant name, 2DEC is the type, and 3.141592643 B-4 is the value. Presumably some of the numbers before PI/16 are address information, but which ones? I spent close to an hour looking through the documentation and various source files to try to figure it out, but wasn't able to sort it out in a reasonable amount of time.
At this point I was spinning my wheels on plan B too. I was sure I could figure out how the addressing was represented if I spent a more time looking through the documentation and maybe the source code for the assembler if that failed, but I only had about two hours left in the CTF at this point. Time to move to plan C...
# Phase 2.75 - Plan C
...which didn't exist yet. When I get stuck like this, I try and take a step back and look over all the resources I've collected so far to come up with a new direction to move in. In this case I looked through the files I had available in the Comanche 055 directory after building Virtual AGC, hoping to find something that gave me a new idea. There was a file named Comanche055.binsource that looked interesting, it seemed to be 8 groups of octal words per line, and based on the name it seemed like this was likely the intermediate file generated before generating the raw binary output.
Thinking more about how this file might be useful, I realized that if I knew the 2DEC octal representation of pi, I would be able to locate it within this file. Once I had that, if I asked the AGC for data at a few addresses in a row starting from a random point, then searched the binsource file for the data it gave me, I should be able to locate where the address I had entered into the AGC was in this file. At that point, the difference between the two would be the distance to where pi was stored, and I could offset the address I was entering into the AGC by that amount to get PI/16.
I looked at the documentation on how 2DEC represents numbers, but I knew it was going to take me awhile to write a tool to do it. Thinking about it further before I started coding, I realized that the AGC compiler must know how to do it, and the compiler was open source! I hacked in a few extra printf statements to output the decimal and octal equivalents every time it encountered a 2DEC, recompiled Comanche055, and it spit out the octal 2DEC of the accurate pi constant, 06220 37553. As a sidenote, it wasn't until putting this writeup together than I noticed the line from the .lst file above actually includes this information, but I had no way to know what those numbers represented at the time and instrumenting the compiler was the quickest way I could think of to get it.
Now that I knew /what/ pi was, I had to find /where/ pi was. I brought the DSKY back up and asked for the contents of the core rope at a random address, followed by another two addresses after that, writing each value down. Searching the binsource file, I found only one place those three values occurred in a row, which told me where in the binsource file matched up with the address I'd entered into the DSKY/AGC. I did a rough calculation of how far I was from where PI/16 should be, then asked for a few addresses starting at that point:
RESET
VERB 27 NOUN 02 ENTER
57361 ENTER [recorded value appearing in R1]
57362 ENTER [recorded value appearing in R1]
57363 ENTER [recorded value appearing in R1]
Searching the binsource for the values I got (37700 00000 00100) back from the AGC, I found that PI/16 should be just few words below where I was looking! I wanted a "window" of values before, within, and after the PI/16 constant so I could be sure I was in the right place. I brought the DSKY back up, asked for the values at core rope locations 57352 to 57363, and got the following:
57352: 06030
57353: 24775
57354: 30424
57355: 01106
57356: 06572
57357: 10012
57360: 37777
57361: 37700
57362: 00000
57363: 00100
Comparing that with the unmodified source, the values at 57352 through 57354 matched, and 57360-57363 matched though offset up by one. 57355-57357 didn't match between the data I was getting back and the binsource. I was a bit confused and honestly discouraged at this point, since I had expected one or two words to have changed, not three!
I spent a few minutes thinking the results over, and noticed that my DSKY had disconnected as it had been doing every 5 minutes due to the timeout on the CTF servers. I reconnected and, just to make sure my confusion wasn't because I had written something down wrong, re-checked the three different values and one on either side to make sure I had them right. Two of the values were different this time, but the others were identical to before. Had I written them down wrong, or did the CTF change the values for every new connection? I disconnected and reconnected a couple more times, writing down the 5 values, and confirmed that only two of the values changed during each connection. Those two values must be PI/16! It turned out to be a good coincidence that the system just happened to time me out when it did.
# Phase 3 - Solving
At this point I had the location where I believed PI/16 was being stored, so all that was left was to ask the DSKY for the values in locations 57356 and 57357, convert this to decimal, multiply it by 16, and enter it into the challenge to obtain the flag. The remaining problem was, I had instrumented the compiler in order to convert decimal input to its 2DEC octal form, but there was no way to do that in reverse. I was very low on time at this point and wasn't confident that I could implement a tool to do the conversion in the time I had left, so I took a step back and re-evaluated what information and tools I had available.
Since Virtual AGC was built from historical specifications and information and attempts to be as accurate as possible, there is a very large validation suite included with it to ensure it acts as the original compilers and systems would have. Given this, I thought that there might be a bit of C somewhere in the validation suite that I could relatively easily pull out and use in my own tool to do the conversion.
As I started looking for any validation tools that I could "take apart", I noticed an executable in the Tools folder called 'checkdec' which seemed promising from the name at least. Upon running it, it informed that it was for converting DEC or 2DEC in either direction between octal and decimal, to validate that the compiler was doing it correctly. Even better than having to put something together myself using bits of their C code!
In theory, using this tool with all the information I now had should make the solve fairly straightforward. I re-connected the DSKY, and keyed in:
RESET
VERB 27 ENTER
NOUN 02 ENTER
57356 ENTER [wrote down the result, 07457]
57357 ENTER [wrote down the result, 30066]
I then entered the two values that the DSKY had displayed into 'checkdec'. which gave me 0.2372896299. That looked vaguely like what I was looking for, so I multiplied this by 16 (as PI is stored as PI/16) which gave me 3.7966340784, entered that into the netcat session, and out came the flag!
# Lessons Learned
* While I prefer to understand everything I'm looking at, sometimes during a CTF there are faster ways to accomplish a task that don't require as much reviewing of documentation or in-depth learning of a system. I probably would have spent a lot longer trying to find the right documentation that described exactly how addressing information was represented in AGC assembler than it took me to "guess and check" to orient myself in the remote AGC's address space.
* I'm still working on getting myself to step back more often and look at "the bigger picture" when working on things like this, and this challenge reinforced how important that is. If I had kept going with "plan A", I'm not sure I would have found the correct addresses in the amount of time I had. If I had tried to write my own tool to do the conversion, I might not have finished it in time either. By stepping back and re-evaluating when I'm either stuck or about to switch to the next phase of solving, I can see better solutions than if I'd just kept going on the same path.
* Shifting your brain into a 15-bit-wide octal system when you're so used to 8/16/32/64-bit hex, decimal, and binary is hard! I kept getting confused when addresses went from 57357 to 57360 or values rolled over after 77777.