esDynamic
Manage your attack workflows in a powerful and collaborative platform.
Expertise Modules
Executable catalog of attacks and techniques.
Infrastructure
Integrate your lab equipment and remotely manage your bench.
Lab equipments
Upgrade your lab with the latest hardware technologies.
Side Channel Attacks
Evaluate cryptography algorithms from data acquitition to result visualisation.
Fault Injection Attacks
Laser, Electromagnetic or Glitch to exploit a physical disruption.
Photoemission Analysis
Detect photon emissions from your IC to observe its behavior during operation.
Evaluation Lab
Our team is ready to provide expert analysis of your hardware.
Starter Kits
Build know-how via built-in use cases developed on modern chips.
Cybersecurity Training
Grow expertise with hands-on training modules guided by a coach.
esReverse
Static, dynamic and stress testing in a powerful and collaborative platform.
Extension: Intel x86, x64
Dynamic analyses for x86/x64 binaries with dedicated emulation frameworks.
Extension: ARM 32, 64
Dynamic analyses for ARM binaries with dedicated emulation frameworks.
Penetration Testing
Identify and exploit system vulnerabilities in a single platform.
Vulnerability Research
Uncover and address security gaps faster and more efficiently.
Code Audit & Verification
Effectively detect and neutralise harmful software.
Digital Forensics
Collaboratively analyse data to ensure thorough investigation.
Software Assessment
Our team is ready to provide expert analysis of your binary code.
Cybersecurity training
Grow expertise with hands-on training modules guided by a coach.
Semiconductor
Automotive
Security Lab
Gov. Agencies
Academics
Defense
Healthcare
Energy
Why eShard?
Our team
Careers
Youtube
Gitlab
Github
Here's the write-up for SRA challenge of the PicoCTF 2023, running from March 14 to March 28. These challenge is part of the Cryptography category.
The challenge description is the following:
I just recently learnt about the SRA public key cryptosystem... or wait, was it supposed to be RSA? Hmmm, I should probably check... Connect to the program on our server:
nc saturn.picoctf.net 60330
Download the program:chal.py
No hint is given for this challenge.
The server should run the chal.py
. To well understand it, we can first change the variable names:
from Crypto.Util.number import getPrime, inverse, bytes_to_long from string import ascii_letters, digits from random import choice import time time.clock = time.time plain = "".join(choice(ascii_letters + digits) for _ in range(16)) # pride p = getPrime(128) # gluttony q = getPrime(128) # greed # Use fixed values, easier for demonstration purpose p, q = 321315087699876844450938908960149703981, 236046326807786585102625365485104504817 n = p * q # lust e = 65537 # sloth phi = (p - 1) * (q - 1) d = inverse(e, phi) # envy cipher = pow(bytes_to_long(plain.encode()), e, n) # anger
Which is a RSA-256 key-pair generation. Then the server returns the cipher
and d
.
To obtain the flag, we must compute the plain
from the cipher
and d
. But to do that, we need n
...
n
from d
and e
From key equation we derive:
So if we try to factor
For complementary explanations, you can read this nice crypto.stackexchange post.
Let us start with the factorization of primefac
module, which is a bit slow, use Sage or this very fast website. I use this later solution and then use a small script to parse the website output.
dem1 = d * e - 1 dem1
1845238994787093868829071101511950464175922457296917469750101517162126249781286720
factors_string = '2^6 × 3^2 × 5 × 587 × 24329 × 129631 × 407807 × 80787 180953 × 1241 605438 156271 × 84761 673627 946307 × 99817 949863 851199' factors = [] for factor in factors_string.split(' × '): factor = factor.replace(' ', '').strip() if '^' in factor: factor = factor.split('^') factors += [int(factor[0])] * int(factor[1]) else: factors.append(int(factor)) factors = sorted(factors, reverse=True) print(factors)
[99817949863851199, 84761673627946307, 1241605438156271, 80787180953, 407807, 129631, 24329, 587, 5, 3, 3, 2, 2, 2, 2, 2, 2]
Because
Then size of
We have a set of prime numbers, and from this set we need to find all subsets such that the product of factors in these subsets correspond to
More formally, with
So, thinking in bit-size we see that we are facing a subset sum problem instance.
Well, let’s convert the finding of product to a subset sum problem …
The subset sum problem is a well known problem.
The following code comes from stackoverflow topic on the subsets sum problem solving, updated for our purpose.
def get_subsets_sum(data, target, epsilon): """Find sums of values in `data` which gives `target` ± `epsilon`.""" subsets, subsets_primes = [], [] differences = {} for value in data: prospects = [] for diff in differences: if (diff >= value) and abs(value - diff) < epsilon: new_subset = [value] + differences[diff] new_subset.sort() if new_subset not in subsets: subsets.append(new_subset) if value - diff < 0.: prospects.append((value, diff)) # update the differences record for prospect in prospects: new_diff = target - sum(differences[prospect[1]]) - prospect[0] differences[new_diff] = differences[prospect[1]] + [prospect[0]] differences[target - value] = [value] return subsets
And we can use it to find a subset for product using the bit size of the factors:
import math def get_subsets_product(data, target, epsilon): """Find products of values in `data` which gives `target` ± `epsilon` (`epsilon` is given as bit size).""" data = sorted(data, reverse=True) candidates = [] bit_sizes = [math.log2(value) for value in data] target_bit_size = math.log2(target) bit_sizes_substets = get_subsets_sum(bit_sizes, target_bit_size, epsilon) for subset in bit_sizes_substets: values_subset = [data[bit_sizes.index(bit_size)] for bit_size in subset] candidates.append(math.prod(values_subset)) return candidates
We can first reduce the list of possibles factors for
k_size_max = int(math.log2(dem1) - math.log2(cipher)) k_size_max
17
k_factors = list(filter(lambda factor: math.log2(factor) < k_size_max, factors)) k_factors
[129631, 24329, 587, 5, 3, 3, 2, 2, 2, 2, 2, 2]
k_candidates = get_subsets_product(k_factors, 2**17, 1) k_candidates
[121645, 72987, 97316, 105660, 70440, 84528, 93920, 112704]
In the following, we will need to remove the factors of
def remove_factors(factors, k_value, k_factors): factors = sorted(factors, reverse=True) k_factors = sorted(k_factors, reverse=True) result = factors.copy() for k_factor in k_factors: if k_value % k_factor == 0: k_value = k_value // k_factor result.remove(k_factor) return result
For each
import primefac porqm1_candidates = set() for k_candidate in k_candidates: remaining_factors = remove_factors(factors, k_candidate, k_factors) porqm1_candidate_set = get_subsets_product(remaining_factors, 2**128, 5) for porqm1_candidate in porqm1_candidate_set: if primefac.isprime(porqm1_candidate + 1): porqm1_candidates.add(porqm1_candidate) porqm1_candidates = list(porqm1_candidates) porqm1_candidates
[110451235237789359826286740748914534620, 147528954254866615689140853428190315510, 11582308472669131473870323426904724338, 29505790850973323137828170685638063102, 34919787361941703640524468294085435040, 236046326807786585102625365485104504816, 18091216071735087475943777451592941846, 321315087699876844450938908960149703980, 187582124573757824249206295828661487320]
In case when the resulting set is longer than two, we can brute force the remaining combinations.
for idx, pm1 in enumerate(porqm1_candidates): for qm1 in porqm1_candidates[idx + 1:]: n_candidate = (pm1 + 1) * (qm1 + 1) possible_plain = pow(cipher, d, n_candidate) possible_plain = possible_plain.to_bytes(length=256, byteorder='big') try: possible_plain = possible_plain.decode('ascii') except UnicodeDecodeError: continue print(possible_plain)
ke1CBDLfTKQtEDCw
Well done, there is only one solution !
the PicoCTF team for providing this nice CTF. I learned a lot in many domains I'm not familiar with. I can't wait to read the write-up for the challenges I haven't solved.