Rating:

We are given the following script.

#!/usr/bin/env python3

from Crypto.PublicKey import RSA
from Crypto.Util import number
from Crypto.Util.number import bytes_to_long, long_to_bytes
import sys
from secret import flag

key = RSA.generate(2048, e = 3)

def encrypt(msg : bytes, key) -> int:
    m = bytes_to_long(msg)
    if m.bit_length() + 128 > key.n.bit_length():
        return 'Need at least 128 Bit randomness in padding'
    shift = key.n.bit_length() - m.bit_length() - 1
    return pow(m << shift | number.getRandomInteger(shift), key.e, key.n)

def loop():
    print('My public modulus is:\n%d' % key.n)
    print('Here is your secret message:')
    print(encrypt(flag, key))

    while True:
        print('You can also append a word on your own:')
        sys.stdout.flush()
        PS = sys.stdin.buffer.readline().strip()
        print('With these personal words the cipher is:')
        print(encrypt(flag + PS, key))

if __name__ == '__main__':
    try:
        loop()
    except Exception as err:
        print(repr(err))

The service encrypts the flag + our input + a random padding, we can minimize the padding by sending the longest input allowed, so the random padding will be around 128 bits, since the e = 3, we can do a coppersmith short pad attack to recover the flag.

The script originated from here.

# sage
from Crypto.Util.number import *

def short_pad_attack(c1, c2, e, n):
    PRxy.<x,y> = PolynomialRing(Zmod(n))
    PRx.<xn> = PolynomialRing(Zmod(n))
    PRZZ.<xz,yz> = PolynomialRing(Zmod(n))

    g1 = x^e - c1
    g2 = (x+y)^e - c2

    q1 = g1.change_ring(PRZZ)
    q2 = g2.change_ring(PRZZ)

    h = q2.resultant(q1)
    h = h.univariate_polynomial()
    h = h.change_ring(PRx).subs(y=xn)
    h = h.monic()

    kbits = 130
    diff = h.small_roots(X=2^kbits, beta=0.5, epsilon=1/30)[0]  # find root < 2^kbits with factor >= n^0.5

    return diff

def related_message_attack(c1, c2, diff, e, n):
    PRx.<x> = PolynomialRing(Zmod(n))
    g1 = x^e - c1
    g2 = (x+diff)^e - c2

    def gcd(g1, g2):
        while g2:
            g1, g2 = g2, g1 % g2
        return g1.monic()

    return -gcd(g1, g2)[0]

from pwn import *
ps = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
e = 3
r = remote("52.59.124.14",int(10009))
r.recvuntil("My public modulus is:\n")
n = int(r.recvline().strip())
r.recvline()
r.sendlineafter("You can also append a word on your own:\n", ps)
r.recvline()
C1 = int(r.recvline().strip())
r.sendlineafter("You can also append a word on your own:\n", ps)
r.recvline()
C2 = int(r.recvline().strip())
print(n, C1, C2)
assert C1 != C2

diff = short_pad_attack(C1, C2, e, n)
m = related_message_attack(C1, C2, diff, e, n)
print(long_to_bytes(int(m)))
Original writeup (https://hackmd.io/@vidner/nullcon-sksd#PS-cry).