6th April, 2024

Zero Days CTF (2024) RE - 4

Dive into the intriguing world of binary analysis with the fourth challenge, "acup.exe". Discover the secrets hidden within this packed executable file, as we unravel its obfuscated code and delve into its cryptographic algorithms. Through meticulous examination, we uncover the ingenious methods employed to conceal its true purpose. However, the journey doesn't end there - further exploration reveals the key to decrypting the flag, awaiting those who dare to venture into the depths of its intricacies.
ctf image

The fourth challenge is named ‘acup.exe’.

2024 04 05_18h09_11

Figure 1: Illustrates the executable file ‘acup.exe

Upon analysis with Detect-It-Easy, it is revealed that the file is packed using PyInstaller.

2024 04 05_18h21_21

Figure 2: Analysis with Detect-It-Easy reveals the utilization of PyInstaller

PyInstaller is a popular tool used to convert Python scripts into standalone executable files. It bundles the Python interpreter and all necessary dependencies into a single executable, making it easier to distribute Python applications to users who do not have Python installed.

To unpack and retrieve the compiled Python bytecode, pyinstxtractor.py available here, is utilized.

After downloading and executing the pyinstxtractor.py file according to the provided readme instructions, probable entry points and extracted Python bytecode files are obtained.

2024 04 05_18h30_18

Figure 3: Output of pyinstxtractor.py displaying potential entry points

2024 04 05_18h21_21%20%281%29

Figure 4: Directory generated by pyinstxtractor.py

2024 04 05_18h21_21%20%282%29

Figure 5: Python bytecode (.pyc) file extracted by pyinstxtractor.py

We analyze the generated acup.pyc file, a probable entry point, using the same method as described in ZeroDays CTF 2024 RE - 1, employing pycdc.exe.

2024 04 06_00h14_05%20%284%29

Figure 6: Represents the file selected for analysis

2024 04 06_00h14_05%20%285%29

Figure 7: Illustrates the decompilation output of the ‘acup.pyc’ file using pycdc.exe

The generated output is provided below.

# Source Generated with Decompyle++
# File: acup.pyc (Python 3.10)

Warning: Stack history is not empty!
Warning: block stack is not empty!
import base64
import time
from tkinter import Tk as xg
from arc4 import ARC4 as pe

def ebg46(x):
    d = abs(13) * -1
    c = ''.join((lambda .0: [ chr(97 + i) for i in .0 ])(range(26)))
    t = c[d:] + c[:d]

    rc = lambda z = None: if c.find(z) > -1:
t[c.find(z)]
    return None((lambda .0 = None: for c in .0:
rc(c))(x))

gbbe = xg()
gbbe.withdraw()
zhgnq = gbbe.clipboard_get()
if ebg46(zhgnq.lower()) != 'zvfpuvrs_znantrq':
    time.sleep(5)
    zhgnq = gbbe.clipboard_get()
    if not ebg46(zhgnq.lower()) != 'zvfpuvrs_znantrq':
        rfno = base64.b64encode(str.encode(zhgnq.lower()))
        rfno = rfno.decode('utf-8')
        k = str.encode(''.join([
            rfno[7],
            rfno[15],
            chr(ord(rfno[0]) + 1),
            rfno[-3].lower()]))
        _pe = pe(k)
        erucvp = '5465f059dbae33e458a46ae1fd8f8b89b13f2429d62a132546a4787d6f1c58b5a36cc19fcdce577a7a66'
        prq = _pe.decrypt(bytes.fromhex(erucvp))
        print(prq.decode('utf-8'))
        return None

The code employs obfuscation techniques such as unintelligible variable names, base64 encoding, and decryption logic to unveil the final flag.

To comprehend the provided code, we'll dissect it section by section and examine the following code.

import base64
import time
from tkinter import Tk as xg

gbbe = xg()
gbbe.withdraw()
zhgnq = gbbe.clipboard_get()
if ebg46(zhgnq.lower()) != 'zvfpuvrs_znantrq':
    time.sleep(5)
    zhgnq = gbbe.clipboard_get()
    if not ebg46(zhgnq.lower()) != 'zvfpuvrs_znantrq':
        rfno = base64.b64encode(str.encode(zhgnq.lower()))
        rfno = rfno.decode('utf-8')
        k = str.encode(''.join([
            rfno[7],
            rfno[15],
            chr(ord(rfno[0]) + 1),
            rfno[-3].lower()]))

