HTB Wide - Reverse Engineering Challenge

The Wide Challenge is a simple reverse engineering task. Let's analyze the binary and write a custom c program to decrypt the flag.

0xDev

17 min read

Overview

The Wide challenge is a simple reverse-engineering task. The description says:

We've received reports that Draeger stashed a huge arsenal in the pocket dimension Flaggle Alpha. You managed to smuggle a discarded access terminal for the Widely Inflated Dimension Editor out of his headquarters, but the entry for the dimension is encrypted. Can you get in and take control?

You get two files: db.exe and wide. I usually start with the file command to see what I'm dealing with.

Inspecting the files

$ file db.ex
db.ex: data

$ file wide
wide: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=13869bb7ce2c22f474b95ba21c9d7e9ff74ecc3f, not stripped

As we can see the wide file is a linux binary and the db.exe is a data file. So lets try to read the content of db.exe

Inspecting db.ex

$ strings db.ex
Primus
people breathe variety practice
Our home dimension
Cheagaz
scene control river importance
The Ice Dimension
Byenoovia
fighting cast it parallel
The Berserk Dimension
Cloteprea
facing motor unusual heavy
The Hungry Dimension
Maraqa
stomach motion sale valuable
The Water Dimension
Aidor
feathers stream sides gate
The Bone Dimension
Flaggle Alpha
admin secret power hidden
HOt*
0ANe

