I have participated with my Team 0xWraith, and was able to clear all reverse engineering challenges. Here are the solutions to them..
[REV][Starwars]
We are given a game with 3 files

By inspecting the exe file in DIE, we get this

It is a game made with the GameMaker engine
Upon opening it, you will be controlling a dart that shoots circles, and your score will increase at the top left.

The idea here is that the win condition is not implemented, so let’s dig deeper to see what we can do.
The main logic, objects, functions, variables, and fonts of the game are in a file called data.win, and there is an open-source tool called UndertaleModTool that enables you to decompile the game.
opening the data.win file ->

By exploring the functions, I saw that one

This is the win function obviously
the flag drawing is in the draw_text function, so we have to move this code in the obj_game events to be triggered


And then we see the flag

CONCTF{WH4t_4_hAck3r_y0u_ar3}
[REV][Yoru]
We are given a PE file as shown

Upon executing it, we are prompted to a window with two input fields :

Let’s explore the file in IDA to see what is going on
We got so many useless functions for our analysis

But since we saw text on the window like {“wrong”,“length”,“Username”} and so on, it is probably hard-coded in the program, and if we use the search text feature in IDA, we will spot an important function in IDA, which checks for username and password.
The function does simple XOR encryption with our input and compares the result to hard hard-coded expected data


The login info is:
username: “Did_you_really”
password:“fall_for_my_trap?_xD”

And we got that window mentioning that pc name is wrong, that could be a hint or something.
i found this function that get my computer name and inputs it along with a key (0x7ff7ea47a000) to an encryption function, and then compares the result to hardcoded data in 0x7FF7EA47A010
address

by searching inside this encryption function, we find it is the AES_ECB algorithm

Since we have the key and the ciphertext, we can decrypt it to obtain the plain text which is the computer name that was supposedly intended, but we still do not know why we need that computer name, any way let’s continue
from Crypto.Cipher import AES
key = bytes([
0x2B, 0x7E, 0x15, 0x16,
0x28, 0xAE, 0xD2, 0xA6,
0xAB, 0xF7, 0x15, 0x88,
0x09, 0xCF, 0x4F, 0x3C
])
result_data= bytes([
0x4F, 0x03, 0xE8, 0xE7,
0x76, 0x8B, 0x0E, 0x8B,
0x29, 0x25, 0x63, 0x62,
0x45, 0x66, 0xD5, 0x1F
])
c= AES.new(key, AES.MODE_ECB)
p= c.decrypt(result_data)
print(p)
C0nN3ctEd_aDmi1n
by digging deeper in the program, we found that flag check function it takes the pc name as a key and encrypts bytes, and then compares it to the true data.

This is a straightforward RC4 decryption:
def rc4(input_bytes: bytes, key: bytes) -> bytes:
# Initialize S array like in the binary (256..511)
S = list(range(256)) # This corresponds to v5[256..511]
keylen = len(key)
# Copy key into v5[0..255]
v = [key[i % keylen] for i in range(256)]
# KSA (key scheduling)
j = 0
for i in range(256):
j = (j + v[i] + S[i]) & 0xFF
S[i], S[j] = S[j], S[i]
# PRGA (stream generation & XOR)
out = bytearray()
i = 0
j = 0
for b in input_bytes:
i = (i + 1) & 0xFF
j = (j + S[i]) & 0xFF
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) & 0xFF]
out.append(b ^ K)
return bytes(out)
if __name__ == "__main__":
cipher_bytes = bytes([
0x8F, 0x06, 0xC0, 0x3D, 0x54, 0x9C, 0xFA, 0x26,
0xCF, 0x14, 0xC8, 0xBC, 0x1D, 0xC7, 0x3B, 0x1B,
0x9C, 0xE4, 0xD5, 0xDD, 0x1B, 0x9C, 0x61, 0x0B,
0xD8, 0x8B, 0xAF, 0xD4, 0x3D, 0x5F, 0x25, 0x00,
0xA0, 0xFA, 0xCA, 0xED, 0xD0, 0xA2, 0x6A, 0x7A,
0x71, 0x89, 0x3B, 0xD9])
key = b"C0nN3ctEd_aDmi1n"
result = rc4(cipher_bytes, key)
print(result)
CONCTF{c0NNect3d_Successfully_T0_Th3_CTF_:D}
[REV][Roblox Active Developer]
We are given a luac file, we will use luadec to decompile it.
We see many dummy function that has nothing to do with the flag, but again, we will search
by words like {“flag”,“correct”,“wrong”}.

