Skip to content

Commit

Permalink
OliCyberIT 2024 - Competizione Nazionale - Writeups
Browse files Browse the repository at this point in the history
  • Loading branch information
Gaspare Ferraro committed Jun 15, 2024
1 parent dd14391 commit f08ed25
Show file tree
Hide file tree
Showing 15 changed files with 1,307 additions and 0 deletions.
20 changes: 20 additions & 0 deletions 2024-nazionale/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# OliCyber.IT 2024 - Competizione nazionale

Writeups ufficiali delle challenge proposte alla competizione nazionale nell'edizione 2024 del progetto [OliCyber.IT](https://olicyber.it/)

## Challenges

| # | categoria | challenge | writeup | risoluzioni | autore |
| --: | :-------- | :---------------------------------------------------------------------------- | :------------------------ | ----------: | :------------------------------ |
| 1 | crypto | [Next flag](https://training.olicyber.it/challenges#challenge-645) | [writeup](crypto01.md) | 96 | Lorenzo Demeio <@Devrar> |
| 2 | crypto | [Choose your OTP](https://training.olicyber.it/challenges#challenge-646) | [writeup](crypto02.md) | 11 | Matteo Protopapa <@matpro> |
| 3 | crypto | [Keyless Signatures](https://training.olicyber.it/challenges#challenge-647) | [writeup](crypto03.md) | 0 | Matteo Rossi <@mr96> |
| 4 | misc | [Around the World](https://training.olicyber.it/challenges#challenge-648) | [writeup](misc01.md) | 11 | Lorenzo Catoni <@lorenzcat> |
| 5 | misc | [Kinda diffusion](https://training.olicyber.it/challenges#challenge-649) | [writeup](misc02.md) | 59 | Aleandro Prudenzano <@drw0if> |
| 6 | misc | [so far so good](https://training.olicyber.it/challenges#challenge-650) | [writeup](misc03.md) | 9 | Andrea Raineri <@Rising> |
| 7 | binary | [MIC](https://training.olicyber.it/challenges#challenge-651) | [writeup](software01.md) | 31 | Alberto Carboneri <@Alberto247> |
| 8 | binary | [Dragon fighters club](https://training.olicyber.it/challenges#challenge-652) | [writeup](software02.md) | 7 | Giulia Martino <@Giulia> |
| 9 | binary | [30 e lode](https://training.olicyber.it/challenges#challenge-653) | [writeup](software03.md) | 0 | Giulia Martino <@Giulia> |
| 10 | web | [Guess the flag!](https://training.olicyber.it/challenges#challenge-654) | [writeup](web01.md) | 92 | Lorenzo Leonardini <@pianka> |
| 11 | web | [!phishing](https://training.olicyber.it/challenges#challenge-655) | [writeup](web02.md) | 13 | Aleandro Prudenzano <@drw0if> |
| 12 | web | [AffiliatedStore](https://training.olicyber.it/challenges#challenge-656) | [writeup](web03.md) | 8 | Lorenzo Leonardini <@pianka> |
Binary file added 2024-nazionale/attachments/registers_restore.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 2024-nazionale/attachments/stack_state.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions 2024-nazionale/crypto01.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# OliCyber.IT 2024 - Finale Nazionale

## [crypto] Next flag (96 risoluzioni)

Un semplice oneliner, così passi alla prossima challenge in fretta!

Author: Lorenzo Demeio <@Devrar>

## Panoramica

La challenge ci fornisce un sistema di codifica, dove ogni carattere `x` è sostituito con `x+next_prime(x)`.

## Soluzione

Per invertire la codifica possiamo creare una tabella con un carattere e la sua corrispondente codifica. Notiamo che questa codifica è unica, poiché `x+next_prime(x)` è una funzione strettamente crescente.

## Exploit

```py
import gmpy2
import json

sbox = [x+int(gmpy2.next_prime(x)) for x in range(256)]
flag = json.loads(open("next_output.txt").read())

res = ''

for el in flag:
res += chr(sbox.index(el))

print(res)
```
59 changes: 59 additions & 0 deletions 2024-nazionale/crypto02.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# OliCyber.IT 2024 - Finale Nazionale

## [crypto] Choose your OTP (11 risoluzioni)

Il futuro è già qua: prova il nostro sistema rivoluzionario!

`nc chooseyourotp.challs.olicyber.it 38302`

Author: Matteo Protopapa <@matpro>

## Panoramica

La sfida chiede semplicemente un intero $n$, poi calcola un intero casuale $0\le r \le n$ e calcola lo XOR tra $r$ e la flag. Il risultato viene poi troncato alla lunghezza in bit di $n$.

## Soluzione

Dato che abbiamo completa libertà su $n$, possiamo inviare $n = 2^k$, per valori crescenti di $k$. In questo modo, $n$ ha $k+1$ bit, ma il più significativo è quasi sempre 0. Interrogare il server alcune volte ci dà abbastanza fiducia sul bit $k$-esimo (con il più a sinistra che è lo $0$-esimo), eccetto per i primi 2 bit, che possono essere indovinati poiché la flag termina con `}`.

## Exploit

```python
#!/usr/bin/env python3

import logging
import os
from pwn import remote

logging.disable()

HOST = os.environ.get("HOST", "chooseyourotp.challs.olicyber.it")
PORT = int(os.environ.get("PORT", 1337))

flag = b'\x01'
bits = '01'

r = remote(HOST, PORT)

k = 2
while not flag.startswith(b'flag'):
recovered = [0, 0]

for i in range(10):
inp = 2**k
r.sendlineafter(b'> ', str(inp).encode())
recovered_bit = bin(int(r.recvline(False).decode()))[2:].zfill(k+1)
recovered_bit = int(recovered_bit[0])
recovered[recovered_bit] += 1

if recovered[0] > recovered[1]:
bits = '0' + bits
else:
bits = '1' + bits

flag = int.to_bytes(int(''.join(bits), 2), k//8 + 1, 'big')
# print(flag)
k += 1

print(flag.decode())
```
147 changes: 147 additions & 0 deletions 2024-nazionale/crypto03.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# OliCyber.IT 2024 - National Final

## [crypto] Keyless Signatures (0 risoluzioni)

Se non hai nessuna chiave non puoi rompere nulla... Vero?

`nc keyless.challs.olicyber.it 38300`

Author: Matteo Rossi <@mr96>

## Panoramica

La challenge espone un servizio con 3 opzioni possibili:
1. firmare qualcosa per un utente a scelta, eccetto l'utente `admin`
2. verificare se stessi, fornendo il proprio nome utente e una firma di `Verifying myself: I am {user} on OliCyber.IT`, dove `user` è il nome utente scelto
3. inviare un messaggio a un utente scelto; questo restituirà la risposta dell'utente, che è sempre `ACK`, e la corrispondente firma (con la chiave dell'utente destinatario)

L'obiettivo è verificarsi come `admin`.

_Nota: questa challenge ha (almeno) 3 soluzioni possibili trovate durante lo sviluppo e il testing. Le riporteremo tutte e 3 nel writeup per completezza._

## Soluzione 1 (soluzione intended)
Analizziamo la funzione di verifica: data una firma $(s_1, s_2)$, verifica se

$$
s_2^e \equiv h(u)s_1^{h(m)} \pmod{n}
$$

dove $u$ è il nome utente per cui il messaggio viene verificato, $m$ è il messaggio e $h$ è la funzione di hash `sha256`. Ignoriamo la funzione di firma per il momento, poiché non è necessaria per questa soluzione.

Dall'opzione 3 possiamo ottenere una firma di `ACK` dall'admin. Chiamiamo questa firma $(s_1, s_2)$. Utilizzando l'extended gcd algorithm, possiamo trovare due coefficienti $a$ e $b$ tali che

$$
a\cdot e - b\cdot h(m_t) = -h(m_a)
$$

dove $m_t$ è il messaggio target (quello di verifica per l'admin) e $m_a$ è semplicemente `ACK`. A questo punto prendiamo $s_1' = s_1^e$ e $s_2'=s_2s_1^a$. La nuova coppia è una firma valida per $m_t$. Infatti sostituendo nell'equazione di verifica vediamo che il controllo diventa

$$
(s_2s_1^a)^e \equiv h(u)s_1^{b\cdot h(m_t)} \pmod{n}
$$

che, portando il termine in $s_1$ a sinistra e utilizzando la nostra identità, possiamo vedere che è lo stesso controllo per $m_a$, quindi verifica correttamente per l'utente admin.

## Soluzione 2 (trovata durante il testing da @Devrar)
Per questa seconda soluzione, ci serve sapere come funziona la funzione di firma. Dato un messaggio $m$ e un utente $u$, prende un numero casuale $r$ e la firma è $(s_1, s_2) = (r^e, h(u)^d\cdot r^{h(m)})$, dove $d$ è l'esponente privato (come in RSA) della chiave pubblica $(N, e)$ del server.

### Recuperare $h(u)^d$
Dalla prima query possiamo ottenere firme per qualsiasi utente $u$ diverso da `admin` e qualsiasi messaggio $m$. Sia $m$ tale che $h(m) = 0 \pmod{e}$ e $(s_1, s_2) = (r^e, h(u)^d\cdot r^{h(m)})$; in questo caso possiamo recuperare il valore di $h(u)^d$ calcolando

$$
s_2\cdot s_1^{\frac{h(m)}{e}} \pmod{n}.
$$

### Recuperare il valore di $r$ dalla query 3
Sia $u$ un utente per cui abbiamo recuperato $h(u)^d$ come spiegato nel passaggio precedente. Possiamo ora utilizzare la terza query per recuperare un random $r$ utilizzato per la firma di `ACK`. Sia $(s_1, s_2) = (r^e, h(u)^d\cdot r^{h(m_a)})$. Poiché conosciamo $h(u)^d$, possiamo recuperare il valore di $r^{h(m_a)}$. Poiché $\gcd(e, h(m_a)) = 1$, utilizzando l'identità di Bézout possiamo trovare $a, b$ tali che $a\cdot e + b\cdot h(m_a) = 1$. Possiamo quindi calcolare

$$
(r^e)^a \cdot (r^{h(m_a)})^b \pmod{n} = r^{a\cdot e + b\cdot h(m_a)} = r \pmod{n} = r,
$$

poiché $r < n$.

### Rompere il generatore di numeri casuali
I numeri casuali $r$ sono ottenuti attraverso il modulo `random` di Python, che utilizza l'algoritmo Mersenne Twister, che non è crittograficamente sicuro. Pertanto, una volta recuperate abbastanza osservazioni di $r$ con il passaggio precedente, siamo in grado di recuperare lo stato del Mersenne Twister e prevedere i futuri valori di $r$.

### Creare la firma
Ora che conosciamo i valori di $r$ utilizzati nella query 3, possiamo semplicemente inviare un messaggio a `admin` e recuperare $h(u_a)^d$ (l'utente admin) dividendo $s_2$ per $r^{h(m_a)}$ (cosa possibile poiché conosciamo $r$).

A questo punto possiamo ottenere una firma per il messaggio $m_t$ selezionando un valore casuale $r$ e restituendo

$$
(s_1, s_2) = (r^e, h(u_a)^d\cdot r^{h(m_t)}).
$$

Infine verifichiamo la firma e otteniamo la flag.

## Soluzione 3 (trovata durante il testing da @PhiQuadro)

Questa soluzione è simile alla prima nella struttura, ma utilizza il fatto che conosciamo il nome utente dell'admin.

Risolviamo $a\cdot h(m_t)+1 = e\cdot b$. A questo punto abbiamo che, in maniera simile alla soluzione 1, $s_1 = h(u_a)^a \pmod{n}$ e $s_2 = h(u_a)^b \pmod{n}$ formano una firma valida.

## Exploit (per la soluzione 1)
```py
#!/usr/bin/env python3

import logging
import os
from pwn import remote
from hashlib import sha256

logging.disable()

def h(m):
return int.from_bytes(sha256(m).digest(), "big")

def extended_gcd(aa, bb):
lastremainder, remainder = abs(aa), abs(bb)
x, lastx, y, lasty = 0, 1, 1, 0
while remainder:
lastremainder, (quotient, remainder) = remainder, divmod(lastremainder, remainder)
x, lastx = lastx - quotient*x, x
y, lasty = lasty - quotient*y, y
return lastremainder, lastx * (-1 if aa < 0 else 1), lasty * (-1 if bb < 0 else 1)

HOST = os.environ.get("HOST", "localhost")
PORT = int(os.environ.get("PORT", 38300))

r = remote(HOST, PORT)

pk = r.recvline()
n = int(pk.split()[-2][1:-1])
e = 65537

r.recvuntil(b"> ")
r.sendline(b"3")
r.recvuntil(b": ")
r.sendline(b"test")
r.recvuntil(b": ")
r.sendline(b"admin")
r.recvlines(2)
sig = r.recvline()
s1 = int(sig.split()[-2][1:-1])
s2 = int(sig.split()[-1][:-1])

# a*e - b*h("I am...") = -h("ACK")

to_forge = h(b"Verifying myself: I am admin on OliCyber.IT")
base = h(b"ACK")

g, c1, c2 = extended_gcd(e, -to_forge)
c1 *= -base
c2 *= -base
s1f = pow(s1, c2, n)
s2f = (s2*pow(s1, c1, n)) % n

r.recvuntil(b"> ")
r.sendline(b"2")
r.recvuntil(b": ")
r.sendline(b"admin")
r.recvuntil(b": ")
r.sendline(str(s1f).encode())
r.recvuntil(b": ")
r.sendline(str(s2f).encode())
print(r.recvline())
```
91 changes: 91 additions & 0 deletions 2024-nazionale/misc01.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# OliCyber.IT 2024 - Finale Nazionale

## [misc] Around the World (11 risoluzioni)

Il Gabibbo è appena tornato da un lungo viaggio: ha seguito un percorso interessante, queste sono le foto che ha scattato.

Il formato della flag è: `FLAG{[A-Z]+}`.

Author: Lorenzo Catoni <@lorenzcat>

## Panoramica

La challenge fornisce un file zip contenente una serie di immagini.

## Soluzione

Le immagini contengono metadati di localizzazione e tempo, che possono essere letti utilizzando, per esempio, `exiftool`.

```bash
$ exiftool -DateTimeOriginal -GPSLatitude -GPSLongitude around-the-world/02dc17ab.jpg
Date/Time Original : 2023:04:19 09:30:00
GPS Latitude : 45 deg 0' 0.00"
GPS Longitude : 14 deg 46' 55.20"
```
Dobbiamo tracciare queste posizioni e collegarle in ordine cronologico. L'unico avvertimento è che, per rendere il grafico più leggibile, è più conveniente collegare solo i punti relativi allo stesso giorno.
## Exploit
```py
import itertools
import os
import re
import subprocess
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import List
import sys
import matplotlib.pyplot as plt
if len(sys.argv) != 2:
print("Usage: python solution.py <input_dir>")
sys.exit(1)
INPUT_DIR = Path(sys.argv[1])
_, _, photos = next(os.walk(INPUT_DIR))
print(len(photos))
@dataclass
class Metadata:
time: datetime
lat: float
lon: float
metadatas: List[Metadata] = []
def degstr_to_dec(degstr: str) -> float:
"""Converts a degree string to decimal
es. 37 deg 37' 6.00" -> 37.61833333333333
"""
dms = re.split(r"deg|'|\"", degstr)[:3]
d, m, s = list(map(float, dms))
return d + m / 60 + s / 3600
for photo in photos:
print(photo)
# parse time, latitute and longitude from exif data
exif_command = f"exiftool -DateTimeOriginal -GPSLatitude -GPSLongitude {INPUT_DIR / photo}"
out = subprocess.check_output(exif_command.split()).decode().split("\n")
time = datetime.strptime(out[0].split(": ")[1], "%Y:%m:%d %H:%M:%S")
lat = degstr_to_dec(out[1].split(": ")[1])
long = degstr_to_dec(out[2].split(": ")[1])
metadatas.append(Metadata(time, lat, long))
metadatas.sort(key=lambda x: x.time)
# groupby same day and plot
for _, ms in itertools.groupby(metadatas, key=lambda x: x.time.day):
ms = list(ms)
plt.plot([m.lon for m in ms], [m.lat for m in ms], "-o")
plt.show()
```
Loading

0 comments on commit f08ed25

Please sign in to comment.