Shimbles the E-L-F

Reverse Engineering · BearcatCTF 2025 · Nyla

Problem

Description

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)

The main function simulates a decryption challenge from Schimbles the gnome:

  1. Introduction: Prints an ASCII art gnome and taunts the user.

  2. User Input: Prompts the user for a decryption key, reads input, and compares it with an encrypted key.

  3. 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).

  4. 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)

This function decrypts a byte array using two XOR and rotation layers:

  1. First Layer: XORs each byte with param_5, then rotates by param_6.

  2. Second Layer: XORs the result with param_3, then rotates by param_4.

  3. 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 (the b 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 address 0x4010 to GDB to inspect the contents at that specific location in memory.

gef➤ info address encrypted_key

Symbol "encrypted_key" is at 0x4010 in a file compiled without debugging.

GDB

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