Vishwa CTF Mini 2025 Reverse Write-ups
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.
-
Encrypting the message using Vigenère encryption with the key
"ctfworld"
. -
Encoding the encrypted message using Huffman encoding.
-
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
After saving the binary data as flag.png
, we opened the image file, which revealed the hidden flag:
VishwaCTF{it_w4s_A_byt3_b4th}