$ hexdump -C db.ex
00000000  00 00 00 00 50 72 69 6d  75 73 00 00 00 00 00 00  |....Primus......|
00000010  00 00 00 00 70 65 6f 70  6c 65 20 62 72 65 61 74  |....people breat|
00000020  68 65 20 76 61 72 69 65  74 79 20 70 72 61 63 74  |he variety pract|
00000030  69 63 65 00 4f 75 72 20  68 6f 6d 65 20 64 69 6d  |ice.Our home dim|
00000040  65 6e 73 69 6f 6e 00 00  00 00 00 00 00 00 00 00  |ension..........|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000000b0  00 00 00 00 00 00 00 00  43 68 65 61 67 61 7a 00  |........Cheagaz.|
000000c0  00 00 00 00 00 00 00 00  73 63 65 6e 65 20 63 6f  |........scene co|
000000d0  6e 74 72 6f 6c 20 72 69  76 65 72 20 69 6d 70 6f  |ntrol river impo|
000000e0  72 74 61 6e 63 65 00 00  54 68 65 20 49 63 65 20  |rtance..The Ice |
000000f0  44 69 6d 65 6e 73 69 6f  6e 00 00 00 00 00 00 00  |Dimension.......|
00000100  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000160  00 00 00 00 00 00 00 00  00 00 00 00 42 79 65 6e  |............Byen|
00000170  6f 6f 76 69 61 00 00 00  00 00 00 00 66 69 67 68  |oovia.......figh|
00000180  74 69 6e 67 20 63 61 73  74 20 69 74 20 70 61 72  |ting cast it par|
00000190  61 6c 6c 65 6c 00 00 00  00 00 00 00 54 68 65 20  |allel.......The |
000001a0  42 65 72 73 65 72 6b 20  44 69 6d 65 6e 73 69 6f  |Berserk Dimensio|
000001b0  6e 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |n...............|
000001c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000220  43 6c 6f 74 65 70 72 65  61 00 00 00 00 00 00 00  |Cloteprea.......|
00000230  66 61 63 69 6e 67 20 6d  6f 74 6f 72 20 75 6e 75  |facing motor unu|
00000240  73 75 61 6c 20 68 65 61  76 79 00 00 00 00 00 00  |sual heavy......|
00000250  54 68 65 20 48 75 6e 67  72 79 20 44 69 6d 65 6e  |The Hungry Dimen|
00000260  73 69 6f 6e 00 00 00 00  00 00 00 00 00 00 00 00  |sion............|
00000270  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000002d0  00 00 00 00 4d 61 72 61  71 61 00 00 00 00 00 00  |....Maraqa......|
000002e0  00 00 00 00 73 74 6f 6d  61 63 68 20 6d 6f 74 69  |....stomach moti|
000002f0  6f 6e 20 73 61 6c 65 20  76 61 6c 75 61 62 6c 65  |on sale valuable|
00000300  00 00 00 00 54 68 65 20  57 61 74 65 72 20 44 69  |....The Water Di|
00000310  6d 65 6e 73 69 6f 6e 00  00 00 00 00 00 00 00 00  |mension.........|
00000320  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000380  00 00 00 00 00 00 00 00  41 69 64 6f 72 00 00 00  |........Aidor...|
00000390  00 00 00 00 00 00 00 00  66 65 61 74 68 65 72 73  |........feathers|
000003a0  20 73 74 72 65 61 6d 20  73 69 64 65 73 20 67 61  | stream sides ga|
000003b0  74 65 00 00 00 00 00 00  54 68 65 20 42 6f 6e 65  |te......The Bone|
000003c0  20 44 69 6d 65 6e 73 69  6f 6e 00 00 00 00 00 00  | Dimension......|
000003d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000430  00 00 00 00 00 00 00 00  01 00 00 00 46 6c 61 67  |............Flag|
00000440  67 6c 65 20 41 6c 70 68  61 00 00 00 61 64 6d 69  |gle Alpha...admi|
00000450  6e 20 73 65 63 72 65 74  20 70 6f 77 65 72 20 68  |n secret power h|
00000460  69 64 64 65 6e 00 00 00  00 00 00 00 48 4f 74 2a  |idden.......HOt*|
00000470  1f e8 cf 8e 87 80 7b 58  74 0e 1c a3 ee f8 95 30  |......{Xt......0|
00000480  41 4e 65 0b b9 d8 00 00  00 00 00 00 00 00 00 00  |ANe.............|
00000490  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000004e0  00 00 00 00 00 00 00 00  00 00 00 00              |............|
000004ec

Looks like nothing special. Next, let's see which strings the wide binary contains.

Inspecting wide (strings)

$ strings wide
/lib64/ld-linux-x86-64.so.2
libc.so.6
exit
fopen
ftell
puts
mbstowcs
stdin
printf
strtol
fgets
calloc
fseek
fclose
wcscmp
fread
__cxa_finalize
__libc_start_main
GLIBC_2.2.5
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
AWAVI
AUATL
[]A\A]A^A_
Which dimension would you like to examine?
That option was invalid.
[X] That entry is encrypted - please enter your WIDE decryption key:
[X]                          Key was incorrect                           [X]
Usage: %s db.ex
[*] Welcome user: kr4eq4L2$12xb, to the Widely Inflated Dimension Editor [*]
[*]    Serving your pocket dimension storage needs since 14,012.5 B      [*]
[x] There was a problem accessing your database [x]
[*]                       Displaying Dimensions....                      [*]
[*]       Name       |              Code                |   Encrypted    [*]
[X] %-16s | %-32s | %6s%c%7s [*]
;*3$"
GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
crtstuff.c
deregister_tm_clones
__do_global_dtors_aux
completed.7698
__do_global_dtors_aux_fini_array_entry
frame_dummy
__frame_dummy_init_array_entry
wide.c
__FRAME_END__
__init_array_end
_DYNAMIC
__init_array_start
__GNU_EH_FRAME_HDR
_GLOBAL_OFFSET_TABLE_
__libc_csu_fini
wcscmp@@GLIBC_2.2.5
_ITM_deregisterTMCloneTable
puts@@GLIBC_2.2.5
fread@@GLIBC_2.2.5
stdin@@GLIBC_2.2.5
mbstowcs@@GLIBC_2.2.5
_edata
fclose@@GLIBC_2.2.5
menu
printf@@GLIBC_2.2.5
__libc_start_main@@GLIBC_2.2.5
fgets@@GLIBC_2.2.5
calloc@@GLIBC_2.2.6
__data_start
ftell@@GLIBC_2.2.5
__gmon_start__
strtol@@GLIBC_2.2.5
__dso_handle
_IO_stdin_used
__libc_csu_init
fseek@@GLIBC_2.2.5
__bss_start
main
fopen@@GLIBC_2.2.5
exit@@GLIBC_2.2.5
__TMC_END__
_ITM_registerTMCloneTable
__cxa_finalize@@GLIBC_2.2.5
.symtab
.strtab
.shstrtab
.interp
.note.ABI-tag
.note.gnu.build-id
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rela.dyn
.rela.plt
.init
.plt.got
.text
.fini
.rodata
.eh_frame_hdr
.eh_frame
.init_array
.fini_array
.dynamic
.data
.bss
.comment

Looks like a little game - let's try running it.

Running the binary

