Intro

This year I only managed to solve 9 challenges. Compared to last year, I guess I have improved myself. Some of the challenges were pretty hard(3) and some of them required too much detective work and guessing. Even though number 3 was hard, it was a fair challenge. However, number 10 was just a riddle. It was like someone dealt with that CPU or maybe wrote an emulator for it and wanted us to suffer too lol. I hope next year they don’t ask questions related to the abacus or some ancient computers.

In this blog post, I will only show the solutions to the challenges that I enjoyed solving.

3 mypassion

This was a very hard challenge even though it was the 3rd one. My biggest mistake was patching everything. However, there is no patching necessary to solve this challenge. You were required to run the exe with a command line so that it shows the flag. Every part of the command line is there for a reason.

I will put the solution first and then I will explain the parts

01c34R@brUc3E/1337pr.ost/20ABCDEFGH/)pizza/AMu$E`0R.MAZe/YPXEKCZXYIGMNOXNMXPYCXGXNE/ob5cUr3/fin/
  • 01c34R@brUc3E: This is pretty much a constant string. The first char must be 0. (second char << 2) + third = 0x127
  • 1337pr.ost: Constant
  • 20ABCDEFGH: This string is made of 2 parts 20 and ABCDEFGH. 20 is actually a number in base 4. So 20 is equal to 8 which is the length of the next part. This number(8) is being used for Sleep function. If you don’t let the program sleep for a while it crashes.
  • )pizza: The first character is based on the day of the month. This value is important. Because the app first adds the day of the month + 0x1F to buffer and then uses the first char of pizza to subtract it. Therefore, to cancel the effect of the day, you need to get the day of the month and add 0x1F to calculate the character.
  • AMu$E`0R.MAZe: This is the missing bytes of the shellcode. It was a pain to recover.
  • YPXEKCZXYIGMNOXNMXPYCXGXNE: Constant
  • ob5cUr3: Constant
  • fin: Constant

If you run the app with the correct cmd line, you will get the flag b0rn_t0_5truc7_b4by@flare-on.com

6 Flaresay

This is a two-part challenge. First, you need to run the game in DOS. I used DOSBox to debug the application. You need to do the exact movements the game shows so that you can score. I first thought the game was using the moves of the game directly and self-modifying itself. I even wrote a brute-forcer for it. It gave me MPHMKMHMPHPMHPMH I put that string into the position it expected. It passed the hash check of the main exe but decryption failed lol. Time to go back to the DOS part and analyze again.

Every time you start the game, it starts with different moves. Even if you beat the game, you can’t know if that was the correct game to beat. Let’s see how the seed is calculated for random moves.

SetRandomSeed   proc near              
                pusha
                mov     di, 953h
                xor     cx, cx
                mov     al, 0
                out     70h, al        
                                     
                in      al, 71h    
                mov     byte_10952, al

loc_108D2:                             
                call    sub_10076 ; Check the moves and fill the buffer
                jz      short loc_10908
                cmp     al, 0Dh
                jz      short loc_10916
                sub     al, 20h ; ' '
                cmp     al, 41h ; 'A'
                jz      short loc_108FB
                cmp     al, 42h ; 'B'
                jz      short loc_108FB
                cmp     ah, 48h ; 'H'
                jz      short loc_108FF
                cmp     ah, 50h ; 'P'
                jz      short loc_108FF
                cmp     ah, 4Bh ; 'K'
                jz      short loc_108FF
                cmp     ah, 4Dh ; 'M'
                jz      short loc_108FF
                jmp     short loc_10908

loc_108FB:                            
                                     
                mov     [di], al
                jmp     short loc_10901

loc_108FF:                            
                                       
                mov     [di], ah

loc_10901:                            
                inc     di
                inc     cx
                cmp     cx, 0Bh
                jz      short loc_10916

loc_10908:                           
                                      
                mov     al, 0
                out     70h, al       
                                      
                in      al, 71h       
                sub     al, byte_10952
                cmp     al, 0Ah
                jl      short loc_108D2

loc_10916:                            
                                     
                mov     cx, 0Ah
                mov     si, offset unk_10953
                mov     di, offset aHhppkmkmba ; "HHPPKMKMBA"
                cld
                repe cmpsb
                jnz     short loc_1093B
                mov     cx, 5
                mov     si, 953h
                xor     ax, ax

loc_1092C:                            
                mov     bx, [si]
                shr     bx, 5
                add     ax, bx
                inc     si
                loop    loc_1092C
                call    sub_10732
                jmp     short loc_1094D
loc_1093B:                          
                xor     bx, bx
                mov     al, 2
                out     70h, al
                                        
                in      al, 71h         
                mov     bl, al
                mov     al, 0
                out     70h, al 
                                        
                in      al, 71h         
                mov     ah, bl

loc_1094D:                              
                call    sub_10094
                popa
                retn
SetRandomSeed   endp

