aboutsummaryrefslogtreecommitdiff
path: root/content/articles/2022-09-19-gctf-readysetaction.md
blob: 0d5b71942bdb6475287243e0c21bc693b03c6a54 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# Google Beginner CTF: ReadySetAction

**SPOILER WARNING: Go play the CTF at
[capturetheflag.withgoogle.com](https://capturetheflag.withgoogle.com/) first**

The challenge was a short python script with the following code:

```py
from Crypto.Util.number import *

flag = b"REDACTED"

p = getPrime(1024)
q = getPrime(1024)
n = p*q

m = bytes_to_long(flag)


c = pow(m,3,n)

print(c)
print(n)
#154780 ... 6709
#210348 ... 4477
#(i removed both 617 digit long numbers here)
```

It was immediatly obvious to me that this has to do with RSA cryptography
because it multiplies large primes and then does some powmod magic. Because I
didn't remember exactly how RSA worked, I quickly read the Wikipedia article
again - specifically section
[4.1 Attacks against plain RSA](https://en.wikipedia.org/wiki/RSA_%29cryptosystem%29#Attacks_against_plain_RSA)

The first item already seems like the solution:

> When encrypting with low encryption exponents (e.g., e = 3) and small values
> of the m (i.e., m < n1/e), the result of me is strictly less than the modulus
> n. In this case, ciphertexts can be decrypted easily by taking the eth root of
> the ciphertext over the integers.

Which would mean that $\sqrt[3]{c}$ is a solution _if_ $m$ is small enough.
However

$c = m^3 \mod n \\$

so the exponentiation could yield a much higher result that we dont get because
it is wrapped at $n$. So we can try out how often it wrapped:

$m = \sqrt[3]{c + x*n} \quad x \in \N\\$

where $x$ can be brute-forced if $m$ is sufficiently small. Back in python it
looks like this:

```py
c = ...
n = ...

# simple but bad binary search for n-th root
def nth_root(c,e):
    high = 1
    while high**e < c:
        high *= 2
    low = 0
    mid = 0
    last_mid = 0
    while mid**e != c:
        mid = (low + high) // 2
        if last_mid == mid:
            return 0 # i use 0 to indicate a failure
        if mid**e > c:
            high = mid
        else:
            low = mid
        last_mid = mid
    return mid

for x in range(100000):
    m = nth_root(c + n*x, e)
    # the probability of finding a perfect cube number is low, so any result is what we want
    if m != 0: print(long_to_bytes(m))
```

Thats it! With $x=1831$ we get our flag: `CTF{34sy_RS4_1s_e4sy_us3}`