Vishwa CTF Mini 2025 Reverse Write-ups

Posted on Jan 1, 0001
Table of contents:

All the write-ups for the Vishwa CTF Mini 2025 Reverse category.

You can find all the challenges here.

CrackMe

Challenge Title: CrackMe
Category: Reverse Engineering
Description: Crack me.


Load the binary into Radare2 (r2):

╰─> r2 -A exploit.exe
WARN: Missing name for section
WARN: Relocs has not been applied. Please use `-e bin.relocs.apply=true` or `-e bin.cache=true` next time
INFO: Analyze all flags starting with sym. and entry0 (aa)
... # truncated
INFO: Type matching analysis for all functions (aaft)
INFO: Propagate noreturn information (aanr)
INFO: Use -AA or aaaa to perform additional experimental analysis
 -- Most likely your core dump fell into a blackhole, can't see it.

Before anything, let’s check the strings in the binary:

[0x004012e0]> iz # cmd in r2 to list strings
nth paddr      vaddr      len size section type  string
―――――――――――――――――――――――――――――――――――――――――――――――――――――――
0   0x00003400 0x00405000 18  19   .rdata  ascii libgcc_s_dw2-1.dll
1   0x00003413 0x00405013 21  22   .rdata  ascii __register_frame_info
2   0x00003429 0x00405029 23  24   .rdata  ascii __deregister_frame_info
3   0x00003441 0x00405041 13  14   .rdata  ascii libgcj-16.dll
4   0x0000344f 0x0040504f 19  20   .rdata  ascii _Jv_RegisterClasses
5   0x00003467 0x00405067 10  11   .rdata  ascii 0xg00db33f
6   0x00003472 0x00405072 8   9    .rdata  ascii br3ach35
7   0x00003480 0x00405080 23  24   .rdata  ascii Mingw runtime failure:\n
8   0x00003498 0x00405098 48  49   .rdata  ascii   VirtualQuery failed for %d bytes at address %p
9   0x000034cc 0x004050cc 49  50   .rdata  ascii   Unknown pseudo relocation protocol version %d.\n
10  0x00003500 0x00405100 41  42   .rdata  ascii   Unknown pseudo relocation bit size %d.\n
11  0x0000352e 0x0040512e 16  17   .rdata  ascii glob-1.0-mingw32
12  0x00003548 0x00405148 16  17   .rdata  ascii GCC: (GNU) 6.3.0
13  0x0000355c 0x0040515c 16  17   .rdata  ascii GCC: (GNU) 6.3.0
14  0x00003570 0x00405170 34  35   .rdata  ascii GCC: (MinGW.org GCC-6.3.0-1) 6.3.0
... # Removed for brevity
31  0x000036d4 0x004052d4 16  17   .rdata  ascii GCC: (GNU) 6.3.0

Among the strings, one particularly interesting string stands out: 0xg00db33f. This could be a hint or part of the flag.

Using the axt command, I found that the string 0xg00db33f is referenced in a function at 0x004015b6:

[0x004012e0]> axt @0x00405067
fcn.004015b6 0x4015ca [STRN:r--] mov dword [s2], str.0xg00db33f

disassembling the function fcn.004015b6:

[0x004012e0]> s fcn.004015b6
[0x004015b6]> pdf
            ; CALL XREF from fcn.004015f3 @ 0x401628(x)
