BDSec CTF 2025

Minar ’52 Enigma – Reverse Engineering Challenge Writeup | BDSec CTF 2025

Downloaded the file and made it executable . Nothing interesting there , it is basically a flag checker and so the flag is inside the programme encrypted or not .

Opening it with Radare 2 and checking the main function didn't give much other than the fact it is comparing something .

Now opening it with Ghidra take a lot of time but when it is done I had get creative to find the main function . Did a ctrl + f aka find for the expression "Enter" as the main function had the printf for "Enter the secret"

After finding the function analyzing it throughly I arrived at the decryption logic and variables ,

    sVar4 = strcspn(__s,"\n");
    local_75[8] = 0x5f;
    local_75[sVar4 + 0x1d] = '\0';
    local_75[0x11] = '\x02';
    local_75[0x12] = '\x05';
    builtin_strncpy(local_75,"1952\x10#7K",8);
    local_75[9] = 'O';
    local_75[10] = 'l';
    local_75[0xb] = 's';
    local_75[0xc] = '\x0e';
    local_75[0xd] = '\x1f';
    local_75[0xe] = '\b';
    local_75[0xf] = 'i';
    local_75[0x10] = '\x1b';
    local_75[0x13] = -0x4e;
    local_75[0x14] = 'F';
    local_75[0x15] = 'V';
    local_75[0x16] = '\v';
    local_75[0x17] = '\t';
    local_75[0x18] = '~';
    local_75[0x19] = 'p';
    local_75[0x1a] = 'o';
    local_b8[0] = 0x4f;
    local_b8[1] = 0x6c;
    local_b8[2] = 0x73;
    local_b8[3] = 0xe;
    local_b8[4] = 0x1f;
    local_b8[5] = 8;
    local_b8[6] = 0x69;
    local_b8[7] = 0x1b;
    local_b8[8] = 2;
    local_b8[9] = 5;
    local_b8[10] = 0xb2;
    local_b8[0xb] = 0x46;
    local_b8[0xc] = 0x56;
    local_b8[0xd] = 0xb;
    local_b8[0xe] = 9;
    local_b8[0xf] = 0x7e;
    local_b8[0x10] = 0x70;
    local_b8[0x11] = 0x6f;
    local_98[0] = 'B';
    local_75[0x1b] = '>';
    local_75[0x1c] = -0x3e;
    uVar6 = 1;
    local_b8[0x12] = 0x3e;
    local_b8[0x13] = 0xc2;
    do {
      auVar1._8_8_ = 0;
      auVar1._0_8_ = uVar6;
      local_98[uVar6] =
           ((local_b8[uVar6] ^ 0xcc) - local_75[(uint)uVar6 & 3]) -
           local_75[(uVar6 - ((SUB168(auVar1 * ZEXT816(0xcccccccccccccccd),8) & 0xfffffffffffffffc)
                             + uVar6 / 5)) + 4];
      uVar6 = uVar6 + 1;
    } while (uVar6 != 0x14);
    local_84 = 0;
    iVar2 = strcmp(__s,local_98);
    if (iVar2 == 0) {
      puts("\nAccess granted! Well done.");

Wow big .... well the main cipher text is obviously local_b8 which is 20 character long xor key is 0xCC . And more is local_75 which is big too but not the whole thing is needed .

local_b8 = [ 0x4F, 0x6C, 0x73, 0x0E, 0x1F, 0x08, 0x69, 0x1B, 0x02, 0x05, 0xB2, 0x46, 0x56, 0x0B, 0x09, 0x7E, 0x70, 0x6F, 0x3E, 0xC2]

as most is in hexadecimal then 8 , 2 , 5 etc are 0x08 , 0x05 and others .

now the formula took a long time to understand but the gist of it is

flag = ((local_b8[i] ^ 0xCC) - local_75[i % 4]) - local_75[(i - (i/5)*5) + 4]

Now why is local_75 is not fully is- index mod 4 returns only 0 , 1 , 2 , 3 and ((i - (i/5)x5) + 4) returns 4 , 5 , 6 , 7 , 8

so local_75 is

local_75 = [0x31, 0x39, 0x35, 0x32, 0x10, 0x23, 0x37, 0x4B, 0x5F ]

and first 7 bytes are taken from #1952\x10#7K

builtin_strncpy(local_75,"1952\x10#7K",8);

so reversing all these with a very easy python script made by me will give the flag .

Below is the script's decryption part for anyone hardstuck .


#local_98[0] = 'B';
array_flag = ['B']


while i < 20 :
    array_flag.append(chr((xor[i] ^ key) - value[i % 4] - value[(i % 5) + 4] & 0xFF))
    i += 1
    

0 people love this