0

I am trying to solve a simple RSA CTF challenge, but I am facing problems that go beyond the theory behind the attack (or at least I guess so). Basically, I have an oracle at disposal that will first print the encrypted flag and then encrypt and decrypt whatever I want (except for the decryption of the flag). The idea for the attack is to encrypt 2*encrypted_flag and then decrypt the given cipher. By dividing the obtained number by 2 I should then get the flag. Am I missing something, below you can find both the oracle's code and my exploit.

The attack idea is a chosen ciphertext attack and I "stole" the equations from this video: https://www.youtube.com/watch?v=ZjYzrn8M3w4&ab_channel=BillBuchananOBE.

Oracle's code:

#!/usr/bin/env python3

import signal
from binascii import hexlify
from Crypto.PublicKey import RSA
from Crypto.Util.number import *
from random import randint
from secret import FLAG
import string

TIMEOUT = 300 # 5 minutes time-out

def menu():
    print()
    print('Choice:')
    print('  [0] Exit')
    print('  [1] Encrypt')
    print('  [2] Decrypt')
    print('')
    return input('> ')

def encrypt(m):
    return pow(m, rsa.e, rsa.n)

def decrypt(c):
    return pow(c, rsa.d, rsa.n)

rsa = RSA.generate(1024)
flag_encrypted = pow(bytes_to_long(FLAG.encode()), rsa.e, rsa.n)
used = [bytes_to_long(FLAG.encode())]

def handle():
  print("================================================================================")
  print("=                      RSA Encryption & Decryption oracle                      =")
  print("=                                Find the flag!                                =")
  print("================================================================================")
  print("")
  print("Encrypted flag:", flag_encrypted)

  while True:
    choice = menu()

    # Exit
    if choice == '0':
      print("Goodbye!")
      break

    # Encrypt
    elif choice == '1':
      m = int(input('\nPlaintext > ').strip())
      print('\nEncrypted: ' + str(encrypt(m)))

    # Decrypt
    elif choice == '2':
      c = int(input('\nCiphertext > ').strip())

      if c == flag_encrypted:
        print("Wait. That's illegal.")
      else:
        for no in used:
          if m % no == 0:
            print("Wait. That's illegal.")
            break
        else:
          print('\nDecrypted: ' + str(m))

    # Invalid
    else:
      print('bye!')
      break

if __name__ == "__main__":
    signal.alarm(TIMEOUT)
    handle()

My current approach:

from Crypto.Util.number import *
from math import gcd
import gmpy2
import sys
#sys.set_int_max_str_digits(0)
r = remote('oracle.challs.cyberchallenge.it', 9041)
r.recvuntil(b'Encrypted flag: ')
encrypted_flag = int(r.recvline().strip().decode())
e = 65537

# Let's first gather the ciphertext of the new num
"""
Here's another hint: suppose I encrypt 2. The oracle will give me back c2= pow(2, 65537, rsa.n). Now I can also compute 2**65537 as an integer. We know that 2**65537 - c2 is divisible by N. So we can try to factor 2**65537 - c2 using, say, the elliptic curve method (ECM). If we are incredibly lucky, 2**65537 - c2 = N * (bunch of relatively small primes), and after ECM finds all the small factors we'll be left with N. But, suppose, instead, that I also encrypt 3, so I get c3 = pow(3, 65537, rsa.n). And maybe even c5 = pow(5, 65537, rsa.n) How can I combine these to find rsa.n
"""

public_exponent = 65537
numbers = [2,3,4,5,6]
numbers_bytes = [b'\x02',b'\x03',b'\x04',b'\x05',b'\x06']
ciphers = []
diffs = []
for i in range(4):
    r.recvuntil(b'>')
    r.sendline(b'1')
    r.recvuntil(b'Plaintext > ')
    r.sendline(str(bytes_to_long(numbers_bytes[i])))
    r.recvuntil(b'Encrypted: ')
    cipher = int(r.recvline().strip().decode())
    ciphers.append(cipher)
    diffs.append(gmpy2.sub(pow(numbers[i], public_exponent),cipher))

print(diffs)
common_factor = None
for diff in diffs:
    if common_factor is None:
        common_factor = diff
    else:
        common_factor = gmpy2.gcd(common_factor, diff)
print(common_factor) 
#let's check whether the common factor is N
print(ciphers[0] == pow(bytes_to_long(b'\x02'), public_exponent, common_factor))
# We have found N if True
# To trick the decryption method just sum to the original ciphertext N once
print(common_factor)
encrypted_flag += int(common_factor)
r.recvuntil(b'>')
r.sendline(b'2')
r.recvuntil(b'Ciphertext > ')
r.sendline(str(encrypted_flag))
r.recvuntil('Decrypted: ')
flag = int(r.recvline().decode())
print(long_to_bytes(flag))
Shark44
  • 593
  • 1
  • 4
  • 11
  • *Am I missing something?* I don't know, you haven't stated what the problem is. – President James K. Polk Aug 16 '23 at 11:35
  • I was asking whether you could see something I am mistaking which I cannot see. I have already checked the code and it seems fine to me but I cannot understand why it won't work. The theory should be correct, so I thought the problem could be the implementation itself, but I cannot figure out where the error is. I have updated the question adding some details regarding the theory. – Shark44 Aug 16 '23 at 12:16
  • 1
    ok, I don't think your approach is correct. At least, that's not how I solved the problem, though there can be multiple ways to solve it. I'll give you some hints as to how I solved it. The problem is that you don't know the value of `rsa.n`. `rsa.e` defaults to 65537, so you know that. You also know the value of the encrypted flag. Furthermore, once you know `rsa.n` and the encrypted_flag, there is an easy way to circumvent the check that you're trying to decrypt the encrypted flag. Thus you have 2 problems: find `rsa.n` and then circumvent the check. – President James K. Polk Aug 16 '23 at 13:27
  • I am trying to figure out how to extract N from the equation. I know C=(M**e)%N, but how can you extract N from this equation? For the second problem I may have a solution, which would be factoring N and seeing the 2 factors, either 1 or the other is d, so we can try to decrypt the message (without making the oracle do it) with both and check which one gives the flag. – Shark44 Aug 16 '23 at 20:53
  • Here's another hint: suppose I encrypt 2. The oracle will give me back `c2= pow(2, 65537, rsa.n)`. Now I can also compute 2\*\*65537 as an integer. We know that 2\*\*65537 - c2 is divisible by N. So we can try to factor 2\*\*65537 - c2 using, say, the elliptic curve method (ECM). If we are incredibly lucky, 2\*\*65537 - c2 = N * (bunch of relatively small primes), and after ECM finds all the small factors we'll be left with N. But, suppose, instead, that I *also* encrypt 3, so I get c3 = `pow(3, 65537, rsa.n)`. And maybe even c5 = `pow(5, 65537, rsa.n)` How can I combine these to find `rsa.n`? – President James K. Polk Aug 16 '23 at 21:31
  • Thanks to your hints I was able to write a working script (I updated the code in the question). Basically I take the common factors of all the ciphers I got. But now I am wondering, if the oracle was added a check on the passed value (other than just checking if the cipher equals the encrypted value), would we still be able to bypass this check so easily? I updated also the oracle's code adding an additional check. – Shark44 Aug 17 '23 at 19:51
  • If you added a check for it then, no, you wouldn't be able to bypass the check, you'd have to try something else. – President James K. Polk Aug 18 '23 at 01:23

0 Answers0