Skip to main content
FAZer0
←cd ../writeups
reversemedium
🏆 500 PTS

Arkavidia 10.0 - NeuralCipher Walkthrough

Reversing a linear algebraic neural network cipher by computing matrix inversion modulo 256 using Gauss-Jordan elimination.

EventArkavidia 10.0 CTF (Penyisihan)
PublishedFebruary 1, 2026
Estimated Read3 min read
ToolsPython 3, NumPy, PyLingual

1. Challenge Overview

Challenge Name: NeuralCipher
Author: sandwicheese
Category: Reverse Engineering
Team: KataIrfanNamaTimnyaPranowo (Fazer0, moe-shinobi-kyuun~~)
Date: 1 Februari 2026

We intercepted a leaked experimental AI kernel from a shadowy research lab. They claim it’s a “neural network cipher” - but something feels… reversible. Can you recover the original input from the encrypted output?

The distribution package dist.zip contains three key components:

  1. output: Text file containing 52 numeric integer values.
  2. weights: Serialized NumPy array with shape (52, 52) and dtype <i4.
  3. chall.pyc: Obfuscated and compressed Python bytecode.

2. Mathematical Modeling & Analysis

Decompressing and inspecting chall.pyc reveals that the transformation is a classic Linear Transformation Cipher disguised as a neural network dense layer without non-linear activation:

y = (W · x) mod p

Where:

  • x is the flag vector (input characters as ASCII byte values, length 52).
  • W is the weight matrix of dimensions 52 x 52.
  • y is the given output vector of length 52.
  • p is the modular base (256).

Because the output numbers fall within the range [0, 255], and since the determinant det(W) mod 256 is coprime to 256 (an odd integer), the weight matrix W is strictly invertible in the ring Z/256Z.

Therefore, we can invert the cipher directly:

x = (W^-1 · y) mod 256

3. Exploit / Solver Script

We implement Gauss-Jordan elimination with modular arithmetic over modulo 256:

import numpy as np

# 1. Load weights matrix (52x52)
with open('weights', 'rb') as f:
    f.read(128)  # Skip standard NumPy header
    weights = np.frombuffer(f.read(), dtype=np.int32).reshape(52, 52)

# 2. Load output vector (52 elements)
with open('output', 'r') as f:
    output = np.array([int(line.strip()) for line in f if line.strip()])

def mod_inv_matrix(matrix: np.ndarray, mod: int) -> np.ndarray:
    n = matrix.shape[0]
    augmented = np.hstack([matrix % mod, np.eye(n, dtype=np.int64)])

    for i in range(n):
        # Find pivot
        pivot = -1
        for j in range(i, n):
            if augmented[j, i] % 2 != 0:  # Must be odd for modular inverse mod 256
                pivot = j
                break
        if pivot == -1:
            raise ValueError(f"Matrix is not invertible mod {mod}")

        # Swap rows
        augmented[[i, pivot]] = augmented[[pivot, i]]

        # Normalize with modular inverse
        inv = pow(int(augmented[i, i]), -1, mod)
        augmented[i] = (augmented[i] * inv) % mod

        # Gauss-Jordan elimination
        for j in range(n):
            if j != i:
                factor = augmented[j, i]
                augmented[j] = (augmented[j] - factor * augmented[i]) % mod

    return augmented[:, n:] % mod

# 3. Decrypt the Flag
mod = 256
W_inv = mod_inv_matrix(weights, mod)
input_vec = np.dot(W_inv, output) % mod
flag = ''.join([chr(int(x)) for x in input_vec])

print(f"[+] Recovered Flag: {flag}")

4. Flag

ℹ️Flag Capture
Challenge Flag
Click to Decrypt