KnightCTF 2026 - Reversing50 - E4sy P3asy
Published: 2026-01-21
Read time: 2 min
Task
## E4sy P3asy
### 50 Points
Author
NomanProdhan
An easy RE...
Flag Format : KCTF{flag}
File: https://drive.google.com/file/d/1WqG5W-UkDFf4pAeRf5tdTs9ClN1UD1K6/view
It's and elf stripped file.
~/Vault/isec/ctf/knight2k26 ✓ $ file E4sy_P3asy.ks
E4sy_P3asy.ks: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ccaa420882d2b8eb1bf3a2cc82b527aa19911512, for GNU/Linux 4.4.0, stripped
It has some hashes in strings which may be useful later:
~/Vault/isec/ctf/knight2k26 ✓ $ strings E4sy_P3asy.ks
...
flag>
GoogleCTF{
%s%zu%c
Try again!
FLAG{
KCTF{
Good job! You got it!
========================================
E4sy P3asy - KnightCTF 2026
[*] Enter the flag to prove your worth!
[!] Interesting... but that's a decoy flag from a different universe.
[!] You're in KnightCTF, not GoogleCTF :)
[?] Format looks suspicious... but not quite.
781011edfb2127ee5ff82b06bb1d2959
4cf891e0ddadbcaae8e8c2dc8bb15ea0
d06d0cbe140d0a1de7410b0b888f22b4
d44c9a9b9f9d1c28d0904d6a2ee3e109
e20ab37bee9d2a1f9ca3d914b0e98f09
d0beea4ce1c12190db64d10a82b96ef8
ac87da74d381d253820bcf4e5f19fcea
ce3f3a34a04ba5e5142f5db272b6cb1f
13843aca227ef709694bbfe4e5a32203
ca19a4c4eb435cb44d74c1e589e51a10
19edec8e46bdf97e3018569c0a60baa3
972e078458ce3cb6e32f795ff4972718
071824f6039981e9c57725453e005beb
66cd6098426b0e69e30e7fa360310728
f78d152df5d277d0ab7d25fb7d1841f3
dba3a36431c4aaf593566f7421abaa22
8820bbdad85ebee06632c379231cfb6b
722bc7cde7d548b81c5996519e1b0f0f
c2862c390c830eb3c740ade576d64773
94da978fe383b341f9588f9bab246774
bea3bb724dbd1704cf45aea8e73c01e1
ade2289739760fa27fd4f7d4ffbc722d
3cd0538114fe416b32cdd814e2ee57b3
8af7f29b21564b87336ed4e4cdfb1a20
4d3f509284784ab67818b13e74fd5ebe
5a45666e1387ff739eb470f840532099
c96997e23323e502d5d0b07d24a68d50
efb388057adc3fe734d8b6ffb2bdd1e1
6df2aaf58e39f89d7a31a72e19e0efbf
9bbd077d3df7faedfd22fae85043d6c0
dc72837ea6ba778eebbd0401a35182f4
efa0bc7c5da3545ba15548b4b85eaf76
1c36dbad9144b1e1d23ddecb5d4df3e9
980663d212aed1ba720f7735873fa73c
9df295c8874bcac93c98babe78b9c946
2672e4d30c7da0d30749f09bc1b1eefa
;*3$"
GCC: (GNU) 15.2.1 20251112
.....
Also, regarding those hashes, md5 is also mentioned in strings:
EVP_MD_CTX_new
EVP_md5
EVP_DigestUpdate
EVP_DigestFinal_ex
Reversing
This binary does the following, in order:
- Reads user input (flag)
- Extracts part of solution from KCTF{...}
- Using each character from submitted user flag and salt "KnightCTF_2026_s@lt" it builds a string in format **salt + index + char **
snprintf(&var_2c8, 0x80, "%s%zu%c", var_358, i, (&var_148)[i])
sub_401660(&var_2c8, strlen(&var_2c8), &var_338)
if (strcmp(&var_338, (&data_403ca0)[i]) != 0)
fail
- Hashes it with MD5 and compares it to the hash table which we saw in strings
- If all hashes are ok, profit!
00401140 int64_t sub_401140()
00401151 void* fsbase
00401151 int64_t rdi = *(fsbase + 0x28)
00401171 puts("========================================")
0040117d puts(" E4sy P3asy - KnightCTF 2026")
00401189 puts("========================================")
00401195 puts("[*] Enter the flag to prove your worth!")
004011a1 puts(&data_402009[6])
004011af printf("flag> ")
004011cb void var_248
004011cb
004011cb if (fgets(&var_248, 0x100, stdin) != 0)
004011d4 int64_t rax_2 = strlen(&var_248)
004011dc int32_t* var_358
004011dc
004011dc if (rax_2 != 0)
004011e3 char* rax_3 = &var_358 + rax_2 + 0x10f
004011e3
0040120d do
0040120f char rdx_1 = *rax_3
0040120f
0040121a if (rdx_1 != 0xa && rdx_1 != 0xd)
0040121a break
0040121a
00401200 *rax_3 = 0
00401206 rax_3 -= 1
0040120d while (1 - &var_248 != neg.q(rax_3))
0040120d
00401235 char var_148
00401235 sub_401660(&var_248, strlen(&var_248), &var_148)
0040124b char var_147
0040124b
0040124b if ((var_148 ^ var_147) == 0x5a)
0040132b int32_t var_33c_1 = 1
0040133c int32_t var_33c_2 = 0x1336
0040133c
00401262 void var_338
00401262 int32_t var_308
00401262 void var_2c8
00401262
00401262 if (sub_401760(&var_248, "GoogleCTF{") == 0)
00401293 label_401293:
00401293 int32_t rax_9 = sub_401760(&var_248, "CTF{")
0040129a int32_t rax_10
0040129a
0040129a if (rax_9 == 0)
004012aa rax_10 = sub_401760(&var_248, "FLAG{")
004012aa
004012b4 if (rax_9 != 0 || rax_10 != 0)
0040134c puts("[?] Format looks suspicious... but not quite.")
00401358 puts("Try again!")
004012b4 else if (sub_401760(&var_248, "KCTF{") == 0)
004012f9 labelid_3:
004012f9 puts("Try again!")
004012cb else if (sub_4017a0(&var_248) == 0)
004012f9 label_4012f9:
004012f9 puts("Try again!")
004012d7 else
004012dc int64_t rax_13 = strlen(&var_248)
004012dc
004012ec if (rax_13 - 6 u<= 0xff)
00401476 void var_243
00401476 __builtin_memcpy(dest: &var_148, src: &var_243, count: rax_13 - 6)
0040147d char var_14e[0x6]
0040147d var_14e[rax_13] = 0
0040148d var_358 = &var_308
00401491 int64_t rcx_5
00401491 int64_t rdi_17
00401491 rdi_17, rcx_5 = __memfill_u32(&var_308, rax_10, 0x10)
00401493 __builtin_strncpy(dest: &var_308, src: "KnightCTF_2026_s@lt",
00401493 count: 0x13)
00401493
004014c2 if (rax_13 != 0x1d)
004012f9 label_4012f9_1:
004012f9 puts("Try again!")
004014c2 else
004014cf int64_t i = 0
004014d9 char const* const var_350_2 = "%s%zu%c"
004014d9
004014e8 do
0040150b snprintf(&var_2c8, 0x80, var_350_2, var_358, i,
0040150b zx.q(sx.d((&var_148)[i])))
0040152d sub_401660(&var_2c8, strlen(&var_2c8), &var_338)
0040152d
00401542 if (strcmp(&var_338, (&data_403ca0)[i]) != 0)
00401542 goto label_4012f9_2
00401542
004014e0 i += 1
004014e8 while (i != 0x17)
004014e8
00401550 puts("Good job! You got it!")
004012ec else
004012f9 label_4012f9_2:
004012f9 puts("Try again!")
00401262 else
0040126e if (sub_4017a0(&var_248) == 0)
0040126e goto label_401293
0040126e
00401273 int64_t rax_8 = strlen(&var_248)
00401273
00401283 if (rax_8 - 0xb u> 0xff)
00401283 goto label_401293
00401283
0040136d void var_23e
0040136d __builtin_memcpy(dest: &var_148, src: &var_23e, count: rax_8 - 0xb)
00401374 char var_153[0x5]
00401374 var_153[rax_8] = 0
00401383 var_358 = &var_308
00401387 __builtin_memset(dest: &var_308, ch: 0, count: 0x40)
00401389 __builtin_strcpy(dest: &var_308, src: "G00gleCTF_s@lt_2026")
00401389
004013ba if (rax_8 != 0x18)
004013ba goto label_401293
004013ba
004013c7 int64_t i_1 = 0
004013d1 char const* const var_350_1 = "%s%zu%c"
004013d1
0040143d do
004013f7 snprintf(&var_2c8, 0x80, var_350_1, var_358, i_1,
004013f7 zx.q(sx.d((&var_148)[i_1])))
00401419 sub_401660(&var_2c8, strlen(&var_2c8), &var_338)
00401419
0040142f if (strcmp(&var_338, (&data_403d60)[i_1]) != 0)
0040142f goto label_401293
0040142f
00401435 i_1 += 1
0040143d while (i_1 != 0xd)
0040143d
00401446 puts("
00401446 [!] Interesting... but that's a decoy flag from a different universe.")
00401452 puts("[!] You're in KnightCTF, not GoogleCTF :)")
0040145e puts("Try again!")
0040145e
00401308 *(fsbase + 0x28)
00401308
00401311 if (rdi == *(fsbase + 0x28))
0040132a return 0
0040132a
0040155a int64_t rdx_9
0040155a int64_t rsi_7
0040155a int64_t rdi_22
0040155a rdx_9, rsi_7, rdi_22 = __stack_chk_fail()
0040155f noreturn _start(rdi_22, rsi_7, rdx_9) __tailcall
Solution
I wrote a python script which takes our salt and index and brute-forces each char until it matches hash from the hash table. The binary checks:
expected = [
MD5("KnightCTF_2026_s@lt0" + flag[0]),
MD5("KnightCTF_2026_s@lt1" + flag[1]),
...
]
The plan is following:
for each position/index:
try all ASCII chars
find the one that matches
Here is the python script implementation:
import hashlib
targets = [
"781011edfb2127ee5ff82b06bb1d2959",
"4cf891e0ddadbcaae8e8c2dc8bb15ea0",
"d06d0cbe140d0a1de7410b0b888f22b4",
"d44c9a9b9f9d1c28d0904d6a2ee3e109",
"e20ab37bee9d2a1f9ca3d914b0e98f09",
"d0beea4ce1c12190db64d10a82b96ef8",
"ac87da74d381d253820bcf4e5f19fcea",
"ce3f3a34a04ba5e5142f5db272b6cb1f",
"13843aca227ef709694bbfe4e5a32203",
"ca19a4c4eb435cb44d74c1e589e51a10",
"19edec8e46bdf97e3018569c0a60baa3",
"972e078458ce3cb6e32f795ff4972718",
"071824f6039981e9c57725453e005beb",
"66cd6098426b0e69e30e7fa360310728",
"f78d152df5d277d0ab7d25fb7d1841f3",
"dba3a36431c4aaf593566f7421abaa22",
"8820bbdad85ebee06632c379231cfb6b",
"722bc7cde7d548b81c5996519e1b0f0f",
"c2862c390c830eb3c740ade576d64773",
"94da978fe383b341f9588f9bab246774",
"bea3bb724dbd1704cf45aea8e73c01e1",
"ade2289739760fa27fd4f7d4ffbc722d",
"3cd0538114fe416b32cdd814e2ee57b3",
]
salt = "KnightCTF_2026_s@lt"
flag = ""
for i in range(len(targets)):
for c in range(32, 127):
s = f"{salt}{i}{chr(c)}".encode()
h = hashlib.md5(s).hexdigest()
if h == targets[i]:
flag += chr(c)
print(i, chr(c))
break
print("KCTF{" + flag + "}")
Running the python script above successfully brute-forces the flag:
~/Vault/isec/ctf/knight2k26 ✓ $ python crack3.py
0 _
1 L
2 0
3 T
4 S
5 _
6 o
7 F
8 _
9 b
10 R
11 u
12 T
13 E
14 _
15 f
16 o
17 R
18 C
19 E
20 _
21 :
22 P
KCTF{_L0TS_oF_bRuTE_foRCE_:P}
~/Vault/isec/ctf/knight2k26 ✓ $ ./E4sy_P3asy.ks
========================================
E4sy P3asy - KnightCTF 2026
========================================
[*] Enter the flag to prove your worth!
flag> KCTF{_L0TS_oF_bRuTE_foRCE_:P}
Good job! You got it!
Flag: KCTF{L0TS_oF_bRuTE_foRCE:P}
