Hoy ! Ca faisait longtemps que je n'avais pas posté d'article ! Pour dire, y'a rien eu en mai, c'est navrant ! Mais, comme dirait Ivanlef0u, au lieu de poster pour rien dire,
j'ai préféré fermer ma gueule
.
Avant de commencer, je lui dois un grand merci. Sans lui, ma lanterne ne serait pas éclairée. Petit big up également à Lord 0vercl0k, qui a su se montrer très patient. Ils forment une belle paire, si vous voulez mon avis.
Bon, bon, bon. Entrons dans le vif du sujet : l'instruction sysenter en assembleur. On rencontre souvent cette instruction lorsqu'on trace dans les appels aux APIs pendant une session de debuging. Etant curieux, je m'étais posé la question suivante :
Mais c'est quelle instruction qui affiche du flux à l'écran à l'intérieur de WriteFile() ?. Eh bien, j'ai été surpris de tomber sur l'instruction Sysenter qui faisait tout basculer. Mais à quoi correspond-elle ?
L'instruction sysenter correspond au passage du mode "userland" (appelé "ring 3") au kerneland ("ring 0"). Les
rings, ou
anneaux sont représentés par un numéro codé sur 2 bits (donc de 0 à 3). Plus le chiffre est bas, plus les privilèges sont élevés. Les APIs se contentent donc de faire exécuter tel ou tel code en ring0, et nous, on a juste à faire de simples appels sur les APIs. Ca facilite la vie, disons.
Mon objectif premier était de déclencher ma propre apparition de flux à l'écran à l'aide d'une interruption noyau. En me référant à la
Windows System Call Table, j'avais essayé avec la fameuse NtWriteFile(), sans succès.
C'est là que je viens voir Ivanlef0u, et il me conseille de tenter avec NtQuerySystemInformation(). Cette fonction, très bas niveau, sert visiblement à obtenir des informations sur le système. Enfin, au lieu d'utiliser les fonctions de type Nt, il m'a plutôt fallu utiliser les fonctions en Zw ; c'est-à-dire ZwQuerySystemInformation().
Sa documentation dans la MSDN :
http://msdn.microsoft.com/en-us/library/ms725506(VS.85).aspx.
Sa description est on ne peut plus claire : "Retrieves the specified system information.", ou "réquisitionne les informations systèmes spécifiées".
Sa signature n'étant pas disponible dans un .h, il faut aller récupérer son adresse dans
ntdll.dll et sauter dessus, dont la plupart des fonctions ne sont nullement documentées. Pour cela, on va utiliser deux fonctions de l'API Win32 :
- LoadLibrary() : elle prend exactement un paramètre : le nom de la DLL. Elle retourne un "handle" (ou poignée) sur cette DLL. Un "handle", en informatique, permet de manipuler un bloc de donnée. Imaginez par exemple une simple raquette de tennis : il vous faut une poignée pour manipuler la grande surface. Ici, c'est pareil ;
- GetProcAddress() : elle prend respectivement en argument le handle retourné par LoadLibrary, et le nom de la fonction dont on veut l'adresse : ici, ça sera ZwQuerySystemInformation. GetProcAdress retourne aussi un handle, et cette fois, sur la fonction.
(Vous verrez qu'en API WIN32, beaucoup de chose sont une histoire d' "handle".)
Liens MSDN :
LoadLibrary() et
GetProcAddress().
Voici une solution de code C que j'ai pu pondre :
#include <windows.h>
#include <stdio.h>
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation,
SystemProcessorInformation,
SystemPerformanceInformation,
SystemTimeOfDayInformation,
SystemPathInformation,
SystemProcessInformation,
SystemCallCountInformation,
SystemDeviceInformation,
SystemProcessorPerformanceInformation,
SystemFlagsInformation,
SystemCallTimeInformation,
SystemModuleInformation,
SystemLocksInformation,
SystemStackTraceInformation,
SystemPagedPoolInformation,
SystemNonPagedPoolInformation,
SystemHandleInformation,
SystemObjectInformation,
SystemPageFileInformation,
SystemVdmInstemulInformation,
SystemVdmBopInformation,
SystemFileCacheInformation,
SystemPoolTagInformation,
SystemInterruptInformation,
SystemDpcBehaviorInformation,
SystemFullMemoryInformation,
SystemLoadGdiDriverInformation,
SystemUnloadGdiDriverInformation,
SystemTimeAdjustmentInformation,
SystemSummaryMemoryInformation,
SystemNextEventIdInformation,
SystemEventIdsInformation,
SystemCrashDumpInformation,
SystemExceptionInformation,
SystemCrashDumpStateInformation,
SystemKernelDebuggerInformation,
SystemContextSwitchInformation,
SystemRegistryQuotaInformation,
SystemExtendServiceTableInformation,
SystemPrioritySeperation,
SystemPlugPlayBusInformation,
SystemDockInformation,
SystemPowerInformation2,
SystemProcessorSpeedInformation,
SystemCurrentTimeZoneInformation,
SystemLookasideInformation
} SYSTEM_INFORMATION_CLASS, *PSYSTEM_INFORMATION_CLASS;
typedef struct _SYSTEM_BASIC_INFORMATION {
BYTE Reserved1[24];
PVOID Reserved2[4];
CCHAR NumberOfProcessors;
} SYSTEM_BASIC_INFORMATION;
typedef int (*MYPROC)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG);
int main(int argc, char **argv) {
HINSTANCE hinstLib;
MYPROC ProcAdd;
BOOL fFreeResult, fRunTimeLinkSuccess = FALSE;
SYSTEM_BASIC_INFORMATION arg1;
hinstLib = LoadLibrary(TEXT("ntdll.dll"));
if (hinstLib != NULL) {
ProcAdd = (MYPROC)GetProcAddress(hinstLib, "ZwQuerySystemInformation");
if (NULL != ProcAdd) {
fRunTimeLinkSuccess = TRUE;
ProcAdd(SystemBasicInformation, (PVOID)&arg1, sizeof(arg1), NULL );
printf("Processeur : %d\n",arg1.NumberOfProcessors);
system("pause > nul");
}
fFreeResult = FreeLibrary(hinstLib);
}
return 0;
}
La source est disponible ici dans son intégralité :
http://venom630.free.fr/geo/blog/sysenter/ZwQuerySystemInformation_c.txt.
Je n'ai pas commenté le code (et c'est très mal), mais je vais vous expliquer concrètement ce que j'ai fait. D'abord, j'ai récupéré la grosse "enum" sur un site qui regroupe un tas de structures/enums/fonctions non documentées par Microsoft (merci à 0vercl0k, d'ailleurs) :
http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/System%20Information/SYSTEM_INFORMATION_CLASS.html, et la structure SYSTEM_BASIC_INFORMATION sur la MSDN.
Petite parenthèse, pour ceux que ça intéresse : la signification des préfixes Nt et Zw : Nt signifie "New technologie", et Zw n'a aucune signification. Pourquoi ? Pour éviter les collisions entre les noms de fonction.
Enfin, j'ai fourni l'exécutable en plus du code. Tout est téléchargeable
ici.
Maintenant, on va debugger notre programme et tracer à l'intérieur du call à notre fonction chargée dynamiquement. On pose donc un break à l'adresse
00401320 comme ceci :