┌ 61: fcn.004015b6 (signed int arg_8h, char *s1);
`- args(sp[0x4..0x8]) vars(1:sp[0x18..0x18])
│           0x004015b6      55             push ebp
│           0x004015b7      89e5           mov ebp, esp
│           0x004015b9      83ec18         sub esp, 0x18
│           0x004015bc      837d0802       cmp dword [arg_8h], 2
│       ┌─< 0x004015c0      7e2e           jle 0x4015f0
│       │   0x004015c2      8b450c         mov eax, dword [s1]
│       │   0x004015c5      83c008         add eax, 8
│       │   0x004015c8      8b00           mov eax, dword [eax]
│       │   0x004015ca      c744240467..   mov dword [s2], str.0xg00db33f ; str.0xg00db33f
│       │                                                              ; [0x405067:4]=0x30677830 ; "0xg00db33f" ; const char *s2
│       │   0x004015d2      890424         mov dword [esp], eax        ; const char *s1
│       │   0x004015d5      e85e260000     call sub.msvcrt.dll_strcmp  ; int strcmp(const char *s1, const char *s2)
│       │   0x004015da      85c0           test eax, eax
│      ┌──< 0x004015dc      7512           jne 0x4015f0
│      ││   0x004015de      8b450c         mov eax, dword [s1]
│      ││   0x004015e1      89442404       mov dword [s2], eax
│      ││   0x004015e5      8b4508         mov eax, dword [arg_8h]
│      ││   0x004015e8      890424         mov dword [esp], eax
│      ││   0x004015eb      e889ffffff     call fcn.00401579
│      ││   ; CODE XREFS from fcn.004015b6 @ 0x4015c0(x), 0x4015dc(x)
│      └└─> 0x004015f0      90             nop
│           0x004015f1      c9             leave
└           0x004015f2      c3             ret

This function appears to compare an input string with 0xg00db33f. If the comparison is successful, it calls another function fcn.00401579.

Click to expand
[0x004015b6]> s fcn.00401579
[0x00401579]> pdf
            ; CALL XREF from fcn.004015b6 @ 0x4015eb(x)
 61: fcn.00401579 (signed int arg_8h, char *s1);
 `- args(sp[0x4..0x8]) vars(1:sp[0x18..0x18])
           0x00401579      55             push ebp
           0x0040157a      89e5           mov ebp, esp
           0x0040157c      83ec18         sub esp, 0x18
           0x0040157f      837d0803       cmp dword [arg_8h], 3
       ┌─< 0x00401583      7e2e           jle 0x4015b3
          0x00401585      8b450c         mov eax, dword [s1]
          0x00401588      83c00c         add eax, 0xc                ; 12
          0x0040158b      8b00           mov eax, dword [eax]
          0x0040158d      c744240464..   mov dword [s2], 0x405064    ; 'dP@'
                                                                     ; [0x405064:4]=0x30003531 ; "15" ; const char *s2
          0x00401595      890424         mov dword [esp], eax        ; const char *s1
          0x00401598      e89b260000     call sub.msvcrt.dll_strcmp  ; int strcmp(const char *s1, const char *s2)
          0x0040159d      85c0           test eax, eax
      ┌──< 0x0040159f      7512           jne 0x4015b3
      ││   0x004015a1      8b450c         mov eax, dword [s1]
      ││   0x004015a4      89442404       mov dword [s2], eax
      ││   0x004015a8      8b4508         mov eax, dword [arg_8h]
      ││   0x004015ab      890424         mov dword [esp], eax
      ││   0x004015ae      e8adfeffff     call fcn.00401460
      ││   ; CODE XREFS from fcn.00401579 @ 0x401583(x), 0x40159f(x)
      └└─> 0x004015b3      90             nop
           0x004015b4      c9             leave
           0x004015b5      c3             ret

The function fcn.00401579 further calls fcn.00401460 with "15" as an argument, which seems to be a decryption routine. Disassembling fcn.00401460 reveals that it performs a Caesar cipher decryption on a string:

