Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Solutions for BUAACTF2023 Challenges

Notes May 7 3

Miscellaneous

Which Element

A PCAPNG file named Element.pcapng was provided. Extracting a TCP stream revealed four files. The password hint led to a Hexahue cipher decoder. The decoded password 3.1415 unlocked flag.zip, containing three files: flag1.png, flag2.png, and hint.txt. The file sizes suggested a blind watermark. Using a Python 2 script with the --oldseed parameter on the images extracted the flag.

chatgpt

This challenge required solving a SHA256 proof-of-work and answering a series of questions about CTF events. After five correct answers, a partial flag was revealed: BUAACTF{C0Ngr@~tuLati0N5_Y0u_R~. Two additional paths were then acessible:

  • /pss: Exploiting a negative integer input in a betting game.
  • /hard-pss: Using a complex number input (-10000000000j). The complete flag was: BUAACTF{C0Ngr@~tuLati0N5_Y0u_R~4~r3a1_MA5Ter_of_0ur_s1llY_CH@tgpT__5_L0vvvvv3_fr0m_M1sc!}

zhuzhu

Four files were provided. Using the enc1.m MATLAB script, the image data from gift4you was displayed with imshow(W) to reveal the flag.

zhuzhu's revenge

The gift file lacked image data. The enc2 encryption was reversed using AES-CBC decryption with a known key. The IV was unknown, but the PNG header was repaired. The resulting wm.png was then processed by reversing the enc1.m operations (SVD and DWT) to reconstruct the original image and obtain the flag.

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from Crypto.Util.number import long_to_bytes
import os

with open('enc.png', 'rb') as f:
    encrypted = f.read()
key_val = 0x839fff08948936e7d78c5105dde1e95a
cipher_key = long_to_bytes(key_val)
cipher = AES.new(cipher_key, AES.MODE_CBC, iv=bytes(16))
decrypted = unpad(cipher.decrypt(encrypted), AES.block_size)
with open('wm.png', 'wb') as f:
    f.write(decrypted)

Screenshot

An image of a social media post was given. The platform was identified as Twitter. The user's GitHub repository was examined, focusing on commit history around April 19. An image hosting link (smms.app) found in the code led to the flag.

carzymaze

A 750x750 matrix was provided. The goal was to find a path moving only right, up-right, or down-right that summed to a target value within 1 second. A hybrid approach was used: dynamic programming to compute maximum sums for the first N columns, then backtracking from the N-th column to match the exact target. The solution involved a proof-of-work bypass and sending the computed path.

import numpy

def find_path(matrix, start_col, target_sum, start_row):
    rows = len(matrix[0])
    cols = len(matrix)
    def search(col, row, current_sum, path):
        if col == cols - 1:
            if current_sum == target_sum:
                return path
            return None
        for offset in (-1, 0, 1):
            next_row = row + offset
            if 0 <= next_row < rows:
                result = search(col + 1, next_row, current_sum + matrix[col + 1][next_row], path + [next_row])
                if result is not None:
                    return result
        return None
    return search(start_col, start_row, matrix[start_col][start_row], [start_row])

# Preprocess matrix with DP for first 744 columns
for c in range(1, 744):
    for r in range(750):
        if r == 0:
            neighbors = [matrix[c-1][r], matrix[c-1][r+1]]
        elif r == 749:
            neighbors = [matrix[c-1][r-1], matrix[c-1][r]]
        else:
            neighbors = [matrix[c-1][r-1], matrix[c-1][r], matrix[c-1][r+1]]
        matrix[c][r] += max(neighbors)

MC

A memory dump (system.raw) and a text file were analyzed with Volatility. Notable processes included notepad, mspaint, and VeraCrypt.exe. Dumping the mspaint process and viewing it as an image revealed a reversed string matching hint.txt. The hash 8a24367a1f46c141048752f2d5bbd14b was cracked to P@ssw0rd!, which decrypted a VeraCrypt volume. Inside, a corrupted traffic.pcapng contained UDP streams on ports 12345 and 12346. Data from these streams was reassembled into a ZIP file. The PCAPNG's trailing data, when reversed, was recognized as mouse movement macros. Playing them back in a macro recorder revealed a password. This password unlocked a final ZIP containing a MaxiCode barcode. After correcting the central bullseye pattern, the barcode decoded to the flag.

Cryptography

Block Cipher

A simple XOR-based block cipher was used. Given the IV, key, and ciphertext blocks, decryption involved XORing each block with the previous ciphertext (or IV for the first block) and the key.

from functools import reduce
import operator

def xor_bytes(a, b):
    return bytes(x ^ y for x, y in zip(a, b))

iv = b'\xba=y\xa3\xc6)\xcf\xf7'
key = b'}6E\xeb(\x91\x08\xa0'
parts = [b'\x85^}\t\xad\xec\x81,', b'\xba\x04W\xa1\xee"\xea\xc5', b'\xb7ZW\x18\x99\x82\xd6:', b'\x99\x03}\x9c\xde|\xb1\xc5', b'\xa1Tk.\x8b\xee\xaf']

def decrypt(parts, iv, key):
    plaintext = []
    prev = iv
    for block in parts:
        plain = xor_bytes(xor_bytes(block, prev), key)
        plaintext.append(plain)
        prev = block
    return b''.join(plaintext)

result = decrypt(parts, iv, key)
print(result.decode())

Math

Three RSA challenges with side-channel leaks:

  1. Challenge 1: Leak = 2p + k(q-1). Brute-forcing k allowed solving for p and q.
  2. Challenge 2: Leak = d + p + q. Using the equation (e - k)φ(n) = e(n - leak + 1) + 1, k was brute-forced to recover φ(n).
  3. Challenge 3: Leak = (p^q + q^p) mod n. It was found that leak = p + q. Solving the system p*q = n and p + q = leak yielded the factors.

KeyExchange

A key exchange based on a Linear Congruential Generator (LCG) was implemented. Given parameters p, s = a*b, public keys pk_a, pk_b, and a leak leak = a^5 - b^3, the private values were recovered. Using SageMath to solve x^8 - s^3 - leak*x^3 = 0 mod p yielded a. Then b = s / a. The shared secret was computed and used to derive an AES key for decrypting the flag.

from Crypto.Cipher import AES
from hashlib import sha256
import gmpy2

# Given values
p = 110045941581246566163852236169863726493322518614291259738650243772537777220447239564993293840178002270803358414801874876416861655681706091960526864981702297311975108477231430850046449802619712390151394990272883738498246287828012579651055043742174921894177086628278972268977919519134723993840866617728971125501
s = 103083435269546339253719292770367969157635923310796947624819807524514689714230038488629636887588073083408362510426148038690685619752778205013088349018307009939108678198113606930036410404549808586987764543060403391315169316818289657581541763263327670573813529568272160960214773113382466107788108424376942305179
pk_a = 104249886397359747249273092453488313866919664956033370839273165940023672119280749446507600277914299719461300855987124305389892009846065290488788845690592952592538823773157304003987554741970547804984472643400001801640946933980112156093413786073659705669257814748840719625590155866880068232234070029543099326008
pk_b = 73943176009325878591909652784371325868093784459904155452171082544674415915957790552228923092972996591011414909913381038800791214834557764389213534028507829053436031472854955231610581309098048200856194242071844838535785588222371659431886892907007805510914868507217421041100993332508303713741693778352086616092
leak = 29040777564893836055576935754958349303880224434489606637225123765874859084299305072404829973337935904266301029232783978449958360875461566254630838251549934932706914467564998782070045758886430608783726619661530870388938391442492043731894115397400015449950051951769344001061013880264004181568996494392695873947102851715933737547680797285706746028093745905399081868517548754953070086517995947839613006470389399208171194194504365074914947024889949570680226668773867669172700292722732348438746947857754535156207414199788546164281701388891687436649862876851080136461979528906440369488791240880833837652254029174364644864043838626162283607643013213942931346399529512644954169730170267869381208558883655869400666658812252861841021468606579935712846111089021383151894750921351386
iv = bytes.fromhex('e759b23de290a6d4ea11dab4b0c306f3')
ciphertext = b'\xa8\x83\x8b,\x14\x1ad\xfb\xfd\x12\xc6\xc1=\xa0+k\x05\xc7\xc9\xed\xde\xca>\x04\xe1;\xcc\xc1\xb6\x9a+\xe66d\xcf\x9b\xfftp\x81\xab\xbf(\x02\xcd\x0b\xa4\xc0Lpg\x83F\x82l\xad2\xdcI\x1ab\x08\x88\n'

# Solve for a (from SageMath output)
a = 7809111936301730050325531123556941524419043237553061253494514954511014517669231983792610985652302681729865258538952534506348431869920157739027505606648803
b = s // a

# Compute shared secret
ap = (gmpy2.invert((a - 1) * s + b, p) * ((a - 1) * pk_b + b)) % p
shared_secret = (ap * pk_a + gmpy2.invert(a - 1, p) * (ap - 1) * b) % p

# Derive AES key
key_material = str(shared_secret + s).encode()
key = sha256(key_material).digest()[:16]

# Decrypt
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
plaintext = cipher.decrypt(ciphertext)
print(plaintext)

Web

mota

A web-based game where the flag was hidden within the JavaScript source. Examining Events.js revealed a Base64-encoded string and an array of character codes. Decoding and concatenating them produced the flag.

import base64

partial = "NV9tb3RhXzFz"
decoded = base64.b64decode(partial).decode()
codes = [95, 115, 48, 95, 102, 117, 110, 33, 125]
flag = "BUAACTF{HT" + decoded + ''.join(chr(c) for c in codes)
print(flag)

Reverse Engineering

oneQuiz's revenge

A Java-based Android APK contained a maze navigation game. Reverse engineering with GDA revealed the maze logic. The solution path was: VaaaVaVVaaaaaVVVVAAJJAAVVVAVVaaaaaJa.

Snake

A classic Snake game. The score was directly modified using Cheat Engine to reach the target value and trigger the flag.

Minesweep

The game automatically swept the board. The core logic involved a predefined minefield matrix and an encryption function. The matrix was processed to compute neighbor counts, then transformed. The encryption XORed the flag characters with the processed matrix. A brute-force approach recovered the flag: dmzsw_Ts_nice!.

obfu

A heavily obfuscated JavaScript challenge. Debugging in the browser console revealed the logic: the input flag was Base64 encoded, each byte was XORed with the previous one, and the result was compared against a hardcoded array. Reversing this process yielded the flag.

import base64

encrypted = [81, 61, 107, 41, 120, 45, 99, 54, 100, 10, 126, 53, 123, 51, 106, 90, 57, 11, 69, 60, 113, 41, 107, 91, 3, 49, 1, 49, 80, 42, 100, 2, 96, 52, 122, 28, 69, 118, 63, 15, 106, 4, 111, 7, 97, 48, 13, 48]

# Reverse XOR chain
decoded = [encrypted[0]]
for i in range(1, len(encrypted)):
    decoded.append(encrypted[i] ^ encrypted[i-1])

# Convert to string and decode Base64
bytes_data = bytes(decoded)
flag = base64.b64decode(bytes_data).decode()
print(flag)

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.