SEKAICTF 2023: A letter from the Human Resource Management

| Tags: Articles in English CTF Writeups

This is another challenge from a CTF I did alongside CzechCyberTeam. I didn’t have much time or brainpower to participate and solved my only challenge on a sleepless night in a train. Nevertheless, SEKAICTF was very cool, including a very nicely made Nintendo-game-esque CTF web portal.

A letter from the Human Resource Management was a misc-reversing challenge. Human Resource Machine is a wonderful game from the Tomorrow Corporation, where you create simple programs alongside a cutesy but dystopian story. The programming model of the game is also rather simple. There are two queues: the INBOX and OUTBOX from which you pull input data and push output data respectively. Other than that, you have a few numbered memory cells which can either contain letters or numbers and a limited set of instructions performing operations on said cells and a single cell in your hands.

This game has a way to export your program for sending to your friends or using later, which was utilised by the authors of the challenge and this saved program was the only file that was available to the contestants, alongside a short story-based intro.

instructions.txt
    COPYFROM 22
    COPYTO   20
    COPYTO   19
    COPYTO   17
    COPYTO   16
    COPYTO   15
a:
    COPYFROM 21
    COPYTO   15
    BUMPUP   15
b:
    JUMPZ    c
    COPYFROM 18
    ADD      16
    COPYTO   16
    BUMPDN   15
    JUMP     b
c:
    COMMENT  0
    INBOX   
    COPYTO   15
    BUMPDN   20
    ADD      20
    COPYTO   20
    ADD      20
    COPYTO   20
    ADD      20
    COPYTO   20
d:
    COPYFROM 19
    SUB      19
    COPYTO   19
    BUMPUP   19
    COPYFROM 20
    COPYTO   22
    BUMPUP   22
    JUMPZ    f
e:
    COPYFROM 19
    ADD      19
    COPYTO   19
    BUMPUP   22
    JUMPN    e
f:
    COPYFROM 15
    SUB      19
    JUMPN    h
    COPYFROM 15
    SUB      19
    COPYTO   15
    COPYFROM 16
    SUB      19
    JUMPN    g
    COPYFROM 16
    SUB      19
    COPYTO   16
    JUMP     j
g:
    COPYFROM 17
    ADD      19
    COPYTO   17
    JUMP     k
h:
    COPYFROM 16
    SUB      19
    JUMPN    i
    COPYFROM 16
    SUB      19
    COPYTO   16
    COPYFROM 17
    ADD      19
    COPYTO   17
i:
j:
k:
    BUMPUP   20
    JUMPN    d
    COPYFROM 17
    SUB      [21]
    JUMPZ    l
    COPYFROM 24
    OUTBOX  
    JUMP     n
l:
    COPYFROM 20
    COPYTO   17
    BUMPDN   21
    JUMPN    m
    JUMP     a
m:
    COPYFROM 23
    OUTBOX  
n:


DEFINE COMMENT 0
eJwrZWBgSOY+pVsodLm5XWzuMSCXoYEnRSvB/rUeiK0vzNy30eKZ/S/HQCMQv1yzfNY1zYc9BTpfWrz1
LjcvNhRru2bG3NdjlTFRzVa6FKQm321R5TFP27JjYSYxID6jz2X3lnDV4FchRfk6IbkNILHS2PMyR5OF
okHsBakbm0C0UYVJjMJ+oej097w5EV9PZoLE9haXXdv/SfAyiP3pO2/Ozy8b9pV+m3EdxP9eKhQtU5Jx
FsS+VbJmcle12wSHehD+N6+pjmcFbxXPCv/Kk0tPV/2bB1IT2xA6s7rRbYJl07PqFx0zZue035n7vrV1
sUyj926QvHTfs+pTU8Ta3k9aM5l1euti1uk8K0Qmsu/81q+5CyQvNbsoP3TutsKORc+qk5fkNhxacrk5
e+GayYeW3Jl7f+nyJe/ms+8EqfNdmzHxz+ZzU0Ds5I03iq5v3d99favbhEMbi1Yf2nh9HUjc9sCiynv7
Bacf3PdzPYivfjKqI/G0Vt29M1p1O899afl8oaZz00XV/qCLaya7nFq0teDS0+0gdWqPGyvE7+itFL8z
ec3rB4u2Oj95uv3X88lrxF8uXxL54s5cxqddkxhGwSggAQAAS0PLkQ;

