SecurityInsider
Le blog des experts sécurité Wavestone

Invoke-CleverSpray - Jamais 1 sans 3


Avant l'existence du niveau fonctionnel Windows Server 2003, lorsqu'un utilisateur tentait de s'authentifier à l'aide d'un mot de passe n'étant pas le sien, son nombre de tentative d'authentification échouée (représenté par l'attribut "badPwdCount") se voyait automatiquement incrémentée. 

Depuis l'introduction du niveau fonctionnel Windows Server 2003, lorsqu’un utilisateur essaie de s'authentifier à l'aide d'un de ses deux précédents mots de passe, l'attribut "badPwdCount" n'est plus incrémenté. D'une part, cette fonctionnalité permet de limiter les verrouillages de comptes utilisateurs dues à des tentatives de connexion émises par des applications suite à une modification de mot de passe non répercutée sur ces dernières (Exchange, Skype, etc.).  D'autre part, cette évolution a pour objectif de limiter le nombre de verrouillages de comptes utilisateur et ainsi les interventions futiles des équipes de support. En effet, les mauvaises tentatives d'authentification émanant d'utilisateurs légitimes sont plus susceptibles d'être la cause de tentatives d'authentification à l'aide de mots de passe précédemment valides.

Fonctionnement du mécanisme de verrouillage de compte utilisateur

Différents paramètres interviennent au sein du mécanisme de verrouillage de compte utilisateur :


Attribut Active Directory Propriété PowerShell Paramètre de la stratégie de groupe Périmètre
lockoutThreshold LockoutThreshold Seuil de verrouillage Domaine
lockoutDuration LockoutDuration Durée du verrouillage Domaine
lockoutObservationWindow LockoutObservationWindow Fenêtre d’observation du verrouillage Domaine
pwdHistoryLength PasswordHistoryCount Nombre de mots de passe antérieurs à conserver Domaine
lockoutTime AccountLockoutTime - Utilisateur
logonCount - - Utilisateur
pwdLastSet PasswordLastSet - Utilisateur
pwdProperties ComplexityEnabled Mot de passe doit respecter des exigences de complexité Utilisateur
badPwdCount BadLogonCount - Utilisateur
badPasswordTime LastBadPasswordAttempt - Utilisateur


La majeure partie de ces attributs disposent d'un nom autoporteur. Néanmoins, il convient de préciser que la fenêtre d'observation du verrouillage ("lockoutObservationWindow") ne représente pas la durée pendant laquelle les tentatives d'authentification infructueuses doivent avoir lieu pour verrouiller un compte, ni le temps nécessaire à la réinitialisation de l'attribut "badPwdCount" si aucune tentative infructueuse de connexion n'est conduite. Au contraire, c'est la durée nécessaire à la réinitialisation de l'attribut "badPwdCount" depuis la dernière mise à jour de l'attribut "badPasswordTime". 

Par ailleurs, les attributs "badPwdCount" et "badPasswordTime" ne sont pas répliqués au sein du domaine mais seulement sauvegardés sur le contrôleur de domaine sur lequel l'utilisateur essaye de s'authentifier. Néanmoins, ces attributs sont synchronisés sur le contrôleur de domaine disposant du rôle FSMO d’émulateur de contrôleur principal de domaine (ou PDCe).

Seuls les protocoles Kerberos et NTLM utilisés lors d'une authentification via mot de passe ou Smart Card bénéficient de cette fonctionnalité (sous réserve que le PDCe soit joignable par le contrôleur de domaine gérant la demande d'authentification). 

Jamais un sans trois

Du point de vue d'un attaquant, cette nouvelle fonctionnalité offre la possibilité d'attaquer non seulement le mot de passe actuel d'un utilisateur mais aussi ses deux précédents via la vérification de l'incrémentation de l'attribut "badPwdCount" sur le PDCe suite à une tentative d'authentification. En effet, si la tentative d'authentification échoue mais que l'attribut "badPwdCount" ne se voit pas incrémenter, alors un mot de passe précédemment valide vient d'être découvert.
La découverte d'un mot de passe précédemment utilisé par un utilisateur permet à un attaquant d'identifier une éventuelle structure de création de mot de passe employée par cet utilisateur, pouvant parfois conduire à la découverte de son mot de passe actuel. 
D'autre part, il est fréquent que des utilisateurs réutilisent leurs anciens mots de passe ; un précédent mot de passe découvert pourrait donc être réemployé par la suite par ce même utilisateur.
Enfin, les anciens mots de passe de domaine découverts peuvent parfois être encore valides sur certains applicatifs se reposant sur un référentiel n'imposant aucun changement de mot de passe.

Invoke-CleverSpray - Script PowerShell automatisant la découverte de mots de passe (actuel, N-1 et N-2)

Un script a été développé dans le but d'identifier, outre les mots de passe actuels des utilisateurs d'un domaine Windows, les mots de passe présents dans les historiques des mots de passe utilisateur :



Le schéma de fonctionnement de ce dernier est le suivant : 
  • Récupération de la liste des utilisateurs du domaine Windows ou au sein d'un fichier passé en paramètre ;
  • Pour chacun des utilisateurs, le contrôleur de domaine disposant du rôle de PDCe va être contacté afin de connaître la valeur initiale de l'attribut "badPwdCount" de l'utilisateur, puis, si cette dernière est inférieure à un seuil défini par l'attaquant, une tentative de connexion à l'aide d'un mot de passe spécifié en paramètre au script (ou présent au sein d'une liste de mot de passe passée en paramètre) va être tentée ;
  • Si l'authentification est réussie : 
    • Le mot de passe correspond au mot de passe actuel de l'utilisateur ciblé ;
  • Si l'authentification échoue : 
    • La valeur de l'attribut "badPwdCount" va alors être analysée : 
    • Si cette dernière n'a pas été incrémentée, le mot de passe essayé correspond à un des deux mots de passe précédemment défini par l'utilisateur
    • Si cette dernière a été incrémentée, alors le mot de passe ne correspond ni au mot de passe actuel ni a un précédemment mot de passe de l'utilisateur ciblé. Le script va donc passer à l'utilisateur suivant afin de poursuivre l'attaque.

Il est à noter que le seuil de verrouillage d'un compte utilisateur ne peut être collecté par un utilisateur standard du domaine. De fait, il convient par sécurité d'exécuter le script avec une valeur limite de l'attribut "badPwdCount" faible afin d'éviter tout verrouillage de compte utilisateur.


François LELIEVRE

Conférence S4x19: compte-rendu



Voici notre compte-rendu de la conférence S4, dédiée à la sécurité des SI industriels, se tenant à Miami Beach du 14 au 17 janvier.

Vous pouvez également retrouver un compte-rendu de cette conférence dans le podcast NoLimitSecu:




Keynote

Pour lancer la conférence, Dale Peterson nous parle du chemin parcouru. Selon lui, ce n’est que très récemment, en 2015, que le sujet de la sécurité des systèmes industriels a été pris en main. Cela se manifeste notamment par la mise en place, dans la plupart des organisations, d’audits et de suivi de la conformité. Il est devenu commun de suivre le niveau de sécurité de ses systèmes industriels, de s’assurer d’un niveau d’ “hygiène” minimum.
Mais est-ce bien suffisant ? Selon Dale, nous ne sommes pas au sommet de la montagne; ce n’est que le début.

Il faut poser de meilleures questions. Par exemple : Comment réduire l’effort sécurité qui doit être porté par les équipes OT ?

Et c’est bien le thème de cette édition de S4: présenter des idées novatrices, dont certaines pourraient étonner voire choquer, mais qui font réfléchir.

Digital Ghost: Real Time, Active Defense For ICS

Lors de cette présentation, deux intervenats de General Electric présentaient leur vision d’un des concepts de l’industrie 4.0, le Digital Twin

https://twitter.com/lsamain/status/1085183783786037248

