Category: Rev

Difficulty: Insane

Points: 493 (9 solves)

Challenge Description

Mcvaru skate aka skate homie is trying to put in some practice at skatepark IOR, but scooter kids keep getting in his way and snaking him. Out of anger he yells at one of the kids, but it's 2023, scooter kids have evolved. They now demand some sort of code if you want them to move. Find the flag and help varu skate land a hardflip at the 6 stair.

PS: ./compiler program password

Overview

In this challenge we are given a compiler and file containing some custom assembly code (skatepark.IOR). The compiler takes in said file and a passwords and runs the custom assembly with the password as input.

Looking at the IOR file we can guess what some of the instructions do and how the compiler works in general:

shuv/bowl2,trickbag1[aframe]
shuv/bowl3,trickbag2[bowl2]
ollie/yes,0

slide/trickbag1[aframe],trickbag4[aframe]
ollie/no,1
ollie/no,-1

fifty/aframe,1
ollie/loc4,-2

no:
shuv/trickbag5[0],89
shuv/trickbag5[1],111
  • From this we can deduce that shuv is probably mov. Additionally, since the second operand in line 1 contains a literal, we can assume that the first operand is is the destination register and the second operand is the source register.
  • We can also deduce that trickbag1, trickbag2, ..., trickbag5 are pointer types and that bowl1, bowl2, ..., bowl6 are general purpose registers.
  • Lastly, this also has labels just like normal assembly and ollie is a conditional jump to a label.

Reversing the compiler binary

Luckily, the fact that the binary is stripped is not a huge problem since the instruction names exist in the binary, e.g. we can search for shuv and we find this.

instructions

For the sake of a good example, lets pick fifty. The fifty crossreference points to FUN_01051c0, which is the IOR parsing function.

We see that a buffer containing the current instruction is compared to the string fifty. If the instruction matches (i.e. cmpResult == 0) then it goes into a big if statement, else it moves onto the next comparison.

stringcmp

A bit further down inside the if statement we find a function that consumes the rightmost operand and returns the address which the operand pointed to.

consumeop

Here we see that fifty adds two numbers.

add

Translating IOR to assembly

After following the above procedure to decode all of the operations we get the following table

IORASMDescription
shuvMOVdst = src
crookXORxors two numbers
fiftyADDadds two numbers
fiveoIDIVdivides the dst by the src
nosegrindMULmultiplies dst by src
ollieCJMPconditional jump to label
slamRETexit
slideTEST0 if dst == src is equal, 1 if greater, -1 if less
smithMODdst %= src

Using this we can translate the skatepark.IOR

; Description: 8-bit SBOX, for all aframe in range(64) this calculates:
; trickbag1[aframe] = trickbag2[trickbag1[aframe]]
MOV aframe,0

loc1:
TEST aframe,63
JE loc_1
MOV bowl2,trickbag1[aframe]
MOV bowl3,trickbag2[bowl2]
MOV trickbag1[aframe],bowl3
ADD aframe,1
JMP loc1

;;;;;;;;;;;;; NEW SECTION ;;;;;;;;;;;;;
; Description: for all idx in range(64) this calculates:
; trickbag3[idx] = (trickbag1[3 * idx]**2 * idx + trickbag1[3 * idx + 1] * idx + trickbag1[3 * idx + 2]) % 256

loc_1:
MOV aframe,0

loc2:
TEST aframe,63
JE loc_2

IDIV aframe,3
MOV rail,aframe
MUL aframe,3

MOV bowl4,aframe
MOV bowl5,aframe
MOV bowl6,aframe

ADD bowl5,1
ADD bowl6,2

; ledge = trickbag1[idx]**2 * rail
MOV ledge,1
MUL ledge,trickbag1[bowl4]
MUL ledge,trickbag1[bowl4]
MUL ledge,rail

; stairs = trickbag1[idx + 1] * rail
MOV stairs,trickbag1[bowl5]
MUL stairs,rail

