Processing math: 45%

10 Solves in General Division. Author : Barkingdog

 

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
#!/usr/bin/python3
from Crypto.Util.number import *
import os
 
BITS = 512
UPPER_BITS = 296
LOWER_BITS = BITS - UPPER_BITS
 
UPPER = bytes_to_long(os.urandom(UPPER_BITS // 8)) << LOWER_BITS
FLAG = b'codegate2022{this_is_a_sample_flag}'
 
def menu1():
    while True:
        lower = bytes_to_long(os.urandom(LOWER_BITS // 8))
        p = UPPER | lower
        if isPrime(p): return lower
 
def menu2():
    p = UPPER + menu1()
    q = getPrime(512)
    e = 0x10001
    n = p * q
    return n, pow(bytes_to_long(FLAG + b'\x00' + os.urandom(128 - 2 - len(FLAG))), e, n)
 
while True:
    print("1. Generate 10 random primes (only lower bits)")
    print("2. Encrypt a flag")
    idx = int(input("> "))
    if idx == 1:
        print("How many? (Up to 10)")
        num = int(input("> "))
        for _ in range(min(10, num)):
            print(menu1())
    elif idx == 2:
        n, c = menu2()
        print(f"n : {n}")
        print(f"c : {c}")
cs

 

Solution

 

This is a RSA challenge. We see that menu1 generates primes that have equal upper 296 bits, i.e. UPPER. It then returns the value lower, which is the value of the lower 216 bits. If we can somehow find the value of UPPER, this leads us to knowing much more than half of the upper bits of p, so we will be able to factor n via standard RSA attacks using coppersmith algorithm.

 

Now we focus on finding UPPER. Each time we are given lower, we know that UPPER+lower is a prime. 

Therefore, for each small primes p<700, we actually have a relatively strong information UPPER + lower \not\equiv 0 \pmod{p} or UPPER \not\equiv -lower \pmod{p} which is good enough to remove one possible candidate of UPPER \pmod{p}

 

Given sufficient number of lower such that UPPER + lower is a prime, we will be able to remove all but one possible candidate for UPPER \pmod{p} for each prime p <700. This essentially means that we can recover UPPER \pmod{p} for each prime p<700.

 

Combining these information with Chinese Remainder Theorem along with the bound UPPER < 2^{512} is strong enough to deduce the value of UPPER. This solves the problem. The code below is due to the challenge author barkingdog.

 

 

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
85
86
87
88
89
90
91
92
#!/usr/bin/sage
from Crypto.Util.number import *
from pwn import *
from sage.all import *
import math
 
= remote("localhost"9001)
 
BITS = 512
UPPER_BITS = 296
LOWER_BITS = BITS - UPPER_BITS
 
primes = [ 357111317192329,
31374143475359616771,
7379838997101103107109113,
127131137139149151157163167173,
179181191193197199211223227229,
233239241251257263269271277281,
283293307311313317331337347349,
353359367373379383389397401409,
419421431433439443449457461463,
467479487491499503509521523541,
547557563569571577587593599601,
607613617619631641643647653659]
 
remainders = [set([x for x in range(p)]) for p in primes]
 
def prod(L):
    val = 1
    for x in L:
        val *= x
    return val
 
def get_lower():
    r.recvuntil(b"> ")
    r.sendline(b"1")
    r.recvuntil(b"> ")
    r.sendline(b"10")
    return [int(r.recvline()) for _ in range(10)]
 
def get_nc():
    r.recvuntil(b"> ")
    r.sendline(b"2")
    r.recvuntil(b"n : ")
    n = int(r.recvline())
    r.recvuntil(b"c : ")
    c = int(r.recvline())
    return n, c
 
def rsa_high_bits_known(n, c, upper):
    F.<x> = PolynomialRing(Zmod(n), implementation='NTL'); 
    pol = x - upper
    beta = 0.48  # we should have q >= N^beta
    XX = 2 ** LOWER_BITS
    epsilon = beta / 7
    rt = pol.small_roots(XX, beta, epsilon)  
    q = int(gcd(rt[0- upper, n))
    p = int(n) // int(q)
    assert(p*== n and p > 1 and q > 1)
    phi = (p-1)*(q-1)
    e = 0x10001
    d = int(pow(e, -1, phi))
    plain = int(pow(c, d, n))
    print(long_to_bytes(plain))
 
#### STEP 1. Recover UPPER using crt ####
print("[+] STEP 1. Recover UPPER using crt")
crt_a = [0]
crt_m = [2**LOWER_BITS]
 
cnt = 0
while prod(crt_m) < 2**BITS:
    cnt += 1
    if cnt % 10 == 0:
        print(f"Gather {cnt*10} primes.. progress : {int(100 * (math.log2(prod(crt_m))-LOWER_BITS) / UPPER_BITS)}%")
    lowers = get_lower()
    for lower in lowers:
        for i in range(len(primes)):
            rem = lower % primes[i]
            if rem in remainders[i]:
                remainders[i].remove(rem)
                if len(remainders[i]) == 1:
                    crt_a.append(primes[i] - remainders[i].pop())
                    crt_m.append(primes[i])
 
upper = crt(crt_a, crt_m)
print(f"[+]UPPER = {upper.hex()}")
 
#### STEP 2. Recover FLAG using RSA Factoring with high bits known attack ###
print("[+] STEP 2. Recover FLAG using RSA Factoring with high bits known attack")
n, c = get_nc()
rsa_high_bits_known(n, c, upper)
cs

 

 

 

'CTF' 카테고리의 다른 글

0CTF 2022 TCTF NFT Market  (0) 2022.09.19
WACon 2022 Quals: RSA Secret Sharing  (0) 2022.06.27
CODEGATE 2022 Preliminary : Dark Arts  (0) 2022.02.28
SECCON CTF 2021 Writeups  (0) 2021.12.14
N1CTF 2021 Writeups  (1) 2021.11.22