The code retrieves clipboard data, ideally input by the user, processes it through the ebg46() function, and compares its lowercase version to "zvfpuvrs_znantrq". If unequal, the program sleeps and retrieves clipboard data again, repeating the process once again to check if they match. If matched, the variable rfno is created by converting the lowercase clipboard data to bytes, base64 encoding it, and then UTF-8 decoding it. Subsequently, variable k is formed using parts of the rfno variable.

Let's examine the ebg46() function in detail.

def ebg46(x):
    d = abs(13) * -1
    c = ''.join((lambda .0: [ chr(97 + i) for i in .0 ])(range(26)))
    t = c[d:] + c[:d]

    rc = lambda z = None: if c.find(z) > -1:
t[c.find(z)]
    return None((lambda .0 = None: for c in .0:
rc(c))(x))

The given function, ebg46(), appears to be a simple Caesar cipher implementation with a fixed shift of 13 characters (commonly known as ROT13). It first generates a lowercase alphabet string c using a lambda function and shifts it by 13 characters to create the cipher key t. Then, it defines a lambda function rc to perform the character substitution based on the shift. Finally, it applies the substitution to each character in the input string x using another lambda function and returns the resulting string. 

Based on the observed pattern, it can be inferred that applying the ROT13 cipher to the expected output "zvfpuvrs_znantrq" results in the original string. This is due to the property of ROT13, where applying the cipher twice (ROT13(ROT13(x))) returns the original input string x.

Utilizing CyberChef, we can execute the operation on the expected string, “zvfpuvrs_znantrq”, to obtain the input string required for the second condition to match. However, since there is no scenario where both if conditions in the previous code are met, this challenge cannot be resolved by executing the program. Instead, it can only be resolved through static analysis of the code.

2024 04 06_00h14_05%20%286%29

Figure 8: Illustrates the expected input string for the second condition in the code

As displayed above, the expected input for the second condition to match is "mischief_managed".

Now, let's comprehend the final section of the code.

import base64
import time
from tkinter import Tk as xg
from arc4 import ARC4 as pe

       rfno = base64.b64encode(str.encode(zhgnq.lower()))
       rfno = rfno.decode('utf-8')
       k = str.encode(''.join([
            rfno[7],
            rfno[15],
            chr(ord(rfno[0]) + 1),
            rfno[-3].lower()]))
        _pe = pe(k)
        erucvp = '5465f059dbae33e458a46ae1fd8f8b89b13f2429d62a132546a4787d6f1c58b5a36cc19fcdce577a7a66'
        prq = _pe.decrypt(bytes.fromhex(erucvp))
        print(prq.decode('utf-8'))
        return None

Firstly, let's calculate the value of the variable rfno, which is "bWlzY2hpZWZfbWFuYWdlZA==".

2024 04 06_00h14_05%20%287%29

Figure 9: Illustrates the base64 encoding of the expected input string

Now, the variable k can be generated, resulting in “puca”.

To understand the arc4 import and its usage, we examine an example of its usage, sourced from here.

The arc4 package is a Python implementation of the ARCFOUR (RC4) cipher, prized for its compactness and exceptional speed, as indicated on the referenced page above.

>>> from arc4 import ARC4
>>> arc4 = ARC4(b'key')
>>> cipher = arc4.encrypt(b'some plain text to encrypt')

Upon comparing this with the preceding code, it's evident that the variable k is utilized as the key. Next, the variable erucvp is converted from hexadecimal, decrypted using RC4, and decoded as UTF-8 to form the flag string.

These operations can be replicated in CyberChef to manually retrieve the flag.

2024 04 06_00h14_05%20%288%29

Figure 10: Displays the output flag

I have also written a Python script to generate the decoded text utilized to form the key and the key itself.

This results in obtaining the flag. The acquired flag is as follows -

import base64

def decode_caesar_cipher(ciphertext, shift):
    plaintext = ""
    for char in ciphertext:
        if char.isalpha():
            shifted_char = chr(((ord(char.lower()) - ord('a') - shift) % 26) + ord('a'))
            if char.isupper():
                shifted_char = shifted_char.upper()
            plaintext += shifted_char
        else:
            plaintext += char
    return plaintext

ciphertext = "zvfpuvrs_znantrq"
shift_amount = 13
original_text = decode_caesar_cipher(ciphertext, shift_amount)
print("Decoded text:", original_text)

zhgnq = original_text
rfno = base64.b64encode(str.encode(zhgnq.lower()))
rfno = rfno.decode('utf-8')
k = str.encode(''.join([
            rfno[7],
            rfno[15],
            chr(ord(rfno[0]) + 1),
            rfno[-3].lower()]))
print(k)

The acquired flag is as follows -

Flag - zerodays{lord_what_fools_these_mortals_be}
contact
logo
Custom HTML here.