> For the complete documentation index, see [llms.txt](https://k70n0s510.gitbook.io/k70n0s510-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://k70n0s510.gitbook.io/k70n0s510-docs/platforms/uoftctf/guess-the-number.md).

# Hacking Time Itself: UofTCTF 2026 “Guess the Number” Writeup

Event: UofTCTF 2026 Category: Cryptography / Side-Channel Team: w4llz Rank: 48th out of 1,225 Teams (Top 4%!) 🚀 Author:K70n0s510\Nicholas…

***

### Hacking Time Itself: UofTCTF 2026 “Guess the Number” Writeup

**Event:** *UofTCTF 2026* **Category:** *Cryptography / Side-Channe*l **Team:** *w4llz* **Rank:** *48th out of 1,225 Teams (Top 4%!*) 🚀 \*\*Author:\*\*K70n0s510\Nicholas Mullenski

![](https://cdn-images-1.medium.com/max/800/1*Rjq2kE9IG6bNs5dvB1WVLA.png)

Image taken by Nicholas Mullenski

> [**Not a Member Click Here to Read Full-Story**](https://medium.com/bugbountywriteup/hacking-time-itself-uoftctf-2026-guess-the-number-writeup-7ccd4651e72d?sk=bb9e7ff52147822264d5b5b0d1e3bd27)

### The Event

This weekend, The team that i am apart of, **w4llz**, participated in the University of Toronto CTF (UofTCTF) 2026. It was a massive event with over 1,200 teams competing from around the world. We pushed hard and secured a **48th place finish**, landing us squarely in the top 4%.

While my teammates were doing the heavy lifting on the Web and Pwn challenges **(huge shoutout to the whole squad for carrying the load!)**, I dove into a Cryptography challenge that looked impossible at first glance.

### The Challenge: Guess the Number

**Points:** *179 (Dynamic Scoring)* **Difficulty:** *Medium*

The premise was deceptively simple. The server generates a random **100-bit integer** (x). We have to guess it.

**The Catch:**

* We can send expressions to the server to be evaluated.
* We only have **50 queries** allowed.
* We need to find a 100-bit number.

In a standard binary search (asking “Is x>y?”), you eliminate half the possibilities with each question. This yields **1 bit of information** per query. To find a 100-bit number, you mathematically need **100 queries**.

We only had 50.

Mathematically, this should be impossible. We needed to extract **2 bits of information** for every single query we sent.

### The Vulnerability: Timing Side-Channel

The server was executing our input using Python’s **`literal_eval`**. This meant that if we sent a computationally expensive mathematical operation, the server would take longer to process it.

We realized we could ask two questions at once:

1. **The Explicit Question (Bit 0):** “Is the k-th bit a 1?” (The server replies “Yes” or “No”).
2. **The Implicit Question (Bit 1):** “Is the (k+1)-th bit a 1?” (We measure *how long* the server takes to reply).

If the (k+1)-th bit is 1, we force the server to calculate `3**600000` (a massive number). If it's 0, we skip the calculation.

* **Fast Response (< 0.2s):** The hidden bit is 0.
* **Slow Response (> 1.5s):** The hidden bit is 1.

![](https://cdn-images-1.medium.com/max/800/1*8uWUODp1wA0RefXp7bgieA.png)

### The Exploit Script

I wrote this solver using **`pwntools`** to automate the timing attack. It recovers 2 bits per query, solving the 100-bit integer in exactly 50 requests.

```
#!/usr/bin/env python3
from pwn import *
import time

# Target Configuration
HEAVY_BASE = 3
HEAVY_EXP = 600000  # Tuned to force a >1.5s delay on the target server
TIME_THRESHOLD = 0.5

def get_payload(bit_index_k):
    # Logic: ((Next_Bit == 1) AND (Heavy_Calc)) OR (Curr_Bit == 1)

    # Check the (k+1)-th bit
    bit_next_check = {
        'op': '%',
        'arg1': {'op': '/', 'arg1': 'x', 'arg2': 2**(bit_index_k + 1)},
        'arg2': 2
    }

    # Check the k-th bit
    bit_current_check = {
        'op': '%',
        'arg1': {'op': '/', 'arg1': 'x', 'arg2': 2**bit_index_k},
        'arg2': 2
    }

    # Heavy calculation to cause delay (evaluates to 0)
    heavy_zero = {
        'op': '-',
        'arg1': {'op': '**', 'arg1': HEAVY_BASE, 'arg2': HEAVY_EXP},
        'arg2': {'op': '**', 'arg1': HEAVY_BASE, 'arg2': HEAVY_EXP}
    }

    # Construct the side-channel logic
    payload = {
        'op': 'or',
        'arg1': {
            'op': 'and',
            'arg1': bit_next_check,
            'arg2': heavy_zero
        },
        'arg2': bit_current_check
    }
    return str(payload)

def solve():
    # Connect to challenge
    r = remote('35.231.13.90', 5000)

    final_number = 0
    print("[*] Starting Side-Channel Extraction...")

    for i in range(50):
        k = i * 2
        payload = get_payload(k)

        # Send payload and measure execution time
        r.recvuntil(b"Input your expression")
        start_time = time.time()
        r.sendline(payload.encode())
        response = r.recvline().decode().strip()
        duration = time.time() - start_time

        # Analyze results
        bit_k = 1 if "Yes!" in response else 0
        bit_next = 1 if duration > TIME_THRESHOLD else 0

        # Reconstruct the integer
        final_number += (bit_k * (2**k))
        final_number += (bit_next * (2**(k+1)))

        print(f"Query {i+1}: Time={duration:.2f}s -> Bits: {bit_next}{bit_k}")

    print(f"[*] Recovered Number: {final_number}")
    r.sendline(str(final_number).encode())
    print(r.recvall().decode())

if __name__ == "__main__":
    solve()
```

**Flag:** **`uoftctf{h0w_did_y0u_gu3ss_7h3_numb3r}`**

### Rooted in Faith

As I was working on this challenge, staring at the screen and trying to figure out how to see a number that was invisible, this verse came to mind:

> ***“It is the glory of God to conceal a matter; to search out a matter is the glory of kings.”***\* — \*Proverbs 25:2

In this CTF, the flag was concealed behind strict limitations — it was “hidden” from plain sight. But the joy of cybersecurity (and life) isn’t just in knowing the answer; it’s in the *search*. Just as we had to look at the invisible “timing” of the server to find the truth, we often have to look past the surface level in our spiritual lives to find the wisdom God has hidden for us. He doesn’t hide things to keep them *from* us, but to invite us to seek Him deeper.

By [Nicholas Mullenski](https://medium.com/@nicholasmullenski) on [January 15, 2026](https://medium.com/p/7ccd4651e72d).

[Canonical link](https://medium.com/@nicholasmullenski/hacking-time-itself-uoftctf-2026-guess-the-number-writeup-7ccd4651e72d)

Exported from [Medium](https://medium.com) on September 1, 2026.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://k70n0s510.gitbook.io/k70n0s510-docs/platforms/uoftctf/guess-the-number.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `build a script that syncs our docs to a CMS` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