Click to expand
[0x00401579]> s fcn.00401460
[0x00401460]> pdf
            ; CALL XREF from fcn.00401579 @ 0x4015ae(x)
 281: fcn.00401460 (char *str);
 `- args(sp[0x8..0x8]) vars(11:sp[0x10..0x33])
           0x00401460      55             push ebp
           0x00401461      89e5           mov ebp, esp
           0x00401463      83ec48         sub esp, 0x48
           0x00401466      c745d14b78..   mov dword [s], 0x7768784b   ; 'Kxhw'
           0x0040146d      c745d56c70..   mov dword [var_2bh], 0x4952706c ; 'lpRI'
           0x00401474      c745d9557b..   mov dword [var_27h], 0x61727b55 ; 'U{ra'
           0x0040147b      c745dd315f..   mov dword [var_23h], 0x67345f31 ; '1_4g'
           0x00401482      c745e1766a..   mov dword [var_1fh], 0x33626a76 ; 'vjb3'
           0x00401489      c745e56369..   mov dword [var_1bh], 0x5f356963 ; 'ci5_'
           0x00401490      c745e96c77..   mov dword [var_17h], 0x3f30776c ; 'lw0?'
           0x00401497      66c745ed7d00   mov word [var_13h], 0x7d    ; '}' ; 125
           0x0040149d      8b450c         mov eax, dword [str]
           0x004014a0      83c00c         add eax, 0xc                ; 12
           0x004014a3      8b00           mov eax, dword [eax]
           0x004014a5      890424         mov dword [esp], eax        ; const char *str
           0x004014a8      e8e3270000     call sub.msvcrt.dll_atoi    ; int atoi(const char *str)
           0x004014ad      8945f0         mov dword [var_10h], eax
           0x004014b0      c745f40000..   mov dword [var_ch], 0
       ┌─< 0x004014b7      e99c000000     jmp 0x401558
          ; CODE XREF from fcn.00401460 @ 0x401565(x)
      ┌──> 0x004014bc      8d55d1         lea edx, [s]
      ╎│   0x004014bf      8b45f4         mov eax, dword [var_ch]
      ╎│   0x004014c2      01d0           add eax, edx
      ╎│   0x004014c4      0fb600         movzx eax, byte [eax]
      ╎│   0x004014c7      8845ef         mov byte [var_11h], al
      ╎│   0x004014ca      807def40       cmp byte [var_11h], 0x40    ; '@'
     ┌───< 0x004014ce      7e40           jle 0x401510
     │╎│   0x004014d0      807def5a       cmp byte [var_11h], 0x5a    ; 'Z'
    ┌────< 0x004014d4      7f3a           jg 0x401510
    ││╎│   0x004014d6      0fbe45ef       movsx eax, byte [var_11h]
    ││╎│   0x004014da      83e841         sub eax, 0x41               ; 65
    ││╎│   0x004014dd      2b45f0         sub eax, dword [var_10h]
    ││╎│   0x004014e0      8d481a         lea ecx, [eax + 0x1a]
    ││╎│   0x004014e3      ba4fecc44e     mov edx, 0x4ec4ec4f
    ││╎│   0x004014e8      89c8           mov eax, ecx
    ││╎│   0x004014ea      f7ea           imul edx
    ││╎│   0x004014ec      c1fa03         sar edx, 3
    ││╎│   0x004014ef      89c8           mov eax, ecx
    ││╎│   0x004014f1      c1f81f         sar eax, 0x1f
    ││╎│   0x004014f4      29c2           sub edx, eax
    ││╎│   0x004014f6      89d0           mov eax, edx
    ││╎│   0x004014f8      6bc01a         imul eax, eax, 0x1a
    ││╎│   0x004014fb      29c1           sub ecx, eax
    ││╎│   0x004014fd      89c8           mov eax, ecx
    ││╎│   0x004014ff      83c041         add eax, 0x41               ; 65
    ││╎│   0x00401502      89c1           mov ecx, eax
    ││╎│   0x00401504      8d55d1         lea edx, [s]
    ││╎│   0x00401507      8b45f4         mov eax, dword [var_ch]
    ││╎│   0x0040150a      01d0           add eax, edx
    ││╎│   0x0040150c      8808           mov byte [eax], cl
   ┌─────< 0x0040150e      eb44           jmp 0x401554
   │││╎│   ; CODE XREFS from fcn.00401460 @ 0x4014ce(x), 0x4014d4(x)
   │└└───> 0x00401510      807def60       cmp byte [var_11h], 0x60    ; '`'
    ┌───< 0x00401514      7e3e           jle 0x401554
    │╎│   0x00401516      807def7a       cmp byte [var_11h], 0x7a    ; 'z'
   │┌────< 0x0040151a      7f38           jg 0x401554
   │││╎│   0x0040151c      0fbe45ef       movsx eax, byte [var_11h]
   │││╎│   0x00401520      83e861         sub eax, 0x61               ; 97
   │││╎│   0x00401523      2b45f0         sub eax, dword [var_10h]
   │││╎│   0x00401526      8d481a         lea ecx, [eax + 0x1a]
   │││╎│   0x00401529      ba4fecc44e     mov edx, 0x4ec4ec4f
   │││╎│   0x0040152e      89c8           mov eax, ecx
   │││╎│   0x00401530      f7ea           imul edx
   │││╎│   0x00401532      c1fa03         sar edx, 3
   │││╎│   0x00401535      89c8           mov eax, ecx
   │││╎│   0x00401537      c1f81f         sar eax, 0x1f
   │││╎│   0x0040153a      29c2           sub edx, eax
   │││╎│   0x0040153c      89d0           mov eax, edx
   │││╎│   0x0040153e      6bc01a         imul eax, eax, 0x1a
   │││╎│   0x00401541      29c1           sub ecx, eax
   │││╎│   0x00401543      89c8           mov eax, ecx
   │││╎│   0x00401545      83c061         add eax, 0x61               ; 97
   │││╎│   0x00401548      89c1           mov ecx, eax
   │││╎│   0x0040154a      8d55d1         lea edx, [s]
   │││╎│   0x0040154d      8b45f4         mov eax, dword [var_ch]
   │││╎│   0x00401550      01d0           add eax, edx
   │││╎│   0x00401552      8808           mov byte [eax], cl
   │││╎│   ; CODE XREFS from fcn.00401460 @ 0x40150e(x), 0x401514(x), 0x40151a(x)
   └└└───> 0x00401554      8345f401       add dword [var_ch], 1
      ╎│   ; CODE XREF from fcn.00401460 @ 0x4014b7(x)
      ╎└─> 0x00401558      8d55d1         lea edx, [s]
          0x0040155b      8b45f4         mov eax, dword [var_ch]
          0x0040155e      01d0           add eax, edx
          0x00401560      0fb600         movzx eax, byte [eax]
          0x00401563      84c0           test al, al
      └──< 0x00401565      0f8551ffffff   jne 0x4014bc
           0x0040156b      8d45d1         lea eax, [s]
           0x0040156e      890424         mov dword [esp], eax        ; const char *s
           0x00401571      e8e2260000     call sub.msvcrt.dll_puts    ; int puts(const char *s)
           0x00401576      90             nop
           0x00401577      c9             leave
           0x00401578      c3             ret
[0x00401460]> # str = KxhwlpRIU{ra1_4gvjb3ci5_lw0?} // it looks like a flag format

The string KxhwlpRIU{ra1_4gvjb3ci5_lw0?} is stored in the buffer, and the function applies a Caesar cipher shift to decrypt it. The shift value is derived from the input argument.

Given that the shift value is 15, we can write a simple Python script based on the function:

  • Iterates through each character in Buffer.
  • If the character is uppercase (A-Z), it shifts it back using the formula: [ (v4 - 65 - v5 + 26) % 26 + 65 ]
  • If the character is lowercase (a-z), it shifts it back using: [ (v4 - 97 - v5 + 26) % 26 + 97 ]
  • Other characters ({}, _, ?, 0-9) remain unchanged.

