MCIOR writeup
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 probablymov
. 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 thatbowl1
,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.
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.
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.
Here we see that fifty
adds two numbers.
Translating IOR to assembly
After following the above procedure to decode all of the operations we get the following table
IOR | ASM | Description |
---|---|---|
shuv | MOV | dst = src |
crook | XOR | xors two numbers |
fifty | ADD | adds two numbers |
fiveo | IDIV | divides the dst by the src |
nosegrind | MUL | multiplies dst by src |
ollie | CJMP | conditional jump to label |
slam | RET | exit |
slide | TEST | 0 if dst == src is equal, 1 if greater, -1 if less |
smith | MOD | dst %= 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}