$ ./wide db.ex
[*] Welcome user: kr4eq4L2$12xb, to the Widely Inflated Dimension Editor [*]
[*]    Serving your pocket dimension storage needs since 14,012.5 B      [*]
[*]                       Displaying Dimensions....                      [*]
[*]       Name       |              Code                |   Encrypted    [*]
[X] Primus           | people breathe variety practice  |                [*]
[X] Cheagaz          | scene control river importance   |                [*]
[X] Byenoovia        | fighting cast it parallel        |                [*]
[X] Cloteprea        | facing motor unusual heavy       |                [*]
[X] Maraqa           | stomach motion sale valuable     |                [*]
[X] Aidor            | feathers stream sides gate       |                [*]
[X] Flaggle Alpha    | admin secret power hidden        |       *        [*]
Which dimension would you like to examine? 1
The Ice Dimension
Which dimension would you like to examine? 6
[X] That entry is encrypted - please enter your WIDE decryption key: test
[X]                          Key was incorrect                           [X]
Which dimension would you like to examine?

So, interesting... the Flaggle Alpha entry shows an encrypted field. I tried a random key and got the "Key was incorrect" message. Maybe we can decrypt that and get the flag. Time to fire up Ghidra and take a look at the decompiled binary.

Static analysis (Ghidra)

Ghidra.png

After decompiling with Ghidra we get the following code. Let's walk through it.

main() overview


undefined8 main(int param_1,undefined8 *param_2)

{
  int iVar1;
  FILE *__stream;
  ulong uVar2;
  void *__ptr;
  uint uVar3;
  int local_28;

  if (param_1 < 2) {
    printf("Usage: %s db.ex\n",*param_2);
                    /* WARNING: Subroutine does not return */
    exit(-1);
  }
  puts("[*] Welcome user: kr4eq4L2$12xb, to the Widely Inflated Dimension Editor [*]");
  puts("[*]    Serving your pocket dimension storage needs since 14,012.5 B      [*]");
  __stream = fopen((char *)param_2[1],"r");
  if (__stream == (FILE *)0x0) {
    puts("[x] There was a problem accessing your database [x]");
                    /* WARNING: Subroutine does not return */
    exit(-1);
  }
  fseek(__stream,0,2);
  uVar2 = ftell(__stream);
  fseek(__stream,0,0);
  uVar2 = (uVar2 - uVar2 % 0xb4) / 0xb4;
  iVar1 = (int)uVar2;
  __ptr = calloc((long)iVar1,0xb4);
  fread(__ptr,0xb4,(long)iVar1,__stream);
  fclose(__stream);
  puts("[*]                       Displaying Dimensions....                      [*]");
  puts("[*]       Name       |              Code                |   Encrypted    [*]");
  for (local_28 = 0; local_28 < iVar1; local_28 = local_28 + 1) {
    if (*(int *)((long)__ptr + (long)local_28 * 0xb4) == 0) {
      uVar3 = 0x20;
    }
    else {
      uVar3 = 0x2a;
    }
    printf("[X] %-16s | %-32s | %6s%c%7s [*]\n",(long)__ptr + (long)local_28 * 0xb4 + 4,
           (long)__ptr + (long)local_28 * 0xb4 + 0x14,&DAT_0010132d,(ulong)uVar3,&DAT_0010132d);
  }
  menu(__ptr,uVar2 & 0xffffffff);
  return 0;
}

At first glance, main expects two arguments. If db.ex isn't passed it exits. The program prints the welcome messages, reads the database into memory, closes the file, displays the entries, and then calls a function called menu. Let's check out menu.

menu() and entry parsing

Ghidra2.png

void menu(long param_1,int param_2)