to decrypt the string:

def caesar_decrypt(ciphertext, shift):
    plaintext = ""
    for char in ciphertext:
        if 'A' <= char <= 'Z': # Uppercase letters
            plaintext += chr((ord(char) - 65 - shift + 26) % 26 + 65)
        elif 'a' <= char <= 'z': # Lowercase letters
            plaintext += chr((ord(char) - 97 - shift + 26) % 26 + 97)
        else:
            plaintext += char # Keep special characters unchanged
    return plaintext

ciphertext = "KxhwlpRIU{ra1_4gvjb3ci5_lw0?}"
shift = 15

print("Decrypted flag:", caesar_decrypt(ciphertext, shift))

Running this script gives us the decrypted flag:

Decrypted flag: VishwaCTF{cl1_4rgum3nt5_wh0?}

Alternatively, we could have used a Caesar cipher brute-forcing tool like chepy to find the correct shift:

╰─> chepy 'KxhwlpRIU{ra1_4gvjb3ci5_lw0?}' - rotate_bruteforce
...
11: b'VishwaCTF{cl1_4rgum3nt5_wh0?}'
...

Faulpelz

Challenge Title: Faulpelz
Category: Reverse Engineering
Description: There was this secret message sent by somebody that I want to recover. So, I hacked somebody’s P.C. and found the source but I am too lazy to work on it anymore. So, I came up with a clever idea that you all can help me in it. Here you go:
Message-001101111000010110010110001111100110101001110110
Pre-Order-IIILQLHIILALBILULGIILKLEIILPLNLF
Flag format- VishwaCTF{message-decoded}

After going through the main function, I found that the binary reads a message and a pre-order traversal of a binary tree. The binary tree is used to decode the message. Further analysis showed that the binary tree is a Huffman tree. 1. Prompting the user to enter a message.

  1. Encrypting the message using Vigenère encryption with the key "ctfworld".

  2. Encoding the encrypted message using Huffman encoding.

  3. Printing the Huffman-encoded message and the pre-order traversal of the Huffman tree.

Click to expand
[0x004012e0]> afl~+main
0x004012a0    1     63 sym.__mingw32_init_mainargs
0x004099e8    1      6 sym.___getmainargs
0x00401300    1     32 sym._WinMainCRTStartup
0x004019c1    1    224 sym._main
0x00401fe0    3     23 sym.___main
0x00402660   14    108 main
[0x004012e0]> 0x004019c1
[0x004019c1]> pdf
            ; CALL XREF from fcn.004011b0 @ 0x401283(x)
 224: int sym._main (char **argv);
 afv: vars(9:sp[0x8..0x794])
           0x004019c1      55             push ebp
           0x004019c2      89e5           mov ebp, esp
           0x004019c4      57             push edi
           0x004019c5      83e4f0         and esp, 0xfffffff0
           0x004019c8      81ec90070000   sub esp, 0x790
           0x004019ce      e80d060000     call sym.___main
           0x004019d3      c784241002..   mov dword [var_210h], 0
           0x004019de      8d94241402..   lea edx, [var_214h]
           0x004019e5      b800000000     mov eax, 0
           0x004019ea      b9ff000000     mov ecx, 0xff               ; 255
           0x004019ef      89d7           mov edi, edx
           0x004019f1      f3ab           rep stosd dword es:[edi], eax
           0x004019f3      c744241000..   mov dword [var_10h], 0
           0x004019fb      8d542414       lea edx, [var_14h]
           0x004019ff      b800000000     mov eax, 0
           0x00401a04      b97f000000     mov ecx, 0x7f               ; '\x7f' ; 127
           0x00401a09      89d7           mov edi, edx
           0x00401a0b      f3ab           rep stosd dword es:[edi], eax
           0x00401a0d      c7042473b0..   mov dword [esp], str.Enter_message: ; [0x40b073:4]=0x65746e45 ; "Enter message: " ; const char *format
           0x00401a14      e8377f0000     call sym._printf            ; int printf(const char *format)
           0x00401a19      8d84241007..   lea eax, [var_710h]
           0x00401a20      89442404       mov dword [var_4h], eax
           0x00401a24      c7042483b0..   mov dword [esp], str._127s  ; [0x40b083:4]=0x37323125 ; "%127s" ; const char *format
           0x00401a2b      e8107f0000     call sym._scanf             ; int scanf(const char *format)
           0x00401a30      8d84241006..   lea eax, [var_610h]
           0x00401a37      89442404       mov dword [var_4h], eax     ; int32_t arg_ch
           0x00401a3b      8d84241007..   lea eax, [var_710h]
           0x00401a42      890424         mov dword [esp], eax        ; int32_t arg_8h
           0x00401a45      e865faffff     call sym._vigenereEncrypt
           0x00401a4a      8d442410       lea eax, [var_10h]
           0x00401a4e      89442408       mov dword [var_8h], eax     ; int32_t arg_10h
           0x00401a52      8d84241002..   lea eax, [var_210h]
           0x00401a59      89442404       mov dword [var_4h], eax     ; int32_t arg_ch
           0x00401a5d      8d84241006..   lea eax, [var_610h]
           0x00401a64      890424         mov dword [esp], eax        ; int32_t arg_8h
           0x00401a67      e873fcffff     call sym._HuffmanEncoding
           0x00401a6c      8d442410       lea eax, [var_10h]
           0x00401a70      89442404       mov dword [var_4h], eax
           0x00401a74      c7042489b0..   mov dword [esp], str.Huffman_Tree_Pre_order:__s_n ; [0x40b089:4]=0x66667548 ; "Huffman Tree Pre-order: %s\n" ; const char *format
           0x00401a7b      e8d07e0000     call sym._printf            ; int printf(const char *format)
           0x00401a80      8d84241002..   lea eax, [var_210h]
           0x00401a87      89442404       mov dword [var_4h], eax
           0x00401a8b      c70424a5b0..   mov dword [esp], str.Huffman_Encoded:__s_n ; [0x40b0a5:4]=0x66667548 ; "Huffman Encoded: %s\n" ; const char *format
           0x00401a92      e8b97e0000     call sym._printf            ; int printf(const char *format)
           0x00401a97      b800000000     mov eax, 0
           0x00401a9c      8b7dfc         mov edi, dword [var_bp_4h]
           0x00401a9f      c9             leave
           0x00401aa0      c3             ret