; bank = trickbag1[idx + 2]
MOV bank,trickbag1[bowl6]

; ledge = trickbag1[3 * idx]**2 * idx + trickbag1[3 * idx + 1] * idx + trickbag1[3 * idx + 2]
ADD ledge,stairs
ADD ledge,bank

; in bank I have last
; trickbag3[rail] = ledge % 256
MOD ledge,256
MOV trickbag3[rail],ledge


ADD aframe,3
JMP loc2

;;;;;;;;;;;;; NEW SECTION ;;;;;;;;;;;;;
; Description: for each `aframe` in range(64) it computes
; trickbag1[aframe] ^= trickbag3[(aframe + 1) % 21] ^ trickbag3[(aframe + 2) % 21]

loc_2:
MOV aframe,0
loc3:
TEST aframe,63
JE loc_3
MOV bowl1,aframe
MOV bowl2,aframe

ADD bowl1,1
ADD bowl2,2

MOD bowl1,21
MOD bowl2,21

; trickbag1[aframe] ^= trickbag3[bowl1]^trickbag3[bowl2]
MOV bowl1,trickbag3[bowl1]
MOV bowl2,trickbag3[bowl2]

XOR trickbag1[aframe],bowl1
XOR trickbag1[aframe],bowl2

ADD aframe,1
JMP loc3

;;;;;;;;;;;;; NEW SECTION ;;;;;;;;;;;;;
; Description: checks if trickbag1 and trickbag4 are equal

loc_3:
MOV aframe,0 ; reset aframe

; test all aframe in range(64)
loc4:
TEST aframe,63
JE yes

; test if trickbag 1 and 4 on idx aframe is equal, if not go to `no`
TEST trickbag1[aframe],trickbag4[aframe]
JG no
JL no

ADD aframe,1
JMP loc4

;;;;;;;;;;;;; NEW SECTION ;;;;;;;;;;;;;
; Description: checks if trickbag1 and trickbag4 are equal

no:
MOV trickbag5[0],89
MOV trickbag5[1],111
MOV trickbag5[2],117
MOV trickbag5[3],32
MOV trickbag5[4],103
MOV trickbag5[5],111
MOV trickbag5[6],116
MOV trickbag5[7],32
MOV trickbag5[8],115
MOV trickbag5[9],110
MOV trickbag5[10],97
MOV trickbag5[11],107
MOV trickbag5[12],101
MOV trickbag5[13],100
MOV trickbag5[14],32
MOV trickbag5[15],98
MOV trickbag5[16],121
MOV trickbag5[17],32
MOV trickbag5[18],116
MOV trickbag5[19],104
MOV trickbag5[20],101
MOV trickbag5[21],32
MOV trickbag5[22],115
MOV trickbag5[23],99
MOV trickbag5[24],111
MOV trickbag5[25],111
MOV trickbag5[26],116
MOV trickbag5[27],101
MOV trickbag5[28],114
MOV trickbag5[29],32
MOV trickbag5[30],107
MOV trickbag5[31],105
MOV trickbag5[32],100
MOV trickbag5[33],33
RET 0,0


