Skip to content

Instantly share code, notes, and snippets.

@ndren
Created August 21, 2025 18:38
Show Gist options
  • Save ndren/b54ed8fe194175185c26500a729ebc81 to your computer and use it in GitHub Desktop.
Save ndren/b54ed8fe194175185c26500a729ebc81 to your computer and use it in GitHub Desktop.
Mod writeup

Challenge

#!/usr/local/bin/python3
import os
secret = int(os.urandom(32).hex(),16)
print("Welcome to Mod!")
num=int(input("Provide a number: "))
print(num % secret)
guess = int(input("Guess: "))
if guess==secret:
    print(open('flag.txt').read())
else:
    print("Incorrect!")

One way to solve this is to notice that if you use a number that is -1 mod secret, you'll get back secret-1.

For each secret, the number must be -1 mod secret. So the lcm of all the possible numbers would work.

import os
from math import lcm
lcm(range(int(len(os.urandom(32).hex())*'f',16)))-1 # waiting...

This is a bit too large. According to https://oeis.org/A003418, we have a bound of a(n) >= 2^(n-1), and we want a(2^256)-1>=2^(2^256-1)-1. For reference, there's only about 10^80 < 2^267 atoms in the universe.

Then I remembered I read an article about benchmarking that said that most random numbers are very large: https://www.vidarholen.net/contents/blog/?p=1160 We can use this to get an oracle into the secret.

We can use statistics. As it turns out, half the numbers are really large (> 1/2 * MAX_VALUE) and we have a way to check that scenario. For numbers that are close together, modulo looks like subtraction!

For some secret x where x > 1/2 * MAX (which will happen roughly half the time):

MAX % x = (MAX-x) % x

We know that MAX - x < 1/2 * MAX because x > 1/2 * MAX. But that also means MAX-x < x, so (MAX-x)%x = MAX-x

Therefore, for half the numbers, you have the relation MAX % x == MAX - x

But you know what MAX is, so X is MAX - (MAX - x) = x

So try a guess of int(len(os.urandom(32).hex())*'f',16), take the output and subtract it from your input, and half the time you'll get the correct answer! No need to write a script, it's fairly reliable anyway.

Flag: scriptCTF{-1_f0r_7h3_w1n_4a3f7db1_0da9a3a24e3f}

And now I realise I could have just used -1 all along... Why did I think it had to be a positive number??

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment