BHack 2022 - Ultimate Encryptor - Reverse Engineering a Custom Cipher
This challenge provided an encrypted flag file and the binary that produced it. I used Ghidra to decompile the program and inspect both the assembly and the reconstructed C code. After reading through it, I identified three important functions:
read_file(): reads a file and returns its contents as a stringUltimateEncryption(): encrypts the message and returns the resultmain():- calls
read_file()with the first argument, which is the plaintext file; - passes that content to
UltimateEncryption(); - saves the encrypted output to the second file argument.
- calls
After some debugging, variable renaming, and cleanup, I reconstructed the core function into something like this:
char* UltimateEncryption(char* msg) {
char tmp, zeroChar;
int index4, index3, index2, index, keyLen;
size_t msgLen = strlen(msg);
keyLen = (int)msgLen;
// Step 0
if ((msgLen & 1) != 0) { // Check if is Odd or Even with Bitwise Operators
zeroChar = '0';
strncat(msg, (char*)&zeroChar, 2);
keyLen = keyLen + 1;
}
printf("%s\n", msg); // print to debug
// Step 1
for (index = 0; index < keyLen; index = index + 1) {
msg[index] = msg[(index + 1) % keyLen] ^ msg[index];
}
printf("%s\n", msg); // print to debug
// Step 2
for (index2 = 0; index2 < keyLen; index2 = index2 + 1) {
msg[index2] = msg[index2] ^ 0x2b;
}
printf("%s\n", msg); // print to debug
// Step 3
for (index3 = 0; index3 < keyLen; index3 = index3 + 1) {
msg[index3] = msg[index3] + '\x0f';
}
printf("%s\n", msg); // print to debug
// Step 4
for (index4 = 0; index4 < keyLen; index4 = index4 + 2) {
tmp = msg[index4];
msg[index4] = msg[(long)index4 + 1];
msg[(long)index4 + 1] = tmp;
}
printf("%s\n", msg); // print to debug
return msg;
}
To make the analysis easier, I copied the raw main() and read_file() functions from Ghidra into my local encrypt.c file along with the function above. Then I commented out the loops one by one so I could understand what each stage was doing. My conclusions were:
- Step 0: if the string length was odd, append
'0'at the end; - Step 1: XOR each byte with the next byte, wrapping the last one to the first;
- Step 2: XOR each byte with
0x2b(+in ASCII); - Step 3: add
0x0fto each byte; - Step 4: swap characters in pairs.
Once I understood the transformation order, I implemented the reverse process and tested each step independently while encrypting and decrypting. This was the final script:
key = list(open('out.txt', 'rb').read().strip())
plain_size = len(key)
# Step 4
i4 = 0
while i4 < plain_size:
c = key[i4]
key[i4] = key[i4 + 1]
key[i4 + 1] = c
i4 += 2
# Step 3
for i3 in range(plain_size):
key[i3] = key[i3] - 0x0f
# Step 2
for i2 in range(plain_size):
key[i2] = key[i2] ^ 0x2b
# Step 1
index = plain_size-1
while index >= 0:
key[index] = key[(index + 1) % plain_size] ^ key[index]
index -= 1
print(''.join(map(chr,key)))