DEFINE LABEL 0
eJxTYmBg2OIXo+bgdU0xzSZTv9i8zaXZ0CiFSz+zm0v/x1QmI9HVn8w37Euz2Xv8ovPJC3u8z11mAIGk
EIXIglsNm+pOXlhff+0mSGhT3S2Vr6n9y3hjY+6B+E0Tzkr/mxijVjX5kdaDOYoRm9fq7dVff/AUSM5k
7UGh8AU7M35MFl3d0m9/koFIcKnFKZW3XSHFvTMlbX7Posp/PZeb5/dYtB/u2t89oXV/963GLy2xDVp1
8+vjyx/WmcTENjAHhTVbeL9v/epxs0/c61u/hbffZPcQt2lRPpWzo3z65z8M4F4I5C82iWFZkpLmuyBj
osH8yCMguxakrpW2yhCSL8n6r5abW2ysnRdv3ZV3N1E770sLX275LL7cLwe68swPPyqMPFJSXn10a3Pk
kdC5bmdAeu+dOZlpdvhk5pK9+rmrd9mWhWz/0lKx9dWyiq3sO79u+3LAZ9f+41EHz164dv79xcXXmU7d
vVl99Nod790C918tO3dfcPq5+w97gu5tbJp9K778wI2fxTvPvc4iNoxGwSggBQAAtyS7MQ;

DEFINE LABEL 1
eJzTYWBgMPa/pRLg4i8b6nRJLtsqRafYvND0j3GbS7Ph7OA2gy2pbQYH8/4bMcypMF2ytcDC/uQbS+cz
Nx18LgU7X7rm7HnpGtAIBtGoWyoCMd8tmRL1Ei8V+VwCiZ0tuaWSlr0ksi6panpbvNKWaZHOZ0Diy5su
yc1vLTQV61SLrposue7zVL29H6advACSS58Vofxi5iMtiY4fU9Wa1+w2q712EyR+YEWIwt6VEHNHwSgY
BdQDAC40R+0;

DEFINE LABEL 2
eJwTYWBgcPa8p+7glal/0NP5jEHA0bMpOQdPAYUZzhdHKJ8qfaSl1rxh3/xW9/PzW89dbu9L7UiZ/d3y
7txripHzT0qA1Jmv8ZbavYpjkeXqo2cZRsEoGAVDCgAArVkitA;

DEFINE LABEL 3
eJxTZmBgSLOJUUu21Ut8YiO5LsHu6FmgEEOy7SOtWPvPZqFOTY4XnVmcHbzaXAwCGBy0g75bLgl5rrsk
5JGWQthnsznhldZC0QwOLQlNjmWpn81yMm+pgPQ7Vj7Ssq5mcbasiahfX+9zCSS2vzJBc29Vpr52I4uz
REebi0A3izNHX53dv4m5Rp+n+ssykAhup8+YnZsgvfF21P7jx8KMr+QF/70FEm8J3zUnIqZo9atEsUOi
WXdulmQ53gWJv6w2UdjT8EH2YbeQ/MPuNu2ULi2nw13MQSC5nKoD/pZN8/xy2h8GgPj+ldsKP1R8afGv
XPYAxOetOpmZ086bs6xTPzelqyhfqfvsBdauEw9Bcs8XCclfmrjYFsQ+tGSj27c5QtGNM/4lfetvrACJ
8UzRcuJeeKRq6eL+RhDfbfHl5qJ5jgt3z+rbmz3F+ApEjduE/vlfDrAsWX3+/lKInXM3L7ZlOn3UDsSe
ethd4sthiD82bzeJCdkuvfH61tXn/2yGiJleOC9jerdCqfZZmCKEf8C/50GUj9yzKJ/IF/P8QGKCl6YU
FFzKmLjpYuvioIvvL065ALHH+wpvTt1N3hyb59Kls583VtQ+e9gj90z+zuznyx6YvDrxcOW7ZQ8efwi5
6vSx8xxIvfyZ91PfXO6axPnIbULP23NTQGIup45s+32WfeeO25q7njzW3LXjxdPt6e/Zd5Iap6OA/gAA
HL/u9w;

DEFINE LABEL 4
eJzjZ2Bg2OLX5mIQMD1AKPrkBSCXITPrZAmI3lr7x0avod+Lrf/o2X8TJde9mHmp6va8NfGBi1ic/Re/
N2agM9jTcLk5tkEhZU9DaKxlU1fYhFb3kJt97iFrJ5vEPJ/qlHp4+qLKw9OZ+zqmJi96P8lm07+eucdS
ut5fXNa56wZrV+v9+T3+z272bX1pOOn6K7/Jk1/Q2/2jYBQMRgAAtHtFDA;