[0x004019c1]> pdg

// WARNING: Unable to track spacebase fully for stack

uint sym._main(void)

{
    int32_t iVar1;
    uchar *puVar2;
    uint *puVar3;
    uint uStack_7a4;
    char *pcStack_7a0;
    int32_t aiStack_79c [3];
    uint auStack_790 [128];
    uint auStack_590 [256];
    uchar auStack_190 [256];
    uchar auStack_90 [136];

    *(*0x10 + -0x7a4) = 0x4019d3;
    sym.___main();
    *(*0x10 + -0x590) = 0;
    puVar3 = *0x10 + -0x58c;
    for (iVar1 = 0xff; iVar1 != 0; iVar1 = iVar1 + -1) {
        *puVar3 = 0;
        puVar3 = puVar3 + 4;
    }
    *(&stack0x00000000 + -0x790) = 0;
    puVar3 = *0x10 + -0x78c;
    for (iVar1 = 0x7f; iVar1 != 0; iVar1 = iVar1 + -1) {
        *puVar3 = 0;
        puVar3 = puVar3 + 4;
    }
    *&stack0xfffff860 = "Enter message: ";
    *(&stack0xfffff860 + -4) = 0x401a19;
    sym._printf();
    *(&stack0xfffff860 + 4) = &stack0xfffff860 + 0x710;
    *(*0x10 + -0x7a0) = "%127s";
    *(*0x10 + -0x7a4) = 0x401a30;
    sym._scanf();
    *(*0x10 + -0x79c) = *0x10 + -400;
    *(*0x10 + -0x7a0) = *0x10 + -0x90;
    *(&stack0xfffff860 + -4) = 0x401a4a;
    sym._vigenereEncrypt();
    puVar2 = &stack0xfffff860;
    *(&stack0xfffff860 + 8) = &stack0xfffff860 + 0x10;
    *(&stack0xfffff860 + 4) = &stack0xfffff860 + 0x210;
    *(&stack0xfffff864 + -4) = &stack0xfffff860 + 0x610;
    *(*0x10 + -0x7a4) = 0x401a6c;
    sym._HuffmanEncoding();
    *(&stack0xfffff864 + 0) = &stack0xfffff864 + 0xc;
    *&stack0xfffff860 = "Huffman Tree Pre-order: %s\n";
    *(puVar2 + -4) = 0x401a80;
    sym._printf();
    *(puVar2 + 4) = puVar2 + 0x210;
    *(&stack0xfffff860 + 0) = "Huffman Encoded: %s\n";
    *(&stack0xfffff860 + 0 + -4) = 0x401a97;
    sym._printf();
    return 0;
}