Les catalyseurs de croissance majeurs d’aujourd’hui ont d’abord été présentés: transformation digitale, intelligence artificielle, mais aussi la capacité à s’adapter à un marché changeant rapidement. Pour cela, l’exemple d’Amazon a notamment été explicité. Au départ basé sur la capacité à faire des prédictions sur des groupes de personnes (femmes enceintes, célibataires de moins de 25 ans, …), le ciblage commercial s’est recentré sur l’individu. En analysant les parcours d’achat, les recherches, on peut alors proposer des produits d’intérêt pour une personne unique. Pour aller encore plus loin, c’est la notion de programmation neuro-linguistique (PNL) qui est utilisée. On va ainsi proposer à une personne identifiée comme femme enceinte des couches, mais aussi des livres sur la maternité, un appareil photo pour prendre des photos de l’enfant, on anticipe ses envies futures.

Ces notions orientées marketing se reflètent également sur les systèmes industriels: optimisation en continu, prédiction de panne & maintenance prédictive, etc…

Pour cela, GE peut modéliser une copie numérique (Digital Twin) de ces turbines, voire d’une installation d’un client. En modélisant à la fois le processus physique et les SI associés (capteurs, mais aussi supervision du procédé), on peut alors simuler le comportement “normal” et détecter lorsque l’on s’en éloigne. Il devient même possible d’aller plus loin et de réaliser le scénario suivant :
des données incohérentes sont remontées par 6 capteurs sur un ensemble de 15. On utilise alors le Digital Twin et la modélisation du processus pour injecter dans la supervision du procédé les valeurs dérivées des capteurs “sains”.
GE affirme avoir réalisé des simulations et arriver à une fiabilité de plus de 99%.

Bien qu'impressionante et engageante, ce type de simulation ne nous apparaît possible que sur des systèmes très standardisés, et sur des procédés modélisables physiquement. Cette stratégie ne semble pas applicable à tous les domaines industriels, comme le manufacturing par exemple.

Is The Purdue Model Dead? And What's Next?

Le modèle de Purdue a été créé dans les années 1990 pour décrire les architectures d’entreprise, et correspond à la pyramide CIM (Computer Integrated Manufacturing).


Modèle PERA de Purdue
(Albert Jones [Public domain], via Wikimedia Commons)

Le modèle de Purdue est notamment utilisé dans l’ISA95, et définit comme suit les différents niveaux:
  • Niveau 0 — La procédé physique
  • Niveau 1 — Les capteurs et effecteurs
  • Niveau 2 — La supervision et le contrôle du procédé (systèmes SCADA, DCS..)
  • Niveau 3 — MES (Manufacturing Execution System)
  • Niveau 4 — Logique métier,  ERP


Avec l’arrivée de l’industrie 4.0, et par conséquent d’architectures intégrant de plus en plus de services Cloud, l’intérêt et surtout la pertinence de ce modèle peuvent être remis en cause.
Un débat, animé par Dale Peterson, a permi à deux personnes au point de vue opposé d’exposer leur point de vue.
D’un point de vue de l’architecture réseau, le modèle est effectivement bousculé par les récentes innovations et notamment l’adoption de capteurs connectés et du cloud.
Cependant, ce modèle reste pertinent du point de vue conceptuel. On peut citer la gradation dans les échelles de temps à chaque niveau, qui ne change pas : plus on monte dans les niveaux, plus les échelles de temps sont élevées.
On peut donc considérer que la convergence IT/OT n’a pas tué le modèle de Purdue, bien que la représentation graphique ne soit plus réellement pertinente. On peut faire l’analogie avec le modèle OSI de l’ISO : bien que ne reflétant aucunement la réalité technique des communications TCP/IP, il reste utile pour conceptualiser l’encapsulation protocolaire et le fonctionnement global des communications.

Layered Blueprints: A Method for Engineering OT Security

Sarah Fluchs a commencé sa présentation avec la déclaration suivante : “L’ingénierie en sécurité industrielle n’est pas de l’ingénierie.
Et si nous contredisions cette affirmation ?

Les principaux objectifs de sécurité sont la confidentialité et l’intégrité des données mais ce n’est pas du tout pertinent pour les systèmes industriels. En effet, une phrase que l’on entend souvent chez les industriels est la suivante : “Mon usine doit fonctionner”. Les objectifs sont donc imprécis.

L’idée des layered blueprints est de fournir une méthode de sécurité d’un point de vue OT. 4 niveaux doivent être pris en compte :
  • Les fonctions
  • Les risques
  • Les prérequis 
  • L’implémentation

https://twitter.com/JozefSulwinski/status/1085216716483977216


Ces 4 niveaux ont été détaillés lors de la conférence. 
Les fonctions permettent de clarifier les préconditions et les objectifs :
  • Le modèle réseau : un schéma réseau trop détaillé n’est pas nécessaire ; seule une vue globale avec une représentation de chaque type d’équipement est nécessaire
  • L’analyse des communications : la programmation des automates, l’exploitation du procédé industriel, les connexions entre sites distants 

Les risques permettent d’analyser les incertitudes ou les effets sur les objectifs :
  • Identification des incertitudes
  • Évaluation des incertitudes ou analyse des risques 

Les deux derniers niveaux permettent d’apporter les solutions de sécurité :
  • Prérequis : la première étape est de déduire des risques les prérequis en matière de sécurité
  • Implémentation : le seconde étape est de concevoir et implémenter les solutions de sécurité
Afin de mettre en oeuvre ces 4 niveaux, un modèle lisible par des machines est nécessaire. Par ailleurs, il est préférable d’utiliser des outils spécifiques OT.
Finalement, l’ingénierie en sécurité industrielle est une forme d’ingénierie et tout le monde peut y contribuer.

PASTA: Portable Automotive Security Testbed with Adaptability

Tsuyoshi Toyama a présenté “PASTA”, un environnement de démonstration/test de la cybersécurité des systèmes embarqués automobiles.
Le système est contenu dans une mallette aux dimensions standard, et contient plusieurs ECU, des écrans simulant les différents systèmes du véhicule (phares, volant, tachymètre…).




Il est également possible de connecter cette valise à un simulateur de conduite, avec un volant et des pédales, ou via Bluetooth à un modèle réduit de véhicule pour visualiser les actions réalisées.
Bien que développé par des employés de Toyota InfoTech, ce modèle n’a pas pour vocation de simuler un véhicule Toyota. L’ensemble des ECU, et des codes CAN ID utilisés ont été défini spécifiquement pour ce démonstrateur.

Malheureusement, les créateurs de ce kit ne se sont rendu-compte de la difficulté de publier leurs travaux qu’après leur présentation à la BlackHat Europe, et n’ont pas encore obtenu l’autorisation finale.

Persisting In Level 1 - The Building Is Alive

Lors de cette présentation, l’oratrice a exposé sa démarche de recherche pour identifier des vulnérabilités sur les systèmes de gestion technique des bâtiments (caméras, lumières, climatisation, contrôle d’accès…).

L’objectif était de réaliser une attaque permettant de laisser un minimum de traces et de pouvoir persister sur le réseau. Pour cela, une démarche classique a été employée :

  • Recherche d’informations
  • Identification de vulnérabilités
  • Compromission
  • Persistence

Après avoir acheté du matériel représentatif du marché, la société a permis à des étudiants de rechercher des vulnérabilités sur ceux-ci. Plus d’une dizaine de vulnérabilités inconnues ont été identifiées : 

  • Présence d’identifiants en dur dans les firmwares
  • Dépassement de tampon
  • XSS sur les interfaces web
  • Absence d’authentification

On regrettera que cette présentation n’ait pas été faite par une des personnes ayant techniquement identifié les vulnérabilités présentées.

Triton (We Can’t Say)

Cette conférence sur Triton présentée par Julian Gutmanis n’était pas une présentation sur le détail de l’attaque mais plutôt un bilan sur les enseignements de l’attaque et en particulier comment se préparer à des incidents similaires en tant qu’industriel.

https://twitter.com/infowaropcenter/status/1085754071317966848


Une première panne a été détectée en Juin 2017, un samedi soir. Un seul contrôleur a été impacté et le constructeur a été appelé pour enquêter. Aucun incident particulier n’a été détecté et les opérations ont continué.
Deux mois plus tard, en août 2017, un vendredi soir, une nouvelle panne a été détectée, impactant cette fois-ci 6 contrôleurs. Une session RDP inhabituelle a été identifiée ; une équipe de réponse à incident est intervenue et le constructeur a conseillé la mise en oeuvre d’actions de remédiation.