{
  int iVar1;
  long lVar2;
  undefined8 *puVar3;
  long in_FS_OFFSET;
  uint local_1d4;
  wchar_t local_1c8 [16];
  undefined8 local_188;
  undefined8 local_180;
  undefined8 local_178;
  undefined8 local_170;
  undefined8 local_168;
  undefined8 local_160;
  undefined4 local_158;
  undefined4 uStack_154;
  undefined4 local_150;
  undefined4 uStack_14c;
  undefined4 local_148;
  undefined4 uStack_144;
  undefined4 local_140;
  undefined4 uStack_13c;
  undefined4 local_138;
  undefined4 uStack_134;
  undefined4 local_130;
  undefined4 uStack_12c;
  undefined4 local_128;
  undefined4 uStack_124;
  undefined4 local_120;
  undefined4 uStack_11c;
  undefined4 local_118;
  undefined4 uStack_114;
  undefined4 local_110;
  undefined4 uStack_10c;
  undefined4 local_108;
  undefined4 uStack_104;
  undefined4 local_100;
  undefined4 uStack_fc;
  undefined4 local_f8;
  undefined4 uStack_f4;
  undefined4 local_f0;
  undefined4 uStack_ec;
  undefined4 local_e8;
  undefined4 uStack_e4;
  undefined4 local_e0;
  undefined4 uStack_dc;
  undefined4 local_d8;
  char local_c8 [16];
  undefined8 local_b8;
  undefined8 local_b0;
  undefined8 local_a8;
  undefined8 local_a0;
  undefined8 local_98;
  undefined8 local_90;
  undefined8 local_88;
  undefined8 local_80;
  undefined8 local_78;
  undefined8 local_70;
  undefined8 local_68;
  undefined8 local_60;
  undefined8 local_58;
  undefined8 local_50;
  undefined8 local_48;
  undefined8 local_40;
  undefined8 local_38;
  undefined8 local_30;
  undefined8 local_28;
  undefined8 local_20;
  undefined8 local_10;

  local_10 = *(undefined8 *)(in_FS_OFFSET + 0x28);
  local_b8 = 0;
  local_b0 = 0;
  local_a8 = 0;
  local_a0 = 0;
  do {
    while( true ) {
      while( true ) {
        printf("Which dimension would you like to examine? ");
        fgets((char *)&local_b8,0x20,stdin);
        lVar2 = strtol((char *)&local_b8,(char **)0x0,10);
        iVar1 = (int)lVar2;
        if ((-1 < iVar1) && (iVar1 < param_2)) break;
        puts("That option was invalid.");
      }
      puVar3 = (undefined8 *)(param_1 + (long)iVar1 * 0xb4);
      local_188 = *puVar3;
      local_180 = puVar3[1];
      local_178 = puVar3[2];
      local_170 = puVar3[3];
      local_168 = puVar3[4];
      local_160 = puVar3[5];
      local_158 = (undefined4)puVar3[6];
      uStack_154 = (undefined4)((ulong)puVar3[6] >> 0x20);
      local_150 = (undefined4)puVar3[7];
      uStack_14c = (undefined4)((ulong)puVar3[7] >> 0x20);
      local_148 = (undefined4)puVar3[8];
      uStack_144 = (undefined4)((ulong)puVar3[8] >> 0x20);
      local_140 = (undefined4)puVar3[9];
      uStack_13c = (undefined4)((ulong)puVar3[9] >> 0x20);
      local_138 = (undefined4)puVar3[10];
      uStack_134 = (undefined4)((ulong)puVar3[10] >> 0x20);
      local_130 = (undefined4)puVar3[0xb];
      uStack_12c = (undefined4)((ulong)puVar3[0xb] >> 0x20);
      local_128 = (undefined4)puVar3[0xc];
      uStack_124 = (undefined4)((ulong)puVar3[0xc] >> 0x20);
      local_120 = (undefined4)puVar3[0xd];
      uStack_11c = (undefined4)((ulong)puVar3[0xd] >> 0x20);
      local_118 = (undefined4)puVar3[0xe];
      uStack_114 = (undefined4)((ulong)puVar3[0xe] >> 0x20);
      local_110 = (undefined4)puVar3[0xf];
      uStack_10c = (undefined4)((ulong)puVar3[0xf] >> 0x20);
      local_108 = (undefined4)puVar3[0x10];
      uStack_104 = (undefined4)((ulong)puVar3[0x10] >> 0x20);
      local_100 = (undefined4)puVar3[0x11];
      uStack_fc = (undefined4)((ulong)puVar3[0x11] >> 0x20);
      local_f8 = (undefined4)puVar3[0x12];
      uStack_f4 = (undefined4)((ulong)puVar3[0x12] >> 0x20);
      local_f0 = (undefined4)puVar3[0x13];
      uStack_ec = (undefined4)((ulong)puVar3[0x13] >> 0x20);
      local_e8 = (undefined4)puVar3[0x14];
      uStack_e4 = (undefined4)((ulong)puVar3[0x14] >> 0x20);
      local_e0 = (undefined4)puVar3[0x15];
      uStack_dc = (undefined4)((ulong)puVar3[0x15] >> 0x20);
      local_d8 = *(undefined4 *)(puVar3 + 0x16);
      if ((int)local_188 != 0) break;
      puts((char *)&uStack_154);
    }
    local_98 = CONCAT44(local_150,uStack_154);
    local_90 = CONCAT44(local_148,uStack_14c);
    local_88 = CONCAT44(local_140,uStack_144);
    local_80 = CONCAT44(local_138,uStack_13c);
    local_78 = CONCAT44(local_130,uStack_134);
    local_70 = CONCAT44(local_128,uStack_12c);
    local_68 = CONCAT44(local_120,uStack_124);
    local_60 = CONCAT44(local_118,uStack_11c);
    local_58 = CONCAT44(local_110,uStack_114);
    local_50 = CONCAT44(local_108,uStack_10c);
    local_48 = CONCAT44(local_100,uStack_104);
    local_40 = CONCAT44(local_f8,uStack_fc);
    local_38 = CONCAT44(local_f0,uStack_f4);
    local_30 = CONCAT44(local_e8,uStack_ec);
    local_28 = CONCAT44(local_e0,uStack_e4);
    local_20 = CONCAT44(local_d8,uStack_dc);
    printf("[X] That entry is encrypted - please enter your WIDE decryption key: ");
    fgets(local_c8,0x10,stdin);
    mbstowcs(local_1c8,local_c8,0x10);
    iVar1 = wcscmp(local_1c8,L"sup3rs3cr3tw1d3");
    if (iVar1 == 0) {
      for (local_1d4 = 0;
          (local_1d4 < 0x80 && (*(char *)((long)&local_98 + (long)(int)local_1d4) != '\0'));
          local_1d4 = local_1d4 + 1) {
        *(byte *)((long)&local_98 + (long)(int)local_1d4) =
             *(byte *)((long)&local_98 + (long)(int)local_1d4) ^
             (char)(local_1d4 * 0x1b) + (char)((int)(local_1d4 * 0x1b) / 0xff);
      }
      puts((char *)&local_98);
    }
    else {
      puts("[X]                          Key was incorrect                           [X]");
    }
  } while( true );
}