// The analysis of other functions were very bad from ghidra so I used IDA to analyze them.
char *__cdecl generateHuffmanCodes(int a1, const char *a2, int a3)
{
  char *result; // eax
  char Buffer[24]; // [esp+20h] [ebp-18h] BYREF

  if ( a1 )
  {
    snprintf(Buffer, 0x10u, "%s%c", a2, *(char *)(a1 + 16));
    if ( *(_DWORD *)(a1 + 8) || *(_DWORD *)(a1 + 12) )
    {
      generateHuffmanCodes(*(_DWORD *)(a1 + 8), Buffer, a3);
      return (char *)generateHuffmanCodes(*(_DWORD *)(a1 + 12), Buffer, a3);
    }
    else
    {
      return strcpy((char *)(a3 + 16 * *(unsigned __int8 *)(a1 + 4)), Buffer);
    }
  }
  return result;
}
_BYTE *__cdecl vigenereEncrypt(char *a1, int a2)
{
  _BYTE *result; // eax
  char v3; // [esp+13h] [ebp-15h]
  signed int v4; // [esp+14h] [ebp-14h]
  int i; // [esp+1Ch] [ebp-Ch]

  v4 = strlen("ctfworld");
  for ( i = 0; a1[i]; ++i )
  {
    v3 = toupper(a1[i]);
    *(_BYTE *)(i + a2) = (v3 - 65 + (char)toupper(aCtfworld[i % v4]) - 65) % 26 + 65;
  }
  result = (_BYTE *)(strlen(a1) + a2);
  *result = 0;
  return result;
}
int __usercall HuffmanEncoding@<eax>(int a1@<eax>, int a2, char *Destination, int a4)
{
  void *v4; // esp
  int v5; // ebx
  int result; // eax
  int v7; // [esp+18h] [ebp-1830h] BYREF
  void *Block[256]; // [esp+1Ch] [ebp-182Ch] BYREF
  char v9[4096]; // [esp+41Ch] [ebp-142Ch] BYREF
  _DWORD v10[256]; // [esp+141Ch] [ebp-42Ch] BYREF
  _DWORD *Node; // [esp+181Ch] [ebp-2Ch]
  int n; // [esp+1820h] [ebp-28h]
  int m; // [esp+1824h] [ebp-24h]
  int k; // [esp+1828h] [ebp-20h]
  int v15; // [esp+182Ch] [ebp-1Ch]
  int v16; // [esp+1830h] [ebp-18h]
  int j; // [esp+1834h] [ebp-14h]
  int v18; // [esp+1838h] [ebp-10h]
  int i; // [esp+183Ch] [ebp-Ch]

  v4 = alloca(a1);
  memset(v10, 0, sizeof(v10));
  memset(v9, 0, sizeof(v9));
  for ( i = 0; *(_BYTE *)(i + a2); ++i )
    ++v10[*(unsigned __int8 *)(i + a2)];
  memset(Block, 0, sizeof(Block));
  v18 = 0;
  for ( j = 0; j <= 255; ++j )
  {
    if ( (int)v10[j] > 0 )
    {
      v5 = v18++;
      Block[v5] = (void *)createNode(v10[j], (char)j);
    }
  }
  while ( v18 > 1 )
  {
    v16 = 0;
    v15 = 1;
    if ( *(_DWORD *)Block[1] < *(_DWORD *)Block[0] )
    {
      v16 = 1;
      v15 = 0;
    }
    for ( k = 2; k < v18; ++k )
    {
      if ( *(_DWORD *)Block[k] >= *(_DWORD *)Block[v16] )
      {
        if ( *(_DWORD *)Block[k] < *(_DWORD *)Block[v15] )
          v15 = k;
      }
      else
      {
        v15 = v16;
        v16 = k;
      }
    }
    Node = (_DWORD *)createNode(*(_DWORD *)Block[v16] + *(_DWORD *)Block[v15], 0);
    Node[2] = Block[v16];
    Node[3] = Block[v15];
    *(_BYTE *)(Node[2] + 16) = 48;
    *(_BYTE *)(Node[3] + 16) = 49;
    Block[v16] = Node;
    Block[v15] = Block[--v18];
  }
  v7 = 0;
  generatePreorder(Block[0], a4, &v7);
  *(_BYTE *)(v7 + a4) = 0;
  generateHuffmanCodes(Block[0], &unk_40B072, v9);
  *Destination = 0;
  for ( m = 0; *(_BYTE *)(m + a2); ++m )
    strcat(Destination, &v9[16 * *(unsigned __int8 *)(m + a2)]);
  for ( n = 0; ; ++n )
  {
    result = n;
    if ( n >= v18 )
      break;
    free(Block[n]);
  }
  return result;
}

Based on that information, we can write a Python script to decode the message:

class HuffmanNode:
    def __init__(self, char=None, left=None, right=None):
        self.char = char
        self.left = left
        self.right = right

def build_huffman_tree(preorder_iter):
    """Recursively builds the Huffman tree from preorder traversal."""
    val = next(preorder_iter)
    if val == 'I':  # Internal node
        left = build_huffman_tree(preorder_iter)
        right = build_huffman_tree(preorder_iter)
        return HuffmanNode(left=left, right=right)
    else:  # Leaf node ('L' is followed by the character)
        return HuffmanNode(char=next(preorder_iter))

def decode_huffman(tree, binary_string):
    """Decodes a Huffman-encoded binary string using the given tree."""
    decoded_text = ""
    node = tree
    for bit in binary_string:
        if bit == '0':
            node = node.left
        else:
            node = node.right

        if node.char:  # Reached a leaf node
            decoded_text += node.char
            node = tree  # Restart from root
    return decoded_text

def vigenere_decrypt(ciphertext, key):
    """Decrypts a Vigenère cipher using a given key."""
    plaintext = ""
    key_length = len(key)
    for i, char in enumerate(ciphertext):
        if 'A' <= char <= 'Z':  # Uppercase
            shift = ord(key[i % key_length].upper()) - ord('A')
            plaintext += chr((ord(char) - shift - 65) % 26 + 65)
        elif 'a' <= char <= 'z':  # Lowercase
            shift = ord(key[i % key_length].lower()) - ord('a')
            plaintext += chr((ord(char) - shift - 97) % 26 + 97)
        else:
            plaintext += char
    return plaintext