Des actions classiques de réponse à incident ont été réalisées : image de disque, capture réseau, logs, etc. L’attaquant a pu compromettre le réseau industriel en créant un script Python sur la console opérateur et en exploitant un défaut de configuration de la DMZ.

Suite à la phase de réponse à incident, la panique a pris le dessus : l’environnement industriel complet pourrait être compromis et la confiance dans le système était perdue. Des actions de confinement ont ensuite été entreprises pour isoler le système.

Avec le recul, la cible a eu de la chance avec cette attaque mais cela a néanmoins coûté de l’argent et des manques de sécurité ont été identifiés. Notamment, la première investigation n’était pas suffisante et cela a permis à l’attaquant de parfaire son attaque. De plus, les premières actions recommandées par le constructeur ont mis le reste du système dans une position à risque.
A plusieurs points de vue, l’attaque aurait pu être évitée, identifiée ou stoppée plus tôt.

Les points clés à retenir sont les suivants :
  • Il est important de maintenir une culture cyber sécurité : les anomalies doivent être considérées d’un point de vue cyber
  • Il est important de s’assurer que les rôle et responsabilités sont correctement définis
  • Il est important de prévoir un plan de repli en cas d’attaque
  • Le mot de la fin est certainement le plus important : demander de l’aide avant d’en avoir besoin !

AI + Human

L’utilisation massive de l’intelligence artificielle va modifier considérablement la façon dont nous travaillons. De nouveaux postes/métiers et de nouveaux challenges existent pour ce secteur. 

Selon James Wilson, l’intelligence artificielle va pousser la croissance. Cependant il est nécessaire de garder l’humain. L’IA et l’humain ensemble vont permettre d’aller plus loin.  L’exemple du diagnostic médical a été présenté : 92% des diagnostics étaient corrects pour l’IA, 96% pour les humains, et la combinaison des deux permettait d’atteindre 99,5%.

Trois nouveaux métiers collaboratifs vont voir le jour avec l’IA :
  • Trainers : les robots vont devoir être formés (à avoir de l’empathie par exemple). Dans nos secteurs, nous voyons donc apparaître des développeurs de chatbots ou des data scientists. 
  • Explainers : il faut pouvoir comprendre le comportement des IA et s’assurer que ces systèmes ne sont pas des boîtes noires. Ces nouveaux métiers vont permettre d'accroître la confiance dans l’IA.
  • Sustainers : il faudra également être en mesure de maintenir la sécurité de ces systèmes et des postes comme ingénieur en sûreté de l’IA commencent à apparaître.  

Cinq nouveaux challenges vont également devoir être pris en compte :
  • L’IA peut apprendre à écrire son propre code
  • L’IA a la capacité d’imiter une voix humaine en moins de 60 secondes
  • L’IA peut générer des “deep fakes” 
  • L’humain peut tromper l’IA
  • L’IA peut lire dans les pensées

Enfin, l’impératif concernant l’intelligence artificielle est de développer de nouvelles compétences : des compétences techniques, des compétences pour résoudre des problèmes complexes, renforcer la créativité, etc.

Risk, utility and the public good

Selon Eireann Leverett, nous sommes à moment charnière en termes de cyberassurance : les premiers cas vont déterminer ce que ça va devenir mais l’assurance cyber va rester.

On pense souvent qu’on ne peut pas assurer les risques cyber car ils ne sont pas accidentels mais “adversaires”, cependant il existe également des assurances pour d’autres types d’événements adversaires: guerre, terrorisme, kidnapping, piraterie.

On peut quantifier certains risques cyber, par exemple les rançongiciels. On peut aussi calculer quel serait le plus gros DDoS théorique (taille des liens, nombre de réflecteurs DNS, etc.). Et lorsqu’il est possible de quantifier, il est possible d’assurer. 

En Grande Bretagne, il est par exemple possible de s’assurer contre un acte de cyber-terrorisme dans le secteur du nucléaire. 

Debate: Are Specialized OT Tools and Talent Required to Detect Attacks on ICS?

Ce débat, animé par Dale Peterson opposait les deux points de vue :
Steve Miller de FireEye soutenait que des outils ou compétences spécifiques OT n’étaient pas requis pour détecter des attaques sur les systèmes industriels.
Ben Miller de Dragos soutenait le contraire.

L’argument principal de Steve était qu’il n’y a pas réellement d’attaque purement industrielle. L’attaque commence toujours par des éléments IT. Il a notamment parlé du cas Triton où seuls des outils IT ont été utilisés pour l’attaque (sysinternals, nmap, meterpreter, etc.)

Ben assure au contraire que de nombreux outils classiques, comme les scans de vulnérabilités, ne sont pas adaptés pour les systèmes industriels. De plus, de nombreux équipements utilisent des protocoles de communication non standards et parfois même non IP. Il a également utilisé l’exemple de Triton pour montrer que des compétences OT avaient été nécessaires pour comprendre en profondeur l’attaque. 

Il est vrai qu’aujourd’hui, même les SOC IT ne sont pas toujours très efficaces. Peut-être que dans les prochaines années, des SOC OT vont voir le jour et permettre de détecter davantage d’événements de sécurité. 

On peut conclure qu’un détection IT standard efficace permettra sans doute de détecter une attaque (notamment dans ses premières phases), mais que sa compréhension et la réponse à incident ne sera efficace qu’avec des compétences OT et métier.

PLC backdoor in disguise

Lors de cette conférence, le but de Roee Stark n’était pas de montrer les vulnérabilités bien connues des protocoles industriels mais plutôt de montrer comment utiliser des automates exposés pour rebondir sur le réseau, sans modifier le programme de l’automate.

La classe 0x02 (message router) du protocole CIP permet de transférer des paquets entre automates. 
En effet, il a démontré qu’un automate, normalement non accessible via une requête ping, pouvait le devenir en utilisant un message CIP et en indiquant comme relais un automate exposé. Il est donc possible de faire du scan de port en utilisant CIP et ainsi trouver des contrôleurs non exposés, en contournant le filtrage réseau.

Cette fonction n’est pas limitée aux paquets IP. En effet, il existe aussi une classe socket CIP, pour les équipements ne disposant pas d’Ethernet et ne nécessitant aucune authentification.

Cette méthode pourrait être utilisée d’une manière plus offensive et pourrait permettre l’exploitation de vulnérabilités. 

Étant une fonctionnalité de CIP, il n’existe pas de contre mesure simple. Une bonne hygiène réseau permet de se prémunir de ce genre d’attaque en implémentant notamment une liste blanche de routes réseau.

New ICS protocol techniques to detects attacks

Cette session, présentée par Tatsumi Oba, se déroulait sur le “stage 2”, dédié aux conférences plus techniques. Tatsumi nous a présenté les résultats de ses recherches sur la détection d’attaque sur le protocole BACNET/IP. Ce protocole industriel est très fréquemment utilisé dans les domaines du chauffage, de la ventilation et de la climatisation (HVAC). Ce protocole est non-authentifié, et repose sur UDP. Il est donc facile d’usurper l’identité d’un équipement et d’envoyer des commandes arbitraires.
La difficulté principale dans la détection d’une attaque sur ce type de protocole non-authentifié est qu’une attaque va utiliser des commandes légitimes existantes. On ne peut donc pas se contenter de définir une signature pour une attaque.

On se concentrera donc sur la détection d’anomalies plus que d’attaques, via l’apprentissage automatisé. Pour cela, les deux techniques les plus courantes sont les suivantes:
L’analyse du flux de données (taille du paquet, metadonnées), qui se prête mal au protocole BACNET/IP en raison du faible nombre de paquets échangés
L’analyse de la charge utile : on range la charge utilise dans un tableau de bits, on réalise un apprentissage des charges standards et on alerte sur un écart. Cela permet d’éviter les faux positifs mais ne permets pas efficacement de détecter les attaques qui vont envoyer des paquets forgés mais ayant une charge légitime.

