Category: Misc

scriptCTF is a 48-hour jeopardy style CTF for hackers to test their skills against creative and innovative CTF challenges. We have mostly beginner friendly challenges, with a few hard ones. scriptCTF makes CTFs fun and approachable for all skill levels! This is hosted by ScriptSorcerers and some members of n00bzUnit3d (as a replacement of n00bzCTF).
When we examine the chall.py file given to us by the challenge, the goal is to find a secret 128-bit number. A flag is also displayed when guessed correctly.
Code Analysis
secret = secrets.randbelow(1 << 127) + (1 << 127)
- Here, a 128-bit secret number is chosen.
1 << 127
= 2^127.
randbelow (1 << 127
) = [0, 2^127 − 1] a random number between them.
- When added, the number is in the range [
2^127, 2^128 − 1
] → a number exactly 128 bits long.
A structure that works like an infinite loop:
for _ in range(1000):
print("[1] Provide a number\n[2] Guess the secret number")
choice = int(input("Choice: "))
The user is presented with two options:
- Enter a number, and the program will return a hint.
- Try to guess the secret number.
Option 1: Give a number
if choice == 1:
num = input('Enter a number: ')
fl_num = decimal.Decimal(num)
assert int(fl_num).bit_length() == secret.bit_length()
div = secret / fl_num
print(int(div))
- A number is received from the user (
num
). It is processed like a high-precision float with a decimal. assert int(fl_num).bit_length() == secret.bit_length():
- The bit length (approximate size) of the number provided by the user must be the same as the secret. In other words, the number must also be 128-bit.
div = secret / fl_num
→ the secret number is divided by the user’s number.print(int(div))
→ the integer part of the division is given to the user.
This actually gives you information about the secret.
- If num < secret → partition ≥ 1 will be output.
- If num ≈ secret → partition ≈ 1 will be output.
- If num > secret → partition 0 will be output.
And if you choose the numbers wisely, you can gradually narrow down the secret.
Option 2: Guess
if choice == 2:
guess = int(input("Enter secret number: "))
if guess == secret:
print(open('flag.txt').read().strip())
else:
print("Incorrect!")
exit(0)
- It takes the user’s secret guess.
- If correct, it displays the contents of
flag.txt
. - If incorrect, it displays “Incorrect!” and exits.
Solution Hypothesis
A 128-bit secret number is given. Direct guessing is impossible (2^128 possibilities). But thanks to the division oracle function, we can narrow down the secret by using the secret / num
information.
This is solved using logic similar to binary search:
- If we give numbers like 2^127 as num,
- We learn the range of the secret from the resulting quotient.
- By narrowing down the range, we finally find the secret.
Code Analysis
In this solving code,
- Establishing a connection → The script either connects to the remote server (
--host, --port
) or runs the binary locally (--bin
). It reads the output from the menu and sends commands.
- Oracle query → A 128-bit number is sent with the [1] Provide a number option. The returned result is
int(secret / num)
→ This can only be0
or1
.1
→ Our number is less than/equal to the secret.0
→ Our number is greater than the secret.
- Binary search → Initially, the secret’s range is [
2^127, 2^128 - 1
].- The middle number (
mid
) is selected and requested from Oracle. - The range is narrowed down based on the result.
- This process reaches the exact secret in ~128 steps.
- The middle number (
- Receiving a flag → The found secret is sent with the [2] Guess the secret number option, and if true, a flag is returned.
And, access the flag by running the code with this command
python oracle_bs.py --host play.scriptsorcerers.xyz --port 10040 --verbose
FLAG: scriptCTF{b1n4ry_s34rch_u51ng_d1v1s10n?!!}