GreyCTF 2025
1661 words | 8 minutes
Update CAA 260625: Currently, I’ve only got the qualifier writeups here. That’ll change after finals.
Reverse Engineering
whr cat
I don’t usually document cheese solves (blatant lie). but this one was especially funny so I’ll jot it down.
Initial Scoping
We’re given three files, runner
, chall.sad
and catt.enc
. Trying to run runner
shows us we need to pass chall.sad
as a file to run, and finnicking with the arguments more reveals how the encryption made to produce catt.enc
is conducted.
![]() |
---|
Fig: LGTM |
So the challenge is clear: figure out the input catt.txt
that produces catt.enc
. Cool. This is probably some byte VM challenge, so we toss runner
into Ghidra to take a looky, and well…
![]() |
---|
Fig: Yucky |
This byte VM is a bit yucky. Here we have two options: solve this the way God intended, or cheese it.
Decompiling Like a Good Kitty
We will be obedient. Let’s break down each function one by one. I’ll be using the default variable names that Ghidra produces, so bear with me. Our byte VM running code starts at 0x001018df
, and the important arguments are:
param_4
is bytecode, starting at position 0x38, skippinggoogoogaga
andsDda
(which may be constants?)param_5
is the length ofparam_6
to the closest multiple of 8param_6
is the filename we supply as the input text.
Jumping into the function…
1 FUN_00103298(local_5e88);
2 FUN_0010371a(local_5df8);
3 FUN_0010371a(local_3ea8);
4 FUN_001037b9(local_5df8,local_3ea8);
5 FUN_001037b9(local_3ea8,0xffffffffffffffff);
local_5e88
are your “registers”, zeroed out by the functionFUN_00103298
. We label that asregs
local_5df8
andlocal_3ea8
are both “stacks”, zeroed out by the functionFUN_0010371a
. We label them asstack1
andstack2
(How creative!)FUN_001037b9
is a push function.
We push on and define a bunch of the functions. Cleaning up, we get the following:
1 while (iVar2 = is_empty(stack1), iVar2 == 0) {
2 opcode = *(byte *)(bytecode + ip);
3 iVar2 = (int)ip;
4 if (opcode == 0x99) {
5 opcode = read_char(bytecode,iVar2 + 1);
6 local_5e08 = 0;
7 syscall((long)regs,stack2_,(ulong)opcode);
8 if (local_5e08 == -1) break;
9 ip = ip + 2;
10 }
11 else if (opcode < 0x9a) {
12 if (opcode == 0x40) {
13 ip = *stack2_;
14 if (ip == -1) break;
15 stack2_ = (long *)pop(stack1);
16 }
17 else if (opcode < 0x41) {
18 if (opcode == 0x3f) {
19 jmp_pos = read_int(bytecode,iVar2 + 1);
20 clear_stack(local_1f58);
21 push(stack1,local_1f58);
22 push(local_1f58,ip + 9);
23 ip = jmp_pos + ip;
24 }
25 else if (opcode < 0x40) {
26 if (opcode == 0x3e) {
27 jmp_pos = read_int(bytecode,iVar2 + 1);
28 uVar1 = read_char(bytecode,(int)ip + 9);
29 uVar4 = get_reg(regs,uVar1);
30 uVar1 = read_char(bytecode,(int)ip + 10);
31 uVar5 = get_reg(regs,uVar1);
32 iVar2 = neq(uVar4,uVar5);
33 if (iVar2 == 0) {
34 ip = ip + 0xb;
35 }
36 else {
37 ip = jmp_pos + ip;
38 }
39 }
40 else if (opcode < 0x3f) {
41 if (opcode == 0x3d) {
42 jmp_pos = read_int(bytecode,iVar2 + 1);
43 uVar1 = read_char(bytecode,(int)ip + 9);
44 uVar4 = get_reg(regs,uVar1);
45 uVar1 = read_char(bytecode,(int)ip + 10);
46 uVar5 = get_reg(regs,uVar1);
47 iVar2 = eq(uVar4,uVar5);
48 if (iVar2 == 0) {
49 ip = ip + 0xb;
50 }
51 else {
52 ip = jmp_pos + ip;
53 }
54 }
55 else if (opcode < 0x3e) {
56 if (opcode == 0x3c) {
57 jmp_pos = read_int(bytecode,iVar2 + 1);
58 uVar1 = read_char(bytecode,(int)ip + 9);
59 uVar4 = get_reg(regs,uVar1);
60 uVar1 = read_char(bytecode,(int)ip + 10);
61 uVar5 = get_reg(regs,uVar1);
62 iVar2 = leq(uVar4,uVar5);
63 if (iVar2 == 0) {
64 ip = ip + 0xb;
65 }
66 else {
67 ip = jmp_pos + ip;
68 }
69 }
70 else if (opcode < 0x3d) {
71 if (opcode == 0x3b) {
72 jmp_pos = read_int(bytecode,iVar2 + 1);
73 uVar1 = read_char(bytecode,(int)ip + 9);
74 uVar4 = get_reg(regs,uVar1);
75 uVar1 = read_char(bytecode,(int)ip + 10);
76 uVar5 = get_reg(regs,uVar1);
77 iVar2 = geq(uVar4,uVar5);
78 if (iVar2 == 0) {
79 ip = ip + 0xb;
80 }
81 else {
82 ip = jmp_pos + ip;
83 }
84 }
85 else if (opcode < 0x3c) {
86 if (opcode == 0x3a) {
87 jmp_pos = read_int(bytecode,iVar2 + 1);
88 uVar1 = read_char(bytecode,(int)ip + 9);
89 uVar4 = get_reg(regs,uVar1);
90 uVar1 = read_char(bytecode,(int)ip + 10);
91 uVar5 = get_reg(regs,uVar1);
92 iVar2 = lt(uVar4,uVar5);
93 if (iVar2 == 0) {
94 ip = ip + 0xb;
95 }
96 else {
97 ip = jmp_pos + ip;
98 }
99 }
100 else if (opcode < 0x3b) {
101 if (opcode == 0x39) {
102 jmp_pos = read_int(bytecode,iVar2 + 1);
103 uVar1 = read_char(bytecode,(int)ip + 9);
104 uVar4 = get_reg(regs,uVar1);
105 uVar1 = read_char(bytecode,(int)ip + 10);
106 uVar5 = get_reg(regs,uVar1);
107 iVar2 = gt(uVar4,uVar5);
108 if (iVar2 == 0) {
109 ip = ip + 0xb;
110 }
111 else {
112 ip = jmp_pos + ip;
113 }
114 }
115 else if (opcode < 0x3a) {
116 if (opcode == 0x38) {
117 jmp_pos = read_int(bytecode,iVar2 + 1);
118 ip = jmp_pos + ip;
119 }
120 else if (opcode < 0x39) {
121 if (opcode == 0x23) {
122 uVar1 = read_char(bytecode,iVar2 + 1);
123 uVar4 = get_reg(regs,uVar1);
124 uVar5 = read_int(bytecode,(int)ip + 2);
125 movi_(uVar4,uVar5);
126 ip = ip + 10;
127 }
128 else if (opcode < 0x24) {
129 if (opcode == 0x22) {
130 uVar1 = read_char(bytecode,iVar2 + 1);
131 plVar3 = (long *)get_reg(regs,uVar1);
132 iVar2 = read_short(bytecode,(int)ip + 2);
133 if (plVar3 == &local_5e08) {
134 for (j = 0; j < param_3; j = j + 1) {
135 if (iVar2 == *(int *)(param_1 + (long)j * 4)) {
136 local_5e08 = *(long *)(param_2 + (long)j * 8);
137 }
138 }
139 }
140 else {
141 movi__(plVar3,iVar2);
142 }
143 ip = ip + 6;
144 }
145 else if (opcode < 0x23) {
146 if (opcode == 0x21) {
147 uVar1 = read_char(bytecode,iVar2 + 1);
148 uVar4 = get_reg(regs,uVar1);
149 uVar1 = read_char(bytecode,(int)ip + 2);
150 movi(uVar4,uVar1);
151 ip = ip + 3;
152 }
153 else if (opcode < 0x22) {
154 if (opcode == 0x20) {
155 uVar1 = read_char(bytecode,iVar2 + 1);
156 uVar4 = get_reg(regs,uVar1);
157 uVar1 = read_char(bytecode,(int)ip + 2);
158 uVar5 = get_reg(regs,uVar1);
159 mov___(uVar4,uVar5);
160 ip = ip + 3;
161 }
162 else if (opcode < 0x21) {
163 if (opcode == 0x19) {
164 uVar1 = read_char(bytecode,iVar2 + 1);
165 puVar6 = (undefined8 *)get_reg(regs,uVar1);
166 uVar4 = pop_(stack2_);
167 *puVar6 = uVar4;
168 ip = ip + 2;
169 }
170 else if (opcode < 0x1a) {
171 if (opcode == 0x18) {
172 uVar1 = read_char(bytecode,iVar2 + 1);
173 uVar4 = get_reg(regs,uVar1);
174 push_(stack2_,uVar4);
175 ip = ip + 2;
176 }
177 else if (opcode < 0x19) {
178 if (opcode == 0x17) {
179 uVar1 = read_char(bytecode,iVar2 + 1);
180 uVar4 = get_reg(regs,uVar1);
181 uVar1 = read_char(bytecode,(int)ip + 2);
182 uVar5 = get_reg(regs,uVar1);
183 xor(uVar4,uVar5);
184 ip = ip + 3;
185 }
186 else if (opcode < 0x18) {
187 if (opcode == 0x16) {
188 uVar1 = read_char(bytecode,iVar2 + 1);
189 uVar4 = get_reg(regs,uVar1);
190 uVar1 = read_char(bytecode,(int)ip + 2);
191 uVar5 = get_reg(regs,uVar1);
192 and(uVar4,uVar5);
193 ip = ip + 3;
194 }
195 else if (opcode < 0x17) {
196 if (opcode == 0x15) {
197 uVar1 = read_char(bytecode,iVar2 + 1);
198 uVar4 = get_reg(regs,uVar1);
199 uVar1 = read_char(bytecode,(int)ip + 2);
200 uVar5 = get_reg(regs,uVar1);
201 or(uVar4,uVar5);
202 ip = ip + 3;
203 }
204 else if (opcode < 0x16) {
205 if (opcode == 0x14) {
206 uVar1 = read_char(bytecode,iVar2 + 1);
207 uVar4 = get_reg(regs,uVar1);
208 uVar1 = read_char(bytecode,(int)ip + 2);
209 uVar5 = get_reg(regs,uVar1);
210 div(uVar4,uVar5);
211 ip = ip + 3;
212 }
213 else if (opcode < 0x15) {
214 if (opcode == 0x13) {
215 uVar1 = read_char(bytecode,iVar2 + 1);
216 uVar4 = get_reg(regs,uVar1);
217 uVar1 = read_char(bytecode,(int)ip + 2);
218 uVar5 = get_reg(regs,uVar1);
219 local_5e78 = divmod(uVar4,uVar5);
220 ip = ip + 3;
221 }
222 else if (opcode < 0x14) {
223 if (opcode == 0x12) {
224 uVar1 = read_char(bytecode,iVar2 + 1);
225 uVar4 = get_reg(regs,uVar1);
226 uVar1 = read_char(bytecode,(int)ip + 2);
227 uVar5 = get_reg(regs,uVar1);
228 mul(uVar4,uVar5);
229 ip = ip + 3;
230 }
231 else if (opcode < 0x13) {
232 if (opcode == 0x10) {
233 uVar1 = read_char(bytecode,iVar2 + 1);
234 uVar4 = get_reg(regs,uVar1);
235 uVar1 = read_char(bytecode,(int)ip + 2);
236 uVar5 = get_reg(regs,uVar1);
237 add(uVar4,uVar5);
238 ip = ip + 3;
239 }
240 else if (opcode == 0x11) {
241 uVar1 = read_char(bytecode,iVar2 + 1);
242 uVar4 = get_reg(regs,uVar1);
243 uVar1 = read_char(bytecode,(int)ip + 2);
244 uVar5 = get_reg(regs,uVar1);
245 sub(uVar4,uVar5);
246 ip = ip + 3;
247 }
248 }
249 }
250 }
251 }
252 }
253 }
254 }
255 }
256 }
257 }
258 }
259 }
260 }
261 }
262 }
263 }
264 }
265 }
266 }
267 }
268 }
269 }
270 }
The most interesting of the bunch is opcode 0x22 as there’s sort of a lookup table bit. Also, get_reg
is a bit of a misnomer as it could also be getting the value as opposed to the register pointer. Anyways, we now have enough to press on and get some LLM to make a decompiler for us:
The Way of the Cheese
meowware
meowware is a tricky “fileless” malware challenge which required some hoop jumping to solve. During the course of the qualifiers, I decided to focus my efforts on other challenges (cough, blockchain, cough), but this was a challenge I felt would be good to upsolve.
We’re given a client file and a PCAP file. Unfortunately, the PCAP doesn’t give us much to work with as the communication is encrypted:
![]() |
---|
Fig: Looks encrypted to me |