Here is the fun part! As you can see from this code, it checks special movements before you start the game. If those special movements are correct, it jumps to loc_1092C and sets the correct seed. The special move is the sequence of HHPPKMKMBA which is also called as Konami Code. Just do Up, Up, Down, Down, Left, Right, Left, Right, B, A and start the game. If you don’t want to waste time, you can patch the game so that it can play itself. I also removed the code responsible for the preview. After you run the DOS game, it will correctly modify the exe. Running the main exe on Windows will show us the correct flag: Ha$htag_n0_rc4_aNd_Th3n_s0me@flare-on.com

8 AmongRust

Challenge says that this is malware. Therefore, I run this on VM with a Sandboxie-Plus. It infected all the exe files on my user’s folder and created svchost.exe inside AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup for persistence. You can easily decrypt payloads from the main exe by using HcPeterr as the xor key. If we run the server part of the ransomware, we can easily see that it is listening on port 8345 If we open the WireShark packet dump and filter by port tcp.port == 8345, we can easily see the traffic. The client first sends the key and nonce. After that, the client issues a command to get the name of the computer and uploads files to the infected machine

00000000  65 74 21 2c 9b 4d 93 34  d8 93 be c2 47 7c b8 6a   et!,.M.4 ....G|.j
00000010  70 98 3b 3c 33 95 2d 68  a8 cc 5c 02 26 07 0a bf   p.;<3.-h ..\.&...
    00000000  41 43 4b 5f 4b 0d                                  ACK_K.