Et on lance le programme (F9) pour ensuite tracer dans le call pas à pas (F7). On tombe sur ça :
7C91D92E > B8 AD000000 MOV EAX,0AD
7C91D933 BA 0003FE7F MOV EDX,7FFE0300
7C91D938 FF12 CALL DWORD PTR DS:[EDX]
7C91D93A C2 1000 RETN 10
La première instruction met le numéro du syscall dans EAX. Si l'on regarde la Windows System Call Table fournie par MetaSploit, on voit bien qu'il s'agit de NtQuerySystemInformation. Ensuite, on met dans Edx une adresse qui correspond à celle de la fonction
KiFastSystemCall() (merci à 0vercl0k pour ce petit rectificatif), et on l'appelle via le call. On trace dedans :
7C91E510 > 8BD4 MOV EDX,ESP
7C91E512 0F34 SYSENTER
7C91E514 > C3 RETN
La première instruction charge dans EDX la valeur d'ESP, ou le sommet de la pile. Probablement pour permettre, lors du passage dans ring0, de pouvoir revenir en Ring3. Apparement, les arguments de la fonction se situent aussi en-dessous dans la pile. On trace dans Sysenter, et on s'aperçoit que notre espace mémoire correspondant à arg1 en C a été modifié. A l'adresse 0022FF58, on a le nombre de processeurs dans notre ordinateur. Pour ma part, j'ai "1" car j'ai un monocore qui date un peu. Mais pour les processeurs double coeur, vous devriez voir "2" (merci à Ornlu pour avoir testé).
Voici ce que j'ai en mode console, en tout cas :
Processeur : 1
Quant aux fonctions qui affichent du flux à l'écran, elles appellent ZwRequestWaitReplyPort. Ivanlef0u m'a expliqué que
la console est gérée bizarrement
. Ca à donc l'air compliqué d'afficher du flux soi-même avec un sysenter. Si y'a un commentaire pour me dépanner, il est le bienvenue. Mes connaissances sur le sujet ne sont que peu conséquentes, je suis débutant.
Conclusion ?
Même windows utilise les syscalls, mais ceux-ci sont moins faciles d'implémentation qu'avec linux où, pour certaines fonctions, on peut se contenter de mettre les arguments dans les registres et d'appeler le service 80h en interruption noyau. Pour Windows, ça reste compliqué mais vachement intéressant.
On va se quitter avec quelques articles qui parlent de l'instruction sysenter, notamment avec des concepts mieux détaillés voire avancés :
Ivanlef0u's Blog > SYSENTER, stepping into da ring0Plongeon dans les appels sytème windows, par Emilien Girault (big up à toi aussi, d'ailleurs)First Steps Into Ring0, par 0vercl0kA la prochaine !
Geo