And we get this function
io.write("Enter flag: ")
local r23_0 = r6_0(io.read("*l") or "")
local r24_0 = r15_0()
local r26_0 = r20_0(r23_0, r24_0, r16_0(r24_0))
local r27_0 = #r26_0 == #r21_0
if r27_0 then
for r31_0 = 1, #r26_0, 1 do
if r26_0[r31_0] ~= r21_0[r31_0] then
r27_0 = false
break
end
end
end
if r27_0 then
print("[+] Correct! You solved it.")
else
print("[-] Nope.")
end
Note that in that weird language, a ‘#’ before a var means the length of the data IN the var.
The function seems to do some encryption to our input (r23_0) and compare the result to some data, which is r21_0.
The target_bytes (r21_0) are :
ciphertext = [250, 114, 60, 118, 65, 181, 249, 18, 67, 31, 28, 124, 135, 132, 173, 161, 183, 49, 71, 88, 22, 212, 47, 194, 9, 127, 177, 61, 8, 188, 53, 224, 47, 15, 243, 226, 43, 88, 249, 232, 84, 26, 226, 176, 221, 64, 202, 223]
let’s see what r15_0 has…
by extracting all needed functions for r15_0, which are: r11,r5,r12,and the r14_0 variable and running it, we got that array:
bytes:
[1] = 131
[2] = 73
[3] = 49
[4] = 35
[5] = 131
[6] = 228
[7] = 99
[8] = 178
[9] = 223
[10] = 168
[11] = 84
[12] = 165
[13] = 118
[14] = 83
[15] = 178
[16] = 67
And here is the complete code:
local function r11_0(r0_1012, r1_1012)
-- line: [3058, 3065] id: 1012
local r2_1012 = r1_1012 or 2166136261
for r6_1012 = 1, #r0_1012, 1 do
r2_1012 = (r2_1012 ~ r0_1012[r6_1012]) * 16777619 & 4294967295
end
return r2_1012
end
local function r5_0(r0_1006, r1_1006)
-- line: [3018, 3021] id: 1006
r1_1006 = r1_1006 & 31
return (r0_1006 << r1_1006 | r0_1006 >> 32 - r1_1006) & 4294967295
end
local function r12_0(r0_1013)
-- line: [3067, 3072] id: 1013
r0_1013 = r0_1013 ~ r0_1013 << 13 & 4294967295
r0_1013 = r0_1013 ~ r0_1013 >> 17 & 4294967295
r0_1013 = r0_1013 ~ r0_1013 << 5 & 4294967295
return r0_1013 & 4294967295
end
local r14_0 = {
99, 188, 92, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118
, 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192
, 183, 253, 147, 38, 54, 63, 247, 140, 52, 165, 229, 241, 113, 216, 49, 21
, 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 175, 150, 117
, 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 43, 47, 42, 132
, 83, 209, 0, 237, 32, 252, 161, 91, 122, 203, 190, 57, 74, 76, 88, 206
, 209, 239, 170, 237, 32, 252, 185, 91, 190, 203, 190, 57, 74, 76, 88, 206
, 208, 238, 66, 204, 233, 186, 101, 205, 132, 54, 226, 8, 133, 125, 188, 15
, 9, 101, 44, 28, 29, 110, 84, 170, 82, 59, 214, 25, 222, 47, 41, 25
, 97, 129, 79, 220, 20, 20, 144, 26, 74, 16, 170, 20, 223, 43, 10, 219
, 224, 50, 56, 10, 73, 6, 36, 82, 194, 163, 121, 211, 25, 106, 228, 121
, 231, 5, 35, 109, 133, 250, 78, 234, 108, 86, 101, 122, 124, 122, 174, 8
, 186, 120, 37, 46, 28, 166, 181, 200, 232, 221, 116, 223, 148, 31, 139, 138
, 112, 124, 37, 93, 72, 3, 232, 110, 1, 84, 101, 29, 205, 115, 65, 46
, 225, 240, 152, 17, 14, 31, 246, 71, 107, 85, 155, 89, 90, 213, 187, 22
, 140, 161, 137, 13, 191, 230, 163, 96, 65, 153, 45, 47, 73, 84, 187, 186
} -- paste the table definition
-- Copy r15_0 function
function r15_0()
-- line: [6107, 6149] id: 2014
local r0_2014 = {
108,
117,
97
}
local r1_2014 = {
45,
114,
51,
118
}
local r2_2014 = {
45,
99,
104,
97,
108,
108,
101,
110,
103,
101
}
local r3_2014 = {
33
}
local r4_2014 = {}
for r8_2014, r9_2014 in ipairs({
r0_2014,
r1_2014,
r2_2014,
r3_2014
}) do
for r13_2014 = 1, #r9_2014, 1 do
r4_2014[#r4_2014 + 1] = r9_2014[r13_2014]
end
end
local r5_2014 = r11_0(r4_2014)
local r6_2014 = {
3,
1,
4,
1,
5,
9,
2,
6,
5,
3,
5,
8
}
for r10_2014 = 1, #r6_2014, 1 do
local r11_2014 = (r5_2014 ~ r6_2014[r10_2014] * 2654435761 & 4294967295) & 4294967295
r5_2014 = r5_0(r11_0({
[1] = r11_2014 & 255,
[2] = r11_2014 >> 8 & 255,
[3] = r11_2014 >> 16 & 255,
[4] = r11_2014 >> 24 & 255,
}, r11_2014), r6_2014[r10_2014] % 13 + 3)
end
local r7_2014 = r5_2014 ~ 2779096485
local r8_2014 = {}
for r12_2014 = 0, 3, 1 do
r7_2014 = r12_0((r7_2014 ~ 3287226785 + r12_2014 * 2654435761 & 4294967295) & 4294967295)
local r13_2014 = r7_2014 ~ r5_0(r7_2014, r12_2014 + 1) ~ 322420463
r8_2014[#r8_2014 + 1] = r13_2014 & 255
r8_2014[#r8_2014 + 1] = r13_2014 >> 8 & 255
r8_2014[#r8_2014 + 1] = r13_2014 >> 16 & 255
r8_2014[#r8_2014 + 1] = r13_2014 >> 24 & 255
end
for r12_2014 = 1, 16, 1 do
r8_2014[r12_2014] = (r8_2014[r12_2014] ~ r14_0[(r8_2014[(r12_2014 - 2) % 16 + 1] + r12_2014 * 17 & 255) + 1]) & 255
end
return r8_2014
end
-- Run it and print the key
local key = r15_0()
for i = 1, #key do
print(string.format("[%d] = %d", i, key[i]))
end
Now we need to see what R16_0 outputs.
Here is the code of it
local function r12_0(r0_1013)
r0_1013 = r0_1013 ~ r0_1013 << 13 & 4294967295
r0_1013 = r0_1013 ~ r0_1013 >> 17 & 4294967295
r0_1013 = r0_1013 ~ r0_1013 << 5 & 4294967295
return r0_1013 & 4294967295
end
local function r11_0(r0_1012, r1_1012)
local r2_1012 = r1_1012 or 2166136261
for r6_1012 = 1, #r0_1012, 1 do
r2_1012 = (r2_1012 ~ r0_1012[r6_1012]) * 16777619 & 4294967295
end
return r2_1012
end
local r14_0 = {
99, 188, 92, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118,
202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192,
183, 253, 147, 38, 54, 63, 247, 140, 52, 165, 229, 241, 113, 216, 49, 21,
4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 175, 150, 117,
9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 43, 47, 42, 132,
83, 209, 0, 237, 32, 252, 161, 91, 122, 203, 190, 57, 74, 76, 88, 206,
209, 239, 170, 237, 32, 252, 185, 91, 190, 203, 190, 57, 74, 76, 88, 206,
208, 238, 66, 204, 233, 186, 101, 205, 132, 54, 226, 8, 133, 125, 188, 15,
9, 101, 44, 28, 29, 110, 84, 170, 82, 59, 214, 25, 222, 47, 41, 25,
97, 129, 79, 220, 20, 20, 144, 26, 74, 16, 170, 20, 223, 43, 10, 219,
224, 50, 56, 10, 73, 6, 36, 82, 194, 163, 121, 211, 25, 106, 228, 121,
231, 5, 35, 109, 133, 250, 78, 234, 108, 86, 101, 122, 124, 122, 174, 8,
186, 120, 37, 46, 28, 166, 181, 200, 232, 221, 116, 223, 148, 31, 139, 138,
112, 124, 37, 93, 72, 3, 232, 110, 1, 84, 101, 29, 205, 115, 65, 46,
225, 240, 152, 17, 14, 31, 246, 71, 107, 85, 155, 89, 90, 213, 187, 22,
140, 161, 137, 13, 191, 230, 163, 96, 65, 153, 45, 47, 73, 84, 187, 186
}
local function r16_0(r0_3015)
local r1_3015 = {}
for r5_3015 = 1, 16, 1 do
r1_3015[r5_3015] = r0_3015[17 - r5_3015]
end
local r2_3015 = r11_0(r1_3015)
local r3_3015 = {}
for r7_3015 = 1, 16, 1 do
r3_3015[r7_3015] = r0_3015[r7_3015] ~ 90
end
local r4_3015 = r11_0(r3_3015, r2_3015)
local r5_3015 = {}
local r6_3015 = (r2_3015 ~ r4_3015 ~ 3735928559) & 4294967295
for r10_3015 = 0, 15, 1 do
r6_3015 = r12_0(r6_3015 + r10_3015 * 74565 & 4294967295)
r5_3015[r10_3015 + 1] = (r6_3015 ~ r14_0[(r6_3015 >> (r10_3015 & 31) & 255) + 1] ~ r0_3015[r10_3015 + 1]) & 255
end
return r5_3015
end
local r24_0 = {131, 73, 49, 35, 131, 228, 99, 178, 223, 168, 84, 165, 118, 83, 178, 67}
local processed_key = r16_0(r24_0)
print("{" .. table.concat(processed_key, ", ") .. "}")
This gives us
{178, 41, 50, 242, 52, 21, 116, 232, 122, 160, 110, 13, 188, 35, 137, 56}
Finally, we need to know what does r20_0 function does.
local function r20_0(r0_4019, r1_4019, r2_4019)
-- line: [12219, 12231] id: 4019
local r3_4019 = r10_0(r0_4019, 16)
local r4_4019 = {}
local r5_4019 = r8_0(r2_4019)
for r9_4019 = 1, #r3_4019, 16 do
local r10_4019 = {}
for r14_4019 = 0, 15, 1 do
r10_4019[r14_4019 + 1] = r3_4019[r9_4019 + r14_4019]
end
local r12_4019 = r19_0(r9_0(r10_4019, r5_4019), r1_4019)
for r16_4019 = 1, 16, 1 do
r4_4019[#r4_4019 + 1] = r12_4019[r16_4019]
end
r5_4019 = r12_4019
end
return r4_4019
end
Using AI, it does custom AES-CBC encryption, and here is the decryption script:
key = bytes([131, 73, 49, 35, 131, 228, 99, 178, 223, 168, 84, 165, 118, 83, 178, 67])
iv = bytes([178, 41, 50, 242, 52, 21, 116, 232, 122, 160, 110, 13, 188, 35, 137, 56])
ct = bytes([
250, 114, 60, 118, 65, 181, 249, 18, 67, 31, 28, 124, 135, 132, 173, 161,
183, 49, 71, 88, 22, 212, 47, 194, 9, 127, 177, 61, 8, 188, 53, 224,
47, 15, 243, 226, 43, 88, 249, 232, 84, 26, 226, 176, 221, 64, 202, 223
])
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = unpad(cipher.decrypt(ct), AES.block_size)
print("FLAG:", plaintext.decode())
"""
# S-box (r14_0)
SBOX = [
99, 188, 92, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118,
202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192,
183, 253, 147, 38, 54, 63, 247, 140, 52, 165, 229, 241, 113, 216, 49, 21,
4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 175, 150, 117,
9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 43, 47, 42, 132,
83, 209, 0, 237, 32, 252, 161, 91, 122, 203, 190, 57, 74, 76, 88, 206,
209, 239, 170, 237, 32, 252, 185, 91, 190, 203, 190, 57, 74, 76, 88, 206,
208, 238, 66, 204, 233, 186, 101, 205, 132, 54, 226, 8, 133, 125, 188, 15,
9, 101, 44, 28, 29, 110, 84, 170, 82, 59, 214, 25, 222, 47, 41, 25,
97, 129, 79, 220, 20, 20, 144, 26, 74, 16, 170, 20, 223, 43, 10, 219,
224, 50, 56, 10, 73, 6, 36, 82, 194, 163, 121, 211, 25, 106, 228, 121,
231, 5, 35, 109, 133, 250, 78, 234, 108, 86, 101, 122, 124, 122, 174, 8,
186, 120, 37, 46, 28, 166, 181, 200, 232, 221, 116, 223, 148, 31, 139, 138,
112, 124, 37, 93, 72, 3, 232, 110, 1, 84, 101, 29, 205, 115, 65, 46,
225, 240, 152, 17, 14, 31, 246, 71, 107, 85, 155, 89, 90, 213, 187, 22,
140, 161, 137, 13, 191, 230, 163, 96, 65, 153, 45, 47, 73, 84, 187, 186
]
# Helper functions
def r5_0(value, shift):
shift = shift & 31
return ((value << shift) | (value >> (32 - shift))) & 0xFFFFFFFF
def r18_0(key_array, index):
result = 0
for i in range(4):
if index + i < len(key_array):
result |= (key_array[index + i] << (i * 8))
return result & 0xFFFFFFFF
def r17_0(input_bytes, round_key):
result = []
for i in range(8):
key_byte = (round_key >> ((i % 4) * 8)) & 0xFF
xored = (input_bytes[i] ^ key_byte) & 0xFF
sbox_val = SBOX[xored]
pos_val = ((i * 29) ^ round_key) & 0xFF
result.append((sbox_val + pos_val) & 0xFF)
return result
def r9_0(arr1, arr2):
result = []
for i in range(len(arr1)):
val1 = arr1[i] if i < len(arr1) else 0
val2 = arr2[i] if i < len(arr2) else 0
result.append((val1 ^ val2) & 0xFF)
return result
def r19_0_encrypt(input_block, key):
left = input_block[:8]
right = input_block[8:16]
round_keys = []
for round_num in range(8):
packed_key = r18_0(key, (round_num % 12)) # Note: Lua uses 1-based indexing
xored_key = (packed_key ^ (round_num * 2654435761)) & 0xFFFFFFFF
round_key = r5_0(xored_key, (round_num + 3) & 31)
round_keys.append(round_key)
for round_num in range(8):
f_result = r17_0(right, round_keys[round_num])
new_left = r9_0(left, f_result)
left, right = right, new_left
return left + right
def r19_0_decrypt(ciphertext_block, key):
left = ciphertext_block[:8]
right = ciphertext_block[8:16]
round_keys = []
for round_num in range(8):
packed_key = r18_0(key, (round_num % 12))
xored_key = (packed_key ^ (round_num * 2654435761)) & 0xFFFFFFFF
round_key = r5_0(xored_key, (round_num + 3) & 31)
round_keys.append(round_key)
for round_num in range(7, -1, -1): # 7, 6, 5, 4, 3, 2, 1, 0
f_result = r17_0(left, round_keys[round_num])
new_right = r9_0(right, f_result)
left, right = new_right, left
return left + right
def decrypt_cbc(ciphertext, key, iv):
plaintext = []
prev_block = iv[:]
# Process each 16-byte block
for i in range(0, len(ciphertext), 16):
block = ciphertext[i:i+16]
# Decrypt block with custom cipher
decrypted_block = r19_0_decrypt(block, key)
# XOR with previous ciphertext (CBC)
plain_block = r9_0(decrypted_block, prev_block)
plaintext.extend(plain_block)
prev_block = block
return plaintext
key = [131, 73, 49, 35, 131, 228, 99, 178, 223, 168, 84, 165, 118, 83, 178, 67]
iv = [178, 41, 50, 242, 52, 21, 116, 232, 122, 160, 110, 13, 188, 35, 137, 56]
ciphertext = [250, 114, 60, 118, 65, 181, 249, 18, 67, 31, 28, 124, 135, 132, 173, 161, 183, 49, 71, 88, 22, 212, 47, 194, 9, 127, 177, 61, 8, 188, 53, 224, 47, 15, 243, 226, 43, 88, 249, 232, 84, 26, 226, 176, 221, 64, 202, 223]
plaintext_bytes = decrypt_cbc(ciphertext, key, iv)
flag_chars = []
for byte in plaintext_bytes:
if 32 <= byte <= 126: # Printable ASCII
flag_chars.append(chr(byte))
elif byte < 32: # Likely padding
break
flag = ''.join(flag_chars)
print("FLAG:", flag)
CONCTF{lu4_w1th_cust0m3_AES_1s_k1nda_kr4zy}