Ghost in the Shellcode Teaser 2015: Lost To Time

We received a file that looked like it was compressed. Let's just pipe it through xz and see what it really is. Aaah, looks like some old and obscure machine code of a machine that has long since been retired.

The machine code is of the CDC 6600, a very weird machine that has a set of 24 registers, some of them address (A0-A7), index (B0-B1), and arithmetic registers (X0-X7). The ISA is kind of RISC like with some weird quirks.

The address width is 18 bit and there are 60 bits in a word (which is the access granularity). I'm not entirely sure on the endianness but it worked out either way.

Individual A and X registers are coupled, i.e., if something is written into A{1-5} then an implicit memory load to X{1-5} happens. A0 and X0 are scratch registers, B0 is hardwired to 0 (cheap bastards), B1 contains 1 by convention (seems like a waste of a register to me).

Interestingly the machine does not use ASCII characters but the following table:

XXX 000 001 010 011 100 101 110 111
000   :   A   B   C   D   E   F   G
001   H   I   J   K   L   M   N   O
010   P   Q   R   S   T   U   V   W
011   X   Y   Z   0   1   2   3   4
100   5   6   7   8   9   +   -   *
101   \   (   )   $   =       ,   .
110   #   [   ]   %   "   _   !   &
111   '   ?   <   >   @   \   ^   ;

When we start looking at the assembly code we see that there are two functions, CTF that initializes a set of registers and addresses and CTF2 which is basically a loop through the raw data region at the end of the file.

The initialization looks as follows:

CTF      BSS       0
         SB1       1                   B1 = 1
         SA1       CTFB                A1 = &CTFB, X1 = *CTFB
         BX6       X1                  X6 = X1
         SB7       27                  B7 = 27
         SA6       A1                  A6 = &CTFB, *CTFB = X6
         SA2       CTFA                A2 = &CTFA, X2 = *CTFA
         MX0       30                  X0 = 30 bit mask of 1 from top
         SA1       A2+B1               A1 = A2+1 = CTFA+1, X1= *(CTFA+1)

The CTF2 loop looks as follows:

CTF2     BX6       X0*X1               X6 = X0 AND X1 (clear lower 30bit)
         ZR        X6,CTF4             X6 = 0 ? CTF4
         JR        FCT                 call FCT
         SA6       A6+B1               A6 = A6 + 1; *(A6+1) = X6
         BX6       -X0*X1              X6 = -X0 AND X1 (inverted X0)
         ZR        X6,CTF4             X6 = 0 ? CTF4
         JR        FCT                 call FCT
         SA6       A6+B1               A6 = A6 + 1; *(A6+1) = X6
         SA1       A1+B1               A1 = A1 + 1; X1 = *(A1+1)
         NZ        X1,CTF2

CTF2 loads a word and processes the top 30 bits first and then the lower 30 bits. CTF2 then calls a subroutine that either just counts the number of bits for each half word or looks at the first word in the buffer and shifts it right to extract some part of special character for extra fun.

The FCT subroutine looks as follows:

FCT      SUBR
         CX6       X6                  X6 = number of 1's in X6
         SB6       X6                  B6 = X6
         LT        B6,B7,FCT2          B6 < B7? goto FCT2
         SB6       B6-B7               B6 = B6 - B7
         SB6       B6+B6               B6 = 2*B6 < 2 B6
         SB5       B6+B6               B5 = 2*B6 < 4 B6
         SB5       B5+B5               B5 = 2*B5 < 8 B6
         SB6       B5+B6               B6 = B5+B6 < 10 B6
         AX6       X2,B6               X6 = X2 >> B6
         MX7       -10                 X7 = 53 bit mask of 1 from top 110101=53
         BX6       -X7*X6              X6 = last 7 bits
FCT2     JP        FCTX

If the number of 1 bits in the 30 bit half word is lower than 27 then we just print the character. If it is 28 to 30 then we do (nrbits-27)*10 and shift the first word of memory that many bits to the right and then store it as character.

Note that the loop jumps over the first data word and starts processing from the 1st word on. But the 0 data word is used for the two parentheses ( ) in the flag.

So the algorithm is as follows: for each 60bit word, first look at the higher 30 bits, count the bits, use the lookup table above to encode the character. Then look at the lower 30 bit word, count bits and look up the character.

Now let's look at the data block:

0000000052 0244120057  3=C  9=I // 0 address, ignored
4021000310 0774415040  6=F 12=L
0000040000 0442211001  1=A  7=G
7777677777 0010002004 29=2=20; 520244>>2= 0.101.001_00 = 051 = ( 3=C
7737757761 0040000400 25=Y  2=B
0010020034 3556115671  5=E 18=R
0000000444 1625031752  3=C 15=O
6301523622 7165273762 14=N 20=T
2716771305 7111504353 18=R 15=O
1420532413 0006000100 12=L  3=C
6777567373 6000000000 25=Y  2=B
1000700020 4666666661  5=E 18=R
1001001001 0002000000  4=D  1=A
3567107765 0001000000 20=T  1=A
7000000000 7677737566  3=C 25=Y
4000000004 4000700004  2=B  5=E
5713542707 2000400010 18=R  3=C
5471065072 2667170671 15=O 18=R
3750335406 1653521704 16=P 15=O
6753156470 0000000004 18=R  1=A
1234567767 4127002011 20=T  9=I
2731534064 7003653700 15=O 14=N
77777777770000000000B 30=3=30; 052 = )
00000000000000000055B

And we get the flag FLAG(CYBERCONTROLCYBERDATACYBERCORPORATION)

You can find some more information at Wikipedia, ISA manual or Subroutines.

blogroll

social