La proposition de Tatusmi est d’utiliser une 3ème technique, nommé “payload-sequence based detection”. Le principe est le suivant : dans une utilisation normale du protocole BACNET/IP, on retrouve toujours le même enchaînement de paquets avec des charges utiles semblables. On peut donc détecter une anomalie. Les étapes sont les suivantes :

  • Etape 1 : Extraction de la commande et du contexte, puis calcul de l’éloignement via la distance de Levensthein.
  • Etape 2 : On calcule un “histogramme” représentant la distance entre les charges utiles des paquets d’une séquence
  • Etape 3: On calcule “l’éloignement” entre l’histogramme constaté et le référentiel par la méthode EMD (Earth Mover’s Distance), aussi appelée métrique de Wasserstein 
L’efficacité de cette méthode a ensuite été comparée aux deux précédentes sur les attaques suivantes:
  • Envoi massif de paquets
  • Envoi d’une commande de changement de volume d’air et d’extinction
  • Attaque depuis une adresse IP inconnue
  • Envoi d’une commande avec une valeur extrême
  • Envoi d’une commande inhabituelle

Ce n’est que la combinaison des trois méthodes qui permet de détecter efficacement l’ensemble des attaques.


Truth or Consequence

Une façon simple de représenter un risque est l’équation risque = probabilité * impact. Dans la communauté ICS, l’effort de réduction du risque passe très souvent par la mise en oeuvre de contrôles de sécurité afin de réduire la probabilité. Et si nous étudions les moyens de réduire les impacts ?

Deux méthodes de réduction des risques par la réduction des impacts ont été présentées lors de cette conférence.

Andy Bochman de l’INL utilise le Consequence-driven Cyber-informed Engineering ou CCE. John Cusimano utilise quant à lui le Cyber Process Hazard Analysis ou Cyber-PHA.

Le Cyber-PHA se base sur l’IEC 62443-3-2 et tire partie des méthodes existantes pour les analyses de sûreté. Il s’agit d’une méthode d’identification des risques cyber en 5 étapes :

  • Documentation du système existant, en se basant notamment sur les schémas réseau
  • Identification des vulnérabilités
  • Partitionnement du système
  • Identification des risques
  • Établissement du plan de remédiation


Le CCE est composé de 4 étapes :

  • Priorisation des conséquences ou identification des composants essentiels
  • Partitionnement du système
  • Ciblage basé sur les conséquences ou analyse de la kill chain
  • Établissement du plan de remédiation


Ces deux méthodes sont assez différentes mais reposent toutes les deux sur une étape de partitionnement du système.

Un exemple de réduction du risque par la réduction de l’impact pourrait être de configurer des capteurs en mode lecture seule. Ainsi, même si ces équipements deviennent accessibles, il ne sera pas possible de les reprogrammer à distance.

Fixing CVSS

Ce débat autour de l’intérêt et de l’utilisation du standard de notation de vulnérabilité CVSS part d’un constat partagé par de nombreux membres de la communauté de la sécurité des SI industriels : CVSS est trop complexe et n’est pas utilisable opérationnellement.

Evidemment, on se doit de rappeler que CVSS n’a pas pour objectif de noter un risque, ou un impact potentiel sur un système: il s’agit d’un standard de notation des vulnérabilités, qui sont ensuite à contextualiser pour prendre des décisions informées.

Deux alternatives à CVSS ont été présentées :

RSS : Risk Scoring System

Cette initiative a été financé par les DHS (département de la sécurité intérieure des Etats-Unis d’Amérique) et se décline en trois secteurs : médical, aviation civile, et systèmes d’armes. Il s’agit donc d’une méthodologie de notation des risques, et dont une des convictions est le fait que la notion de probabilité n’est pas quantifiable pour un risque adversaire comme la cybersécurité. Ce sont donc les caractéristiques techniques de la vulnérabilité qui déterminent son ordonnée sur la matrice de risques :


Le site http://riskscoringsystem.com/ présente cette initiative, ainsi que des calculatrices pour noter les risques associés à une vulnérabilité.

TEMSL: Threat, Exposure, Mission, Safety, Loss

Cette alternative pour identifier les actions requises suite à l’identification d’une vulnérabilité séduit par son côté simple. Basé sur un arbre de décision, on obtient en sortie trois priorités:

  • Never: il n’est pas indispensable d’appliquer le patch
  • Next: appliquer le correctif lors de la prochaine campagne de patch
  • Now: appliquer le correctif au plus vite


Malheureusement, la méthodologie détaillée n’est pour le moment pas publiée.





ICS Detection Challenge: Analysis and Results

Cette année, pour la seconde édition du ICS Detection Challenge, seules 3 équipes ont finalement décidé de participer : Dragos, Kaspersky et une équipe open source.
Le challenge a même failli être annulé jusqu’au dernier moment, notamment après l’abandon de Claroty en fin d’année dernière.

La préparation du challenge a nécessité plus de 400 heures de travail, notamment pour anonymiser les captures réseau et injecter des données d’attaques. En tout, plus de 400 Go de données ont été capturées sur une entreprise minière. 

Avec le peu de participants, le déroulé initial du challenge a quelque peu évolué. En effet, la capacité des solutions de détection à inventorier les équipements de l’usine n’a finalement pas été évaluée. Seule la partie détection a été évaluée. De plus, le but initial était de laisser seulement 8h à chaque équipe pour intégrer les données à leurs outils, afin d’évaluer en priorité les capacités de l’outil plus que les compétences d’analyse des équipes. Finalement, chacune des équipes a pu travailler 3-4 jours et fournir une vidéo finale expliquant les résultats de l’analyse.

L’attaque était composée de plusieurs scénarios avec des mouvements sur les réseaux IT, OT et DMZ. Les attaques suivantes pouvaient être trouvées : Stuxnet, Havex, grey energy, Mimikatz, XSS sur des produits Rockwell, DoS sur un automate.

Un des aspects les plus difficiles du challenge était le fait que les participants n’avaient aucune information sur l’environnement (pas d’historique, pas de phase de training comme réalisé habituellement avec les outils de détection).

Concernant les résultats, aucun gagnant n’a été désigné et aucune des deux équipes n’a identifié tous les éléments de l’attaque. Kaspersky a détecté plusieurs incidents de sécurité mais n’a pas su lier les événements entre eux, contrairement à Dragos qui n’a suivi qu’une seule piste mais a su remonter assez loin pour détecter plusieurs éléments infectés.

Une analyse du ICS Detection Challenge par Dale Peterson, ainsi que la vidéo des résultats est accessible ici : https://dale-peterson.com/2019/01/31/post-game-analysis-s4-ics-detection-challenge/ 


Virtualization for PLCs

Cette présentation était tout à fait dans la lignée de la keynote de Dale Perterson et plutôt orientée vers le futur de l’ICS : et si on virtualisait les automates ?

En effet, les soft PLC commencent à faire surface mais sont pour l’instant peu répandus. On peut se demander pourquoi il est intéressant de virtualiser : les coûts sont plus faibles ; la virtualisation apporte plus de flexibilité ; le support et la maintenance sont rendus plus faciles. La virtualisation a également des apports en termes de cybersécurité.  En effet, les machines hébergeant les VMs pourraient être plus durcies que les machines elles-mêmes et on pourrait être plus rassurés d’avoir des objets virtuels autour des composants ICS critiques.

On voit assez régulièrement de la virtualisation pour les niveaux 2 à 5 du modèle de Purdue. Mais pour le niveau 1, les automates industriels, qu’est-il possible de virtualiser ? Il pourrait être possible de virtualiser la CPU, les racks, et également les cartes d’entrées / sorties.

Attention cependant aux problèmes de latence, critiques pour l’OT. Les outils de virtualisation pour automates doivent prendre en compte ces aspects afin d’être viables sur le long terme.

Enfin, pour terminer sa présentation, Austin Scott a évoqué les avantages de la virtualisation pour les constructeurs d’automates. Les contrôleurs étant des équipements relativement chers, on se demande si les constructeurs auraient quelque chose à gagner avec la virtualisation. Néanmoins, cela leur permettrait de mettre l’accent sur la partie software de leurs produits. 

CERT-W : Retour sur l'actualité de la semaine du 4 au 9 décembre 2018


