En piratage informatique , un shellcode est un petit morceau de code utilisé comme charge utile dans l' exploitation d'une vulnérabilité logicielle . On l'appelle « shellcode » car il démarre généralement un shell de commande à partir duquel l'attaquant peut contrôler la machine compromise, mais tout morceau de code qui effectue une tâche similaire peut être appelé shellcode. Étant donné que la fonction d'une charge utile ne se limite pas à la simple génération d'un shell, certains ont suggéré que le nom shellcode était insuffisant. Cependant, les tentatives de remplacement du terme n'ont pas été largement acceptées. Le shellcode est généralement écrit en code machine .
Lors de la création d'un shellcode, il est généralement souhaitable de le rendre à la fois petit et exécutable, ce qui lui permet d'être utilisé dans une variété de situations aussi large que possible. Dans le code assembleur, la même fonction peut être exécutée de multiples façons et il existe une certaine variété dans les longueurs des opcodes qui peuvent être utilisés à cette fin ; les bons auteurs de shellcode peuvent utiliser ces petits opcodes pour créer un shellcode plus compact. Certains ont atteint la plus petite taille possible tout en conservant la stabilité.
Types de shellcode
Le shellcode peut être local ou distant , selon qu'il donne à un attaquant le contrôle de la machine sur laquelle il s'exécute (local) ou sur une autre machine via un réseau (distant).
Locale
Le shellcode local est utilisé par un attaquant qui dispose d'un accès limité à une machine mais qui peut exploiter une vulnérabilité, par exemple un dépassement de tampon , dans un processus à privilèges plus élevés sur cette machine. S'il est exécuté avec succès, le shellcode fournira à l'attaquant un accès à la machine avec les mêmes privilèges plus élevés que le processus ciblé.
Télécommande
Le shellcode distant est utilisé lorsqu'un attaquant souhaite cibler un processus vulnérable exécuté sur une autre machine sur un réseau local , un intranet ou un réseau distant . S'il est exécuté avec succès, le shellcode peut fournir à l'attaquant un accès à la machine cible via le réseau. Les shellcodes distants utilisent normalement des connexions de socket TCP/IP standard pour permettre à l'attaquant d'accéder au shell sur la machine cible. Ce shellcode peut être catégorisé en fonction de la façon dont cette connexion est configurée : si le shellcode établit la connexion, il est appelé « shell inversé » ou « shellcode de connexion arrière » car le shellcode se connecte à la machine de l'attaquant. D'un autre côté, si l'attaquant établit la connexion, le shellcode est appelé « bindshell » car le shellcode se lie à un certain port sur la machine de la victime. Il existe un shellcode particulier appelé « bindshell random port » qui ignore la partie de liaison et écoute sur un port aléatoire mis à disposition par le système d'exploitation . Pour cette raison, le port aléatoire du bindshell est devenu le plus petit shellcode bindshell stable pour x86_64 disponible à ce jour. Un troisième type, beaucoup moins courant, est le shellcode de réutilisation de socket . Ce type de shellcode est parfois utilisé lorsqu'un exploit établit une connexion au processus vulnérable qui n'est pas fermée avant l'exécution du shellcode. Le shellcode peut alors réutiliser cette connexion pour communiquer avec l'attaquant. Le shellcode de réutilisation de socket est plus élaboré, car le shellcode doit trouver quelle connexion réutiliser et la machine peut avoir de nombreuses connexions ouvertes.
Un pare-feu peut être utilisé pour détecter les connexions sortantes effectuées par le shellcode de connexion arrière ainsi que les connexions entrantes effectuées par les bindshells. Ils peuvent donc offrir une certaine protection contre un attaquant, même si le système est vulnérable, en empêchant l'attaquant de se connecter au shell créé par le shellcode. L'une des raisons pour lesquelles le shellcode de réutilisation de socket est parfois utilisé est qu'il ne crée pas de nouvelles connexions et est donc plus difficile à détecter et à bloquer.
Télécharger et exécuter
Le téléchargement et l'exécution sont un type de shellcode distant qui télécharge et exécute une certaine forme de malware sur le système cible. Ce type de shellcode ne génère pas de shell, mais demande plutôt à la machine de télécharger un certain fichier exécutable depuis le réseau, de l'enregistrer sur le disque et de l'exécuter. De nos jours, il est couramment utilisé dans les attaques de téléchargement par inadvertance , où une victime visite une page Web malveillante qui tente à son tour d'exécuter un tel téléchargement et d'exécuter un shellcode afin d'installer un logiciel sur la machine de la victime. Une variante de ce type de shellcode télécharge et charge une bibliothèque . Les avantages de cette technique sont que le code peut être plus petit, qu'il ne nécessite pas que le shellcode génère un nouveau processus sur le système cible et que le shellcode n'a pas besoin de code pour nettoyer le processus ciblé car cela peut être fait par la bibliothèque chargée dans le processus.
Mise en scène
Lorsque la quantité de données qu'un attaquant peut injecter dans le processus cible est trop limitée pour exécuter directement un shellcode utile, il peut être possible de l'exécuter par étapes. Tout d'abord, un petit morceau de shellcode (étape 1) est exécuté. Ce code télécharge ensuite un morceau de shellcode plus volumineux (étape 2) dans la mémoire du processus et l'exécute.
Chasse aux oeufs
Il s'agit d'une autre forme de shellcode par étapes , qui est utilisée si un attaquant peut injecter un shellcode plus volumineux dans le processus mais ne peut pas déterminer où il finira dans le processus. Un petit shellcode de type « chasse aux œufs » est injecté dans le processus à un endroit prévisible et exécuté. Ce code recherche ensuite dans l'espace d'adressage du processus le shellcode plus volumineux (l' œuf ) et l'exécute.
Omelette
Ce type de shellcode est similaire au shellcode de chasse aux œufs , mais recherche plusieurs petits blocs de données ( œufs ) et les recombine en un seul bloc plus grand (l' omelette ) qui est ensuite exécuté. Ceci est utilisé lorsqu'un attaquant ne peut injecter qu'un certain nombre de petits blocs de données dans le processus.
Stratégie d'exécution du shellcode
Un exploit injecte généralement un shellcode dans le processus cible avant ou en même temps qu'il exploite une vulnérabilité pour prendre le contrôle du compteur de programme . Le compteur de programme est ajusté pour pointer vers le shellcode, après quoi il est exécuté et exécute sa tâche. L'injection du shellcode se fait souvent en stockant le shellcode dans des données envoyées sur le réseau au processus vulnérable, en le fournissant dans un fichier lu par le processus vulnérable ou via la ligne de commande ou l'environnement dans le cas d'exploits locaux.
Codage du shellcode
Étant donné que la plupart des processus filtrent ou restreignent les données pouvant être injectées, il est souvent nécessaire d'écrire du shellcode pour tenir compte de ces restrictions. Cela implique de rendre le code petit, sans valeur nulle ou alphanumérique . Diverses solutions ont été trouvées pour contourner ces restrictions, notamment :
- Optimisations de conception et d'implémentation pour réduire la taille du shellcode.
- Modifications d'implémentation pour contourner les limitations de la plage d'octets utilisés dans le shellcode.
- Code auto-modifiable qui modifie un certain nombre d'octets de son propre code avant de les exécuter pour recréer des octets qui sont normalement impossibles à injecter dans le processus.
Étant donné que la détection d'intrusion peut détecter les signatures de codes shell simples envoyés sur le réseau, elle est souvent codée, rendue auto-décryptable ou polymorphe pour éviter d'être détectée.
Codage en pourcentage
Les exploits qui ciblent les navigateurs codent généralement le shellcode dans une chaîne JavaScript à l'aide du codage en pourcentage , du codage de séquence d'échappement « \uXXXX » ou du codage d'entité . Certains exploits obscurcissent également davantage la chaîne de shellcode codée pour empêcher la détection par IDS .
Par exemple, sur l' architecture IA-32 , voici à quoi NOPressembleraient deux instructions (sans opération), d'abord non codées :
90 NOP 90 NOP
Cette instruction est utilisée dans les diapositives NOP .
Shellcode sans nullité
La plupart des shellcodes sont écrits sans utiliser d' octets nuls car ils sont destinés à être injectés dans un processus cible via des chaînes terminées par un octet nul . Lorsqu'une chaîne terminée par un octet nul est copiée, elle sera copiée jusqu'au premier octet nul inclus, mais les octets suivants du shellcode ne seront pas traités. Lorsque le shellcode contenant des octets nuls est injecté de cette manière, seule une partie du shellcode serait injectée, ce qui le rendrait incapable de s'exécuter correctement.
Pour produire un shellcode sans octets nuls à partir d'un shellcode contenant des octets nuls , on peut remplacer les instructions machine contenant des zéros par des instructions ayant le même effet mais dépourvues d'octets nuls. Par exemple, sur l' architecture IA-32, on pourrait remplacer cette instruction :
B8 01000000 MOV EAX,1 // Définir le registre EAX sur 0x00000001
qui contient des zéros dans le cadre du littéral ( 1se développe en 0x00000001) avec ces instructions :
33C0 XOR EAX,EAX // Définir le registre EAX sur 0x00000000 40 INC EAX // Augmenter EAX à 0x00000001
qui ont le même effet mais nécessitent moins d'octets à encoder et sont exempts de valeurs nulles.
Shellcode alphanumérique et imprimable
Un shellcode alphanumérique est un shellcode qui se compose ou s'assemble lors de l'exécution en caractères ASCII ou Unicode entièrement alphanumériques tels que 0-9, A-Z et a-z. Ce type de codage a été créé par des pirates informatiques pour cacher le code machine fonctionnel à l'intérieur de ce qui semble être du texte. Cela peut être utile pour éviter la détection du code et pour permettre au code de passer à travers des filtres qui éliminent les caractères non alphanumériques des chaînes (en partie, ces filtres étaient une réponse aux exploits de shellcode non alphanumérique). Un type de codage similaire est appelé code imprimable et utilise tous les caractères imprimables (0-9, A-Z, a-z, !@#%^&*() etc.). Une variante restreinte similaire est le code ECHOable ne contenant aucun caractère qui ne soit pas accepté par la commande ECHO . Il a été démontré qu'il est possible de créer un shellcode qui ressemble à du texte normal en anglais. L'écriture de code alphanumérique ou imprimable nécessite une bonne compréhension de l' architecture du jeu d'instructions de la ou des machines sur lesquelles le code doit être exécuté. Il a été démontré qu'il est possible d'écrire du code alphanumérique exécutable sur plusieurs machines, constituant ainsi un code exécutable multi-architecture .
Dans certaines circonstances, un processus cible filtrera tout octet du shellcode injecté qui n'est pas un caractère imprimable ou alphanumérique . Dans de telles circonstances, la gamme d'instructions pouvant être utilisées pour écrire un shellcode devient très limitée. Une solution à ce problème a été publiée par Rix dans Phrack 57 dans lequel il a montré qu'il était possible de transformer n'importe quel code en code alphanumérique. Une technique souvent utilisée consiste à créer un code auto-modifiable, car cela permet au code de modifier ses propres octets pour inclure des octets en dehors de la plage normalement autorisée, élargissant ainsi la plage d'instructions qu'il peut utiliser. En utilisant cette astuce, un décodeur auto-modifiable peut être créé qui utilise initialement uniquement les octets de la plage autorisée. Le code principal du shellcode est codé, utilisant également uniquement les octets de la plage autorisée. Lorsque le shellcode de sortie est exécuté, le décodeur peut modifier son propre code pour pouvoir utiliser n'importe quelle instruction dont il a besoin pour fonctionner correctement, puis continue à décoder le shellcode d'origine. Après avoir décodé le shellcode, le décodeur lui transfère le contrôle, afin qu'il puisse être exécuté normalement. Il a été démontré qu'il est possible de créer un shellcode arbitrairement complexe qui ressemble à du texte normal en anglais.
Shellcode de preuve Unicode
Les programmes modernes utilisent des chaînes Unicode pour permettre l'internationalisation du texte. Souvent, ces programmes convertissent les chaînes ASCII entrantes en Unicode avant de les traiter. Les chaînes Unicode encodées en UTF-16 utilisent deux octets pour encoder chaque caractère (ou quatre octets pour certains caractères spéciaux). Lorsqu'une chaîne ASCII ( Latin-1 en général) est transformée en UTF-16, un octet zéro est inséré après chaque octet de la chaîne d'origine. Obscou a prouvé dans Phrack 61 qu'il est possible d'écrire un shellcode qui peut s'exécuter avec succès après cette transformation. Il existe des programmes qui peuvent encoder automatiquement n'importe quel shellcode en shellcode alphanumérique résistant à l'UTF-16, basés sur le même principe d'un petit décodeur auto-modifiable qui décode le shellcode d'origine.
Plateformes
La plupart des shellcodes sont écrits en code machine en raison du faible niveau auquel la vulnérabilité exploitée donne accès au processus à un attaquant. Le shellcode est donc souvent créé pour cibler une combinaison spécifique de processeur , de système d'exploitation et de service pack , appelée plate-forme . Pour certains exploits, en raison des contraintes imposées au shellcode par le processus cible, un shellcode très spécifique doit être créé. Cependant, il n'est pas impossible qu'un shellcode fonctionne pour plusieurs exploits, service packs, systèmes d'exploitation et même processeurs. Une telle polyvalence est généralement obtenue en créant plusieurs versions du shellcode qui ciblent les différentes plates-formes et en créant un en-tête qui se ramifie vers la version correcte pour la plate-forme sur laquelle le code s'exécute. Une fois exécuté, le code se comporte différemment pour différentes plates-formes et exécute la bonne partie du shellcode pour la plate-forme sur laquelle il s'exécute.
Analyse du shellcode
Le shellcode ne peut pas être exécuté directement. Pour analyser ce qu'un shellcode tente de faire, il doit être chargé dans un autre processus. Une technique d'analyse courante consiste à écrire un petit programme C qui contient le shellcode sous forme de tampon d'octets, puis à utiliser un pointeur de fonction ou un assembleur en ligne pour lui transférer l'exécution. Une autre technique consiste à utiliser un outil en ligne, tel que shellcode_2_exe, pour intégrer le shellcode dans un exécutable prédéfini qui peut ensuite être analysé dans un débogueur standard. Il existe également des outils d'analyse de shellcode spécialisés, tels que le projet iDefense sclog, initialement publié en 2005 dans le cadre du Malcode Analyst Pack. Sclog est conçu pour charger des fichiers de shellcode externes et les exécuter dans un cadre de journalisation API. Il existe également des outils d'analyse de shellcode basés sur l'émulation, tels que l' application sctest qui fait partie du package multiplateforme libemu. Un autre outil d'analyse de shellcode basé sur l'émulation, construit autour de la bibliothèque libemu, est scdbg , qui comprend un shell de débogage de base et des fonctionnalités de création de rapports intégrées.