00000020  0e 02 f4 a9 a8 b5 be ea  ba 83 48 d6 d2 f8 7c 60   ........ ..H...|`
00000030  68 49 df 9a 5e ef 49 a6  5c 98 cf 07 d4 c2 38 a6   hI..^.I. \.....8.
    00000006  41 43 4b 5f 4e 0d                                  ACK_N.
00000040  65 78 65 63 20 77 68 6f  61 6d 69 0d 0a            exec who ami..
    0000000C  64 65 73 6b 74 6f 70 2d  31 63 6d 72 33 71 6c 5c   desktop- 1cmr3ql\
    0000001C  75 73 65 72 0a 0d                                  user..
0000004D  65 78 65 63 20 6d 6b 64  69 72 20 43 3a 5c 55 73   exec mkd ir C:\Us
0000005D  65 72 73 5c 75 73 65 72  5c 41 6d 6f 6e 67 52 75   ers\user \AmongRu
0000006D  73 74 0d 0a                                        st..
    00000022  0d                                                 .
00000071  75 70 6c 6f 61 64 20 43  3a 5c 55 73 65 72 73 5c   upload C :\Users\
00000081  75 73 65 72 5c 41 6d 6f  6e 67 52 75 73 74 5c 77   user\Amo ngRust\w
00000091  61 6c 6c 70 61 70 65 72  2e 50 4e 47 20 31 32 32   allpaper .PNG 122
000000A1  32 31 38 0d 0a                                     218..
    00000023  41 43 4b 5f 55 50 4c 4f  41 44 0d                  ACK_UPLO AD.

Instead of patching exe, and dealing with files, let’s write a client code and emulate the communication. Dump the file from wireshark dump and save it as wall.png next to the below script. Running the below script will automatically create the decrypted wallpaper in a given directory.

import socket
import os

print("[+] Connecting to the server")
server_address = ('localhost', 8345)

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(server_address)
print("[+] Connection successful!")

key = bytes.fromhex('6574212c9b4d9334d893bec2477cb86a70983b3c33952d68a8cc5c0226070abf')
nonce = bytes.fromhex('0e02f4a9a8b5beeaba8348d6d2f87c606849df9a5eef49a65c98cf07d4c238a6')

client_socket.send(key)
ack_k = client_socket.recv(12)
print("[+] Key exchange result %s" % ack_k)

client_socket.send(nonce)
ack_n = client_socket.recv(12)
print("[+] Nonce exchange result %s" % ack_n)

upload_cmd = b'upload C:\output\wall.png 122218\x0D\x0A'
client_socket.send(upload_cmd)
print("[+] Upload cmd sent")
upload_result = client_socket.recv(12)
print("[+] Upload cmd result %s" % upload_result)

file_path = "wall.png"
data = None

try:
    with open(file_path, 'rb') as file:
        data = file.read()
        client_socket.send(data)
        upload_result = client_socket.recv(12)
        print("[+] Upload binary result %s", upload_result)
except FileNotFoundError:
    print(f"File '{file_path}' not found.")
except Exception as e:
    print(f"An error occurred: {str(e)}")

client_socket.close()

Decrypted PNG will reveal our flag: n0T_SuS_4t_aLl@flare-on.com

9 MBRansom

This one was the most fun challenge for me. When you run this disk inside an emulator, you’re greeted with a ransomware message. You’re supposed to write the correct key so that it will decrypt the disk. You can use Bochs to debug MBR code. It first decrypts itself gets the HD serial and finally jumps to the 1000 address. I run this on Bochs so addresses could be different. Finally, it checks your serial with the following code

seg000:1296                 push    si
seg000:1297                 push    di
seg000:1298                 sub     sp, 8
seg000:129B                 mov     si, offset user_key
seg000:129E                 mov     di, offset key_table
seg000:12A1                 mov     cx, 804h
seg000:12A4
seg000:12A4 loc_12A4:                            
seg000:12A4                 lodsw
seg000:12A5                 shl     al, cl
seg000:12A7                 or      al, ah
seg000:12A9                 stosb
seg000:12AA                 dec     ch
seg000:12AC                 jnz     short loc_12A4
seg000:12AE                 mov     si, offset key_table
seg000:12B1                 mov     di, offset hd_serial
seg000:12B4                 dec     cx
seg000:12B5
seg000:12B5 loc_12B5:                            
seg000:12B5                 lodsw
seg000:12B6                 xor     ax, [di]
seg000:12B8                 inc     di
seg000:12B9                 inc     di
seg000:12BA                 cmp     ax, 5555h
seg000:12BD                 jnz     short loc_1304

Basically, it xors your serial with HD serial and checks if the result is 5555h for each word. It looks so easy. Let’s write a basic code for that

hd_serial = bytes([0x34, 0x87, 0xB3, 0xB4, 0x1F, 0x20])

result = bytearray()
for i in range(0, len(hd_serial), 2):
    word = (hd_serial[i + 1] << 8) | hd_serial[i]
    word ^= 0x5555
    xor_bytes = bytearray([word & 0xFF, (word >> 8) & 0xFF])
    result.extend(xor_bytes)
    
hex_result = "".join(["{:02X}".format(byte) for byte in result])
print("Partial Key:", hex_result)

This code gives only the partial key. We need to find 4 more chars to correctly decrypt the disk. We need a way to brute force it. It is cumbersome to convert 16-bit code to run it on modern machines. The key check routine is below

seg000:11FB                 push    dx
seg000:11FC                 call    sub_1296
seg000:11FF                 pop     dx
seg000:1200                 test    ax, ax
seg000:1202                 jnz     short loc_1205

If the key is wrong, ax returns the offset of the error message. The correct key will give us 0 in the ax register.

I decided to try something different. I dumped the MBR code after it correctly decrypted itself and put the HD serial.

I used unicorn to emulate the environment and bruteforce it. Here is the script that solves the challenge.

from unicorn import *
from unicorn.x86_const import *
import os

mu = Uc(UC_ARCH_X86, UC_MODE_16)

if __name__ == "__main__":

    image_path = os.path.join(os.path.dirname(__file__), "rawimage.bin") # dumped MBR code
    with open(image_path, "rb") as f:
        file_data = f.read()

    hd_serial = bytes([0x34, 0x87, 0xB3, 0xB4, 0x1F, 0x20]) # hd serial

    result = bytearray()
    for i in range(0, len(hd_serial), 2):
        word = (hd_serial[i + 1] << 8) | hd_serial[i]
        word ^= 0x5555
        xor_bytes = bytearray([word & 0xFF, (word >> 8) & 0xFF])
        result.extend(xor_bytes)

    partial_serial = "".join(["{:02X}".format(byte) for byte in result])
    print("Partial Key:", partial_serial)
    byte_serial = b''

    for char in partial_serial:
        byte_value = int(char, 16)
        byte_serial += bytes([byte_value])

    img_base = 0x0600 
    entry_point = 0x11FB

# seg000:11FB                 push    dx
# seg000:11FC                 call    sub_1296 ; check serial
# seg000:11FF                 pop     dx
# seg000:1200                 test    ax, ax

    try:
        # Initialize CPU emulator
        # Write image to the emulator's memory
        mem_size = 0x10000
        mu.mem_map(0, mem_size)
        mu.mem_write(img_base, file_data) # Write MBR
        
        mu.mem_write(0x2A4C,byte_serial) # Partial Key
        mu.mem_write(0x19FC,hd_serial) # HD Serial
        print("Bruteforcing the last 4 chars...")        

        for value in range(65536):
            byte1 = (value >> 12) & 0x0F 
            byte2 = (value >> 8) & 0x0F
            byte3 = (value >> 4) & 0x0F
            byte4 = value & 0x0F
            byte_array = bytes([byte1, byte2, byte3, byte4])

            mu.mem_write(0x2A58, byte_array)
            mu.reg_write(UC_X86_REG_DX, 0x1224)
            # run the serial check routine
            mu.emu_start(entry_point, 0x1200)
            ax_reg = mu.reg_read(UC_X86_REG_AX)
            if (ax_reg == 0): # if ax == 0x18fb serial is wrong.
                # Serial is correct!
                result = byte_serial + byte_array
                hex_string = ''.join(f'{byte:X}' for byte in result)
                print("Full Key: %s" % hex_string)
                exit(0)
        print("Emulation done")
    except UcError as e:
        print("ERROR: %s" % e)

Running this script gave me 61D2E6E14A754ADC as the correct key. When we put our key and hit Enter, it correctly decrypted the disk and booted into FreeDOS. Typing type flag.txt shows us the flag: bl0wf1$h_3ncrypt10n_0f_p@rt1t10n_1n_r3al_m0d3@flare-on.com

You can get all the source codes of this blog post from this repo

I am available for new work
Interested? Feel free to reach