Inshall'hack
Security if God wills it
Inshall'hack

Souper Strong Primes, Hidden Key and Soupstitution Writeups (EasyCTF IV)

This article contains writeups for Souper Strong Primes, Hidden Key and Soupstitution from EasyCTF IV.

Souper Strong Primes

Souper Strong Primes was a 200 point challenge from EasyCTF IV. It was not very complicated, but very long to solve compared to other tasks.

Challenge description

Technically I used strong primes. But are they really strong in this case? They are big, but there might still be an issue here. n.txt e.txt c.txt

n.txt e.txt c.txt

Strong primes

Looking at n, we can see that it is 400,000 bits long, which makes bruteforce completely unusable. Let's dig into Wikipedia to learn more about strong primes:

<Wikipedia>

Definition in number theory

In number theory, a strong prime is a prime number that is greater than the arithmetic mean of the nearest prime above and below (in other words, it's closer to the following than to the preceding prime). Or to put it algebraically, given a prime number , where n is its index in the ordered set of prime numbers, . The first few strong primes are

11, 17, 29, 37, 41, 59, 67, 71, 79, 97, 101, 107, 127, 137, 149, 163, 179, 191, 197, 223, 227, 239, 251, 269, 277, 281, 307, 311, 331, 347, 367, 379, 397, 419, 431, 439, 457, 461, 479, 487, 499 (sequence A051634 in the OEIS).

For example, 17 is the seventh prime. The sixth and eighth primes, 13 and 19, add up to 32, and half that is 16. That is less than 17, thus 17 is a strong prime.

In a twin prime pair (p, p + 2) with p > 5, p is always a strong prime, since 3 must divide p − 2 which cannot be prime.

It is possible for a prime to be a strong prime both in the cryptographic sense and the number theoretic sense. For the sake of illustration, 439351292910452432574786963588089477522344331 is a strong prime in the number theoretic sense because the arithmetic mean of its two neighboring primes is 62 less. Without the aid of a computer, this number would be a strong prime in the cryptographic sense because 439351292910452432574786963588089477522344330 has the large prime factor 1747822896920092227343 (and in turn the number one less than that has the large prime factor 1683837087591611009), 439351292910452432574786963588089477522344332 has the large prime factor 864608136454559457049 (and in turn the number one less than that has the large prime factor 105646155480762397). Even using algorithms more advanced than trial division, these numbers would be difficult to factor by hand. For a modern computer algebra system, these numbers can be factored almost instantaneously. A cryptographically strong prime has to be much larger than this example.

Definition in cryptography

In cryptography, a prime number is strong if the following conditions are satisfied.[1]

  • is sufficiently large to be useful in cryptography; typically this requires to be too large for plausible computational resources to enable a cryptanalyst to factorise products of multiplied by other strong primes.
  • has large prime factors. That is, for some integer and large prime .
  • has large prime factors. That is, for some integer and large prime .
  • has large prime factors. That is, for some integer and large prime

Application of strong primes in cryptography

Factoring-based cryptosystems

Some people suggest that in the key generation process in RSA cryptosystems, the modulus should be chosen as the product of two strong primes. This makes the factorization of using Pollard's p − 1 algorithm computationally infeasible. For this reason, strong primes are required by the ANSI X9.31 standard for use in generating RSA keys for digital signatures. However, strong primes do not protect against modulus factorisation using newer algorithms such as Lenstra elliptic curve factorization and Number Field Sieve algorithm. Given the additional cost of generating strong primes RSA Security do not currently recommend their use in key generation. Similar (and more technical) argument is also given by Rivest and Silverman.[1]

Discrete-logarithm-based cryptosystems

It is shown by Stephen Pohlig and Martin Hellman in 1978 that if all the factors of p-1 are less than , then the problem of solving discrete logarithm modulo p is in P. Therefore, for cryptosystems based on discrete logarithm, such as DSA, it is required that p-1 have at least one large prime factor.

</Wikipedia>

So basically, using two strong primes as defined for cryptography as the factors of n in RSA is super-duper secure. Ugh.

The only thing that would make the challenge solvable is if strong primes from number theory were used, specifically twin primes.

Factoring n

To figure that out, we use sage to solve the equation p(p + 2) = n:

var('p')
solve([p * (p + 2) == n], p)

We actually find a value for p! This enables us to find q, since q = n / p, or even easier, q = p + 2.

Decrypting the ciphertext

We started trying to decrypt the ciphertext, but after more than 1 hour we still weren't able to get a result. The key and the cipher were simply too big.

Our computers being what they are (hint: not really all that powerful), we had to figure out a different way, and stumbled upon this link (TL;DR we can use the Chinese Remainder Theorem to speed up our calculations).

We then implemented the following program:

from gmpy2 import *

e = int(open('e.txt').read())
c = int(open('c.txt').read())
p = int(open('p.txt').read())
q = int(open('q.txt').read())

dp = invert(e, p - 1)
dq = invert(e, q - 1)
qinv = invert(q, p)

m1 = pow(c, dp, p)
m2 = pow(c, dq, q)
h = (qinv * (m1 - m2)) % p 

m = m2 + h * q

print m

which, after about 40 minutes, gave us the flag!

Flag: easyctf{Str0ng_prim3s_n0t_s0_str000ng}

Hidden Key

Cryptography - 250 points

Challenge description

Ugh, another RSA problem? Help me decrypt this message please.

Solution

RSA

equation

equation

equation

equation

equation

equation

Script

n=23771394852910639082583501669530890953667541327452385339244879173587989064734577446471102994475189372684743795254735451944597143864549235990591729400340618643634331527928443630441118443293131567768102415899778261766094189544469362494173326242393482896618565916197135201551162898936577373806915526433247080527802988058032843358388910692815849911082186689778983514879268314194473412440350826066014379363598141942960876294951059670494304420783931550196576699781466293767367193560268678763964125161618511034351111453843215077891836707748402326693186380606250308456070456770861623489696650199318523543231853596082763712307
e=65537
c=15152801953478615231615813174158717826208918111757011747988906836426502895099242747835026205340656958975116313161051866115947302881237114983857653344685927368449097875645938338991888932698175908609811160840398889905427433799960012502893930361988001955295067071781029506130517204169795490975096203536618808080762347655332301145660198939248232962193390588038274758393252137503862620577494319064425602342789523724094298326178304046723002691324112726623878745995975972547721658598172533473268445493568838058261665674787313810379877275689561385909748399944470056206680588572965450454425734736206241548497125876627033884987
# twodphi = 2d+phi(n)
twodphi=37522002584021955249340303907837741680028779890763490946928681750311376852458883273648415268771456048279882988048226472028209114566672640119607895254238612948838681867793974521846321613795940938567631881495710237650749128367284467429616065425589798513915815315514061449179290300231809811071669293115951458555128955662862598401276723507543847080962220156185664629981885527698794059633761338150274671561342202316529169921166193284138591003891171560545510890577376532942003959005722155615213049952143695803658907536762317179556353262346224586373466082695017756636007707783853318052447290904520185444458711097071045593826


def breakIt(n,e,twodphi):
  mtest = 42
  ctest = pow(mtest, e, n)

  for k in range(100001,999999, 2):
    k=103447
    phi = (e*twodphi - 2) // k
    d = (twodphi - phi) >> 1 # k >> 1 <=> k * 1/2
    if d > 0:
      if pow(ctest, d, n) == mtest:
        print "d : ",d
        print
        m = pow(c, d, n)
        print(hex(m)[2:].rstrip('L').decode('hex'))
        return

breakIt(n,e,twodphi)

Running this script instantly gives us the flag!

Flag: easyctf{4rfyb2ud5eixw5dssu}

Soupstitution Cipher

Reverse Engineering - 150 points

Challenge

We had a flag, but lost it in a mess of alphabet soup! Can you help us find it?

Connect to the server via nc c1.easyctf.com 12484.

Solution

First glance

The program provided is ~~obfuscated~~ souped.

org

Let's clean that up!

Beautifying the program

After refactoring the program, it looks like this:

beautify

What are we looking for?

As you can see, if we provide the string 2365552391 as input to the program, it should return the flag. Unfortunately, the length of the input is limited to 7 characters.

So we have to find another input that translates to 2365552391 after the call to parseInt.

Python 3, my love…

After searching for a long time, I noticed that unlike in Python 2, the isdigit function is not limited to ASCII-encoded inputs but also accepts UTF-8 or Unicode-encoded strings in Python 3.

dico = [chr(i) for i in range(9999) if chr(i).isdigit()]

Bruteforcing (or not)

>>> len(dico)
358

As you can see, 358 characters out of the first 9999 pass the isdigit condition.

My first idea was to bruteforce which 7 characters to use; checking the number of possibilities quickly ruled out that possibility.

>>> pow(len(dico),7)
753669927250029952

Actually using my brain (and maybe losing some time)

Let's look at parseInt some more; it basically works like this:

# parseInt('123') =>

out = 0
out = 0
out += ord('1') - ord('0') = 49 - 48 = 1
out = 10
out += ord('2') - ord('0') = 50 - 48 = 12
out = 120
out += ord('3') - ord('0') = 51 - 48 = 123
# but what if we take 'A23' as input =>

out = 0
out = 0
out += ord('A') - ord('0') = 65 - 48 = 17
out = 170
out += ord('2') - ord('0') = 50 - 48 = 172
out = 1720
out += ord('3') - ord('0') = 51 - 48 = 1723

As we can see, we now have one more digit in our output! Success!

Solving the problem

Remember that we want parseInt to output 2365552391 based on an input of at most 7 characters. The key here is that we can split 2365552391 in a lot of different ways, such as [2365, 5, 5, 2, 3, 9, 1] or [236, 55, 5, 2, 3, 9, 1] or [23, 65, 55, 2, 3, 9, 1], and so on.

Let's try to find characters of interest that pass the isdigit check. Let's try with 2365:

>>> chr(2365 + ord('0')).isdigit()
True

Yup, first try! :-)

This means that a possible input would be:

chr(2365 + ord('0')) + "5" + "5" + "2" + "3" + "9" + "1" = "७552391"

Sending it to the online service, and… Flagged!

Flag: easyctf{S0up_soup_soUP_sOuP_s0UP_S0up_s000000OOOOOOuuuuuuuuppPPppPPPp}

Wrapping up

EasyCTF was not very hard, but it had many interesting challenges. Thanks to the organizers for that!


comments powered by Disqus

Receive Updates

ATOM

Contacts