Les piles d'appel fournissent l'information sur l'identité de l'utilisateur.
Les piles d'appels constituent l'un des principaux éléments de différenciation d'Elastic en matière de télémétrie des points d'extrémité Windows.
La plupart des détections s'appuient sur ce qui se passe, ce qui est souvent insuffisant car la plupart des comportements ont un double objectif. Avec les piles d'appels, nous ajoutons la possibilité de déterminer avec précision qui effectue l'activité. Cette combinaison nous donne une capacité inégalée à découvrir les activités malveillantes. En transmettant cette télémétrie approfondie au moteur de règles d'Elastic Defend, nous pouvons réagir rapidement aux menaces émergentes.
Les piles d'appels sont un beau mensonge
En informatique, une pile est une structure de données de type "dernier entré, premier sorti". Comme pour une pile d'objets physiques, il n'est possible d'ajouter ou de retirer que l'élément supérieur. Une pile d'appels est une pile qui contient des informations sur les appels de sous-programmes actuellement actifs.
Sur les hôtes x64, cette pile d'appels ne peut être générée avec précision qu'en utilisant des fonctions de traçage d'exécution sur le processeur, telles que Intel LBR, Intel BTS, Intel AET, Intel IPT et x64 Architectural LBR. Ces fonctions de traçage ont été conçues à des fins de profilage des performances et de débogage, mais elles peuvent également être utilisées dans certains scénarios de sécurité. Toutefois, ce qui est généralement disponible, c'est une pile d'appels approximative qui est récupérée à partir de la pile de données d'un thread par le biais d'un mécanisme appelé " stack walking".
Dans l'architecture x64, le "registre de pointeur de pile" (rsp) pointe sans surprise vers une structure de données de pile, et il existe des instructions efficaces pour lire et écrire les données sur cette pile. En outre, l'instruction call transfère le contrôle à un nouveau sous-programme, mais enregistre également une adresse de retour à l'adresse mémoire référencée par le pointeur de pile. Une instruction ret récupérera ultérieurement cette adresse sauvegardée afin que l'exécution puisse reprendre là où elle s'est arrêtée. Dans la plupart des langages de programmation, les fonctions sont généralement mises en œuvre à l'aide de ces deux instructions, et les paramètres des fonctions ainsi que les variables locales des fonctions sont généralement alloués sur cette pile pour des raisons de performance. La partie de la pile relative à une seule fonction est appelée cadre de pile.
La marche sur la pile consiste à récupérer uniquement les adresses de retour à partir des données hétérogènes stockées sur la pile des threads. Les adresses de retour doivent être stockées quelque part pour le flux de contrôle - la marche sur la pile coopte donc ces données existantes pour obtenir une pile d'appels approximative. Cela convient parfaitement à la plupart des scénarios de débogage et de profilage des performances, mais c'est un peu moins utile pour l'audit de sécurité. Le principal problème est que vous ne pouvez pas démonter l'appareil à l'envers. Vous pouvez toujours déterminer l'adresse de retour d'un site d'appel donné, mais pas l'inverse. La meilleure approche que vous puissiez adopter est de vérifier chacune des 15 longueurs d'instructions précédentes possibles et de voir laquelle se désassemble en une seule instruction d'appel. Même dans ce cas, tout ce que vous avez récupéré est un site d'appel précédent - pas nécessairement le site d'appel précédent exact. En effet, la plupart des compilateurs utilisent l'optimisation des appels de queue pour supprimer les cadres de pile inutiles. Cela crée des scénarios gênants pour la sécurité, comme le fait qu'il n'y a aucune garantie que la fonction Win32StartAddress sera sur la pile même si elle a été appelée.
Ainsi, ce que nous appelons habituellement une pile d'appels est en fait une pile d'adresses de retour.
Les auteurs de logiciels malveillants utilisent cette ambiguïté pour mentir. Ils fabriquent des trames de pile trampoline par l'intermédiaire de modules légitimes afin de dissimuler les appels provenant de codes malveillants, ou ils contraignent la marche de la pile à prédire des adresses de retour différentes de celles que le processeur exécutera. Bien entendu, les logiciels malveillants ont toujours été une tentative de mensonge, et les logiciels antimalveillants ne sont que le moyen de démasquer ce mensonge.
"... mais la vérité finira par éclater."
- William Shakespeare, Le Marchand de Venise, Acte 2, Scène 2
Embellir les piles d'appels
Jusqu'à présent, un parcours de pile n'est qu'une liste d'adresses numériques en mémoire. Pour les rendre utiles à l'analyse, nous devons les enrichir d'un contexte. (Remarque : nous n'incluons pas actuellement les cadres de pile du noyau).
L'enrichissement minimal utile consiste à convertir ces adresses en décalages à l'intérieur des modules (par ex. ntdll.dll+0x15c9c4). Cependant, cela ne permettrait d'attraper que les logiciels malveillants les plus graves - nous pouvons aller plus loin. Les modules les plus importants sous Windows sont ceux qui mettent en œuvre les API Native et Win32. L'interface binaire d'application pour ces API exige que le nom de chaque fonction soit inclus dans le répertoire d'exportation du module contenant. Il s'agit des informations qu'Elastic utilise actuellement pour enrichir les piles d'appels des terminaux.
Un enrichissement plus précis pourrait être obtenu en utilisant les symboles publics (s'ils sont disponibles) hébergés sur l'infrastructure du fournisseur (en particulier Microsoft). Bien que cette méthode offre une plus grande fidélité, elle s'accompagne de coûts opérationnels plus élevés et n'est pas réalisable pour nos clients en situation d'isolement aérien.
Une règle empirique pour le noyau Microsoft et les symboles natifs est que l'interface exportée de chaque composant a un préfixe en majuscule tel que Ldr, Tp ou Rtl. Les fonctions privées prolongent ce préfixe par un p. Par défaut, les fonctions privées avec lien externe sont incluses dans la table des symboles publics. Un décalage très important peut indiquer une fonction très importante, mais aussi une fonction sans nom pour laquelle vous n'avez pas de symboles. Une ligne directrice générale consisterait à considérer toute compensation à trois chiffres ou plus dans une fonction exportée comme appartenant probablement à une autre fonction.
| Pile d'appels | Marche par paquets | Modules de marche en pile | Exportations de la marche des piles (approche élastique) | Symboles publics de Stack Walk |
|---|---|---|---|---|
| 0x7ffb8eb9c9c2 0x12d383f0046 0x7ffb8eb1a9d8 0x7ffb8eb1aaf4 0x7ffb8ea535ff 0x7ffb8da5e8cf 0x7ffb8eaf14eb | 0x7ffb8eb9c9c4 0x7ffb8c3c71d6 0x7ffb8eb1a9ed 0x7ffb8eb1aaf9 0x7ffb8ea53604 0x7ffb8da5e8d4 0x7ffb8eaf14f1 | ntdll.dll+0x15c9c4 kernelbase.dll+0xc71d6 ntdll.dll+0xda9ed ntdll.dll+0xdaaf9 ntdll.dll+0x13604 kernel32.dll+0x2e8d4 ntdll.dll+0xb14f1 | ntdll.dll!NtProtectVirtualMemory+0x14 kernelbase.dll!VirtualProtect+0x36 ntdll.dll!RtlAddRefActivationContext+0x40d ntdll.dll !RtlAddRefActivationContext+0x519 ntdll.dll!RtlAcquireSRWLockExclusive+0x974 kernel32.dll!BaseThreadInitThunk+0x14 ntdll.dll!RtlUserThreadStart+0x21 | ntdll.dll!NtProtectVirtualMemory+0x14 kernelbase.dll!VirtualProtect+0x36 ntdll.dll!RtlTpTimerCallback+0x7d ntdll.dll !TppTimerpExecuteCallback+0xa9 ntdll.dll!TppWorkerThread+0x644 kernel32.dll!BaseThreadInitThunk+0x14 ntdll.dll!RtlUserThreadStart+0x21 |
Comparaison des niveaux d'enrichissement de la pile d'appels
Dans l'exemple ci-dessus, le shellcode situé à 0x12d383f0000 a délibérément utilisé un appel de queue afin que son adresse n'apparaisse pas dans le parcours de la pile. Ce mensonge par omission est évident même si l'on ne fait que marcher. Elastic signale ce problème avec l'heuristique proxy_call, car le logiciel malveillant a enregistré une fonction de rappel de minuterie pour remplacer l'appel à VirtualProtect à partir d'un autre fil d'exécution.
Rendre les piles d'appels plus puissantes
Les piles des appels système que nous surveillons avec Event Tracing for Windows (ETW) ont une structure attendue. Au bas de la pile se trouve l'adresse de démarrage du thread - typiquement ntdll.dll!RtlUserThreadStart. Vient ensuite l'entrée du thread de l'API Win32 - kernel32.dll!BaseThreadInitThunk, puis le premier module utilisateur. Un module utilisateur est un code d'application qui ne fait pas partie de l'API Win32 (ou Native). Ce premier module utilisateur doit correspondre à l'adresse Win32StartAddress du thread (sauf si cette fonction a utilisé un appel de queue). D'autres modules utilisateurs suivront jusqu'à ce que le dernier module utilisateur fasse un appel à une API Win32 qui fait un appel à une API native, ce qui aboutit finalement à un appel système au noyau.
Du point de vue de la détection, le module le plus important de cette pile d'appels est le module utilisateur final. Elastic affiche ce module, y compris son hachage et toutes les signatures de code. Ces détails facilitent le triage des alertes, mais surtout, ils améliorent considérablement la granularité à laquelle nous pouvons évaluer les comportements des logiciels légitimes qui se comportent parfois comme des logiciels malveillants. Plus notre image de la normalité est précise, plus il est difficile pour les logiciels malveillants de se fondre dans la masse.
{
"process.thread.Ext": {
"call_stack_summary": "ntdll.dll|kernelbase.dll|file.dll|rundll32.exe|kernel32.dll|ntdll.dll",
"call_stack": [
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!NtAllocateVirtualMemory+0x14" }, /* Native API */
{ "symbol_info": "c:\\windows\\system32\\kernelbase.dll!VirtualAllocExNuma+0x62" }, /* Win32 API */
{ "symbol_info": "c:\\windows\\system32\\kernelbase.dll!VirtualAllocEx+0x16" }, /* Win32 API */
{
"symbol_info": "c:\\users\\user\\desktop\\file.dll+0x160d8b", /* final user module */
"callsite_trailing_bytes": "488bf0488d4d88e8197ee2ff488bc64883c4685b5e5f415c415d415e415f5dc390909090905541574156415541545756534883ec58488dac2490000000488b71",
"callsite_leading_bytes": "088b4d38894c2420488bca48894db8498bd0488955b0458bc1448945c4448b4d3044894dc0488d4d88e8e77de2ff488b4db8488b55b0448b45c4448b4dc0ffd6"
},
{ "symbol_info": "c:\\users\\user\\desktop\\file.dll+0x7b429" },
{ "symbol_info": "c:\\users\\user\\desktop\\file.dll+0x44a9" },
{ "symbol_info": "c:\\users\\user\\desktop\\file.dll+0x5f58" },
{ "symbol_info": "c:\\windows\\system32\\rundll32.exe+0x3bcf" },
{ "symbol_info": "c:\\windows\\system32\\rundll32.exe+0x6309" }, /* first user module - typically the ETHREAD.Win32StartAddress module */
{ "symbol_info": "c:\\windows\\system32\\kernel32.dll!BaseThreadInitThunk+0x14" }, /* Win32 API */
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlUserThreadStart+0x21" /* Native API - the ETHREAD.StartAddress module */
}
],
"call_stack_final_user_module": {
"path": "c:\\users\\user\\desktop\\file.dll",
"code_signature": [ { "exists": false } ],
"name": "file.dll",
"hash": { "sha256": "0240cc89d4a76bafa9dcdccd831a263bf715af53e46cac0b0abca8116122d242" }
}
}
}
Exemple de pile d'appels enrichie
Enrichissement du module utilisateur final de la pile d'appel :
| name | Le nom du fichier du module call_stack_final_user_module. Peut également être "Unbacked" indiquant une mémoire exécutable privée, ou "Undetermined" indiquant une pile d'appels suspecte. |
|---|---|
| chemin | Le chemin d'accès au fichier du module call_stack_final_user_module. |
| hash.sha256 | Le sha256 du module call_stack_final_user_module, ou le module protection_provenance le cas échéant. |
| code_signature | Signature du code du module call_stack_final_user_module, ou du module protection_provenance le cas échéant. |
| allocation_private_bytes | Nombre d'octets de cette région de mémoire qui sont à la fois +X et non partageables. Des valeurs non nulles peuvent indiquer un accrochage du code, un patching ou un hollowing. |
| protection | La protection de la mémoire pour la région intérimaire des pages est incluse si elle n'est pas RX. Correspond à MEMORY_BASIC_INFORMATION.Protect. |
| protection_provenance | Le nom de la région de la mémoire qui a causé la dernière modification de la protection de cette page. "Unbacked" peut indiquer la présence d'un shellcode. |
| chemin_de_provenance_de_protection | Le chemin du module qui a causé la dernière modification de la protection de cette page. |
| raison | Le résumé anormal de la pile d'appels qui a conduit à une "indéterminée" protection_provenance. |
Un glossaire rapide de la pile d'appels
Lors de l'examen des piles d'appels, il est utile de se familiariser avec certaines fonctions de l'API native. Ken Johnson, qui travaille maintenant chez Microsoft, nous a fourni un catalogue de rappels NTDLL du mode noyau au mode utilisateur pour nous aider à démarrer. Sérieusement, vous devriez vous arrêter ici et lire d'abord cela.
Nous avons rencontré RtlUserThreadStart plus tôt. Elle et sa sœur RtlUserFiberStart ne doivent jamais apparaître qu'au bas de la pile d'appels. Il s'agit des points d'entrée pour les threads et les fibres de l'utilisateur, respectivement. La première instruction de chaque thread est cependant LdrInitializeThunk. Après avoir effectué la composante mode utilisateur de l'initialisation du thread (et du processus, si nécessaire), cette fonction transfère le contrôle au point d'entrée via NtContinue, qui met directement à jour le pointeur d'instruction. Cela signifie qu'il n'apparaîtra pas dans les futures marches de la pile.
Ainsi, si vous voyez une pile d'appels qui inclut LdrInitializeThunk, cela signifie que vous êtes au tout début de l'exécution d'un thread. C'est là qu'opère le moteur de compatibilité des applications Shim, que les produits de sécurité basés sur des crochets préfèrent s'installer et que les logiciels malveillants tentent de s'exécuter avant ces autres produits de sécurité. Marcus Hutchins et Guido Miggelenbrink ont tous deux écrit d'excellents blogs sur ce sujet. Cette course au démarrage n'existe pas pour les produits de sécurité qui utilisent l'ETW du noyau pour la télémétrie.
{
"process.thread.Ext": {
"call_stack_summary": "ntdll.dll|file.exe|ntdll.dll",
"call_stack": [
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!ZwProtectVirtualMemory+0x14" },
{ "symbol_info": "c:\\users\\user\\desktop\\file.exe+0x1bac8" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlAnsiStringToUnicodeString+0x3cb" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!LdrInitShimEngineDynamic+0x394d" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!LdrInitializeThunk+0x1db" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!LdrInitializeThunk+0x63" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!LdrInitializeThunk+0xe" }
],
"call_stack_final_user_module": {
"path": "c:\\users\\user\\desktop\\file.exe",
"code_signature": [ { "exists": false } ],
"name": "file.exe",
"hash": { "sha256": "a59a7b56f695845ce185ddc5210bcabce1fff909bac3842c2fb325c60db15df7" }
}
}
}
Exemple d'exécution d'un point d'entrée préalable
La paire suivante est KiUserExceptionDispatcher et KiRaiseUserExceptionDispatcher. Le noyau utilise le premier pour passer l'exécution à un gestionnaire d'exception structuré en mode utilisateur enregistré après qu'une condition d'exception en mode utilisateur s'est produite. Cette dernière lève également une exception, mais au nom du noyau. Cette deuxième variante n'est généralement détectée que par les débogueurs, y compris Application Verifier, et permet d'identifier les cas où le code en mode utilisateur ne vérifie pas suffisamment les codes de retour des appels de système. Ces fonctions sont généralement présentes dans les piles d'appels liées à la gestion des pannes spécifiques à l'application ou au rapport d'erreur de Windows. Cependant, les logiciels malveillants l'utilisent parfois comme un pseudo-point de rupture - par exemple, s'ils veulent faire fluctuer les protections de la mémoire pour cacher à nouveau leur shellcode immédiatement après avoir effectué un appel au système.
{
"process.thread.Ext": {
"call_stack_summary": "ntdll.dll|file.exe|ntdll.dll|file.exe|kernel32.dll|ntdll.dll",
"call_stack": [
{
"symbol_info": "c:\\windows\\system32\\ntdll.dll!ZwProtectVirtualMemory+0x14",
"protection_provenance": "file.exe", /* another vendor's hooks were unhooked */
"allocation_private_bytes": 8192
},
{ "symbol_info": "c:\\users\\user\\desktop\\file.exe+0xd99c" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlInitializeCriticalSectionAndSpinCount+0x1c6" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlWalkFrameChain+0x1119" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!KiUserExceptionDispatcher+0x2e" },
{ "symbol_info": "c:\\users\\user\\desktop\\file.exe+0x12612" },
{ "symbol_info": "c:\\windows\\system32\\kernel32.dll!BaseThreadInitThunk+0x14" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlUserThreadStart+0x21" }
],
"call_stack_final_user_module": {
"name": "file.exe",
"path": "c:\\users\\user\\desktop\\file.exe",
"code_signature": [ { "exists": false }],
"hash": { "sha256": "0e5a62c0bd9f4596501032700bb528646d6810b16d785498f23ef81c18683c74" }
}
}
}
Exemple de fluctuation de la protection par le biais d'un gestionnaire d'exception
Vient ensuite KiUserApcDispatcher, qui est utilisé pour distribuer les APC des utilisateurs. Il s'agit de l'un des outils préférés des auteurs de logiciels malveillants, car Microsoft ne fournit qu'une visibilité limitée sur son utilisation.
{
"process.thread.Ext": {
"call_stack_summary": "ntdll.dll|kernelbase.dll|ntdll.dll|kernelbase.dll|cronos.exe",
"call_stack": [
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!NtProtectVirtualMemory+0x14" },
{ "symbol_info": "c:\\windows\\system32\\kernelbase.dll!VirtualProtect+0x36" }, /* tail call */
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!KiUserApcDispatcher+0x2e" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!ZwDelayExecution+0x14" },
{ "symbol_info": "c:\\windows\\system32\\kernelbase.dll!SleepEx+0x9e" },
{
"symbol_info": "c:\\users\\user\\desktop\\file.exe+0x107d",
"allocation_private_bytes": 147456, /* stomped */
"protection": "RW-", /* fluctuation */
"protection_provenance": "Undetermined", /* proxied call */
"callsite_leading_bytes": "010000004152524c8d520141524883ec284150415141baffffffff41525141ba010000004152524c8d520141524883ec284150b9ffffffffba0100000041ffe1",
"callsite_trailing_bytes": "4883c428c3cccccccccccccccccccccccccccc894c240857b820190000e8a10c0000482be0488b052fd101004833c44889842410190000488d84243014000048"
}
],
"call_stack_final_user_module": {
"name": "Undetermined",
"reason": "ntdll.dll|kernelbase.dll|ntdll.dll|kernelbase.dll|file.exe"
}
}
}
Fluctuation de la protection via l'exemple de l'APC
Le gestionnaire de fenêtres de Windows est implémenté dans un pilote de périphérique en mode noyau (win32k.sys). En grande partie. Parfois, le gestionnaire de fenêtres doit faire quelque chose en mode utilisateur, et KiUserCallbackDispatcher est le mécanisme qui permet d'y parvenir. Il s'agit en fait d'un appel de système inversé qui cible les fonctions de user32.dll. L'écrasement d'une entrée dans la table KernelCallbackTable d'un processus est un moyen facile de détourner un thread de l'interface graphique, de sorte que tout autre module suivant cet appel est suspect.
La connaissance de l'objectif de chacun de ces points d'entrée du mode noyau au mode utilisateur aide grandement à déterminer si une pile d'appels donnée est naturelle ou si elle a été détournée pour atteindre d'autres objectifs.
Rendre les piles d'appels compréhensibles
Pour faciliter la compréhension, nous marquons également l'événement avec divers comportements process.Ext.api.que nous identifions. Ces comportements ne sont pas nécessairement malveillants, mais ils mettent en évidence des aspects pertinents pour le triage des alertes ou la recherche de menaces. Pour les piles d'appels, il s'agit des éléments suivants
| native_api | Un appel a été fait directement à l'API native plutôt qu'à l'API Win32. |
|---|---|
| appel_direct | A syscall instruction originated outside of the Native API layer. |
| appel de procuration | La pile d'appels peut indiquer un appel API proxy pour masquer la véritable source. |
| shellcode | Mémoire non image exécutable de deuxième génération appelée API sensible. |
| image_appel_indirect | An entry in the call stack was preceded by a call to a dynamically resolved function. |
| image_rop | Aucune instruction d'appel n'a précédé une entrée dans la pile d'appels. |
| image_rwx | Une entrée dans la pile d'appels est accessible en écriture. Le code doit être en lecture seule. |
| unbacked_rwx | Une entrée dans la pile d'appels n'est pas une image et est accessible en écriture. Même le code JIT doit être en lecture seule. |
| pile_tronquée | The call stack seems to be unexpectedly truncated. This may be due to malicious tampering. |
Dans certains contextes, ces comportements peuvent suffire à détecter les logiciels malveillants.
Spoofing - contournement ou responsabilité ?
L'usurpation d'adresse de retour est une technique de base du piratage et des logiciels malveillants depuis de très nombreuses années. Cette astuce simple permet au code injecté d'emprunter la réputation d'un module légitime avec peu de conséquences. L'objectif de l'inspection approfondie de la pile d'appels et des lignes de base comportementales est d'empêcher les logiciels malveillants de bénéficier de ce passe-droit.
Les chercheurs offensifs ont contribué à cet effort en étudiant des approches permettant d'usurper l'intégralité de la pile d'appels. Notamment :
- Spoofing Call Stacks To Confuse EDRs (Espionnage des piles d'appels pour confondre les EDR ) par William Burgess
- SilentMoonwalk : Implémentation d'un espion dynamique de la pile d'appels par Alessandro Magnosi, Arash Parsa et Athanasios Tserpelis
SilentMoonwalk, en plus d'être une superbe recherche offensive, est un excellent exemple de la façon dont le mensonge peut vous attirer deux fois plus d'ennuis - mais seulement si vous vous faites prendre. De nombreuses techniques d'évasion de la défense reposent sur la sécurité par l'obscurité - et une fois révélées par les chercheurs, elles peuvent devenir un fardeau. Dans ce cas, la recherche comprenait des conseils sur les possibilités de détection introduites par la tentative d'évasion.
{
"process.thread.Ext": {
"call_stack_summary": "ntdll.dll|kernelbase.dll|kernel32.dll|ntdll.dll",
"call_stack": [
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!NtAllocateVirtualMemory+0x14" },
{ "symbol_info": "c:\\windows\\system32\\kernelbase.dll!VirtualAlloc+0x48" },
{
"symbol_info": "c:\\windows\\system32\\kernelbase.dll!CreatePrivateObjectSecurity+0x31",
/* 4883c438 stack desync gadget - add rsp 0x38 */
"callsite_trailing_bytes": "4883c438c3cccccccccccccccccccc48895c241057498bd8448bd2488bf94885c90f84660609004885db0f845d060900418bd14585c97411418bc14803c383ea",
"callsite_leading_bytes": "cccccccccccccccccccccccccccccc4883ec38488b4424684889442428488b442460488944242048ff15d9b21b000f1f44000085c00f8830300900b801000000"
},
{ "symbol_info": "c:\\windows\\system32\\kernelbase.dll!Internal_EnumSystemLocales+0x406" },
{ "symbol_info": "c:\\windows\\system32\\kernelbase.dll!SystemTimeToTzSpecificLocalTimeEx+0x2d1" },
{ "symbol_info": "c:\\windows\\system32\\kernelbase.dll!WaitForMultipleObjectsEx+0x982" },
{ "symbol_info": "c:\\windows\\system32\\kernel32.dll!BaseThreadInitThunk+0x14" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlUserThreadStart+0x21" }
],
"call_stack_final_user_module": {
"name": "Undetermined", /* gadget module resulted in suspicious call stack */
"reason": "ntdll.dll|kernelbase.dll|kernel32.dll|ntdll.dll"
}
}
}
Exemple de pile d'appels pour SilentMoonwalk
Une technique standard pour découvrir des artefacts cachés consiste à les énumérer à l'aide de plusieurs techniques et à comparer les résultats pour vérifier s'il y a des divergences. Voici comment fonctionne RootkitRevealer. Cette approche a également été utilisée dans Get-InjectedThreadEx.exe, qui monte dans la pile de threads tout en la descendant.
Dans certaines circonstances, il est possible de récupérer une pile d'appels de deux manières. S'il y a des divergences, vous verrez la pile d'appels la moins fiable émise en tant que call_stack_summary_original.
{
"process.thread.Ext": {
"call_stack_summary": "ntdll.dll",
"call_stack_summary_original": "ntdll.dll|kernelbase.dll|version.dll|kernel32.dll|ntdll.dll",
"call_stack": [
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!NtContinue+0x12" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!LdrInitializeThunk+0x13" }
],
"call_stack_final_user_module": {
"name": "Undetermined",
"reason": "ntdll.dll"
}
}
}
Résumé de la pile d'appels de l'exemple original
Les Call Stacks sont pour tout le monde
Par défaut, vous ne trouverez que des piles d'appels dans nos alertes, mais cela peut être configuré par le biais d'une politique avancée.
| events.callstacks.emit_in_events | Si cette option est activée, les piles d'appels seront incluses dans les événements réguliers où elles sont collectées. Sinon, ils ne sont inclus que dans les événements qui déclenchent des règles de protection du comportement. Notez que ce paramètre peut augmenter de manière significative les volumes de données. Valeur par défaut : false |
|---|
Pour en savoir plus sur les piles d'appels Windows, consultez les articles suivants d'Elastic Security Labs :
- Faire monter les enchères : Détection des menaces en mémoire à l'aide des piles d'appels du noyau
- Lever le rideau sur les piles d'appels
- Doubler la mise : détecter les menaces en mémoire avec les piles d'appels ETW du noyau
- In-the-Wild Windows LPE 0-days: Insights & Detection Strategies
- Des modalités qui se comportent mal : Détecter des outils et non des techniques
- Trouver la vérité dans l'ombre