DEFINE LABEL 5
eJxTZmBg0A10PqMRrLVrYvTzzg/pPJ4f0j+b5WW8N76bNz/MsfLPDOvq+Zt2VZsf3lobcBWonCFy/s6M
+LnOBc9mnSypmhxQ2d4XUNnffbRwTtv8MOl2DjeJjj82UzrfG/f0pOiw9T/SqpqcovNi5nvjx7MrrWPm
SQbFzFOMuLwwtePywvf9Fxb9meGzRGuX+7JzlxlGwSgYBXQHADjoQ6A;

DEFINE LABEL 6
eJyTYGBgOOjZ5Q6kGJw9Y5oYwHy17SD6l8mSrSD6fm6TI4hOzL1WC6K1GwMqQfSqxjo7EB2y8LsliA5f
cKkKRCtu8S4D0SqbP5uB6AVFjTa5uV891ucc8OfLDY19mdeWrlLImyNaHpD3svpk5vx6hZTvTQopxS0p
aQ+7bcss+h/2hE74Ny90gs0mi/7cPQ49+48/rFt9vqs646xRReQRy9Kf6/cWM/fJlHxp+V5qW+ZfeTIz
p2p5xvKa8rjU2jXhqbU1vh41G938K5/Z3yo5CnbjKBgFowA7AACS/1aW;

DEFINE LABEL 7
eJwTYWBgaDDqcgdSDExGITUgutlQaQuI3iy/ZCuIXhkEkRforrQG0cJd7sUg2rESQrsvg4hP3fHdEkSz
J8dbH01O0XJMVdT4kaGowZebosVbtUIHJBfbe15mWecKnerGrx4Mo2AUjIIBBwB8NB4c;

DEFINE LABEL 8
eJwTY2BgsPDt9wJSDDt9IuoZwPzpa0D0I9vJK0F0SVqXO4QOqATRp0rPlUNoFmcQzdfzxwZE9/S4F4Po
B3MO5oHofz3GM272dZ5jGAWjYBQMWgAAWecaLg;

DEFINE LABEL 9
eJwzZWBgOOKRoHnEo83Fw31NvIf7+/79XoIrtvgpbdENXLFDOXTDPqkIowOdcRv2MSSt2f0pbf6mtOw/
Mx7lvO55lBNS8zTbKOVVpmzoh/TpAWWpov41ydMDWhJkQ1kTFCPa4rXiWBMO5jElniuvSb7X/CMlteNV
5vt+oJUMW2tnB2s3ivp39fJ4Vk2WDPo2ZUlk+RTvst+TQmr6u+81S3QktP6bmNn9bcr7/vzpDHPyp7ct
+DZFcl1734Z9wl07j6yv33t8f6X5YecKrV37K7V2LW32uTSnLfXJnLbCtwyjYBSMAqIBAKVAZhk;

DEFINE LABEL 10
eJzzY2Bg2O472Xenj2yos+ea+DOuG5Ke2BilZFvpJb63UIwoNu/3Kjavs/tiVmn9yfyeeoa1u3iazVHR
CEdnEQ/3kxI7fRI0Tf1eGxgE8HiuD9CKWx9wtNDY/1aDtc/zzjOuP6a+t+hflmc5fU2Eo/1JV4+Yew5e
mc9tvcs+AK1l6It5pMUbe02xLd5dvC7Jiq8s1YovMfeoaFjhWenzxdcUj5aXWhwtV4xwK18TH1x0rvxR
TmpHZUrhxL6Yqult8QxzXmXO33Q3b+eR88WPH4HMS5l9TTFnhrdU0TRvqaYJl+T6u++pC3el6Mxqr7SW
bm9zkW6fHSzRoRY9pXNNPEffltSiaWbpWTONUhgmWmdN6nIvlui4VCXdHtMk3Z7ZLdFROFGgu24Ww0SW
+Q/mtC24O5djUfxcwRV35zqfCVz0+gXDKBgFwwQAAK8ShNQ;

DEFINE LABEL 11
eJxzYGBgWB+gGHHc/Vx5sPOthpsOtxqe2ITUfDFzLvhjbJTSYCTq/8f4j81309cG2Vb31K87xqg5ez7S
Wh9QZ6cRfLRQNzCh1drnz4wzrv3L7tjP3/TcesnWXyYb9l13DLkR4HLv/nH3z++AVjA0Jr43Lkt9rvsi
K0EzLi9B06/ktcGhiu+WdlUszttrFCO21xwt3FV9q8Gu6nXPoYrSKWdLWOan5MhuzMyav+lrqtauv0la
u36krNkdWBxyw60895VzxfvXIHMvLHquG77gkdazWc9186e/N/49icWZeYJkUE+Pc0FPz+uert7SKWz9
f2bUTGKZnzWTd6n/Yt6lJ5ZuOXhgRciNHasznzOMglEwwgEAbaB9xw;

