# step 0

A la mano (wireshark) on observe des données étranges et répétées à chaque début de payload QUIC (8 premiers octets)

Après parsing du pcap et éliminitation des données répétées (si = au bloc précédent => ignore), sans autre traitement on
obtient quasiment l'intégralité de plusieurs scripts python qui constituent un mini-C2 custom, celui actuellement utilisé 
pour émettre les paquets capturés.


# step 1

Le client C2 contient une classe de configuration injectée dynamiquement, qui utilise une clé de session pour déchiffrer 
les données transmises lors de la communication avec le C2. La clé de session chiffrée est vérifiée à partir du set_session_key 
envoyé, et le chiffrement utilisé utilise le mode de padding PKCS7 ce qui permet d'obtenir un oracle lorsqu'on a la bonne 
clé pour un chiffré donné.

Or celle-ci est calculée à partir d'une seed basée sur le timestamp de la communication, qu'on peut BF car en secondes.

La configuration ainsi récupérée permet l'accès au site hébergeant toute la suite du challenge http://51.15.164.185/.


# step 2

A partir du coredump fourni on cherche à comprendre le traitement d'un format de fichier spécifique transmis via un service 
SFTP pour réaliser des actions sur un système distant. Comme il s'agit d'une diode et qu'on en aura besoin pour toute la suite 
a priori d'après les schémas fournis, ça vaut le coup de prendre un peu de temps pour construire et interagir simplement 
avec le service SFTP. De plus un compte par défaut (non privilégié) permet d'accéder au service SFTP depuis l'extérieur. Ce
compte est en mesure de transmettre des fichiers dans le dossier *in* et de récupérer les logs générés depuis le dossier
*log* et son mot de passe peut être trouvé dans la documentation fournie.

Chaque fichier correctement transmis se compose d'un en-tête et de 4 sections à des offsets définis dans l'en-tête :
* une section pour les tags du fichier
* une section pour le contenu / charge utile (package) compressé avec LZO
* une section pour la signature du package
* une section pour un secret

La lecture du coredump orientée vers l'identification de fonctionnalités un peu "cachées". Parmi celles-ci, on se rend
compte d'une étape optionnelle qui ne se déclenche que lorsque le tag _SHA256 est présent. En activant le tag DEBUG pour
un peu plus de verbosité des logs générés après transmission d'un nouveau fichier, on se rend compte que le calcul du sha256 
est en fait réalisé dans une commande shell sha256sum, car en mettant un nom de fichier '*' on obtient la sortie des sha256 
de tous les fichiers dans le dossier courant. L'injection d'un '\n' permet d'exécuter d'autres commandes et de terminer 
l'épreuve en affichant le flag.

Ce flag est nécessaire pour valider les archives et doit constituer le champ "secret" des archives envoyées pour que 
celles-ci soient acceptées. Finalement, une fois cette dernière condition remplie, l'archive est envoyée au service
suivant, diode_dst, qui constitue l'étape 3.

Il y a aussi un tag ARCHIVE qui implique que les fichiers fournis sont sauvegardés dans le dossier *archive*. Ceux-ci
ne sont pas accessibles car protégés de l'accès en lecture au travers du SFTP qui se fait dans le contexte d'une identité
différente de diode_src qui détient l'ownership des fichiers à partir de la racine du serveur SFTP.


# step 3

L'étape la plus difficile et la plus longue pour moi, mais très enrichissante j'ai adoré. J'ai l'impression d'avoir 100 
fois mieux compris des éléments de cryptographie courbe elliptique et des différents corps impliqués avec ce challenge 
que les quelques fois précédentes où j'ai dû traiter avec ce sujet (plutôt à reculon je dois bien avouer).

L'enjeu de l'étape est clair, et le challenge ne laisse place à aucun guess :
* divisé en 2 parties 3.a et 3.b
* un enregistrement audio indiquant une courbe elliptique choisie avec des paramètres a et b tenus secrets
* la méthode d'exponentiation rapide *Montgomery Ladder* utilisée afin d'augmenter les performances et de calculer des 
multiplications de point sur la courbe uniquement en tenant compte de la coordonnée x, en évitant les attaques par canal
auxiliaire