Comme chaque semaine, retrouvez notre revue d'actualité de la sphère cybersécurité. Cette compilation de brèves vous permettra d'alimenter les discussions des prochaines pauses cafés !

Veille cybercriminalité

Les mails du comité du parti Républicain auraient été piratés pendant les élections

Les comptes e-mail de quatre membres du commité national du parti Républicain ont été compromis, par des attaquants inconnus, durant le mois d'avril dernier.

Le DNS de linux.org a été détourné

La journée de mercredi 5 décembre, le compte sur le registrar du domaine linux.org a été détourné et tout le trafic redirigé vers un site comportant le message "G3T 0WNED L1NUX N3RDZ"

Veille vulnérabilité

Une nouvelle itération de SPECTRE : SplitSpectre

Une équipe de chercheurs de la Northeastern University et d'IBM ont publié une nouvelle technique d'exploitation de la version 1 de Spectre. Cette nouvelle attaque, baptisée SplitSpectre, permettrait d'exploiter plus rapidement et plus facilement la vulnérabilité publiée en Janvier dernier.

Les questions de sécurité Windows permettraient aux attaquants de conserver leurs privilèges

Des experts d'Illusive Networks ont présenté la semaine dernière une méthode permettant de garder un accès persistant dans un domaine Windows en exploitant les questions de sécurité.
Contrairement aux mots de passe, les questions de sécurité ne sont pas modifiées et n'expirent pas. Un attaquant pourra donc utiliser cette fonctionnalité pour en faire une porte dérobée vers le domaine compromis.

L'Australie passe une loi anti-chiffrement

L'Australie prépare une loi permettant aux forces de l'ordre, sous des conditions définies, d'exiger des fournisseurs le déchiffrement des communications représentant un intérêt pour la sécurité du pays.

Une nouvelle vulnérabilité Flash activement exploitée

La vulnérabilité CVE-2018-15982 touchant toutes les versions de Flash jusqu'à 31.0.0.101 est actuellement exploitée à travers des mails contenant des fichiers RAR piégés.

Une façon de contourner disable_functions dans PHP

La vulnérabilité CVE-2018-19518 permet de contourner les protections prévues par la directive disable_functions en utilisant le protocole IMAP.

De l'exécution de code arbitraire dans PrestaShop grâce à l'enchaﯥment de multiples vulnérabilités

Un POC pour la vulnérabilité CVE-2018-19126 a été publié cette semaine. Les versions 1.6.X avant 1.6.23 et 1.7.X avant 1.7.4.4 sont vulnérables.

Indicateurs de la semaine

L'attaque de la semaine - Des banques attaquées grâce à du matériel malveillant branché sur le réseau local

Au moins huit banques d'Europe de l'Est ont été victimes de la vague d'attaques connue sous le nom de DarkVishnya. Des attaquants, déguisés en livreurs, candidats, etc., ont introduit dans différentes succursales du matériel permettant un accès à distance au réseau ciblé.

L'exploit de la semaine - Un exploit sur WebKit fonctionnel sur les versions actuelles de Safari fournies avec iOS

L'exploit fonctionne sur iOS version 12.0 et jusqu'à 12.1.1 inclus ainsi que sur macOS version 10.14.0 et jusqu'à 10.14.2

Suivi des versions

Produits
Version actuelle
Adobe Flash Player
Adobe Acrobat Reader DC
Java
Mozilla Firefox
Google Chrome
VirtualBox
CCleaner

Pentesting ICS 101



Wavestone possède depuis plusieurs années des démonstrateurs sur la sécurité des systèmes industriels. En particulier, vous avez peut-être déjà rencontré notre maquette de train et bras robotiques avec un capture the Flag physique !

Cette maquette de train est principalement utilisée pour des workshops dans des conférences de sécurité (Black Hat Europe 2014, BruCON 2015 & 2017, DEF CON 2016 & 2018, Bsides LV, etc.), ou des cours en école d'ingénieurs (EPITA, Mines, ESIEA, Télécom Sud Paris, etc.).

En plus des conférences et des cours en école, nous avons tourné cet été une vidéo de la maquette afin de présenter les principales attaques sur les systèmes industriels, et en particulier l’insécurité des protocoles industriels.

La première partie de la vidéo rappelle ce que sont les systèmes industriels et présente les principales familles de risques et vulnérabilités sur ces systèmes :
  • Des défauts d’organisation et de sensibilisation des acteurs
  • L’absence de supervision de sécurité
  • L’absence de mécanismes de sécurité dans les équipements et les protocoles 
  • La non-maitrise des sous-traitants et de la maintenance 
  • La ségrégation inexistante des réseaux
  • L’absence de patch management



La seconde partie de la vidéo rentre plus dans la pratique, avec tout d’abord la présentation de la maquette : les automates, la supervision ainsi que le fonctionnement général.
L’objectif est d’attaquer les automates : il faut arrêter le train et attraper son drapeau à l’aide des bras robotiques.
Différents outils de lecture et écriture de registres sont alors présentés :
  • S7getDB pour les automates Siemens
  • Mbtget pour les automates Schneider

Enfin, nous concluons sur le fait qu’il est relativement facile de piloter les automates de manière illégitime avec des outils implémentant les protocoles de communication industriel. Par ailleurs, bien que de nouvelles gammes d’automates, plus robustes, existent chez certains constructeurs, la première étape de sécurisation consiste à cloisonner et filtrer ses réseaux industriels




Vous pouvez trouver les différents outils présentés dans la vidéo, ainsi que nos autres outils aux adresses ci-dessous :




Malwarebytes challenge write-up


Malwarebytes published on April 27th a new reverse engineering challenge, an executable mixing malware behavior with a traditional crackme look. It came in the form of a Windows executable
This document describes the solving step of the challenge.


Challenge's icon

Lightweight analysis of “mb_crackme_2.exe”

As we would do with any real malware, we start by performing some basic information gathering on the provided executable. Even if the static and dynamic approaches gave us similar conclusions on the executable’s nature (see 2.4), the different methods have been described nonetheless in the following sections.

Basic static information gathering

Using Exeinfo PE, a maintained successor of the renowned (but outdated) PEiD software, gives us some basic information about the binary:
  •  The program is a 32 bits Portable Executable (PE), meant to be run in console (no GUI);
  •  It seems to be compiled from C++ using Microsoft Visual C++ 8;
  •  No obvious sign of packing is detected by the tool.

Output of Exeinfo PE

Looking for printable strings in the binary already gives us some hints about the executable’s nature:
$ strings -n 10 mb_crackme_2.exe_
[...]
pyi-windows-manifest-filename
[...]
Py_IgnoreEnvironmentFlag
Failed to get address for Py_IgnoreEnvironmentFlag
Py_NoSiteFlag
Failed to get address for Py_NoSiteFlag
Py_NoUserSiteDirectory
[...]
mpyimod01_os_path
mpyimod02_archive
mpyimod03_importers
spyiboot01_bootstrap
spyi_rth__tkinter
bCrypto.Cipher._AES.pyd
bCrypto.Hash._SHA256.pyd
bCrypto.Random.OSRNG.winrandom.pyd
bCrypto.Util._counter.pyd
bMicrosoft.VC90.CRT.manifest
bPIL._imaging.pyd
bPIL._imagingtk.pyd
[...]
opyi-windows-manifest-filename another.exe.manifest
[...]
zout00-PYZ.pyz
python27.dll

Many references to Python libraries, PYZ archives and “pyi” substring indicates the use of the PyInstaller utility to build a PE executable from a Python script.

Basic dynamic information gathering

Running the executable (in a sandboxed environment) gives us the following message:


Using Process Monitor, from SysInternals Tools Suite , allows us to quickly get a glimpse of the actions performed by the executable:


A temporary directory named “_MEI5282” is created under user’s “%temp%” directory, and filled with Python-related resources. In particular, “python27.dll” and “*.pyd” libraries are written and later loaded by the executable.


This behavior is typical of executables generated by PyInstaller.

Error-handling analysis

Without tools, it is often possible to quickly get information about a binary’s internals by testing its error handling. For example, inserting an EOF (End-Of-File) signal in the terminal (“Ctrl+Z + Return” on Windows Command Prompt) makes the program crash, printing the following information:

Python stack trace printed after a crash

This allows us to identify the presence of a Python program embedded inside the executable and gives us the name of the main script: another.py. The error message “[$PID] Failed to execute script $scriptName” is typical of PyInstaller-produced programs.

Python files extraction and decompilation

Every lightweight analysis presented previously points out that the executable has been built using PyInstaller.
The PyInstaller Extractor  program can be used to extract python-compiled resources from the executable.

$ python pyinstxtractor.py mb_crackme_2.exe
[*] Processing mb_crackme_2.exe
[*] Pyinstaller version: 2.1+
[*] Python version: 27
[*] Length of package: 8531014 bytes
[*] Found 931 files in CArchive
[*] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap
[+] Possible entry point: pyi_rth__tkinter
[+] Possible entry point: another
[*] Found 440 files in PYZ archive
[*] Successfully extracted pyinstaller archive: mb_crackme_2.exe

You can now use a python decompiler on the pyc files within the extracted directory

As previously seen, the most interesting file is “another”, as it should contain the “main” function.

Files extracted by PyInstaller Extractor

A quick Internet search  informs us that in a PYZ archive, the main file is in fact a *.pyc file (Python bytecode) from which the first 8 bytes, containing its signature, have been removed. Looking the hex dump of another *.pyc file of the archive confirms this statement and gives us the correct signature for Python 2.7 bytecode files (in purple).

$ hexdump -C another | head -n 3
00000000  63 00 00 00 00 00 00 00  00 03 00 00 00 40 00 00  |c............@..|
00000010  00 73 03 02 00 00 64 00  00 5a 00 00 64 01 00 5a  |.s....d..Z..d..Z|
00000020  01 00 64 02 00 5a 02 00  64 03 00 64 04 00 6c 03  |..d..Z..d..d..l.|

$ hexdump -C out00-PYZ.pyz_extracted/cmd.pyc | head -n 3
00000000  03 f3 0d 0a 00 00 00 00  63 00 00 00 00 00 00 00  |.ó......c.......|
00000010  00 03 00 00 00 40 00 00  00 73 4c 00 00 00 64 00  |.....@...sL...d.|
00000020  00 5a 00 00 64 01 00 64  02 00 6c 01 00 5a 01 00  |.Z..d..d..l..Z..|

Restoring the file’s signature produces a correct Python bytecode file.

$ cat <(printf "\x03\xf3\x0d\x0a\x00\x00\x00\x00") another > another.pyc
$ file another.pyc
another.pyc: python 2.7 byte-compiled

Using the uncompyle6  decompilation tool, we can easily recover the original source code of another.py.

$ uncompyle6 another.pyc > another.py


Stage 1: login

Looking at the main() function of another.py, we see that the first operations are performed by the stage1_login() function.

def main():
    key = stage1_login()
    if not check_if_next(key):
        return
    else:
        content = decode_and_fetch_url(key)
        if content is None:
            print 'Could not fetch the content'
            return -1
        decdata = get_encoded_data(content)
        if not is_valid_payl(decdata):
            return -3
        print colorama.Style.BRIGHT + colorama.Fore.CYAN
        print 'Level #2: Find the secret console...'
        print colorama.Style.RESET_ALL
        #load_level2(decdata, len(decdata))
        dump_shellcode(decdata, len(decdata))
        user32_dll.MessageBoxA(None, 'You did it, level up!', 'Congrats!', 0)
        try:
            if decode_pasted() == True:
                user32_dll.MessageBoxA(None, '''Congratulations! Now save your flag
and send it to Malwarebytes!''', 'You solved it!', 0)
                return 0
            user32_dll.MessageBoxA(None, 'See you later!', 'Game over', 0)
        except:
            print 'Error decoding the flag'

        return


def stage1_login():
    show_banner()
    print colorama.Style.BRIGHT + colorama.Fore.CYAN
    print 'Level #1: log in to the system!'
    print colorama.Style.RESET_ALL
    login = raw_input('login: ')
    password = getpass.getpass()
    if not (check_login(login) and check_password(password)):
        print 'Login failed. Wrong combination username/password'
        return None
    else:
        PIN = raw_input('PIN: ')
        try:
            key = get_url_key(int(PIN))
        except:
            print 'Login failed. The PIN is incorrect'
            return None

        if not check_key(key):
            print 'Login failed. The PIN is incorrect'
            return None
        return key   

Three user inputs are successively checked: the user’s login, password and PIN code.

Finding the login

The check_login() function's code is completely transparent :
def check_login(login):
    if login == 'hackerman':
        return True
    return False

We have found the login, let's search for the password.

Expected login

Finding the password

The check_password() function hashes user’s input using the MD5 hash function, and compares the result with an hardcoded string:

def check_password(password):
    my_md5 = hashlib.md5(password).hexdigest()
    if my_md5 == '42f749ade7f9e195bf475f37a44cafcb':
        return True
    return False

A quick Internet search of this string gives us the corresponding cleartext password: Password123.

Finding the password on a search engine

Finding the PIN code

The PIN code is read from standard input, converted into an integer (cf. stage1_login() function), and passed to the get_url_key() function:



def get_url_key(my_seed):
    random.seed(my_seed)
    key = ‘’
    for I in xrange(0, 32):
        id = random.randint(0, 9)
        key += str(id)

    return key


This function derives a pseudo-random 32 digits key from the PIN code, using it as a seed for Python’s PRNG. The generated key is then verified using the check_key() function, where its MD5 sum is checked against another hardcoded value.


def check_key(key):
    my_md5 = hashlib.md5(key).hexdigest()
    if my_md5 == 'fb4b322c518e9f6a52af906e32aee955':
        return True
    return False 

The key space is obviously too large to be brute-forced, as a 32-digits string corresponds to 10^32 (~2^106) possible combinations. However, we can brute-force the PIN code, being an integer, using the following code:



from another import get_url_key, check_key

PIN = 0
while True:
    key = get_url_key(PIN)
    if check_key(key):
        print PIN
        break
    PIN += 1

The solution is obtained in a few milliseconds:


$ python bruteforcePIN.py
9667

Testing credentials

Using the credentials found in the previous step completes the first stage of the challenge.

Validating stage 1


Clicking “Yes” makes the executable pause after printing the following message in the console:

Waiting for us to find a "secret console"


Let’s find that secret console!


Stage 2: the secret console

Payload download and decoding

Continuing our analysis of the main() function, the next function to be called after credentials verification is decode_and_fetch_url(), with the previously calculated 32-digits key given as argument:



def decode_and_fetch_url(key):
    try:
        encrypted_url = '\xa6\xfa\x8fO\xba\x7f\x9d\[...]\xfe'
        aes = AESCipher(bytearray(key))
        output = aes.decrypt(encrypted_url)
        full_url = output
        content = fetch_url(full_url)
    except:
        return None

    return content 

 
A URL is decrypted using an AES cipher and the 32-digits key. The resource at this URL is then downloaded and its content returned by the function.
To get the decrypted URL, we simply add some logging instructions to the original code of another.py, which can be run independently of mb_crackme_2.exe (given that the required dependencies are present on our machine).


[...]
        full_url = output
        print "DEBUG : URL fetched is : %s " % full_url #added from original code
        content = fetch_url(full_url)
[...]

 
The result execution is the following:



login: hackerman
Password:
PIN: 9667
DEBUG : URL fetched is : https://i.imgur.com/dTHXed7.png

 
The decrypted URL hosts the PNG image displayed bellow:

Image downloaded by the executable


The “malware” then reads the Red, Green and Blue components of each of the image’s pixels, interprets them as bytes and constructs a buffer from their concatenation.


def get_encoded_data(bytes):
    imo = Image.open(io.BytesIO(bytes))
    rawdata = list(imo.getdata())
    tsdata = ''
    for x in rawdata:
        for z in x:
            tsdata += chr(z)

    del rawdata
    return tsdata 


This technique is sometimes used by real malware to download malicious code without raising suspicion of traffic-analysis tools, hiding the real nature of the downloaded resource.
Using the “Extract data…” function of the Stegsolve tool  allows to quickly preview the data encoded in the image, which appears to be a PE file (and more specifically, a DLL):