Niceeee :D this shows how the program handles encrypted entries. There's a hardcoded decryption key: sup3rs3cr3tw1d3. If you enter that key, the program reveals the flag:

<game-snip>
[X] That entry is encrypted - please enter your WIDE decryption key: sup3rs3cr3tw1d3
HTB{som3_str1ng5_4r3_w1d3}

Easy, right? Truth is, lots of binaries contain hardcoded credentials which can make reversing quick. Let's dig deeper and extract the flag without running the original binary. If you look closer, the following snippet contains the relevant logic:

printf("[X] That entry is encrypted - please enter your WIDE decryption key: ");
fgets(local_c8,0x10,stdin);
mbstowcs(local_1c8,local_c8,0x10);
iVar1 = wcscmp(local_1c8,L"sup3rs3cr3tw1d3");
if (iVar1 == 0) {
  for (local_1d4 = 0;
      (local_1d4 < 0x80 && (*(char *)((long)&local_98 + (long)(int)local_1d4) != '\0'));
      local_1d4 = local_1d4 + 1) {
      *(byte *)((long)&local_98 + (long)(int)local_1d4) =
      *(byte *)((long)&local_98 + (long)(int)local_1d4) ^
      (char)(local_1d4 * 0x1b) + (char)((int)(local_1d4 * 0x1b) / 0xff);
   }
   puts((char *)&local_98);
}
else {
   puts("[X]   Key was incorrect   [X]");
}

The program reads input from stdin and compares it to the hardcoded key. If they match, it proceeds. You might wonder why the code checks for iVar1 == 0 - in C, wcscmp returns 0 when the strings are equal.

Return value of wcscmp

Negative value if lhs appears before rhs in lexicographical order. Zero if lhs and rhs compare equal. Positive value if lhs appears after rhs in lexicographical order.

Now let's reverse the loop. I'll simplify the variable names because the decompiler's names are slop.

