I only got to solve one of the three leet challenges. It was a cryptography challenge where I can brute force two parameters known to be between 0 and 1000 and then work backwards to figure out q based on a hint leaked in the output. From there, it’s simple RSA.

## HV23.22

### Challenge

HV23.22 Secure Gift Wrapping Service
Categories: EXPLOITATION
Level: leet
Author: darkice

This year, a new service has been launched to support the elves in wrapping gifts. Due to a number of stolen gifts in recent days, increased security measures have been introduced and the gifts are being stored in a secret place. As Christmas is getting closer, the elves need to load the gifts onto the sleigh, but they can’t find them. The only hint to this secret place was probably also packed in one of these gifts. Can you take a look at the service and see if you can find the secret?

Please note that the libc file in the downloadable archive has been changed to match the one in the Docker.

Exploit the Docker and get the flag!

The sha256sum of the downloadable archive public.tar.xz is a0a416c96fb8daa6b2d50c245ab9b914b64431f4dbad381fbbad52b782f43297

### Not Solved

I didn’t get to solve this challenge.

## HV23.23

### Challenge

HV23.23 Roll your own RSA
Categories: CRYPTO
Level: leet
Author: cryze

Santa wrote his own script to encrypt his secrets with RSA. He got inspired from the windows login where you can specify a hint for your password, so he added a hint for his own software. This won’t break the encryption, will it?The download has (some parts of) the application.

### Solve

#### Script

The download contains a Python script and an output.txt.

The Python script encrypts a FLAG constant:

from Crypto.Util.number import *
from sage.all import *
from secret import FLAG, x, y
import random

# D = {x∈ℕ | 0 ≤ x ≤ 1000}
# D = {y∈ℕ | 0 ≤ y ≤ 1000}

def enc(flag, polynomial_function):
p = getStrongPrime(512)
q = getStrongPrime(512)
N = p * q
e = 65537
hint = p**3 - q**8 + polynomial_function(x=x)
encrypted = pow(bytes_to_long(flag), e, N)
print(f"{N=}")
print(f"{e=}")
print(f"{hint=}")
print(f"{encrypted=}")

def generate_polynomial_function(seed):
x = SR.var("x")
random.seed(seed)
grade = random.choice([2,3])
a = random.randint(9999, 999999)
b = random.randint(8888, 888888)
c = random.randint(7777, 777777)

if grade == 2:
y_x = a*x**2+b*x+c
if grade == 3:
d = random.randint(6666, 666666)
y_x = a*x**3+b*x**2+c*x+d

print(a+b+c)
return y_x

y_x = generate_polynomial_function(y)
enc(FLAG.encode(), y_x)


It sends several things to STDOUT, all of which is found in output.txt:

1709262
N=143306145185651132108707685748692789834391223254420921250753369412889732941905250889012412570851623535344424483564532684976892052830348014035303355261052741504390590825455129003262581199117432362073303998908141781601553213103109295898711066542593102305069363965164592663322089299134520383469241654273153506653
e=65537
hint=-367367861727692900288480576510727681065028599304486950529865504611346573250755811691725216308460956865709134086848666413510519469962840879406666853346027105744846872125225171429488388383598931153062856414870036460329519241754646669265989077569377130467115317299086371406081342249967666782962173513369856861858058676451390037278311316937161756731165929187543148639994660265783994439168583858109082136915810219786390452412584110468513829455001689531028969430907046738225668834761412112885772525079903072777443223873041260072918891696459905352737195384116938142788776947705026132197185926344278041831047013477983297898344933372775972141179163010102537733004410775357501267841845321271140399200044741656474378808452920297777911527159803159582800816951547394087190043792625664885536154225227819735800442814065528155407746556297892931242208688533313054308779657788077807340045465701247210553988059519291363634253248268722975827616752514688291723712069675405995149499947239454505797412122124933836396842943540518521648803348207619354854290787969076059265170474203200482079680136404766877617679652611682327535174212016390608658107555103054183393719700027186913354158961245998591486268846852581402900857595817303811471853325463202817521164757
encrypted=72792762778232160989381071629769766489971170790967414271032682193723004039685063639675377805724567838635943988752706743932748347933013530918279285456553768626331874756049006544553546268049053833014940495217179504587162478219970564159885619559723778613379425375733026859684952880028997538045791748027936366062


The first number is the result of print(a+b+c). The others are labeled.

#### Strategy

This is RSA encryption, so if I can get p and q, I can calculate everything else I need to decrypt. I know N = p*q, but I also get this hint output which is p**3 - q**8 + polynomial_function(x). I don’t know x, but I know it’s between 0 and 1000. If I can get the polynomial, I can brute force x.

To get the polynomial, I’ll need to know y, which is also unknown, but between 0 and 1000. Because I have the sum of a, b, and c, I can easily brute force y.

#### Get y

y is an unknown value between 0 and 1000 that is used to generate a polynomial. I’ll use code from generate_polynomial_function to check y inputs against the a+b+c outputs:

def crack_y():
for y in range(1000):
random.seed(y)
random.choice([2,3])
a = random.randint(9999, 999999)
b = random.randint(8888, 888888)
c = random.randint(7777, 777777)
if a+b+c == 1709262:
print(f"Found y: {y}")
return y


#### Crack x, q

To get x, I just need to generate a polynomial with y, and then solve the hint equation for each x between 0 and 1000, breaking when I get exactly two answers. I’ll use SageMath to solve the equation:

def crack_x(y):
y_x = generate_polynomial_function(y)
for x in range(1000):
q = var('q')
equation = hint == (N/q)**3 - q**8 + y_x(x=x)
print(f"\r{x}", end="")
try:
roots = equation.roots()
print(f"\rFound x: {x}")
print(f"Found q: {roots[0][0]}")
return x, int(roots[0][0])
except RuntimeError:
pass
raise


#### Decrypt

With q, I can now get p, then phi and d, which is used to decrypt:

y = crack_y()
x, q = crack_x(y)
p = N//q
print(f"Found p: {p}")
phi = (p-1)*(q-1)
d = pow(e, -1, phi)
pt = pow(ct, d, N)
print(f"Flag: {long_to_bytes(pt).decode()}")


Getting Sagemath installed in my VM was a pain, so I just opted for Docker:

oxdf@hacky\$ docker run -it -u root -v .:/day23 sagemath/sagemath bash
root@fbbf42ba5b86:~#


After running pip install pycryptodome, running the current script in /day23 solves the challenge:

root@fbbf42ba5b86:/day23# time python3 dec.py
Found y: 787
Found x: 692
Found q: 11766238441137316218698559717070508606046977055528210983326091441049009527090037271072944725167437804861554714103321826526867364254290450294447086896338759
Found p: 12179435756173513536974896795065942195337250662711676240184873598482470068786941630453314528951912732394640958491561461957261830568584848314092787092062267
Flag: HV23{1t_w4s_4b0ut_t1m3_f0r_s0me_RSA_4g41n!}

real    1m14.429s
user    2m33.437s
sys     0m2.805s


Flag: HV23{1t_w4s_4b0ut_t1m3_f0r_s0me_RSA_4g41n!}**

## HV23.24

### Challenge

HV23.24 Santa's Shuffled Surprise
Categories: REVERSE_ENGINEERING
FORENSIC
Level: leet
Author: JokerX

Santa found a dusty old floppy disk in his basement. He started the disk in his A500, but the QR code looks shuffled. Can you help him to read the QR code?

### Solution

I didn’t get to solve this challenge.