Shimbles the E-L-F
Reverse Engineering · BearcatCTF 2025 · Nyla
Problem

Utilizing Ghidra allows us to see functions effectively.
Main function
undefined8 main(void)
{
int iVar1;
char *pcVar2;
undefined8 uVar3;
size_t sVar4;
long in_FS_OFFSET;
undefined8 local_e1;
undefined local_d9;
undefined8 local_d8;
undefined8 local_d0;
undefined2 local_c8;
undefined local_c6;
undefined8 local_b8;
undefined8 local_b0;
undefined2 local_a8;
undefined local_a6;
char local_98 [136];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
puts(
" __\n .-\' |\n / <\\|\n / \\\n |_.- o-o\n / C -._)\\\n /\', |\n | `-,_,__,\'\n (,,)====[_]=|\n \'. ____/\n | -|-|_\n |____)_)\n"
);
puts("Schimbles: Hello, mortal! I am Schimbles, the enchanted gnome.");
puts("Schimbles: I\'ve encrypted your precious data and you\'ll never see it again...");
puts("Schimbles: ...unless you can decrypt my file.\n");
printf("Schimbles: Enter the decryption key: ");
pcVar2 = fgets(local_98,0x80,stdin);
if (pcVar2 == (char *)0x0) {
fwrite("Input error.\n",1,0xd,stderr);
uVar3 = 1;
}
else {
sVar4 = strcspn(local_98,"\n");
local_98[sVar4] = '\0';
local_e1 = encrypted_key;
local_d9 = DAT_00104018;
local_d8 = encrypted_flag;
local_d0 = DAT_00104028;
local_c8 = DAT_00104030;
local_c6 = DAT_00104032;
local_b8 = encrypted_taunt;
local_b0 = DAT_00104048;
local_a8 = DAT_00104050;
local_a6 = DAT_00104052;
sVar4 = strlen((char *)&local_e1);
two_layer_decrypt(&local_e1,sVar4,0xaa,3,0x5f,2);
iVar1 = strcmp(local_98,(char *)&local_e1);
if (iVar1 == 0) {
sVar4 = strlen((char *)&local_d8);
two_layer_decrypt(&local_d8,sVar4,0x77,4,0x3c,3);
puts("\nSchimbles: Ha! You have bested me!");
printf("Schimbles: Here is your flag: %s\n",&local_d8);
}
else {
sVar4 = strlen((char *)&local_b8);
two_layer_decrypt(&local_b8,sVar4,0x6d,2,0x33,5);
printf("\nSchimbles: %s\n",&local_b8);
}
uVar3 = 0;
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return uVar3;
}
Two_layer_decrypt function
void two_layer_decrypt(long param_1,ulong param_2,byte param_3,undefined4 param_4,byte param_5,
undefined4 param_6)
{
byte bVar1;
undefined uVar2;
ulong local_18;
for (local_18 = 0; local_18 < param_2; local_18 = local_18 + 1) {
bVar1 = rotr(*(byte *)(local_18 + param_1) ^ param_5,param_6);
uVar2 = rotr(bVar1 ^ param_3,param_4);
*(undefined *)(param_1 + local_18) = uVar2;
}
return;
}
Explanation
undefined8 main(void)
undefined8 main(void)
The main function simulates a decryption challenge from Schimbles the gnome:
Introduction: Prints an ASCII art gnome and taunts the user.
User Input: Prompts the user for a decryption key, reads input, and compares it with an encrypted key.
Key Validation:
Decrypts the encrypted key using
two_layer_decrypt
with parameters(local_e1, key_length, 0xaa, 3, 0x5f, 2)
.If the input matches, it decrypts and displays the flag with parameters
(local_d8, flag_length, 0x77, 4, 0x3c, 3)
.If incorrect, it decrypts and shows a taunt with parameters
(local_b8, taunt_length, 0x6d, 2, 0x33, 5)
.
Stack Integrity: Checks for stack corruption at the end.
void two_layer_decrypt(long param_1, ulong param_2, byte param_3, undefined4 param_4, byte param_5, undefined4 param_6)
void two_layer_decrypt(long param_1, ulong param_2, byte param_3, undefined4 param_4, byte param_5, undefined4 param_6)
This function decrypts a byte array using two XOR and rotation layers:
First Layer: XORs each byte with
param_5
, then rotates byparam_6
.Second Layer: XORs the result with
param_3
, then rotates byparam_4
.Stores Decrypted Byte: Updates the byte array with the final decrypted value.
This function is used to decrypt keys, flags, and taunts in the main function.
Finding the key
The
x
command in GDB is used for examining memory.8b
specifies that you want to view 8 bytes (theb
stands for byte) of memory.x/8bx
tells GDB to show the bytes in hexadecimal format.0x4010
is the memory address you are examining. You provided the address0x4010
to GDB to inspect the contents at that specific location in memory.

Solution
def rotr(byte, shift):
return ((byte >> shift) | (byte << (8 - shift))) & 0xff
def two_layer_decrypt(data, param_3, param_4, param_5, param_6):
decrypted_data = []
for byte in data:
# First layer: XOR with param_5, then rotate right by param_6
bVar1 = rotr(byte ^ param_5, param_6)
# Second layer: XOR with param_3, then rotate right by param_4
uVar2 = rotr(bVar1 ^ param_3, param_4)
decrypted_data.append(uVar2)
return bytes(decrypted_data)
# Encrypted key extracted from memory
encrypted_key = bytes([0x59, 0x78, 0x39, 0x58, 0xd9, 0x19, 0xd8, 0x99])
# Decrypt the key using the parameters from the challenge
decrypted_key = two_layer_decrypt(encrypted_key, 0xaa, 3, 0x5f, 2)
# Print the decrypted key
print(f"Decrypted Key: {decrypted_key.decode()}")
Decrypted Key: elfmagic
Run the binary file and enter the key.
┌──(zwique㉿kali)-[~/Downloads/slv/Shimbles_the_E-L-F]
└─$ ./Shimbles-the-elf
__
.-' |
/ <\|
/ \
|_.- o-o
/ C -._)\
/', |
| `-,_,__,'
(,,)====[_]=|
'. ____/
| -|-|_
|____)_)
Schimbles: Hello, mortal! I am Schimbles, the enchanted gnome.
Schimbles: I've encrypted your precious data and you'll never see it again...
Schimbles: ...unless you can decrypt my file.
Schimbles: Enter the decryption key: elfmagic
Schimbles: Ha! You have bested me!
Schimbles: Here is your flag: BCC{n0t_t0day_e1f}
Last updated