Context
The company Chip&Fish has developed a new secure architecture called BattleChip. It has implemented a lightweight virtual machine (CHIP-8) and a secure element where a secret key is randomly generated randomly generated each time the system is run.
Produit description
Beyond the original features (see https://en.wikipedia.org/wiki/CHIP-8#Virtual_machine_description), a secure element has been added:
The secure element is not attached to any device. It contains pre-compiled code into which the previously generated secret key previously generated.
Almost all operations take place in 1 cycle, except for those requiring the use of the arithmetic-logic unit (ALU), in which case 2 cycles are required: one cycle to submit the calculation to the ALU, and one cycle to retrieve the result. To overcome this performance problem, the manufacturer has developed a shared cache system between the user VM and the secure element. The cache is based on an LRU-type algorithm and is placed upstream of the ALU. This means that, if an operation has recently been carried out, the CPU has direct access to the result, costing just one cycle.
About cache management, the manufacturer has added the 00E1
operation code.
This new instruction flushes all ALU cache lines.
The manufacturer has also implemented two other operation codes:
0000
and 0001
. Here’s the pseudocode for each
routines respectively:
def encrypt_0000(I):
"""
Xor a buffer stores at I with the secret key.
The secret key is already defined.
I is copied from the insecure context
=> The number of cycles needed to execute this routine
will be copied at the end of execution in the insecure context.
The 0xF-th register will contain the value.
"""
secure_memory[I] ^= k0
I += 1
secure_memory[I] ^= k1
I += 1
secure_memory[I] ^= k2
I += 1
secure_memory[I] ^= k3
I += 1
secure_memory[I] ^= k4
I += 1
secure_memory[I] ^= k5
I += 1
secure_memory[I] ^= k6
I += 1
secure_memory[I] ^= k7
I += 1
secure_memory[I] ^= k8
I += 1
secure_memory[I] ^= k9
def verify_0001(I):
"""
This routine checks if the buffer matches the secret key.
The secret key is already defined.
I is copied from the insecure context
=> 0xF-th register in the secure context will be copied
in the insecure context at the end of this routine execution.
=> if vF is zero, the flag is set at I+10 in the unsecure memory.
The flag's size is 16 bytes. We expected FCSC{<hex_encoded_flag>} on
the platform.
"""
vF = 0
vF |= secure_memory[I] ^ k0
vF |= secure_memory[I] ^ k1
vF |= secure_memory[I] ^ k2
vF |= secure_memory[I] ^ k3
vF |= secure_memory[I] ^ k4
vF |= secure_memory[I] ^ k5
vF |= secure_memory[I] ^ k6
vF |= secure_memory[I] ^ k7
vF |= secure_memory[I] ^ k8
vF |= secure_memory[I] ^ k9
Both actions are performed by the secure element.
The manufacturer has also left a few notes about the peripherals:
--- Keyboard ---
No keyboard, no pong. No pong... no pong.
Using an operation code that requires interaction
the client will terminate program execution.
--- Clock ---
Nothing to declare, Captain.
--- Siren ---
For your listening pleasure, the siren makes no noise.
It is however connected.
--- Display ---
The screen sends ANSI characters to control the display.
A 64x32 or larger console is recommended.
or larger. The VM does not return any specific information
unless an error occurs during execution.
So make good use of the screen.
For Windows users, here’s a sample code to enable support for ANSI characters:
# Windows user ONLY
import os
import win32api
import ctypes
def activate_vti():
"""Enable Virtual Terminal Input
Documentation: https://docs.microsoft.com/en-us/windows/console/setconsolemode
"""
if os.name == "nt":
kernel32 = ctypes.windll.kernel32
hStdout = win32api.GetStdHandle(win32api.STD_OUTPUT_HANDLE)
result = kernel32.SetConsoleMode(hStdout, 0x1 | 0x2 | 0x4)
if result == 0:
raise RuntimeError("Console doesn't support ANSI character")