# Pragyan CTF 2025 - TinyBunnie
**Title:** TinyBunnie
**Description:**
I have been working on my very own blockchain , seems like i got it working. Took a lot of inspiration from microsoft as well , just to make sure things dont go wrong :)
**Files:**
[tiny.py](https://ctf.prgy.in/files/b4b7571c719ddc5cb2f0830945e5f392/tiny.py?token=eyJ1c2VyX2lkIjo1MDksInRlYW1faWQiOjMwNiwiZmlsZV9pZCI6MzJ9.Z6hNRQ.Zrm7I2aWvC8f7h8i0q73F9938NM), [custom_block.py](https://ctf.prgy.in/files/668c83b8ee79cf19eb1d8f3fa72c4ab9/custom_block.py?token=eyJ1c2VyX2lkIjo1MDksInRlYW1faWQiOjMwNiwiZmlsZV9pZCI6MzN9.Z6hNRQ.u112-CzgUGPdfj6O40mcLBwl0lY)
## Solution:
The challenge centers on a modified version of the [TEA](https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm) cipher. Here’s what we found upon examining the source:
1. **TEA’s Key Structure and Rounds:**
The cipher in `tiny.py` splits a 16‑byte key into four 32‑bit words and runs 32 rounds of operations. Each round involves additions modulo 2³², bit shifts, and XOR operations.
2. **The Equivalent-Keys Property:**
A critical observation is that addition modulo 2³² and XOR interact in a way that, for any 32‑bit word `x`, the following holds:
```
x + 0x80000000 (mod 2^32) == x XOR 0x80000000
```
Thus, if you have a key:
```
K = (K₀, K₁, K₂, K₃)
```
you can derive an equivalent key by XORing each 32‑bit word with `0x80000000`:
```
K' = (K₀ ⊕ 0x80000000, K₁ ⊕ 0x80000000, K₂ ⊕ 0x80000000, K₃ ⊕ 0x80000000)
```
Although `K` and `K'` are different as byte strings, the encryption rounds process them identically, yielding the same ciphertext. This is exactly what the server checks: if encrypting a given block with PoW₁ and PoW₂ produces identical outputs, the block is “validated.”
3. **Avoiding Reuse:**
The server also rejects a key pair if it has already been used (in either order). To overcome this, we must generate a fresh equivalent key pair for every block. One easy method is to use a counter as a seed, converting it to a unique 16‑byte PoW₁ and then deriving its equivalent PoW₂.
The following script automates this process. It connects to the remote server, reads the block challenge, and for each block generates a new unique PoW pair using the equivalent-key trick:
```python
from pwn import remote
def compute_equiv_pair(seed):
pow1 = seed.to_bytes(16, 'big')
pow2 = b''.join((int.from_bytes(pow1[i:i+4], 'big') ^ 0x80000000).to_bytes(4, 'big') for i in range(0, 16, 4))
return pow1.hex(), pow2.hex()
def main():
r = remote("block.ctf.prgy.in", 1337, ssl=True)
seed = 0
while True:
try:
if not (line := r.recvline(timeout=5)):
break
line = line.decode().strip()
print(line)
if line.startswith("Validate this block:"):
pow1, pow2 = compute_equiv_pair(seed)
seed += 1
print(f"Sending PoW1: {pow1}\nSending PoW2: {pow2}")
r.sendline(pow1)
r.sendline(pow2)
except Exception as e:
print(f"Error or EOF: {e}")
break
r.close()
if __name__ == "__main__":
main()
```
### How It Works:
- **Generating PoW₁:**
The function `compute_equiv_pair(seed)` converts the given `seed` (an integer) into a 16‑byte big‑endian value. This serves as PoW₁.
- **Deriving PoW₂:**
The same function processes PoW₁ in 4‑byte chunks. Each chunk is XORed with `0x80000000` to produce the equivalent 32‑bit word, and the results are concatenated to form PoW₂. Despite being different from PoW₁, this key is equivalent under the TEA encryption used.
- **Sending Unique Pairs:**
A counter (`seed`) is incremented for every block so that the key pair is never reused. The script waits for the "Validate this block:" prompt, then sends the newly generated PoW pair.
- **Validation and Flag Retrieval:**
The server accepts the block when both keys produce identical ciphertexts. Once all blocks are validated, the flag is eventually printed.
By exploiting TEA’s equivalent-key property—where XORing each key word with `0x80000000` yields a different key that encrypts identically—we bypass the server’s requirement. The script automates generating unique and valid PoW pairs for each block, leading us to retrieve the flag:
```
$ python3 solve.py
[+] Opening connection to block.ctf.prgy.in on port 1337: Done
****************************************************************
___________.__ _________ .__ .__
\__ ___/|__| ____ ___.__.\_ ___ \| |__ _____ |__| ____
| | | |/ < | |/ \ \/| | \\__ \ | |/ \
| | | | | \___ |\ \___| Y \/ __ \| | | \
|____| |__|___| / ____| \______ /___| (____ /__|___| /
\/\/ \/ \/ \/ \/
****************************************************************
Validate this block: c8fdee0e9a5b943cf3c9349074b3daa1e6edc40c4420194878914fa06d3ac668
Sending PoW1: 00000000000000000000000000000000
Sending PoW2: 80000000800000008000000080000000
/mnt/e/CTF/pragyanctf/tiny/solve.py:23: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
r.sendline(pow1)
/mnt/e/CTF/pragyanctf/tiny/solve.py:24: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
r.sendline(pow2)
Enter first ProofOfWork :Enter second ProofOfWork :Good job validating the block!
Validate this block: fb7d8c559e45cc6afb386e67709712ca44bd0245474b10ad7f5e71ba086e2e3e
Sending PoW1: 00000000000000000000000000000001
Sending PoW2: 80000000800000008000000080000001
Enter first ProofOfWork :Enter second ProofOfWork :Good job validating the block!
Validate this block: 5b92f3c0d8ab00008455805f02cbeb6ba492f6adeb294ab06cd916ac9062301d
Sending PoW1: 00000000000000000000000000000002
Sending PoW2: 80000000800000008000000080000002
Enter first ProofOfWork :Enter second ProofOfWork :Good job validating the block!
Validate this block: 9e962d3c42acde05ac7969759294cd66fe52ed63da9e4614a07a27b5522bb919
Sending PoW1: 00000000000000000000000000000003
Sending PoW2: 80000000800000008000000080000003
Enter first ProofOfWork :Enter second ProofOfWork :Good job validating the block!
Validate this block: e32bedda93141e19cf30e96f38323fc78e719da0b6d7fd5017cc7b5c3725a674
Sending PoW1: 00000000000000000000000000000004
Sending PoW2: 80000000800000008000000080000004
Enter first ProofOfWork :Enter second ProofOfWork :Good job validating the block!
Validate this block: 86d3af23ae717180000f57e55865e9e4dfeed2e629ae1c80b91f0785da6d5f61
Sending PoW1: 00000000000000000000000000000005
Sending PoW2: 80000000800000008000000080000005
Enter first ProofOfWork :Enter second ProofOfWork :Good job validating the block!
Wait Noooooooooo: p_ctf{0Hh_No00_I5_T(-)i$_value_Ov3rFl0W!}
Error or EOF:
[*] Closed connection to block.ctf.prgy.in port 1337
```
Which gives us our flag: `p_ctf{0Hh_No00_I5_T(-)i$_value_Ov3rFl0W!}`