preorder = "IIILQLHIILALBILULGIILKLEIILPLNLF"
preorder_iter = iter(preorder)
huffman_tree = build_huffman_tree(preorder_iter)

binary_message = "001101111000010110010110001111100110101001110110"
decoded_text = decode_huffman(huffman_tree, binary_message)

key = "ctfworld"
plaintext = vigenere_decrypt(decoded_text, key)

flag = f"VishwaCTF{{{plaintext}}}"
print("Final Flag:", flag)

Running this script gives us the decoded message:

Final Flag: VishwaCTF{FLAUNTTHEWIERD}

FIGHT FIGHT FIGHT

Challenge Title: FIGHT FIGHT FIGHT
Category: Reverse Engineering
Description: Win this game, by hook or by crook.

[0x000010a0]> iE
nth paddr      vaddr      bind   type   size lib name           demangled
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
19  0x000013a5 0x000013a5 GLOBAL FUNC   70       enemyAttack
23  0x00001189 0x00001189 GLOBAL FUNC   102      displayMenu
24  ---------- 0x00014040 GLOBAL NOTYPE 0        _edata
25  0x00001520 0x00001520 GLOBAL FUNC   0        _fini
27  0x00001263 0x00001263 GLOBAL FUNC   190      defend
29  0x00013030 0x00014030 GLOBAL NOTYPE 0        __data_start
31  0x00013038 0x00014038 GLOBAL OBJ    0        __dso_handle
32  0x000013eb 0x000013eb GLOBAL FUNC   27       whatNext
33  0x00002000 0x00002000 GLOBAL OBJ    4        _IO_stdin_used
35  ---------- 0x00014048 GLOBAL NOTYPE 0        _end
36  0x000010a0 0x000010a0 GLOBAL FUNC   34       _start
37  0x00001321 0x00001321 GLOBAL FUNC   132      heal
38  ---------- 0x00014040 GLOBAL NOTYPE 0        __bss_start
39  0x00001406 0x00001406 GLOBAL FUNC   282      main
40  0x000011ef 0x000011ef GLOBAL FUNC   116      attack
42  ---------- 0x00014040 GLOBAL OBJ    0        __TMC_END__
45  0x00001000 0x00001000 GLOBAL FUNC   0        _init
Click to expand
[0x000010a0]> s main
[0x00001406]> pdg

ulong dbg.main(void)

{
    uint uVar1;
    uchar *puVar2;
    uchar *puVar3;
    uchar *puVar4;
    uchar *puVar5;
    ulong uStack_20;
    uchar auStack_18 [4];
    uint32_t uStack_14;
    uint32_t uStack_10;
    int32_t iStack_c;

    // int main();
    uStack_10 = 100;
    uStack_14 = 100;
    *(*0x20 + -0x20) = 0x1426;
    uVar1 = sym.imp.time(0);
    puVar2 = *0x20 + -0x18;
    *(*0x20 + -0x18 + -8) = 0x142d;
    sym.imp.srand(uVar1);
    *(puVar2 + -8) = 0x143c;
    sym.imp.puts("Welcome to the Adventure Game!");
    puVar3 = puVar2;
    do {
        while( true ) {
            puVar4 = puVar3;
            *(puVar3 + -8) = 0x1446;
            dbg.displayMenu();
            *(puVar4 + -8) = 0x1461;
            sym.imp.__isoc99_scanf(0x121b7, &stack0xfffffffffffffff4);
            if (iStack_c == 4) {
                *(puVar4 + -8) = 0x14d3;
                sym.imp.puts("Exiting game. Thanks for playing!");
                return 0;
            }
            if (iStack_c == 4 || SBORROW4(iStack_c, 4) != iStack_c + -4 < 0) break;
code_r0x000014da:
            *(puVar4 + -8) = 0x14e9;
            sym.imp.puts("Invalid choice! Try again.");
            puVar3 = puVar4 + 0;
        }
        if (iStack_c == 3) {
            *(puVar4 + -8) = 0x14c2;
            dbg.heal(&stack0xfffffffffffffff0);
            puVar5 = puVar4;
        }
        else {
            if (iStack_c != 3 && SBORROW4(iStack_c, 3) == iStack_c + -3 < 0) goto code_r0x000014da;
            if (iStack_c == 1) {
                *(puVar4 + -8) = 0x1490;
                dbg.attack(&stack0xffffffffffffffec);
                puVar5 = puVar4;
                if ((uStack_14 & uStack_14) < 1) {
                    *(puVar4 + -8) = 0x14a1;
                    dbg.whatNext();
                    return 0;
                }
            }
            else {
                if (iStack_c != 2) goto code_r0x000014da;
                *(puVar4 + -8) = 0x14b4;
                dbg.defend(&stack0xfffffffffffffff0);
                puVar5 = puVar4;
            }
        }
        *(puVar5 + -8) = 0x14f8;
        dbg.enemyAttack(&stack0xfffffffffffffff0);
        puVar3 = puVar5;
        if ((uStack_10 & uStack_10) < 1) {
            *(puVar5 + -8) = 0x1512;
            sym.imp.puts("You have been defeated! Game Over.");
            return 0;
        }
    } while( true );
}
[0x00001406]> # dbg.whatNext // Intresting
[0x00001406]> s dbg.whatNext
[0x000013eb]> pdf
            ;-- whatNext:
            ; CALL XREF from dbg.main @ 0x149c(x)
