Worthy Knight

Original Writeup on seall.dev
Another binary, ELF, you know the gist by now… I open Ghidra.
We have to get 5 pairs of characters:
First:
if ((byte)(local_c8[1] ^ local_c8[0]) == 0x24) {
if (local_c8[1] == 0x6a) {
Second:
if ((local_c8[2] ^ local_c8[3]) == 0x38) {
if (local_c8[3] == 0x53) {
Third is a hash, we can crack this with hashcat
/john
as its just 2 characters. I crack it and it is Tf
. But this proves to be useless due to something we see later.
Fourth:
if ((local_c8[6] ^ local_c8[7]) == 0x38) {
if (local_c8[7] == 0x61)
Fifth:
if ((byte)(local_c8[9] ^ local_c8[8]) == 0x20) {
if (local_c8[9] == 0x69) {
But this was incorrect and Ghidra was not being helpful, its decompilation was quite unclear and after calculating the values it was wrong. My teammate (Solopie) puts the binary into dogbolt and the HexRays decompile was a lot more helpful.
First:
if ( (s[0] ^ s[1]) != 36 )
{
puts("\nThe wards reject your Pair 1.");
puts(aTheAncientDrag);
return 1;
}
if ( s[1] != 106 )
{
puts("\nThe wards reject your Pair 1 second char.");
puts(aTheAncientDrag);
return 1;
}
s[1]
= ‘j’ (106)s[0]
= 106 ^ 36 = 78 (‘N’) Result: “Nj”
Second:
if ( (s[3] ^ s[2]) != 56 )
{
puts("\nThe wards reject your Pair 2.");
puts(aTheAncientDrag);
return 1;
}
if ( s[3] != 83 )
{
puts("\nThe wards reject your Pair 2 second char.");
puts(aTheAncientDrag);
return 1;
}
s[3]
= ‘S’ (83)s[2]
= 83 ^ 56 = 107 (‘k’) Result: “kS”
Third is still the hash, but I notice there is some preprocessing (which was in Ghidra but more irritating to spot for myself):
v15 = 0;
v9 = v16;
*(_WORD *)v14 = __ROL2__(*(_WORD *)&s[4], 8);
v10 = strlen(v14);
v11 = s1;
MD5(v14, v10, v16);
do
{
v12 = (unsigned __int8)*v9;
v13 = v11;
v11 += 2;
++v9;
sprintf(v13, "%02x", v12);
}
while ( &v18 != v11 );
v18 = 0;
v7 = strcmp(s1, "33a3192ba92b5a4803c9a9ed70ea5a9c");
if ( v7 )
{
puts("\nThe dragon's eyes glow red... The final seal remains locked.");
puts(aTheAncientDrag);
return 1;
}
What this is doing is before doing a comparison it is modifying the input.
- Takes the two characters as a 16-bit word
- Rotates left by 8 bits (swaps the bytes)
- Calculates MD5 hash
We can replicate this process in Python and find the correct characters. The characters are going to be a lowercase and uppercase letter.
Let’s do it with this Python script:
import hashlib
import string
import itertools
letters = string.ascii_letters
pairs = itertools.product(letters, repeat=2)
for c1, c2 in pairs:
pair = bytes([ord(c1), ord(c2)])
rotated = bytes([pair[1], pair[0]])
md5_hash = hashlib.md5(rotated).hexdigest()
if md5_hash == "33a3192ba92b5a4803c9a9ed70ea5a9c":
print(f"Found match: {c1}{c2}")
break
It returns fT
(the inverse of the cracked hash, :p).
Fourth:
if ( (s[7] ^ s[6]) != 56 )
{
puts("\nThe wards reject your Pair 4.");
puts(aTheAncientDrag);
return 1;
}
if ( s[7] != 97 )
{
puts("\nThe wards reject your Pair 4 second char.");
puts(aTheAncientDrag);
return 1;
}
s[7]
= ‘a’ (97)s[6]
= 97 ^ 56 = 89 (‘Y’) Result: “Ya”
Fifth:
if ( (s[8] ^ s[9]) != 32 )
{
puts("\nThe wards reject your Pair 5.");
puts(aTheAncientDrag);
return 1;
}
if ( s[9] != 105 )
{
puts("\nThe wards reject your Pair 5 second char.");
puts(aTheAncientDrag);
return 1;
}
s[9]
= ‘i’ (105)s[8]
= 105 ^ 32 = 73 (‘I’) Result: “Ii”
The final string is: NjkSfTYaIi
!
$ ./worthy.knight
(Knight's Adventure)
O
<M> .---.
/W\ ( -.- )--------.
^ \|/ \_o_/ ) ^
/|\ | * ~~~~~~~ / /|\
/ \ / \ /|\ / / \
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Welcome, traveler. A mighty dragon blocks the gate.
Speak the secret incantation (10 runic letters) to continue.
Enter your incantation: NjkSfTYaIi
The kingdom's gates open, revealing the hidden realm...
( (
\ \
.--. ) ) .--.
( )/_/ ( )
'--' '--'
"Huzzah! Thy incantation is true. Onward, brave knight!"
The final scroll reveals your reward: KCTF{NjkSfTYaIi}
Flag: KCTF{NjkSfTYaIi}