Néanmoins, si cette dernière classe d'attaque est proscrite, les papiers fournis PDF joints au challenge indiquent une classe 
d'attaque qui elle fonctionne dans les conditions décrites, à savoir les injections par faute. Et notamment, celles qui
conduisent à modifier la clé publique pour que les coordonnées sur lesquelles le calcul de produit se fasse soient invalides
dans le corps initial (dans lequel le calcul d'un logarithme discret est supposé difficile).

## step 3.a

Mais d'abord il faut commencer par la récupération des paramètres de la courbe a et b. Un script de validation permet 
d'obtenir le flag de l'étape 3.a lorsque les bons paramètres a et b ont été récupérés. Ces paramètres sont ensuite à 
fournir au programme de création / validation de signature dans le fichier *ignition.bin*.

Afin d'accélérer le calcul de multiplication scalaire dans le corps ECC considéré, une fenêtre de 3 bits du générateur 
utilisé est fournie sous la forme de 2**3 = 8 points pré-calculés (8 coordonnées X fournies).

Si on pose nos points de la fenêtre G1 = 1 . G, G2 = 2 . G et G3 = 3 . G, alors on a x1, x2 et x3 connus et donnés par 
la définition de la fenêtre, et y1, y2, y3 dépendant de x1, x2, x3, a, et b (équation définissant la courbe elliptique).
Or comme on a des multiples de G, x2, x3, y2 et y3 peuvent s'écrire comme dépendant uniquement de G = G1, a et b 
(équations liées à la somme de 2 points d'une courbe elliptique).

En cherchant à écrire y1 comme dépendant uniquement de y1, x1, x2 et x3 (après simplication de *a* et *b*), on obtient un 
polynôme à annuler en y1, ce qui permet d'obtenir plusieurs solutions potentielles pour y1, puis a et b. On injecte ces 
solutions dans le calcul de la fenêtre complète pour obtenir la solution unique.

```python
E = EllipticCurve(GF(111559192104534069353760890008511275244926479951888026807753167566013787436761), [38518268011844958383984737875894065125464475257272060615078072556169774890831, 81467430943253026863114675468814898031035215312166850155424429235431154214558])
Pub = E(85085914869082369330525395072244563102593688729452879813198283169198884224575, 82414806916802265148636463224500503887441124050399035289632912071330105090025)
```

## step 3.b

Une fois en possession des paramètres de la courbe, il faut maintenant être en mesure de forger une signature valide. Le
programme lobster256 est celui qui est appelé par diode_dst lors de la réception d'une archive en provenance de diode_src.
Il implémente les fonctions de génération de clé, signature et vérification. Seule la fonction de signature est utilisée.

Afin de confirmer la clé publique utilisée, il est possible de rejouer les messages archivés. Seul un message dispose d'une
signature valide, qui correspond bien à la clé publique indiquée dans le script lobster256.sage. On peut également observer
l'émission antérieure d'un paquet changeant la clé publique vers cette valeur, sans la clé privée associée malheureusement
(ou heureusement pour que le challenge soit intéressant). On dispose aussi d'un paquet qui réinitialise la clé publique et
privée, avant que l'attaque survienne. Ce couple clé publique / clé privée est valide et permet de vérifier 2 autres paquets.

Finalement, la stratégie va consister à réémettre ce paquet mais avec une signature modifiée pour être valide dans le 
contexte actuel.

La mention d'une injection de faute changeant la clé publique P conduit naturellement à regarder un peu plus en détail le
fonctionnement du programme de vérification de signature. Il s'avère que seule la taille initiale du bloc de données en
base64 de la signature est vérifiée, et non pas la taille finale. Comme les choses sont bien faites, il s'avère que le 
remplacement des octets de padding (=) par des caractères autres du charset b64 aboutit à une écriture hors limite dépassant
de la signature vers la clé publique P sur 1 octet. Il s'agit de l'octet de poids faible de la clé publique. Cela permet
l'obtention des conditions de l'injection de faute décrites dans le PDF.

Une courbe ellptique E sur un corps K = GF(p) dispose généralement de "twists" quadratiques Ed isomorphique dans K2 = K (sqrt(d)) 
l'extension quadratique de K (où d est un nombre sans racine dans K) qui constituent des courbes elliptiques différentes, 
pour lesquelles les paramètres de sécurité ne sont pas les mêmes que ceux de E.

Pour tout point x de K, il existe y tel que (x, y) est soit dans E, soit dans Ed. Si (x, y) n'est pas dans E, alors (x, y) 
est dans Ed. L'idée est d'utiliser ce principe pour faciliter le calcul du logarithme discret dans Ed si celui-ci est 
associé à un ordre friable / smooth.

Or #E + #Ed = 2 * (p+1) donc ici #Ed = 111559192104534069353760890008511275244850969430123083122289260340585038297653
qui se factorise en 235989105379 * 274321494283 * 596117795627 * 50255902060627 * 59797588771913 * 961946097496477

Les conditions sont donc réunies (50 bits pour le plus grand facteur) pour envisager le calcul du logarithme discret
si on obtient un élément dans Ed. Si on considère la courbe E2 = EllipticCurve(K2, [a, b]), dont l'ordre est celui ci-dessus,
et la méthode de calcul du produit d'un élément Q = (Qx, Qy) de E par un scalaire basée uniquement sur x, alors la même méthode
de calcul appliquée sur l'élément (Q'x, Q'y) avec Q'x dans K mais (Q'x, Q'y) non dans E aboutit à un résultat qui n'a pas 
vraiment de sens dans E2 mais qui produit un nombre qui peut être dérivé en une coordonnée valide y du résultat du produit
dans E2 à partir de ylifted = E2.lift_x(Q'x) et de l'élément initial dans E et de sa coordonnée Qy.
(Bon là il faudrait mettre au propre et tout réécrire pour que ça soit plus clair, mais c'est un WU quick & dirty. Quoiqu'il 
en soit je ne m'étais pas aperçu au départ du fait que le calcul de multiplication n'avait pas de sens dans E2 ni dans E si 
on prenait un x du point multiplié qui ne soit pas un élément de E).

Par construction, la clé publique P = k . G = (Px, Py) est un point de E. Mais par injection de faute, il est possible 
après quelques essais en changeant le dernier octet de Px d'obtenir un point P' qui appartient à E2 et non à E. Ce point
est donc "factorisable" et il est possible de retrouver k tel que k . G' = P' (où G' est considéré dans E2 avec la même 
abscisse que G dans E: G' = (G'x, G'y) = (Gx, E2.lift(Gx))).

Le problème de base à résoudre est celui de la forge d'une signature ECKCDSA (r, s) valide pour un message donné *m*. Si
on fixe un "secret" factice *k*, r est immédiatement fixé à H(k), fixant la valeur de *e* = xor(H(k), H(m)).
L'injection de faute se faisant sur P la clé publique, la valeur de *e . G* reste inchangée et intouchable, et le produit
reste dans E (il est aussi tel quel dans E2).

En écrivant l'équation de vérification de signature que doit vérifier le point de E2 d'abscisse x (après avoir pris en 
compte toutes les subtilités évoquées plus haut, et en injectant les valeurs dérivées de x dans le calcul des produits 
par un scalaire du faux élément de E suite à l'injection de faute), on obtient un polynôme qui est susceptible d'avoir zero ou 
plusieurs racines. Dans le cas où des éléments de K sont des racines valables, on peut tenter de résoudre le problème
du logarithme discret pour le point de E2 construit à partir des équations précédentes, donnant *s*.

L'utilisation de PariGP via *pari.elllog* permet au calcul de se terminer en un temps raisonnable (une vingtaine de minutes
chez moi, même si cela dépend des points choisis).

On peut alors envoyer le message pour réinitialiser la clé à une valeur connue maintenant qu'on a réussi à forger une 
signature valide.


```python
base_public_key_x = 0x9b3d009d95fcef43db6a31a95cc2a9f289afa1f78e9d6f3568f24dfc18b85bae
target_key_byte = 19
target_public_key_x = (base_public_key_x >> 8) << 8 + target_key_byte

def craft_message():
    raw_data_to_send = load('prod_maj_key.sa')  # reset key message
    msg = unpack_data(raw_data_to_send)
    computed_signature = (0x75c4d68ce1b4de1cdf3ab5214d0d4ca1db3b6c05c643b8bd5d3ec6013f45c18b, 0x9b2c27219a9c6d328f3b642225f9b9bdd4f84942bd8fba843b23cbd4ecc4f355)
    r, s = computed_signature
    msg.signature = base64.b64encode(
        int(r).to_bytes(32, byteorder="big") +
        int(s).to_bytes(32, byteorder="big") +
        b'\x02' + bytes([target_key_byte])
    )
    return msg
```

A noter qu'une interface graphique VNC en lecture permet de vérifier ce qui se passe en 3 espaces distincts : en bas la 
réception et le parsing de nouveaux messages / paquets reçus via le SFTP, qui permet de debogguer les messages envoyés
et notamment les erreurs de signature, et plus tard d'obtenir la visualisation du leak info pour avoir un retour malgré
le caractère "diode"; en haut à gauche l'espace affichant l'état du système d'arme et le flag si celui-ci est désactivé;
en haut à droite l'espace pour l'étape 4 et l'affichage des erreurs rencontrées lors des tentatives d'exploitation de 
cette étape.


# step 4

Pour cette dernière étape (et l'obtention du flag final), il faut compromettre un service avec lequel les interactions 
sont limitées à cause de la diode. Les fonctions suivantes sont notamment accessibles depuis l'étape 3 :
* création de la socket vers le service TCP exposé par le binaire de la step4 (**WEAPON_OPEN_SESSION**) ;
* envoi de données arbitraires sur la socket, suivi de la réception de la réponse (query / response uniquement, via
**WEAPONS_MSG**) ;
* fermeture de la session en cours (via **WEAPON_CLOSE_SESSION**).

De plus, le binaire est protégé par une implémentation custom de shadow stack qui empêche (a priori) d'effectuer des 
retours à des adresses non autorisées.

Peu de fonctionnalités sont accessibles :
* Authentification (nécessaire pour la suite, étape initiale forcée) ;
* Obtention de la version du programme (chaîne de caractère simple) ;
* Impersonnification d'un utilisateur ;
* Le reste des fonctionnalités sont transmises directement au système d'arme (get / set target, arm / disarm).

On valide le challenge du SSTIC si on est en mesure d'envoyer un paquet DISARM au système d'arme final.

Par ailleurs, un système de privilège est mis en place pour autoriser ou non l'utilisation de chacune de ces fonctionnalités.
Ce système prend la forme d'un bitfield qui, lorsqu'on cherche à exécuter une des fonctions plus haut, & logique le champ
privilège de l'utilisateur actuel. Celui-ci est déterminé après lecture de la "base de données" des utilisateurs au moment
de l'authentification ou de l'impersonnification. Chaque utilisateur dispose ainsi d'une structure [username, mot de passe
haché, privilèges, liste d'ID d'impersonnification / de groupes] ; la base de données étant constituée d'une liste de 
structures identiques, lue au démarrage du programme et stockée dans une variable globale dans la section mappée .data du binaire.

Evidemment, l'utilisateur actuel (SSTIC_USER, dont le mot de passe est disponible dans la documentation) ne dispose pas du
privilège DISARM, mais il dispose de quasiment tous les privilèges restants et notamment celui d'impersonnification.

A noter que dans le jeu de test fourni, il existe un chemin d'impersonnification en chaîne qui permet d'obtenir le privilège
DISARM, ce qu'on peut garder pour plus tard. Les utilisateurs de la base de données de production ne sont pas les mêmes
et sont presque intégralement changés comme indiqué dans l'énoncé du challenge, ce qui invalide cette piste a priori (sauf
à être en mesure de réaliser un shellcode équivalent capable de parcourir la base de données et d'identifier la chaîne 
d'impersonnification à mettre en place).

La lecture des données et l'écriture de la réponse prend la forme d'une structure de paquet pouvant contenir jusqu'à 0x40
champs TLV (type length value) et d'un type de commande qui détermine la fonction à appeler. Chaque élément est alloué 
dynamiquement (d'abord le paquet, puis chaque TLV) à partir de la taille globale du paquet lue sur 4 octets à chaque début
de requête.

Au départ j'ai rapidement identifié un défaut de réécriture d'octet nul au niveau du impersonate qui place un octet nul
à l'offset de la taille du buffer fourni en entrée qui peut être alignée avec les metadata de tas du bloc suivant. La 
ressource classique *Glibc Adventures: The Forgotten Chunks* permettrait potentiellement d'arriver à ses fins en se basant
uniquement sur cette primitive, néanmoins vu toutes les conditions et cleanup qui sont réalisés, combinés avec l'absence de
possibilité d'avoir plusieurs socket en parallèle dans le setup cible, rend la tâche ardue.

Néanmoins, après avoir conservé cette piste dans un coin, la shadow stack mise en place incite à regarder du côté d'éventuels 
buffer overflow, ce qui mène rapidement à l'identification d'une fonctionnalité suspecte de calcul de crc au moment de la 
réception de données. Il est en effet possible, outre la liste des TLV passés en argument, d'ajouter des données complémentaires
au paquet aboutissant à la réécriture des registres r12, r13, rbx, rbp, r15 et rip. Cela provient du calcul du crc qui réserve 1
octet au lieu de 2 sur la pile, permettant de réécrire 0x80 octets au lieu des 0x40 octets initialement alloués pour le 
calcul du CRC.

En raison du mécanisme de shadow stack, il n'existe que 2 adresses de retour qui sont considérées comme valides par le programme 
et qui permettent d'éviter de déclencher le mécanisme de protection en cas de fourniture d'un pointeur d'instruction arbitraire.
En retournant à la seconde adresse autorisée, on obtient une désynchronisation de la pile et une machine à état nouvelle dans 
la boucle principale du programme. Les modifications des registres r13 (le paquet d'entrée) et r12 (le paquet de sortie) sont 
les plus intéressantes et permettent de réaliser des opérations non prévues.

On peut également noter que le nouveau pointeur de pile reste situé plus haut que la valeur initialement attendue au retour
de la deuxième fonction, ce qui permet d'obtenir une fuite mémoire assez rapidement. En effet, en cas d'erreur 403, le 
programme construit un paquet de retour de taille 0x40 après avoir copié le nom de l'utilisateur, qui pointe normalement
sur le haut de la pile. Après désynchronisation, le pointeur obtenu lors de la construction d'un paquet 403 est situé à un
emplacement qui permet l'obtention :
* d'un pointeur de tas (celui correspondant aux données envoyées par l'utilisateur et allouées sur le tas) ;
* d'un pointeur de pile (pointant vers une structure de type paquet IN) ;
* d'un pointeur d'instruction (celui avec l'adresse modifiée de retour).

Ces trois fuites de données sont suffisantes pour obtenir et localiser toutes les zones importantes exceptées une seule :
la structure globale, allouée sur le tas, qui contient les privilèges de l'utilisateur actuel (entre autres). Cette dernière
est allouée dans la main arena, contrairement à toutes les allocations liées au traitement des données utilisateur, qui 
sont effectées dans des arena par thread. Ainsi, on ne peut pas envisager (sans leak plus précis) la réécriture immédiates
des données de cette structure. Initialement, j'ai donc visé l'obtention d'un leak complet, qui aurait permis d'obtenir
cette valeur.

Avec du heap feng shui et beaucoup d'abnégation, j'ai finalement pu aboutir à la création d'un layout de tas à adresse
connue et parfaitement contrôlé, avec l'idée de pointer sur l'emplacement sur la pile initiale pour modifier le pointeur
de version et obtenir un leak arbitraire permanent, aboutissant au flag de l'étape stocké comme premier utilisateur dans
la base de données.

Néanmoins, pour être réalisé entièrement, j'avais besoin de relancer plusieurs sockets pour repartir d'un état de la pile
"propre". J'ai fini par identifier un moyen de provoquer une attente infinie, permettant d'éviter le segfault naturel 
survenant en cas de fermeture de la socket à la place, la pile étant irrécupérable.

Malheureusement, en testant cela dans les conditions du challenge, je me suis aperçu que c'était une impasse, car le message
de fermeture de session provoque dans tous les cas la fin du thread associé à l'exécution courante, et le segfault, empêchant
de conserver les valeurs fuitées petit à petit.

Las, n'ayant pas envie de me replonger dans un heap feng-shui complexe ni d'exploiter le null byte écrit au niveau du 
impersonate, et n'ayant pas de contrainte particulière pour finir le challenge dans les conditions prévues, j'ai cherché
si il était possible de court-circuiter le challenge en envoyant directement le paquet permettant l'obtention de l'adresse
email finale.

Pour cela, j'ai visé la réalisation du changement de l'adresse des données de la base de données, stockée à un emplacement
connu par rapport à la base du .text fuitée. Malgré tout, cela ne reste pas simple à réaliser en raison notamment des opérations
de cleanup qui surviennent à chaque message; ainsi que de spécificités du challenge.

Toutefois ça reste possible de réaliser les opérations suivantes :
* modifier r13 (le paquet initial) plusieurs fois pour obtenir les conditions où une erreur 403 est toujours obtenue,
tout en se "dirigeant" vers la zone contenant le pointeur de la liste des utilisateurs ;
* écrire et conserver l'adresse d'une fausse liste de 2 utilisateurs créée pour l'occasion, avec notre utilisateur actuel
comme second utilisateur de la liste, avec le privilège DISARM ;
* lancer la commande d'impersonnification avec le même nom d'utilisateur que mis dans le fake record, permettant l'obtention
du privilège recherché ;
* réinitialiser r13 à sa valeur nécessaire pour conserver le contrôle du type de commande, qui est perdu précédemment et
paramétré à une seule valeur choisie (ici le commande type impersonnate) en raison de la désynchronisation de la pile et 
des spécificités du challenge

Une fois tout cela en place, l'émission d'un paquet DISARM est fonctionnelle et l'email obtenu via l'interface VNC :)
(Pas de flag 4 obtenu donc ici)

Je dois dire que la manière de faire et l'idée générale du challenge m'a beaucoup plu, merci aux concepteurs et créateurs !
