Pasticciotto uses the Harvard Architecture meaning its code is separated from its data and also from its stack. This allowed me to materialize my idea for the PoliCTF challenge: I could run the code the partecipants assembled without any hassle!
There are 8 general purpose registers (R0
to S3
) with S0 -> S3
being "scratch" ones. There is a RP
register (Return Pointer), the SP
register (Stack Pointer) and obviously the IP
(Instruction Pointer).
Every instruction varies from 2
to 4
bytes long: the opcode has a fixed size (1
byte).
The VM needs a decryption key to run: the opcodes are "encrypted" with the key by the assembler. The encryption algorithm is the RC4
key scheduling shuffle. Once the values are shuffled, the opcodes
are assigned according to their definition order.
key_ba = bytearray(key, 'utf-8')
# RC4 KSA! :-P
arr = [i for i in range(256)]
j = 0
for i in range(len(arr)):
j = (j + arr[i] + key_ba[i % len(key)]) % len(arr)
arr[i], arr[j] = arr[j], arr[i]
for i, o in enumerate(ops):
o.set_value(arr[i])
JMPI 0x200 # jumps to code[0x200]
CALL foo # jumps to the foo() function
Every opcode ending with R
uses this addressing mode. E.g:
MOVR R0, R1 # R0 = R1
MULR R0, R1 # R0 *= R1
Every opcode ending with I
uses this addressing mode. E.g:
MOVI R0, 0x2 # R0 = 0x2
MULI R0, 0x2 # R0 *= 0x2
The enclosed assembler recognizes labels and functions. The main function has to be defined. Here is an example:
def foo:
addi r0, 0x3
movi r1, 0x1
retn
def main: # main is mandatory
movi r0, 0xff
nope
jmpi label # jumping to label
nope
addi r0, 0x2
label: # defining a label
grmn
call foo
shit
In order to jump to a label or a function, an immediate type jump has to be used (JMPI, JPBI, JPAI
, etc...). The CALL
instruction is used to save where the program has to restore its execution after a function call.
The assembler puts the main function as first in the code section meaning its code will be located at offset 0. Every other function will follow.
The instruction set I come out wants to be "RISC"-oriented but I have to admit that it is more "CISC"-oriented (Confusing Instruction Set Computer).
Also, since I decided that every instruction had to be 4 chars long, some name adaptation may have encountered some quality issue... (yes, POP
, I'm looking at you)
The syntax used is the Intel one!
There three types of instructions:
- with 2 operands (imm2reg, reg2imm, byt2reg, reg2reg)
- with 1 operand
- with no operand at all (single)
Full name: MOVe Immediate to register
Usage: MOVI R0, 0x00
Effect: R0 contains the value 0x00
Full name: MOVe Register to register
Usage: MOVR R1, R0
Effect: R0 is copied into R1
Full name: LOaD Immediate offset @ data section to register
Usage: LODI R0, 0x0
Effect: R0 contains data[0x0]
Full name: LOaD offset in Register @ data section to register
Usage: LODR R1, R0
Effect: R1 contains data[R1]
Full name: SToRe @ immediate offset in data section from register
Usage: STRI 0x0, R0
Effect: data[0x0] contains R0
Full name: SToRe @ offset of Register in data section from register
Usage: STRR R1, R0
Effect: data[R1] contains R0
Full name: ADD Immediate to register
Usage: ADDI R0, 0x1
Effect: R0 is incremented by 0x1
Full name: ADD Register to register
Usage: ADDR R1, R0
Effect: R1 is incremented by R0
Full name: SUBstract Immediate from register
Usage: SUBI R0, 0x1
Effect: R0 is decremented by 0x1
Full name: SUBstract Register from register
Usage: SUBR R1, R0
Effect: R1 is decremented by R0
Full name: AND Byte (immediate)
Usage: ANDB R0, 0xFF
Effect: R0's lower byte is and-ed by 0xFF.
Full name: AND Word (immediate)
Usage: ANDW R0, 0xFFFF
Effect: R0's is and-ed by 0xFFFF.
Full name: AND Register
Usage: ANDR R0, R1
Effect: R0 is and-ed by R1.
Full name: (Y)OR Byte (immediate)
Usage: YORB R0, 0xFF
Effect: R0's lower byte is or-ed by 0xFF.
Full name: (Y)OR Word (immediate)
Usage: YORW R0, 0xFFFF
Effect: R0's is or-ed by 0xFFFF.
Full name: (Y)OR Register
Usage: YORR R0, R1
Effect: R0 is or-ed by R1.
Full name: XOR Byte (immediate)
Usage: XORB R0, 0xFF
Effect: R0's lower byte is xor-ed by 0xFF.
Full name: XOR Word (immediate)
Usage: XORW R0, 0xFFFF
Effect: R0 is xor-ed by 0xFFFF.
Full name: XOR Register
Usage: XORR R0, R1
Effect: R0 is xor-ed by R1.
Full name: NOT Register
Usage: NOTR R0
Effect: Bitwise negation of R0.
Full name: MULtiply by Immediate
Usage: MULI R0, 2
Effect: R0 is multiplied by 2.
Full name: MULtiply by Register
Usage: MULR R0, R1
Effect: R0 is multiplied by R1.
Full name: DIVide by Immediate
Usage: DIVI R0, 2
Effect: R0 is divided by 2. The remainder is not stored.
Full name: DIVide by Register
Usage: DIVR R0, R1
Effect: R0 is divided by R1. The remainder is not stored.
Full name: SHift Left by Immediate
Usage: SHLI R0, 2
Effect: Effect: R0 is shifted 2 bits to the left.
Full name: SHift Left by Register
Usage: SHLR R0, R1
Effect: R0 is shifted R1 bits to the left.
Full name: SHift Right by Immediate
Usage: SHRI R0, 2
Effect: Effect: R0 is shifted 2 bits to the right.
Full name: SHift Right by Register
Usage: SHRR R0, R1
Effect: R0 is shifted R1 bits to the right.
Full name: PUSH
Usage: PUSH R1
Effect: Pushes R1 on top of the stack.
Full name: POP (+ 1 free 'O')
Usage: POOP R1
Effect: Retrieves the element on top of the stack and puts it in R1.
Full name: CoMPare register to Byte
Usage: CMPB R0, 0xff
Effect: Compares R0 to 0xFF (R0's lower byte) and sets the ZF and CF flags.
Full name: CoMPare register to Word
Usage: CMPW R0, 0xffff
Effect: Compares R0 to 0xFFFF and sets the ZF and CF flags.
Full name: CoMPare register to Register
Usage: CMPR R0, R1
Effect: Compares R0 to R1 and sets the ZF and CF flags.
Full name: JuMP to Immediate
Usage: JMPI 0x00
Effect: Unconditional jump to 0x00
Full name: JuMP to Register
Usage: JMPR R0
Effect: Unconditional jump to R0
Full name: JumP if Above to Immediate
Usage: JPAI 0x00
Effect: Jumps to code[0x00] according to last comparison
Full name: JumP if Above to Register
Usage: JPAR R0
Effect: Jumps to code[R0] according to last comparison
Full name: JumP if Below or equal to Immediate
Usage: JPBI 0x00
Effect: Jumps to code[0x00] according to last comparison
Full name: JumP if Below or equal to Register
Usage: JPBR R0
Effect: Jumps to code[R0] according to last comparison
Full name: JumP if Equal to Immediate
Usage: JPEI 0x00
Effect: Jumps to code[0x00] according to last comparison
Full name: JumP if Equal to Register
Usage: JPER R0
Effect: Jumps to code[R0] according to last comparison
Full name: JumP if Not equal to Immediate
Usage: JPNI 0x00
Effect: Jumps to code[0x00] according to last comparison
Full name: JumP if Not equal to Register
Usage: JPNR R0
Effect: Jumps to code[R0] according to last comparison
Full name: CALL function
Usage: CALL *function*
Effect: Saves the next instruction address into RP and jumps to the start of the function
Full name: RETurN
Usage: RETN
Effect: Restores the RP into the IP and jumps to the IP
Full name: Well...
Usage: SHIT
Effect: Halts the execution
Full name: NOP(e)
Usage: NOPE
Effect: Does nothing for an instruction
Full name: GeRMaNo
Usage: GRMN
Effect: Sets every register (excluding IP and RP) to GG (0x4747)
Full name: DEBuG
Usage: DEBG
Effect: Prints the status of every register and the flags.
NOTE: The DBG preprocessor flag has to be enabled!