DEFINE LABEL 12
eJxjZ2Bg2F5TaGpZUzfLutr6GJDLwN17S2VSV64RAxI46Sboc9Azt8/U73mnasjjtilRj9u44jK7O+NY
5vPGCq6QitDatcXP/DBv7JrdP1LW7E7M3bAPpM+vhMPNrbxq+v5K3qXbayTXGdYrbVnVqLZ9abPa9jlt
8zeJdYqu5uhLaGXr35nB0Sfq394nGcTRl9rBPKFtQc2k2et/TFba8mHaih23563YEbJQbfuJpbxL7Ve+
7tm96lrtjtV7s3eslgyyWVVoemJphPLduWelc2aclPg2xV2cYeJJCZD9VYd2ZtQefG2w7fZrAxA/9EKh
aeiF+WEXL0bUX7xYNf3yhaa5t8/yLn1zXHJd037R1a37Jq/s2Nu1mHNv01zOvbca2PadLGHZ71zAeMA+
99vhvdnr7tnnrr6/N3vyC+usCS+3pE54WWjKMApGwRAGAMOUkvo;

DEFINE LABEL 13
eJzjZWBgeJVZaFqYntpRmD57/ae0+ZsK0yNuA4UZcjLfGy9t/mwGYpdMjVH7NiVFp6Wfw42BSqC97979
x7Nj7oHYiXM+T4qfu2Rr/Nx790F8vp75mz5PXbI1a6bSFhBffz2Pp8rmLvePR3k8QfxFm2cHq2wOqQGx
m/aL+rPsj6gHsU3WxjQt2pzQ+uZ4QiuIf+18k2PQRQjb/5Je4s5beokgdtWhprkPTzfNvXa+bcHlC7xL
Qy8s2QoS/3PQ/PDjUxv2RZ3bsA/E/3bY/mTVoZMXqg7du//t8ONHt88+fnTxYuJDrysQd4+CUTDUAQCB
3Gxz;

DEFINE LABEL 14
eJyTYGBgEIqOUeuJ5XDri/G5BOQymNWGKPxI2ZDEmuBddryMY9HWWqUtmk3mh0Fyt+cdFX08+7sld++G
fVWTtxxU32R+eNHmnUfsV54rvzG/0vrBHHdxkLqm/faCgrveGzOMglEwCgY1AACUKSZ0;

DEFINE LABEL 18
eJwTYWBguBLrpPkjw0nToSfQCMhlKI3tb/xeurHpYXdNp2UT+87qxkv7QeKfAuceE4ude0ym0e3Mzdkh
V0Fi/pVTTLaVlcepFD7sWZ8DUTcKRsEoGDoAADKRIMw;

DEFINE LABEL 21
eJwTYGBg2O7/1eNTIIfjvkheA9moG2bpYYKXgcIML6unmDgWXW7+kdG3d0ZT396c9kv7QeLKdVEdynXV
R1NrW+8zjIJRMAqGNAAALsIZzQ;

DEFINE LABEL 22
eJzzZmBgsFVP0Yo0bdPuDgk0YjLSW2mq9eCEqXnnOY6wsmvbY/7eAiphYDIqyi83sy2L8N/fDeI7pvIa
fG/6ZQ5iW2VMMfmRMWP2zIyya3y5u278L9p1I6f9/UWQXEDOl5bOjkWVIPaESTfM3k96GDBhEkTu0sRt
pjdWTDFx3XDD7PrWRpuv257Zb95+2X3z9jXhX7fdTby+tS3dbnNAXsgq27LQuVp1jTO860H6pFb8LFbc
tGHf120Qt+1/etRu0ZNGm7s34617Lz+zD7qY5/L7bJTP4lPnIjJPFOVnnuhvlD+zaKvt2b69by6fvSB/
s+za6wdl1ya/Yjql+zbyyLH3mrvufP43L+JrTef2r/q5P7/MjN//ycK75YOWU/r7o3afXtlazX5ua8Uw
CkbBMAQAjBOTMA;