yes:
MOV trickbag5[0],32
MOV trickbag5[1],84
MOV trickbag5[2],104
MOV trickbag5[3],101
MOV trickbag5[4],32
MOV trickbag5[5],115
MOV trickbag5[6],99
MOV trickbag5[7],111
MOV trickbag5[8],111
MOV trickbag5[9],116
MOV trickbag5[10],101
MOV trickbag5[11],114
MOV trickbag5[12],32
MOV trickbag5[13],107
MOV trickbag5[14],105
MOV trickbag5[15],100
MOV trickbag5[16],32
MOV trickbag5[17],109
MOV trickbag5[18],111
MOV trickbag5[19],118
MOV trickbag5[20],101
MOV trickbag5[21],100
MOV trickbag5[22],32
MOV trickbag5[23],97
MOV trickbag5[24],110
MOV trickbag5[25],100
MOV trickbag5[26],32
MOV trickbag5[27],121
MOV trickbag5[28],111
MOV trickbag5[29],117
MOV trickbag5[30],32
MOV trickbag5[31],100
MOV trickbag5[32],105
MOV trickbag5[33],100
MOV trickbag5[34],32
MOV trickbag5[35],97
MOV trickbag5[36],32
MOV trickbag5[37],116
MOV trickbag5[38],114
MOV trickbag5[39],105
MOV trickbag5[40],99
MOV trickbag5[41],107
MOV trickbag5[42],32
MOV trickbag5[43],119
MOV trickbag5[44],105
MOV trickbag5[45],116
MOV trickbag5[46],104
MOV trickbag5[47],32
MOV trickbag5[48],64
MOV trickbag5[49],118
MOV trickbag5[50],101
MOV trickbag5[51],114
MOV trickbag5[52],115
MOV trickbag5[53],97
MOV trickbag5[54],99
MOV trickbag5[55],101
MOV trickbag5[56],95
MOV trickbag5[57],112
MOV trickbag5[58],108
MOV trickbag5[59],117
MOV trickbag5[60],103
MOV trickbag5[61],32
MOV trickbag5[62],115
MOV trickbag5[63],116
MOV trickbag5[64],121
MOV trickbag5[65],108
MOV trickbag5[66],101
MOV trickbag5[67],33
RET 0,0

Solving for the flag

The program takes in a 64 byte input into trickbag1 and runs each byte though the SBOX trickbag2. It then derives the trickbag3 table and uses it to modify trickbag1. Lastly it checks if all of the elements in trickbag1 and trickbag4 are equal.

This is a perfect opportunity to use z3.

To avoid having to model the SBOX we can solve for trickbag1 post-substitution and then run the result though the inverse SBOX of trickbag2.

To reduce the amount of possible solutions we will assume that the first character is the first character of the flag format TFCCTF{}

from z3 import *

S = Solver()

trickbag1 = [BitVec(f'tb{idx}', 8) for idx in range(64)]
print(trickbag1)
trickbag3 = [None for x in range(21)]
# SBOX
trickbag2 = [
	0x36, 0x32, 0x97, 0xb5, 0x76, 0x49, 0x51, 0x3c, 0x1c, 0xbf, 0x7b, 0x13, 0x2d, 0x21, 0x77, 0xc0, 
	0x19, 0x78, 0x25, 0x3a, 0xc9, 0x01, 0xb6, 0x8e, 0x64, 0x7f, 0xe1, 0x8a, 0xd9, 0x69, 0x7d, 0xda, 
	0x3d, 0x85, 0xfd, 0xf3, 0x10, 0x0c, 0x71, 0x66, 0x84, 0xf2, 0xe8, 0xfc, 0x27, 0x00, 0x9f, 0x1e, 
	0x0b, 0xc4, 0x20, 0xd0, 0xb0, 0x38, 0x09, 0x17, 0x70, 0xa6, 0x29, 0x53, 0x6a, 0x81, 0x06, 0x72, 
	0x15, 0x9b, 0x3e, 0x46, 0x2f, 0x1b, 0x88, 0x23, 0x33, 0x98, 0xfa, 0x16, 0xc7, 0xff, 0x59, 0x37, 
	0x03, 0x5a, 0xae, 0xfb, 0x83, 0x07, 0x2b, 0xa9, 0x6c, 0xc2, 0x57, 0x02, 0x2e, 0xf5, 0x28, 0xdd, 
	0x9e, 0xe6, 0xc1, 0x74, 0x3b, 0x65, 0x7c, 0xb1, 0x80, 0xd7, 0xa3, 0xa1, 0x9c, 0x99, 0x12, 0x24, 
	0x8b, 0x8f, 0xf9, 0x2a, 0xbe, 0xe5, 0xcd, 0x1a, 0x6d, 0xa4, 0x5e, 0xef, 0xd8, 0x4a, 0x73, 0x5b, 
	0xde, 0x62, 0x43, 0xdb, 0x87, 0x67, 0x26, 0x0f, 0xd6, 0xf8, 0xb7, 0x54, 0x1f, 0xba, 0x18, 0xc5, 
	0x31, 0xcf, 0xd1, 0x56, 0xe4, 0x91, 0x30, 0x0a, 0x7a, 0x05, 0x4e, 0x0e, 0x4c, 0x5f, 0xd4, 0x92, 
	0x96, 0x8d, 0xb9, 0xee, 0xbd, 0xdf, 0x45, 0x7e, 0x60, 0xce, 0xc8, 0xe7, 0xac, 0x55, 0xea, 0x2c, 
	0x68, 0xc3, 0xca, 0xe2, 0x44, 0xbc, 0xe9, 0x04, 0xc6, 0x9d, 0x1d, 0xb8, 0x9a, 0x48, 0xaa, 0x75, 
	0x4d, 0x22, 0x6e, 0xbb, 0x11, 0x63, 0xfe, 0x4b, 0xf1, 0xa8, 0xe0, 0xdc, 0xa2, 0x6f, 0xf0, 0xd5, 
	0xf7, 0x35, 0x4f, 0x94, 0x89, 0x79, 0x14, 0x3f, 0x41, 0x86, 0xa0, 0x90, 0x47, 0x52, 0xcb, 0x5c, 
	0x50, 0xb4, 0x61, 0x08, 0x6b, 0xd2, 0xec, 0xb3, 0xf6, 0x34, 0x39, 0xb2, 0xed, 0x0d, 0xeb, 0xf4, 
	0x8c, 0x42, 0xab, 0xd3, 0xad, 0x82, 0x58, 0xa5, 0xcc, 0xaf, 0x93, 0x95, 0xe3, 0x40, 0xa7, 0x5d
]

