Reversing an 8bit RISC microprocessor

Aneesh Dogra
Aneesh Dogra’s Blog
4 min readDec 23, 2019

--

Hey! We have found this old cartridge under a desk in the library of Lapland. It appears to be for a system called “Emu 2.0”, made back in 1978. These systems don’t get produced anymore, and we can’t seem to find anyone that owns one.

Thankfully we have the documentation for it, so maybe we can use it to write an emulator and see what this ROM does?

Files: folder
Author: Milkdrop

The “Emu 2.0” is an 8bit RISC microprocessor. This is a CTF problem from the recent X-Mas CTF 2019. It has two registers, the first being A (Accumulator), an 8bit register used for arithmetic and logic calculations. The second register is called PC (Program Counter), which is a 12bit register used to point the address in RAM of the current instruction that the microprocessor needs to execute.

We are given a custom ROM and some documentation about the architecture and instruction set.

Instruction set
Instruction Set continued

The ROM we are given uses the opcodes/hex codes described above. We need to write an interpreter for these opcodes and emulate the ROM, to get the flag.

The opcodes itself aren’t printable. Let's write a parser for these instructions and see what we get.

Unprintable Hex values
Startup
state = {'a': 0, 'pc': 0x100}
fp = open("rom", "rb")
bs = fp.read()
mem = list('0' * 0x200 + bs.encode('hex').lower())

We set the initial state variables and read the ROM into memory. As suggested in the documentation we set the initial 0x100 bytes to 0 and load the ROM starting at address 0x100.

We start iterating over opcodes starting at 0x100:

while state['pc'] < len(bs):                                                                                                                                                                  
if state['pc'] == 0x408:
break
x = state['pc'] * 2
opcode = ''.join(mem[x:x+2])
second = ''.join(mem[x+2:x+4])
argument = second
if opcode[0] == "8" or opcode[0] == "2" or opcode[0] == "3" or opcode[0] == "4" or opcode[0] == "5" or opcode[0] == "7" or opcode[0] == "9" or opcode[0] == "a" or opcode[0] == "c":
argument = opcode[1] + argument
elif opcode[0] == "d":
argument = opcode[1] + argument
elif opcode[0] == "f":
argument = opcode[1] + argument
ins, jumped = parse_opcode(opcode, argument)
# only increment 'pc' if last instruction was not a jmp

#print hex(x/2) + ">", ins, argument, ";", "a:", state['a']
if not jumped:
state['pc'] += 2
#x += 2
#print state['pc']

We need to adjust offset of our arguments depending on the opcode. For instance:

  • Opcode 2X XX: takes in 3 nibbles as argument and jumps there.
  • while Opcode 60 XX: taken in just 2 nibbles as argument.

After we have the valid argument and opcodes parsed, we shall start parsing the instructions and executing the program in our emulator.

def parse_opcode(opcode, arg):
global state
global mem
global blocked_addrs
jumped = False
x = int(arg, base=16)
if opcode == "00":
state['a'] += x
state['a'] = state['a'] & 0xff
return "add", jumped
elif opcode == "01":
state['a'] = x
return "set_a", jumped
elif opcode == "02":
#print state['a'], x, state['a'] ^ x
state['a'] ^= x
return "xor_a", jumped
....
....
snipped
....
....
elif opcode == "13":
if x == 0x37:
print chr(state['a'])
return "print", jumped
else:
state['a'] -= 1
return "invalid", jumped
else:
state['a'] -= 1
return "invalid", jumped
state['a'] &= 0xff
return opcode, jumped

You should also note that the processor has some quirks for invalid instructions:

quirk

So for all invalid opcodes (if any), we need to decrement the register A. Taking all these things in mind, here is the final emulator for this ROM:

I commented out the part where it prints the instructions getting executed (line 146). Here you can see the ROM is parsed and instructions are converted into more readable ones:

Custom Rom

We also added functionality to execute instructions in python and maintain our state variables ‘A’ and ‘PC’ accordingly. Lets only get the output of the print instructions “13 37”

elif opcode == "13":                                                                                                                                                                      
if x == 0x37:
print chr(state['a'])

Here we have our flag:

$ python parse_rom.py | tr -d ‘\n’ | xargs echo
X-MAS{S4nt4_U5e5_An_Emu_2.0_M4ch1n3}

This was my first time ever I got to disassemble a custom ROM from scratch. Even tho the instruction set was quite limited, it was still a fun learning experience. Thank you for reading :)

--

--

Always been a tinker! Started coding in 2008 (when I was in 8th grade). Fell in love with x86 assembly, C and Linux: Manipulation of memory and getting RCE