Windows doit faire quelque chose pour parsingr l’en-tête PE, charger l’exécutable en mémoire et transmettre les arguments de ligne de commande à main()
.
En utilisant OllyDbg, j’ai mis le débogueur à casser sur main () pour que je puisse voir la stack des appels:
Il semble que des symboles manquent pour que nous ne puissions pas obtenir le nom de la fonction, juste son adresse mémoire telle que vue. Cependant, nous pouvons voir que l’appelant de main est kernel32.767262C4
, qui est l’ ntdll.77A90FD9
de ntdll.77A90FD9
. En bas de la stack, nous voyons RETURN en ntdll.77A90FA4
que je suppose être la première fonction à être appelée pour exécuter un exécutable. Il semble que les arguments notables transmis à cette fonction soient l’adresse du gestionnaire des exceptions structurées de Windows et le point d’entrée de l’exécutable.
Alors, comment ces fonctions finissent-elles par charger le programme en mémoire et le préparer au point d’entrée? Est-ce que le débogueur montre tout le processus exécuté par le système d’exploitation avant main()
?
si vous appelez le système CreateProcess
en interne, appelez ZwCreateThread[Ex]
pour créer le premier thread en cours de traitement
Lorsque vous créez un thread – vous (si vous appelez directement ZwCreateThread
) ou le système, initialisez l’enregistrement CONTEXT
pour le nouveau thread – ici Eip(i386)
ou Rip(amd64)
le point d’entrée du thread. Si vous faites cela, vous pouvez spécifier n’importe quelle adresse. mais quand vous appelez, dites Create[Remote]Thread[Ex]
– comme je l’ai dit – le système remplit CONTEXT
et définit l’auto-routine comme point d’entrée du thread. votre point d’entrée d’origine est enregistré dans le Eax(i386)
ou Rcx(amd64)
.
le nom de cette routine dépend de la version de Windows.
tôt c’était BaseThreadStartThunk
ou BaseProcessStartThunk
(dans le cas de CreateProcess
appelé) de kernel32.dll
.
mais maintenant, le système spécifie RtlUserThreadStart
partir de ntdll.dll
. RtlUserThreadStart
appelle généralement BaseThreadInitThunk
partir de kernel32.dll
(à l’exception des applications natives (boot execute), telles que smss.exe
et chkdsk.exe
qui n’ont pas du tout kernel32.dll
dans l’espace d’adressage). BaseThreadInitThunk
appelle déjà votre point d’entrée de thread d’origine, et après (if) il renvoie – RtlExitUserThread
appelé.
l’objective principal de cette enveloppe de démarrage commune des threads – définir le filtre SEH
niveau supérieur. seulement parce que nous pouvons appeler la fonction SetUnhandledExceptionFilter
. si le thread commence directement à partir de votre point d’entrée, sans wrapper – la fonctionnalité du filtre d’exception de niveau supérieur devient indisponible.
mais quel que soit le point d’entrée du thread – thread dans l’espace utilisateur – ne commencez jamais à exécuter à partir de là!
LdrInitializeThunk
lorsque le thread du mode utilisateur commence à exécuter – le système insère APC
pour LdrInitializeThunk
avec LdrInitializeThunk
comme Apc-routine – ceci est fait par copier (sauvegarder) le thread CONTEXT
dans la stack utilisateur et ensuite appeler KiUserApcDispatcher
qui appelle LdrInitializeThunk
. lorsque LdrInitializeThunk
terminé – nous revenons à KiUserApcDispatcher
qui appelle NtContinue
avec le thread enregistré CONTEXT
– seulement après que le point d’entrée du thread commence à être exécuté.
mais maintenant le système effectue une optimisation dans ce processus – il copie (sauve) thread CONTEXT
à la stack de l’utilisateur et appelle directement LdrInitializeThunk
. à la fin de cette fonction, NtContinue
s’appelle – et le point d’entrée du thread est en cours d’exécution.
Ainsi, chaque thread commence à s’exécuter en mode utilisateur à partir de LdrInitializeThunk
. ( cette fonction avec le nom exact existe et appelée dans toutes les versions de Windows de nt4 à win10 )
qu’est-ce que cette fonction fait? car qu’est-ce que c’est? vous êtes peut-être à l’écoute de la notification DLL_THREAD_ATTACH
? quand le nouveau thread en cours commence à exécuter (avec exception pour les threads spéciaux du système, comme LdrpWorkCallback
) – il passe par une liste de DLL chargée et appelle les points d’entrée des DLL avec DLL_THREAD_ATTACH
notification DLL_THREAD_ATTACH
(bien sûr, si la DLL a un point d’entrée et DisableThreadLibraryCalls
). mais comment cela est-il mis en œuvre? grâce à LdrInitializeThunk
qui appelle LdrpInitialize
-> LdrpInitializeThread
-> LdrpCallInitRoutine
(pour les DLLs EP)
lorsque le premier thread en cours de processus commence – c’est un cas particulier. besoin de faire beaucoup de travaux supplémentaires pour l’initialisation du processus. à ce moment, seuls deux modules chargés dans le processus – EXE
et ntdll.dll
. LdrInitializeThunk
appelle LdrpInitializeProcess
pour ce travail. si très brièvement:
LdrpDoDebuggerBreak
– cette fonction regarde – le débogueur est associé au processus, et si yes- int 3
appelé – le débogueur reçoit donc un message d’exception – STATUS_BREAKPOINT
– la plupart des débogueurs ne peuvent commencer le débogage qu’à partir de ce point. cependant, il existe des débogueurs qui permettent de déboguer à partir de LdrInitializeThunk
– toutes mes captures d’écran de ce débogueur ntdll.dll
(et peut provenir de kernel32.dll
) – code provenant d’une autre DLL, tout code tiers non encore exécuté. DLL_PROCESS_DETACH
Initialisations TLS et rappels TLS appelés (le cas échéant)
ZwTestAlert
est appelée – cette vérification d’appel existe APC dans la queue de thread et exécute son. ce point existe dans toutes les versions de NT4 pour gagner 10. ceci permet par exemple de créer un processus en état suspendu et d’insérer ensuite un appel APC ( QueueUserAPC
) à son thread ( PROCESS_INFORMATION.hThread
) – comme résultat cet appel sera exécuté après le processus entièrement initialisé, tout DLL_PROCESS_DETACH
appelé, mais avant le point d’entrée EXE. dans le contexte du premier thread de processus.
lire aussi Flow of CreateProcess