-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
OliCyberIT 2024 - Competizione Nazionale - Writeups
- Loading branch information
Gaspare Ferraro
committed
Jun 15, 2024
1 parent
dd14391
commit f08ed25
Showing
15 changed files
with
1,307 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
``` |
Oops, something went wrong.