Home HackyEaster 2022 Textbook
Writeup
Cancel

Textbook

Challenge

I’ve got the source code and the output of a simple cipher.

Can you calculate the flag from it?

Solution

Looking at writeupfiles/textbook/generate.py, we see it’s super simple, the numbers are all:

1
ct.append(pow(ord(c), e, n))

We know c, we know e (65537) and we know ct (from the output file).

Eventually we found this answer on Crypto.SE, the important bit reads:

Actually, it turns out that if the public exponent isn’t too large, the attacker is in luck; if the attacker takes two known plaintext/ciphertext pairs P1,C1 and P2,C2, [they] can compute \(gcd(P^{e}_{1}−C_{1}, P^{e}_{2}−C_{2})\)

That’s a trivial attack for us. Looking at the output file, we can even see the repetition of 2022 apparent clearly in the header, the repeated numbers makes it very easy to say it’s the same input and likely we’re looking at the encrypted values of he2022 so let’s pull that into python, pairs of PT + CT:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import itertools
import string
import gmpy2
from output import CT

e = 65537

known = {
    'h': 18775795524598247683907618594648741761388590921639844605467180006396151189786215265758535337575193670309021582855264867028892978548414321559653798152761136051485505665603301349985640126614264715786618396324939151873322657200895440952009868552269929884443913952032345927889429757591197555362257974762300427077,
    'e': 26011351209175025206763581075024853552087390113459179407864027744918423757072640750702521967542438972832007354186124976428214658014339313691584211007102294219513772038953948006839749138993127974767870845453318323126145261576999544252516469455909654786162700125163666914824091205600834887595162995913379417502,
    '2': 31126027103773387351407738595758142992757290966422921906427583547807098239051608888510957245463260061378552690318743836051281072776395967616847139914308771289374849671126133828143242104999626354384556859251290533589200430407800561772545504440865161148516656248368860617679069912661054218632351283389360492636,
    '0': 33684185672169051982585355390624457073781125802593439786734555391921020764135794272438413202017837358694253342130763010770015611379636085226552836884143628891066368992267302059813243306707573393171165117187880314519923028262275396235438297673412439133301420995863550669342151850235528917701042594555186962024,
    '{': 43283051076672963140434066373128449128233454473574366866727769790690168423259916523176359868863407806090707305813302415073332716039344877397177362916244707139453074977025784117197746543408252406901718015246037520507913732166108314251738782237756346797656994325200715896452351937956613340716980498944591436851,
    '}': 3091633148363652646770092010443201598237353278811637957518304391171561834765093317629653127123047081493716413860116204322756599258146171304663380237017195643995093453756601675833814640677571021005978496814057808056012886878634413057457512996940948408306118798039657509816558179390529087357382252480124057030,
}

Then we can compute the requested value:

1
2
3
4
5
6
7
8
9
10
possible = []

for pairs in itertools.combinations(known.keys(), 2):
    gcd = gmpy2.gcd(
        pow(ord(pairs[0]), e) - known[pairs[0]],
        pow(ord(pairs[1]), e) - known[pairs[1]]
    )

    if gcd not in possible:
        possible.append(gcd)

And looking at the possible values against factordb, we see they’re all multiples of the very first value we find

1
n = 48309952986767828810211116437346335010234966410717961253604004949499868025260127897876577906582426195177515813973602817599712854363293887621365505327948627549148720502559259505787493271247264526163068321300112038993135083719786793834890849093167509340135523281225587591461719272832908482103617007228902444181

So let’s check that we have the correct N by using it to encryp:

1
2
3
4
5
# Check if N is right.
for k, v in known.items():
    print('====')
    print(v)
    print(pow(ord(k), e, n))

And it defo is. So we’ll load output and create a simple lookup table from string.printable

1
2
3
4
5
6
7
lookup = {}

for i in string.printable:
    ct = pow(ord(i), e, n)
    lookup[ct] = i

print(''.join([lookup[x] for x in CT]))

Flag

he2022{!!t3xtb00k_crypt0!!}