Il y a quelques temps j’avais rédigé un petit article sur les techniques d’injection de code dans des processus en cours d’exécution.
Toutes ces techniques avaient un point en commun, l’utilisation de fonctions de l’API Windows.
Quel est le problème ?
L’analyse comportementale des antivirus et EDR reposent en partie sur le traçage de fonctions suspectes de l’API Windows.
Comment cela fonctionne ?
A chaque fois qu’un processus Windows est lancé, l’antivirus le détecte et modifie les appels API des DLL chargées en mémoire.
Cela paraît compliqué mais en fait c’est assez simple.
Au début de chaque fonction Windows, l’antivirus ajoute dynamiquement un petit « JUMP » vers lui-même histoire de faire son analyse puis, si rien d’anormal n’a été détecté, retourne tranquillement dans le code de la fonction , c’est du « API Hooking ».
Pour que l’injection de code réussisse et éviter de se faire tracer et bloquer il faut donc soit « défaire » le JUMP, c’est à dire faire du « anti API Hooking » ou bien faire appel à des couches plus basses dans le système.
La stratégie anti-api-hooking fonctionne dans certains cas mais a ses limites car les antivirus et EDR vérifie régulièrement que leur JUMP est toujours là.
Intéressons-nous aux couches basses des API Windows.
Les API Windows utilisées pour de l’injection classique de code dans un process reposent toutes sur la librairie kernel32.dll
Contrairement à son nom, kernel32.dll n’est pas une librairie de bas niveau mais plutôt de haut niveau (et oui on chez Microsoft tout est très logique…).
Les fonctions définies dans kernel32.dll font en fait des appels vers des fonctions de plus bas niveaux, elles sont définies dans ntdll.dll
Le souci avec cette DLL c’est que volontairement il n’y a pas de documentation officielle de la part de Microsoft. C’est grâce au travail de nombreux reverseurs que l’on peut assez facilement faire des appels aux fonctions de cette DLL de bas niveau.
Mais ce n’est pas suffisant, on retombe toujours sur le problème du API Hooking, continuons la descente dans les couches du système.
La librairie ntdll.dll est la dernière librairie dans l’espace utilisateur, le code des fonctions à l’intérieur font ensuite des appels systèmes vers l’espace kernel.
Vous voyez où je veux en venir.
Plutôt que de faire des appels à des fonctions définies dans kernel32.dll ou dans ntdll.dll pourquoi ne pas faire des appels directs au système en assembleur ?
Et bien c’est ce que j’ai fait !
Avec cette technique on peut bypasser toutes les solutions de détections basées sur du API Hooking dans l’espace utilisateur car justement on ne fait plus du tout appel à des fonctions !
Une chose importante à savoir, les appels systèmes sont très dépendant de la version exacte de Windows, il faut donc au préalable déterminer le build de Windows avant de pouvoir faire les bons appels systèmes.
C’est grâce au travail d’un membre de l’équipe de Google Project Zero (https://j00ru.vexillium.org/syscalls/nt/64/) que l’on peut maintenant avoir accès facilement à l’ensemble des valeurs à utiliser pour les registres assembleurs.
Concrètement et sans trop de détails :
Lors de l’injection de code on utilise très souvent à la fin la fonction CreateRemoteThread de la DLL kernel32.dll
HANDLE WINAPI CreateRemoteThread(
__in HANDLE hProcess,
__in LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,
__in LPVOID lpParameter,
__in DWORD dwCreationFlags,
__out LPDWORD lpThreadId
);
Cette fonction fait en fait appel à la fonction NtCreateThreadEx de la DLL ntdll.dll
EXTERN_C NTSTATUS NtCreateThreadEx(
OUT PHANDLE ThreadHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN HANDLE ProcessHandle,
IN PVOID StartRoutine, // PUSER_THREAD_START_ROUTINE
IN PVOID Argument,
IN ULONG CreateFlags, // THREAD_CREATE_FLAGS_*
IN SIZE_T ZeroBits,
IN SIZE_T StackSize,
IN SIZE_T MaximumStackSize,
IN PPS_ATTRIBUTE_LIST AttributeList
);
Qui elle-même fait un « syscall » vers le kernel avec ces paramètres sous Windows 10 :
; NtCreateThreadEx
; Windows 10
; 1507 1511 1607 1703 1709 1803 1809 1903 1909 2004 20H2
; 0x00b3 0x00b4 0x00b6 0x00b9 0x00ba 0x00bb 0x00bc 0x00bd 0x00bd 0x00c1 0x00c1
NtCreateThreadEx proc
mov r10, rcx
mov eax, 0bdh
syscall
ret
NtCreateThreadEx endp
Alors voila, tout mis bout à bout j’ai développé une injection de code sans faire aucun appel à des fonctions API.
Et ça marche du tonnerre !
Ci-joint un screenshot de l’exécution où l’on voit l’injection de la dll testlib64.dll avec le « Hello from testlib » dans le processus sublime_text.exe (au passage c’est du 64 bits, encore plus compliqué) :
![](https://pentester.blog/wp-content/uploads/2020/12/ProcessInjectionDirectSyscall.png)
Je mettrai quand j’aurai un moment l’intégralité du code sur mon Github.
[MAJ 06/12/2020] code disponible
Alors comment contrer ce genre de techniques ?
Et bien il faut faire de l’API Hooking mais au niveau kernel, en développant des drivers, c’est ce que font les bons antivirus !