DEFINE LABEL 23
eJyTZGBgmOySKVnm3Fjxy/HfvNmOyffEnZc90Ak58fB2lN7TvcV6T2UaC58DlTHouovzbQh6JBIR4y5h
2Hpe5n3rTOWw5oC8PQ3yd0DygUFfWo6FCU4vjWXfuSBVtX9BkVOqf+VXD4ZRMApGwaAFAO6EKM0;

DEFINE LABEL 24
eJyTYGBgmOV3QNja77zMdv814WIBXZO2+++68SlwxvVHKcse7Gnwf+Y2Teqt5tSn209N+SAbOiFTkrVL
TXxyoLsEUCvD7ajFto5FHLXr2qQ3gvin222tJlbMjG8u/FnMl/BzPcMoGAWjYFADAEA+KQU;
story intro
Story-themed challenge intro with some hints.
Story-themed challenge intro with some hints.

At first I thought the file was just some arbitrary file format the challenge authors used and only later found out it was a file format used by the game itself. When I did, I learned that COMMENTs are code comments that can be referenced from the program and LABELs label individual numbered cells on the floor in the game. Both are however, not text-based but rather hand drawn and saved in some arbitrary file format. This file format turns out to be a zlib compressed and base64 encoded binary blob (with stripped = padding). The first four bytes denote the length n with a uint32 and then there are n pairs of uint16 point coordinates. A pen-up instruction is denoted with (0,0). There are multiple projects that can decode this format, I chose pyhrm. Rather unfortunately it uses pyturtle to display the images, so I added these two lines to pop the screen out to PostScript each time:

ts = turtle.getscreen()
ts.getcanvas().postscript(file=f"{title}.eps")

After converting PostScript to PNG, I was met with these images:

A screenshot of the labels
A screenshot of the labels

The one comment seems to be some hint which I have not been able to understand to this day. All the labels seem to be numbers in various wild notations (roman, some old eastern, braille and the literal word ZERO) with labels 23 and 24 denoting a checkmark and a crossmark respectively.

The challenge itself has a hint which links to Wikipedia’s list of numeral systems. From there, I was able to translate all the labels into decimal numbers and figured they denote the initial state of the respective cells.

After that we are off to start analyzing the instructions. First thing I noticed was that there are two places, where we read from cells we’ve never wrote to and send those directly to the output. Those cells are numbers 23 and 24, which are labeled with a checkmark and crossmark. I figured these were meant as replacements for return true or return false, so I treated them as such. I also created myself a flowchart to better understand the code and also started using the interpreter.py from pyhrm which I modified slightly – made it die silently when running out of instructions and added the return true × return false mechanism by always setting the cells to 666 and 777 and exiting the program with different exit codes:

diff --git a/interpreter.py b/interpreter.py
index 94f71c3..2da933f 100644
--- a/interpreter.py
+++ b/interpreter.py
@@ -33,7 +33,11 @@ def run_program(opcodes, jumps, memory, inbox, verbose=False):
   hand = None
   ip = 0
   while True:
-    inst = opcodes[ip]
+    try:
+        inst = opcodes[ip]
+    except IndexError:
+        print(memory)
+        exit()
     if verbose:
       memstr = ' '.join('%d:%s' % (i,x) for i,x in enumerate(memory)
                         if x is not None)
@@ -47,6 +51,11 @@ def run_program(opcodes, jumps, memory, inbox, verbose=False):
       else:
         hand = int(val)
     elif op == 'OUTBOX':
+      if hand == 666:
+        print(memory)
+        exit(3)
+      if hand == 777:
+        exit(2)
       yield hand
       hand = None
     elif op == 'COPYFROM':
...

With this I found that when putting random values on the INBOX queue, theres always one value that makes the program continue while all the others lead to exit(2). This enabled me to write a simple fuzzing script in bash:

while true
do
	for i in {0..300}
	do
		echo $i
		cat numbers <(echo $i) | python3 ./pyhrm/interpreter.py --initmem 0 120 1 121 2 56 3 32 4 107 5 89 6 77 7 103 8 78 9 73 10 123 11 125 12 10 13 3 14 24 18 5 21 14 22 0 23 666 24 777 --memsize 25 instructions.txt
		if [[ "$?" == "1" ]]
		then
			echo $i >> numbers
			cat numbers
			break
		fi
	done
done

This produced a list of numbers (the last of which I had to add manually because I was too lazy to modify the script):

83
69
75
65
76
123
99
79
110
71
114
52
55
115
125

This, when translated to ASCII, reads: SEKAL{cOnGr47s}, which is clearly not correct and I must’ve made a mistake reading the numbers from the images, but changing the L to an I was enough to produce the correct flag: SEKAI{cOnGr47s}.