┌ 27: dbg.whatNext ();
│           0x000013eb      55             push rbp                    ; game.c:42 ; void whatNext();
│           0x000013ec      4889e5         mov rbp, rsp
│           0x000013ef      488d05520d..   lea rax, str.00_00_00_0D_49_48_44_52_00_00_01_F4_00_00_01_90_08_06_00_00_00_58_B3_92_C6_00_00_00_01_73_52_47_42_00_AE_CE_1C_E9_00_00_20_00_49_44_41_54_78_5E_ED_9D_05_7B_5B_49_B2_86_4B_32_33_DB_61_66_A6_1D_66_DA_DF_7C_77_26_33_19_D8_81_9D_49_E2_30_33_99_99_49_F7_F9_4A_51_46_56_24_4B_72_6C_8F_D3 ; game.c:43 ; 0x2148 ; "00 00 00 0D 49 48 44 52 00 00 01 F4 00 00 01 90 08 06 00 00 00 58 B3 92 C6 00 00 00 01 73 52 47 42 00 AE CE 1C E9 00 00 20 00 49 44 41 54 78 5E ED 9D 05 7B 5B 49 B2 86 4B 32 33 DB 61 66 A6 1D 66 DA DF 7C 77 26 33 19 D8 81 9D 49 E2 30 33 99 99 49 F7 F9 4A 51 46 56 24 4B 72 6C 8F D3 "
│           0x000013f6      4889c7         mov rdi, rax                ; const char *format
│           0x000013f9      b800000000     mov eax, 0
│           0x000013fe      e83dfcffff     call sym.imp.printf         ; int printf(const char *format)
│           0x00001403      90             nop                         ; game.c:44
│           0x00001404      5d             pop rbp
└           0x00001405      c3             ret

The disassembly revealed that the function was printing a long hex string:

00 00 00 0D 49 48 44 52 00 00 01 F4 00 00 01 90 08 06 00 00 00 58 B3 92 C6 00 00 00 01 73 52 47 42 00 AE CE 1C E9 00 00 20 00 49 44 41 54 78 5E ED 9D 05 7B 5B 49 B2 86 4B 32 33 DB 61 66 A6 1D 66 DA DF 7C 77 26 33 19 D8 81 9D 49 E2 30 33 99 99 49 F7 F9 4A 51 46 56 24 4B 72 6C 8F D3 ...

This hex string looked like it could be part of an image file, specifically a PNG, given the presence of the IHDR chunk, which is a signature of PNG files.

╰─> chepy "0000000D49484452000001F400000190080600000058B392C6000000017352474200AECE1CE90000200049444154785EED9D057B5B49B2864B3233DB6166A61D66DADF7C77263319D
8819D49E23033999949F7F94A514656244B726C8FD3"
>>> from_hex
Could not convert to str, but the data exists in the states. Use o, output or out() to access the values
>>> o
b'\x00\x00\x00\rIHDR\x00\x00\x01\xf4\x00\x00\x01\x90\x08\x06\x00\x00\x00X\xb3\x92\xc6\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00 \x00IDATx^\xed\x9d\x05{[I\xb2\x86K23\xdbaf\xa6\x1df\xda\xdf|w&3\x19\xd8\x81\x9dI\xe203\x99\x99I\xf7\xf9JQFV$Krl\x8f\xd3'
>>>

but it’s still just a part of the image. We need to extract the full hex string to reconstruct the image, so we go back to the address 0x2148 and grabbed whole HEX data

PNG files have a specific header: 89 50 4E 47 0D 0A 1A 0A. The hex string we extracted was missing this header, so we needed to prepend it to the data. We combined the header with the extracted hex string:

89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 00 00 01 F4 00 00 01 90 08 06 00 00 00 58 B3 92 C6 00 00 00 01 73 52 47 42 00 AE CE 1C E9 00 00 20 00 49 44 41 54 78 5E ED 9D 05 7B 5B 49 B2 86 4B 32 33 DB 61 66 A6 1D 66 DA DF 7C 77 26 33 19 D8 81 9D 49 E2 30 33 99 99 49 F7 F9 4A 51 46 56 24 4B 72 6C 8F D3 ...

We then converted this hex string back into binary data using a Python script:

hex_data = "89504E470D0A1A0A0000000D49484452000001F400000190080600000058B392C6000000017352474200AECE1CE90000200049444154785EED9D057B5B49B2864B3233DB6166A61D66DADF7C77263319D8819D49E23033999949F7F94A514656244B726C8FD3..." # Truncated for brevity
binary_data = bytes.fromhex(hex_data)

with open("flag.png", "wb") as f:
    f.write(binary_data)

or we could’ve used CyberChef to convert the hex string to a PNG file. Recipe: Render_Image('Hex') : Link

alt text

After saving the binary data as flag.png, we opened the image file, which revealed the hidden flag:

VishwaCTF{it_w4s_A_byt3_b4th}