Output of the stegsolve tool



The function is_valid_payl() is then used to check whether the decoded payload is correct:


def is_valid_payl(content):
    if get_word(content) != 23117:
        return False
    next_offset = get_dword(content[60:])
    next_hdr = content[next_offset:]
    if get_dword(next_hdr) != 17744:
        return False
    return True



The 23117 and 17744 constants represent the “MZ” and “PE” magic bytes present in the headers of a PE.



>>> import struct
>>> struct.pack("<H", 23117)
'MZ'
>>> struct.pack("<H", 17744)
'PE'


The decoded file is then passed to the load_level2() function, which is a wrapper around prepare_stage().


def load_level2(rawbytes, bytesread):
    try:
        if prepare_stage(rawbytes, bytesread):
            return True
    except:
        return False



def prepare_stage(content, content_size):
    virtual_buf = kernel_dll.VirtualAlloc(0, content_size, 12288, 64)
    if virtual_buf == 0:
        return False
    res = memmove(virtual_buf, content, content_size)
    if res == 0:
        return False
    MR = WINFUNCTYPE(c_uint)(virtual_buf + 2)
    MR()
    return True



This function starts by allocating enough space to store the downloaded code, using the VirtualAlloc API function call. The allocated space is readable, writable and executable, as the provided arguments reveal (12288 being equal to “MEM_COMMIT | MEM_RESERVE”, and 64 to PAGE_EXECUTE_READWRITE).
The downloaded code is then written in the allocated space using the memmove function, and executed like a shellcode from offset 2.

To get a clean dump of the downloaded code (once decrypted), we add a piece of code in the prepare_stage() function, as follows:


def prepare_stage(content, content_size):
    with open("dumped_pe.dll", "wb") as f:
        f.write(content[:content_size])
        print "DEBUG : File dumped in dumped_pe.dll"
    virtual_buf = kernel_dll.VirtualAlloc(0, content_size, 12288, 64)
    if virtual_buf == 0:
        return False
    res = memmove(virtual_buf, content, content_size)
    if res == 0:
        return False
    MR = WINFUNCTYPE(c_uint)(virtual_buf + 2)
    MR()
    return True



After re-executing the program, we observe that the obtained file is indeed a valid 32 bits Windows DLL:


$ file dumped_pe.dll
dumped_file.ext: PE32 executable (DLL) (console) Intel 80386, for MS Windows

Time for us to open our favorite disassembler !


Downloaded DLL’s reverse-engineering

Reflective loading
From the offset 2 of the file, a little shellcode located in the DOS headers transfers the execution to another code that implements Reflective DLL injection. This technique is used to load the library itself from memory, instead of normally loading the DLL from disk using the LoadLibrary API call.


Disassembly of the first bytes of the downloaded DLL


The reflective loader’s code, located at 0x6E0, is documented in Stephen Fewer’s GitHub  and will not be described in this write-up. Since, in the end, the library is loaded by this mechanism as it would be after a normal LoadLibrary call, this downloaded file will be analyzed like a standard DLL in the rest of this write-up.

The list of exported functions being empty (except for the DllEntryPoint function), we start our analysis at the entry point of the DLL.

Exports list



Entry point
Our first goal is to search for the DllMain() function from the entry point. If the reverser is not used to analyzing Windows DLLs, a simple way to start would be to open any random non-stripped 32bit DLL, which (with a little luck) would be compiled with the same compiler (Visual C++ ~7.10 here), and which would have a similar CFG structure for the DllEntryPoint function.
An example of CFG comparisons between the analyzed DLL (left) and another non-stripped 32bit DLL (right) is presented below:
    

DLLEntryPoints in our DLL v/s another non-stripped DLL


DllMainCTRStartup in our DLL / in another non-stripped DLL

This technique allows us to quickly find the DllMain function in our DLL, here being located at 0x10001170.

DllMain (0x10001170)
The function starts by checking if it has been called during the first load of the DLL by a process, by comparing the value of the fdwReason argument  against the DLL_PROCESS_ATTACH constant.
The DllMain() function then registers two exception handlers using the AddVectoredExceptionHandler  API call. The handlers are named “Handler_0” and “Handler_1” in the screenshot below:

DllMain function


An exception is then manually raised using the “int 3” interruption instruction, triggering the execution of Handler_0.

Interlude: debugging a DLL in IDA Pro
To make the reverse-engineering of some functions easier, debugging the code to observe function inputs and outputs can be an effective method.
One simple way to debug a DLL inside IDA is to load the file as usual, then go to “Debugger ->Process options...” and modify the following value:
  • Application:
    •  On a 64 bits version of Windows:
      •   “C:\Windows\SysWOW64\rundll32.exe” to debug a 32 bits library
      •   “C:\Windows\System32\rundll32.exe” to debug a 64 bits library
    •  On a 32 bits version of Windows:
      •   “C:\Windows\System32\rundll32.exe” to debug a 32 bits library
      •   Obviously, you cannot run (therefore debug) a 64 bits library on a 32 bits version of Windows
  •  Parameters:
    •   “PATH_OF_YOUR_DLL”,functionToCall [function parameters if any]

Note: The file extension must be “*.dll” for rundll32.exe to accept it.

IDA "Process options..." menu


To test the configuration, just place a breakpoint at the entry point of the DLL:

Placing a breakpoint on the entry point


Run your debugger (F9). If configured correctly, your debugger should break at the DLL entry point, allowing you to debug any DLL function

Handler_0 (0x10001260)
Looking at Handler_0’s CFG (given below), we see that the function calls two unknown functions (0x100092C0 and 0x1000E61D). To quickly identify these functions, let’s debug the DLL, and look at the functions inputs/outputs:

sub_100092C0

Function sub_100092C0() call


The function seems to take 3 arguments:
  • A buffer (here named “Value”);
  • A value (here 0);
  • The size of the buffer (here 0x104).
Let's look at the buffer’s content before and after the function call:

"Value" buffer before and after the call

The function prototype and its side effects correspond to the memset function.

sub_1000E61D


Function sub_1000E61D() call

The function seems to take 4 arguments:
  • An integer (here the PID of the process);
  • A buffer (here named “Value”);
  • The size of the buffer (here 0x104);
  • A value (here 0xA, or 10).
Looking at the provided buffer’s content after the function call, we see that the representation in base 10 of the first integer passed in parameter is written in the provided buffer.

Value buffer after the call

The function prototype and its side effects correspond to the _itoa_s function .

Handler_0 whole CFG and pseudo-code
Here is the graph of the Handler_0 function:


CFG of function Handler_0()

This corresponds to the following pseudo code:

if isloaded(“python.dll”):
   pid = getpid()
else:
   pid = 0
setEnvironmentVariable(“mb_chall”, str(pid))
return EXCEPTION_CONTINUE_SEARCH


The function checks the presence of the python27.dll library (normally loaded by the main program mb_crackme_2.exe) in the process address space, and sets the “mb_chall” environment variable consequently.
This may be seen as an “anti-debug” trick, because running the DLL independently in a debugger makes the execution follow a different path.

Handler_1 (0x100011D0)
The code of this handler is quite self-explanatory, being similar to the previous handler’s code:



Once again, this corresponds to the following pseudo code:

if getpid() == int(getenv(“mb_chall”):
   tmp = 6
else:
   tmp = 1
exceptionInfo->Context._Eip += tmp
return EXCEPTION_CONTINUE_EXECUTION 

 After this handler, execution restarts at the address of original interruption (“int 3”) +1 or +6 (as presented in the pseudo-code above), whether performed checks pass or not.


We thus continue the analysis at the not_fail function (0x100010D0).

not_fail (0x100010D0)
The function only starts a thread and waits for it to terminate.

CFG of not_fail() function

The created thread executes the MainThread (0x10001110) function, where our analysis continues.

MainThread (0x10001110)
The function loops and calls the EnumWindows  API every second, which in turn calls the provided callback function (EnumWindowsCallback) on every window present on the desktop.


CFG of MainThread() function


EnumWindowsCallback function (0x10005750)
The function, called on each window, uses the SendMessageA  API with the WM_GETTEXT message to retrieve the window’s title.

SendMessageA() call in MainThread()

After being converted to C++ std::string, the substrings “Notepad” and “secret_console” are searched in the window’s title.

Strings "Notepad" and "secret_console" searched for in window title

If both substrings are present, the window’s title is replaced by the hardcoded string “Secret Console is waiting for the commands...”, using the SendMessageA API along with the WM_SETTEXT message. The window is placed to the foreground, using the ShowWindow API call.

Modification of the window's title using SendMessageA()

The PID of the process corresponding to the window is then written in the “malware”’s console, and sub-windows of this window are enumerated, using the EnumChildWindows  API.The function EnumChildWindowsCallback (0x100034C0) is thus called on every sub-window.

EnumChildWindows() function call


EnumChildWindowsCallback function (0x100034C0)
This function gets the content of the sub-window using the SendMessageA API call:

SendMessageA() call in EnumChildWindowsCallback() function

The substring “dump_the_key” is then searched in the retrieved content:

Search for "dump_the_key"

If this string is found, this function calls a decryption routine decrypt_buffer() (0x100016F0) on a buffer (encrypted_buff), using the string “dump_the_key” as argument.

Decrypting a hardcoded buffer using "dump_the_key" as key

Then, the “malware” loads the actxprxy.dll library into the process memory space. The first 4096 bytes (i.e. the first memory page) of the library is made writable using the VirtualProtect API call, and the decrypted payload is written at this location.

Loading a library and writing the decrypted buffer at its location

Since the actxprxy.dll library is not used anywhere in the analyzed DLL after being re-written, it may be seen as a covert communication channel between the analyzed DLL and the main program mb_crackme_2.exe. After this, the function clears every allocated memory and exits. The created thread (see 4.2.6) therefore also exits, and the DllEntryPoint function call terminates, giving the control back to the main python script.


Triggering the secret console

As seen in the DLL analysis, to trigger the required conditions, a file named “secret_console – Notepad” is opened in a text editor. As such, the window title contains the mentioned substrings:

Opening a file named "secret_console_Notepad.txt" on Notepad++

As expected, the title of the window is changed to “Secret Console is waiting for the commands…” by the malware. Writing “dump_the_key” in the window validates the second stage.

Writing "dump_the_key" in the text editor

Stage 3: the colors

After validating the previous step, a message is printed on the console, asking the user to “guess a color”:

Level 3 Message

The three components (R, G and B) of a specific color, with values going from 0 to 255, need to be entered to validate this step.

Level 3 failed guess message

Understanding the code

Looking back at the another.py’s main() function code, it seems that the corresponding operations are performed inside the decode_pasted() function.



def main():
   [...]
      load_level2(decdata, len(decdata))
      user32_dll.MessageBoxA(None, 'You did it, level up!', 'Congrats!', 0)
      try:
         if decode_pasted() == True:
            user32_dll.MessageBoxA(None, '''Congratulations! Now save your flag and 
send it to Malwarebytes!''', 'You solved it!', 0)
            return 0


def decode_pasted():
    my_proxy = kernel_dll.GetModuleHandleA('actxprxy.dll')
    if my_proxy is None or my_proxy == 0:
        return False
    else:
        char_sum = 0
        arr1 = my_proxy
        str = ''
        while True:
            val = get_char(arr1)
            if val == '\x00':
                break
            char_sum += ord(val)
            str = str + val
            arr1 += 1

        print char_sum
        if char_sum != 52937:
            return False
        colors = level3_colors()
        if colors is None:
            return False
        val_arr = zlib.decompress(base64.b64decode(str))
        final_arr = dexor_data(val_arr, colors)
        try:
            exec final_arr
        except:
            print 'Your guess was wrong!'
            return False

        return True 




def dexor_data(data, key):
    maxlen = len(data)
    keylen = len(key)
    decoded = ''
    for i in range(0, maxlen):
        val = chr(ord(data[i]) ^ ord(key[i % keylen]))
        decoded = decoded + val

    return decoded



def level3_colors():
    colorama.init()
    print colorama.Style.BRIGHT + colorama.Fore.CYAN
    print '''Level #3: Your flag is almost ready! But before it will be revealed
, you need to guess it's color (R,G,B)!'''
    print colorama.Style.RESET_ALL
    color_codes = ''
    while True:
        try:
            val_red = int(raw_input('R: '))
            val_green = int(raw_input('G: '))
            val_blue = int(raw_input('B: '))
            color_codes += chr(val_red)
            color_codes += chr(val_green)
            color_codes += chr(val_blue)
            break
        except:
            print 'Invalid color code! Color code must be an integer (0,255)'

    print 'Checking: RGB(%d,%d,%d)' % (val_red, val_green, val_blue)
    return color_codes


According to the decode_pasted() function, the decrypted buffer stored at the start of actxprxy.dll’s address space is read and:
base64-decoded;
  • zlib-decompressed;
  • XOR’ed against the user-provided colors values;
  • Executed by the Python exec function.
To start our cryptanalysis, we modify the decode_pasted() function to dump the val_arr buffer before the dexor_data() operation, and rerun another.py, providing all required credentials:

[...]
if colors is None:
   return False
val_arr = zlib.decompress(base64.b64decode(str))
with open("val_arr.bin", "wb") as f:
   f.write(val_arr)
   print "val_arr dumped !"
exit()
final_arr = dexor_data(val_arr, colors)
[...]

Dumping the XOR'ed array

Decrypting the val_arr buffer

Knowing that the buffer is a string passed to the “exec” Python statement after being decrypted, it should represent a valid Python source code.
To find the right key, the naïve solution would be to run a brute-force attack on all the possible “(R, G, B)” combinations, and look for printable solutions. This solution would need to perform 256^3 = 16’777’216 dexor_data() calls, which is feasible but inefficient.
Instead, we perform 3 independent brute-force attacks on each R, G and B component, therefore performing 256 x 3 = 768 dexor_data() calls. The 3 brute-force attacks are performed on different “slices” of the val_arr string (of each of stride 3). We then test each combination of potential values previously found for each component.
For example, if our 3 brute-force attacks indicate that:
  • R can take values 2 and 37,
  • G can take values 77 and 78,
  • and B can only take the value 3,
Then we test the combinations (2,77, 3), (37,77, 3), (2,78, 3) and (37,78, 3).

The following code implements our attack:


import string
import itertools
from colorama import *
from another import dexor_data

with open("val_arr.bin", "rb") as f:
    val_arr = f.read()

#lists of possible values for R, G and B
potential_solutions = [list(), list(), list()]
for color in range(3): # separate bruteforce on R, G and B
    for xor_value in range(256): #testing all potential values
        valid = True
        for b in val_arr[color::3]: #extracting one every 3 characters, from index 
        # "color" (i.e. extracting all characters xored by the same "color" value)
            if chr(ord(b) ^ xor_value) not in string.printable:
                valid = False
                break
        if valid:
            potential_solutions[color].append(xor_value)

print "Possible values for R, G and B :", potential_solutions

for colors in itertools.product(*potential_solutions):
    print "Testing ", colors
    plaintext = dexor_data(val_arr, map(chr, colors))
    print repr(plaintext)
    if not raw_input("Does it seems right ? [Y/n]\n").startswith("n"):
       print "Executing payload :"
       exec plaintext
       break


Executing this code gives us the solution instantly:

Decrypting the payload

The final flag appears in the console:


flag{"Things are not always what they seem; the first appearance 
deceives many; the intelligence of a few perceives what has been 
carefully hidden." - Phaedrus}


Conclusion

This challenge was very interesting to solve, because apart from being an original crackme, it also included various topics that could be found during a real malware analysis. These topics included:
  • DLL-rewriting techniques, here used as a kind of covert communication channel between a DLL and its main process;
  • “Non-obvious” anti-debugging tricks, like checking the presence of a known library in the process’ memory space to identify standalone DLL debugging;
  • Concealed malware downloading, using « harmless » formats (like PNG) to hide an executable payload from basic traffic analysis;
  • PyInstaller-based malware, (yes, sometimes malware writers can be lazy).

Thanks MalwareBytes for this entertaining challenge!




Maxime MEIGNAN