S.add(trickbag1[0] == trickbag2[ord('T')]) # assume the first character of the password is the first character of the flag

trickbag4 = [
	0x55, 0x23, 0x9d, 0x3f, 0x23, 0x9d, 0x8c, 0x00, 0xf7, 0x8b, 0xc0, 0xbb, 0xbc, 0x07, 0xd6, 0x9c, 
	0x11, 0xd3, 0x8c, 0xc5, 0xad, 0xed, 0x76, 0xd0, 0x05, 0x7d, 0xab, 0xe3, 0xdb, 0x8b, 0x7c, 0x10, 
	0xbb, 0x52, 0x64, 0xb7, 0x6e, 0x72, 0xaf, 0x95, 0x07, 0x43, 0xfc, 0x76, 0xc9, 0x72, 0xba, 0xc8, 
	0x58, 0x00, 0x8b, 0xe6, 0xb9, 0x6d, 0xd9, 0x3c, 0x9e, 0xac, 0xd3, 0x92, 0x86, 0xb7, 0x23, 0x59
]

for idx in range(63//3):
	trickbag3[idx] = (trickbag1[3 * idx] * trickbag1[3 * idx] * idx + trickbag1[3 * idx + 1] * idx + trickbag1[3 * idx + 2])

for idx in range(64):
	trickbag1[idx] ^= trickbag3[(idx + 1) % 21] ^ trickbag3[(idx + 2) % 21]

print(trickbag3)
print(trickbag1)
for idx in range(64):
	S.add(trickbag1[idx] == trickbag4[idx])

print(S.check())

res = []
mdl = S.model()

# AFAIK there is no nice way of extracting all elements out of a model in correct order
for elem in mdl:
	key = int(str(elem)[2:])
	value = int(str(mdl[elem]))
	res.append((key, value))

print('####################################')

res.sort()

FLAG = ''
for (k, v) in res:
	# print(k, chr(trickbag2.index(v)))
	FLAG += chr(trickbag2.index(v))

print(FLAG)

And we get the flag TFCCTF{0k_y0u_g0t__r1d_0f_th3_sc00t3r_k1dss_n0w_d0_4__hardflip}