Skip to content
Permalink
Fetching contributors…
Cannot retrieve contributors at this time
164 lines (127 sloc) 5.33 KB

Creative Cheating

Problem

Mr. Miller suspects that some of his students are cheating in an automated computer test. He captured some traffic between crypto nerds Alice and Bob. It looks mostly like garbage but maybe you can figure something out.

He knows that Alice's RSA key is (n, e) = (0x53a121a11e36d7a84dde3f5d73cf, 0x10001) (192.168.0.13) and Bob's is (n, e) = (0x99122e61dc7bede74711185598c7, 0x10001) (192.168.0.37)

Solution

Credit: @emedvedev

We're given a PCAP dump, so let's examine it with Wireshark:

Alice (192.168.0.13) sends Bob (192.168.0.37) TCP packets with Base64-encoded data. Let's follow the stream, export all the packets and decode them one by one:

import base64

packets = []
with open('stream.txt') as lines:
  for line in lines:
    decoded = base64.b64decode(line)
    packets.append(decoded)

Our decoded packets look like this (notice that SEQ is not unique, we'll need it later):

SEQ = 13; DATA = 0x3b04b26a0adada2f67326bb0c5d6L; SIG = 0x2e5ab24f9dc21df406a87de0b3b4L;
SEQ = 0; DATA = 0x7492f4ec9001202dcb569df468b4L; SIG = 0xc9107666b1cc040a4fc2e89e3e7L;
SEQ = 5; DATA = 0x94d97e04f52c2d6f42f9aacbf0b5L; SIG = 0x1e3b6d4eaf11582e85ead4bf90a9L;
SEQ = 4; DATA = 0x2c29150f1e311ef09bc9f06735acL; SIG = 0x1665fb2da761c4de89f27ac80cbL;
SEQ = 18; DATA = 0x181901c059de3b0f2d4840ab3aebL; SIG = 0x1b8bdf9468f81ce33a0da2a8bfbeL;
SEQ = 2; DATA = 0x8a03676745df01e16745145dd212L; SIG = 0x1378c25048c19853b6817eb9363aL;
SEQ = 20; DATA = 0x674880905956979ce49af33433L; SIG = 0x198901d5373ea225cc5c0db66987L;
SEQ = 0; DATA = 0x633282273f9cf7e5a44fcbe1787bL; SIG = 0x2b15275412244442d9ee60fc91aeL;
[...]

We'll assume that SEQ is the sequence order, DATA is the content and SIG is the signature. The RSA keys we're given are very short, let's query FactorDB and get p and q for both Alice and Bob:

  • Alice's p and q are 38456719616722997 and 44106885765559411.
  • Bob's p and q are 49662237675630289 and 62515288803124247.

Now, to calculate RSA parameters you would normally use RSATool, but since we're going to need some extra internal logic, let's create a custom RSA class. PyCrypto's RSA.construct is going to help us here:

import gmpy
from Crypto.PublicKey import RSA


class RSAPerson(object):

    def __init__(self, e, p, q):
        self.n = p * q
        self.e = e
        self.p = p
        self.q = q
        self.d = long(gmpy.invert(e, (p-1)*(q-1)))
        self.key = RSA.construct((long(self.n), long(self.e), self.d))

    def sign(self, message):
        return self.key.sign(message, '')

    def verify(self, message, signature):
        return self.key.publickey().verify(message, [signature])

    def encrypt(self, message):
        return self.key.publickey().encrypt(message)

    def decrypt(self, message):
        return self.key.decrypt(message)


alice = RSAPerson(
    0x10001,
    38456719616722997,
    44106885765559411
)
bob = RSAPerson(
    0x10001,
    49662237675630289,
    62515288803124247
)

Let's try decrypting the messages now. Since we have Alice sending packets to Bob, we'll have to decode the data with Bob's private key. We'll modify our code to:

  1. Order the data entries by SEQ.
  2. Parse the entries into SEQ, DATA and SIG before storing.
  3. Decrypt DATA with Bob's key.
packets = []
with open('stream.txt') as lines:
    for line in lines:
        decoded = base64.b64decode(line)
        match = regex.match(decoded).groups()
        seq = int(match[0])
        signature = int(match[2], 16)
        data = int(match[1], 16)
        data = bob.decrypt(data)
        data = chr(data)
        packets.append((
            seq,
            data,
            signature
        ))

Let's take a look at the output now:

(0, '\x0b', 411405985302309658304687940458766L)
(0, '&', 873819575920644857617395222352302L)
(0, '(', 254879289019714299901060498187239L)
(0, 'H', 130444345139656587207775038064682L)
(0, 'f', 525203869657262769956125397333553L)
(0, 'f', 1150325363693092780142319416277197L)
(1, '-', 828076894813745622214013032578722L)
(1, 'Y', 1485043620748068753347713371814689L)
(1, 'j', 1298874456026095076333544275801497L)
(1, 'l', 583679654300144088157196927029953L)
(1, 'u', 1055492309091995510087523902347914L)
(2, '<', 394933299120645953370114156475962L)
(2, 'U', 355616958260007551295456729191093L)
(2, 'a', 355616958260007551295456729191093L)
[...]

Clearly, we must only have one character at each position, so we'll have to discard everything else. That's where SIG comes into play: since Alice signs every message, we'll add verification and build the string from verified characters only.

packets = []
with open('stream.txt') as lines:
    for line in lines:
        decoded = base64.b64decode(line)
        match = regex.match(decoded).groups()
        seq = int(match[0])
        signature = int(match[2], 16)
        data = int(match[1], 16)
        data = bob.decrypt(data)
        if alice.verify(data, signature):
            data = chr(data)
            packets.append((
                seq,
                data,
                signature
            ))

print ''.join([packet[1] for packet in sorted(packets)])

Run the script, get the flag:

flag{n0th1ng_t0_533_h3r3_m0v3_0n}
You can’t perform that action at this time.