for (local_1d4 = 0;
      (local_1d4 < 0x80 && (*(char *)((long)&local_98 + (long)(int)local_1d4) != '\0'));
      local_1d4 = local_1d4 + 1)
  • Replace the local_1d4 variable with a simple i variable
  • The hexvalue 0x80 means 128 in decimal
  • (*(char *)((long)&local_98 + (long)(int)i) accesses the byte at position i starting from local_98. Although it looks complicated, it's equivalent to local_98[i], which is why we can treat local_98 as a character array (or buffer).

So the loop condition can be rewritten as:

for (i= 0; i < 128 && local_98[i] != '\0'; i++)

Now let's rewrite the loop body to make the character logic obvious.

{
   *(byte *)((long)&local_98 + (long)(int)local_1d4) =
   *(byte *)((long)&local_98 + (long)(int)local_1d4) ^
   (char)(local_1d4 * 0x1b) + (char)((int)(local_1d4 * 0x1b) / 0xff);
}
  • Replace local_1d4 with i.
  • Replace (byte *)((long)&local_98 + i) with local_98[i].
  • Convert 0x1b to 27 and 0xff to 255.
  • The expression (i * 27) + ((i * 27) / 255) calculates a key based on the current index.
  • ^ is the XOR operator, so each byte in local_98 is XORed with this key, decrypting (or encrypting) the string.

So the decrypted loop looks like this:

for (i= 0; i < 128 && local_98[i] != '\0'; i++)
{
    local_98[i] ^= (i * 27) + ((i * 27) / 255);
}

Decrypting the flag with custom C code

What we can do now is write a small C program that reproduces the decryption routine and extracts the flag without executing the original binary. Since we already understand the XOR algorithm, the only remaining task is to figure out what data is stored inside the local_98 buffer.

local_98 is the buffer that gets decrypted by the following loop:

for (i = 0; i < 128 && local_98[i] != '\0'; i++)
{
    local_98[i] ^= (i * 27) + ((i * 27) / 255);
}

The loop itself does not generate any data, it only transforms the bytes that are already present in local_98. This means that the encrypted flag must be loaded into this buffer before the decryption happens.

Looking further up in the function, we can see that local_98 is filled from data copied from param_1:

local_98 = CONCAT44(local_150, uStack_154);
local_90 = CONCAT44(local_148, uStack_14c);
local_88 = CONCAT44(local_140, uStack_144);
...
local_20 = CONCAT44(local_d8, uStack_dc);

These assignments reconstruct a 128-byte buffer. The values come from the selected entry in the database.

To understand where this data originates, we need to look at the main() function. There we find:

__ptr = calloc((long)iVar1, 0xb4);
fread(__ptr,0xb4,(long)iVar1,__stream);

The program reads a database file into memory. Each entry has a fixed size of 0xb4 bytes (180 bytes). The structure of an entry can be reconstructed as:

Offset      Size        Description
-----------------------------------------
0x00        4 bytes     encrypted flag
0x04        16 bytes    name
0x14        32 bytes    code
0x34        128 bytes   encrypted data

The last 128 bytes are the data that eventually become local_98.

Therefore, instead of running the original binary, we can directly read the database file, extract the encrypted 128-byte field, apply the XOR decryption routine, and recover the plaintext.

So rather than running the original binary, we can read the database file, pull out the 128-byte field, run the XOR routine ourselves, and recover the plaintext.

Once we know the correct key and the decryption algorithm, the check becomes irrelevant. We can bypass the program logic completely and reproduce only the interesting part: extracting the encrypted bytes and decrypting them ourselves.

#include <stdio.h>
#include <stdint.h>

typedef struct {
    int encrypted;
    char name[16];
    char code[32];
    unsigned char secret[128];
} Entry;

int main(void)
{
    FILE *fp = fopen("db.ex", "rb");
    if (!fp) {
        perror("db.ex");
        return 1;
    }

    Entry e;

    while (fread(&e, sizeof(Entry), 1, fp) == 1) {

        if (e.encrypted) {

            for (int i = 0; i < 128 && e.secret[i] != '\0'; i++) {
                e.secret[i] ^= (i * 27) + ((i * 27) / 255);
            }

            printf("Name : %s\n", e.name);
            printf("Code : %s\n", e.code);
            printf("Text : %s\n\n", e.secret);
        }
    }

    fclose(fp);
    return 0;
}

Save the code snippet code.c to a file and compile it with gcc code.c -o decrypter . Now if we run the custom c code, we will get the flag

$ ./decrypter
Name : Flaggle Alpha
Code : admin secret power hidden
Text : HTB{som3_str1ng5_4r3_w1d3}

Conclusion

This was a compact exercise in static analysis and reproducing a decryption routine. By inspecting the binary we found a hardcoded key and the XOR-based transformation, then recreated that logic in a tiny C program to extract the flag without running the original executable. It's a good reminder: embedding secrets in binaries makes them trivial to recover, avoid hardcoding keys and prefer proper secret management.