It's Not My Fault 2 picoCTF 2021 Solution

Published: April 2, 2026

Description

Another SGX challenge with additional complexity. Analyze the algorithm deeply and recover the flag.

Download the provided files.

bash
wget https://mercury.picoctf.net/static/.../fault2.tar.gz && tar xvf fault2.tar.gz
Same simulated fault model as part 1 (instruction skip). The GDB for CTF guide covers the breakpoint + jump primitive used to bypass each stage.
  1. Step 1Map the multi-stage check chain
    Disassemble the verifier and trace the call graph: stage_1 -> stage_2 -> ... -> print_flag. Each stage is a separate gate that must fall in order; skipping a later stage early jumps over uninitialised state. See It's Not My Fault Part 1 for the single-stage baseline.
    bash
    objdump -d fault2 | grep -A2 -E 'call.*stage|jne|je '
    bash
    # Or in GDB: info functions stage
    bash
    # (gdb) rbreak ^stage
    Learn more

    Same fault taxonomy as part 1: bit-flip, voltage/clock glitch, instruction skip. The challenge again simulates instruction skip - the only physically-glitchable primitive that maps cleanly to a CTF box. What changes in part 2 is that there are multiple checks in series, each with its own failure side effect.

    Order matters because of state. Stage i usually decrypts material that stage i+1 consumes. If you skip stage_2's validation but stage_3 reads the buffer stage_2 was supposed to fill, you'll get garbage. List the stages in source order, glitch only the verify branches, and let the body of each stage run normally so its side effects propagate.

  2. Step 2Analyze the file structure
    Examine all provided files. Compare to the Part 1 challenge - this version likely adds encryption layers, multiple rounds, or a more complex key schedule.
    bash
    ls -la
    bash
    file *
    bash
    cat *.py 2>/dev/null
    bash
    cat *.c 2>/dev/null
    bash
    diff <(ls fault1/) <(ls fault2/) 2>/dev/null  # If both are available
    Learn more

    Part 2 SGX challenges typically escalate from Part 1 in one of these ways:

    • Multi-round cipher: The same transformation is applied multiple times with different round keys derived from a key schedule.
    • Compound algorithm: Multiple different ciphers applied in sequence (e.g., AES then XOR then custom substitution).
    • Key derivation complexity: The key is not hardcoded but derived from a password or time value using a complex algorithm.
    • Fault injection simulation: The challenge simulates Intel SGX fault injection attacks, where an attacker induces computation errors to leak secret key bits.
  3. Step 3Reverse the encryption algorithm
    Trace through the algorithm carefully. For multi-round ciphers, implement all rounds in order, then reverse each round's operations in reverse order for decryption.
    python
    python3 - <<'EOF'
    # Reverse the Part 2 SGX algorithm
    
    ciphertext = bytes.fromhex('PASTE_CIPHERTEXT_HERE')
    
    # Implement algorithm in forward direction first to verify understanding:
    def encrypt(plaintext, key):
        # ... implement forward algorithm ...
        return ciphertext
    
    # Then implement decryption (inverse):
    def decrypt(ciphertext, key):
        # Apply inverse operations in reverse order
        # For each round (in reverse):
        #   undo_round_operation(state)
        return plaintext
    
    key = b'...'  # Extract from binary or derive
    flag = decrypt(ciphertext, key)
    print(flag.decode('ascii', errors='replace'))
    EOF
    Learn more

    Block cipher decryption. For a block cipher with N rounds, decryption applies the inverse of round N's operations, then round N-1's, back to round 1. If the round is SubBytes -> ShiftRows -> MixColumns -> AddRoundKey (AES-style), the inverse round is InvAddRoundKey -> InvMixColumns -> InvShiftRows -> InvSubBytes. Order reverses and each step is replaced by its inverse.

    Worked toy example: 3-round Feistel. Plaintext (L0, R0), round function F, round keys k1, k2, k3. Encryption:

    Round 1:  L1 = R0,        R1 = L0 XOR F(R0, k1)
    Round 2:  L2 = R1,        R2 = L1 XOR F(R1, k2)
    Round 3:  L3 = R2,        R3 = L2 XOR F(R2, k3)
    Output:   C = (L3, R3)
    
    Decryption (reverse, same F):
    Round 3':  R2 = L3,       L2 = R3 XOR F(L3, k3)
    Round 2':  R1 = L2,       L1 = R2 XOR F(L2, k2)
    Round 1':  R0 = L1,       L0 = R1 XOR F(L1, k1)
    Output:    P = (L0, R0)   ✓

    Note that Feistel structures use the same round function in both directions - only the order of round keys reverses. AES uses a non-Feistel SPN structure, so each operation needs an explicit inverse (InvSubBytes uses an inverse S-box, InvMixColumns uses a different matrix), but the principle is identical.

    Fault injection / differential fault analysis (DFA). If the challenge simulates SGX fault injection: induce a fault during round N-1 (e.g., flip one byte of state), capture both the correct ciphertext C and the faulty ciphertext C'. The XOR difference C XOR C' propagates through the final round, leaking constraints on the last round key. Piret-Quisquater (2003) showed two faulty AES ciphertexts are typically enough to recover the full 128-bit key. The DFA equations look like: for each candidate last-round key byte k, check if InvSubBytes(C XOR k) XOR InvSubBytes(C' XOR k) matches an expected single-byte fault pattern after InvShiftRows. Only the correct k byte makes that check pass for both fault pairs.

Flag

picoCTF{...}

Part 2 SGX adds algorithm complexity - carefully implement the full multi-round cipher in forward direction first, then build the decryption by reversing each round's operations in reverse order.

Want more picoCTF 2021 writeups?

Useful tools for Cryptography

What to try next