Nouveaux concepts : Utilisation du contexte du thread Nouvelles fonctions API :
SusupendThread |
La structure CONTEXT du thread Comme nous l'avons mentionné dans le dernier tutoriel, chaque thread maintient une structure CONTEXT qui comprend les registres, la pile du noyau, un bloc d'environnement de thread et une pile utilisateur. Nous pouvons lire et modifier les valeurs des registres à partir d'un thread en cours d'exécution dans un processus en utilisant les API GetThreadContext et SetThreadContext pour lire ou écrire dans la structure CONTEXT. Cette structure de données est complexe et seules
certaines parties sont utiles, mais nous pouvons
spécifier ce que nous voulons obtenir/définir à travers le champ ContextFlags. La valeur de ContextFlags spécifie quelles
parties du contexte d'un thread sont récupérées. La documentation sur cela
se trouve dans WINNT.H et non pas dans MSDN : Un problème important lors de l'écriture de loaders est la gestion des autorisations de lecture/écriture de la mémoire. Généralement, lors de l'accès aux sections exécutables d'un processus, ces sections sont déjà lisibles et inscriptibles. Cependant, si vous essayez d'écrire dans d'autres sections qui ne devraient normalement être que des sections en lecture, telles que les ressources du programme, vous échouerez. Écrire un loader pour accéder à des pages mémoire protégées implique donc de modifier les autorisations d'accès de l'adresse modifiée en PAGE_EXECUTE_READWRITE, de lire et d'écrire ce dont nous avons besoin, puis de restaurer la protection précédente. La fonction VirtualProtectEx nous permet de faire cela. Les paramètres suivants sont intéressants : hProcess - Gestionnaire du processus cible - doit avoir un accès PROCESS_VM_OPERATION,
mais généralement l'API CreateProcess le fait pour nous. Points d'arrêt et décision de l'emplacement de la modification Dans les cas simples (comme dans le dernier tutoriel),
nous pouvons peut-être modifier une cible en mémoire dès que son
processus est créé, mais avant l'exécution du thread principal. Dans d'autres
cas, nous devons laisser la cible s'exécuter et l'arrêter à un moment précis (par exemple, après que l'application compressée ait été décompressée en mémoire
ou après une vérification CRC). Notre loader doit
donc être capable d'arrêter l'application à un point spécifié, de modifier le code, puis de laisser l'application se dérouler normalement. Les problèmes liés au timing incluent : • la victime est un processus à thread unique • une fois que l'application est décompressée en mémoire, il n'y a pas
de vérifications d'intégrité • la mémoire du processus cible peut être modifiée • le contexte de sécurité de la victime nous permet d'opérer sur
celui-ci • la victime ne dispose pas de protections complexes contre les modifications Loaders de débogueurs Les loaders standard décrits ci-dessus ont l'avantage considérable d'être simples et de ne pas être soumis à des astuces anti-débogueurs, mais il arrive parfois que le loader doive déboguer l'application cible afin de trouver un point d'arrêt. Un exemple typique en est la création d'un loader pour Asprotect. Nous sommes probablement tous familiers avec l'utilisation de la méthode LAST EXCEPTION pour arriver à l'OEP (Entry Point d'Origine), mais que faire si nous voulons que notre loader fasse la même chose ? Dans ce cas, nous utilisons l'API de débogage pour créer un loader de débogueur
basé sur les événements, qui peut répondre aux événements de débogage.
Les événements de débogage incluent les exceptions générées par le packer de la même
manière que nous les voyons dans Olly, donc notre loader de débogueur utilise
en réalité le SEH (gestion des exceptions structurée). L'approche de test de la mémoire basée sur les fonctions isBad*Ptr dont nous avons discuté dans le dernier tutoriel fonctionne bien lorsque le loader lance le processus cible via l'API CreateProcess. Cependant, si vous avez utilisé OpenProcess avec un processus déjà en cours d'exécution, vous devriez plutôt utiliser VirtualQueryEx qui fournit des informations sur une plage de pages dans l'espace d'adressage virtuel d'un processus spécifié. |
Comme exemple pratique, nous allons compresser SuperCleaner et essayer notre loader à partir du dernier tutoriel. Pour simplifier les choses, nous utiliserons upx v1.25. La ligne de commande habituelle "upx SuperCleaner.exe" a généré une erreur, mais en tapant "upx SuperCleaner.exe --force", cela a fonctionné : Remarquez que notre cible a réduit sa taille de 508 Ko à 209 Ko. Maintenant, il est évident que nos anciens patcheurs ne fonctionneront pas sur la cible compressée et notre premier loader non plus. Essayez le loader et voyez ce qui se passe : Notre loader crée le processus en mode suspendu et essaie immédiatement
de patcher la mémoire, mais l'adresse correcte n'existe pas encore car le stub upx n'a pas décompressé
la cible en mémoire. Lorsque le thread est repris, la cible est décompressée et exécutée,
mais notre patch n'a pas été appliqué, donc nous obtenons l'invite ennuyeuse. Tout d'abord, ouvrez la cible compressée dans Olly et trouvez le saut vers le point d'entrée original (OEP). Dans les applications compressées avec upx, c'est un point idéal pour extraire ou patcher l'application car nous n'avons pas à nous soucier des vérifications CRC ou anti-altération, et trouver le point d'entrée original est très facile. Il suffit de faire défiler jusqu'à la fin du code pour trouver l'instruction POPAD suivie d'un JMP vers le OEP : Dans mon cas, le OEP est 0042AADA comme vous pouvez le voir. Nous savons déjà que l'adresse virtuelle à patcher est 0042374F. Une bonne façon de contourner cela serait d'utiliser la méthode de yAtEs. Nous créons le processus en mode suspendu et remplaçons les 2 premiers octets du saut vers le OEP (E9h E9h) par EBh FEh. Notez que s'arrêter au OEP lui-même n'est pas une option car il n'a pas encore été décompressé en mémoire. Nous devons donc effectuer notre patch en mémoire, restaurer le saut final vers le OEP et reprendre le thread. Nous améliorerons également notre loader en vérifiant les octets corrects à la fois au niveau du OEP et de l'emplacement du patch pour vérifier la version correcte du fichier cible. Créez un nouveau projet dans WinASM et collez le code loader2.asm, puis ajoutez loader.rc dans la section source : Ce loader lance un processus pour notre cible et lit 2 octets à partir de l'adresse que nous pensons être le saut vers le OEP. Si les octets sont corrects (version correcte du fichier cible), il les remplace par le code d'arrêt EBFE avant de reprendre le thread principal et de faire une pause de 10 millisecondes pour permettre à la cible de s'exécuter . La cible s'exécutera jusqu'à ce qu'elle atteigne finalement l'instruction EBFE, ce qui la fera rester bloquée dans une boucle infinie et EIP ne changera pas. Le stub upx a maintenant décompressé l'application en mémoire et est sur le point de sauter vers le OEP. Ensuite, nous préchargeons le champ des indicateurs de notre structure CONTEXT vide avec CONTEXT_FULL pour permettre à GetThreadContext de lire toutes les valeurs des registres et de comparer dans eax la valeur actuelle d'EIP (ThreadContext.regEip) avec l'adresse de notre point d'arrêt (c'est-à-dire si nous sommes bloqués au point d'arrêt). Si nous ne sommes pas encore au point d'arrêt, nous faisons simplement une pause de 10 millisecondes et réessayons GetThreadContext. Si nous sommes au point d'arrêt, nous suspendons le thread et lisons les octets à notre adresse de patch cible (qui a maintenant été décompressée en mémoire) pour vérifier à nouveau la version correcte du fichier. Si nous avons la version correcte, nous restaurons le saut original vers le OEP, écrivons notre octet de patch et reprenons le thread avant de quitter, laissant notre cible patchée s'exécuter heureusement. Si nous avons une version incorrecte de la cible, nous le signalons, terminons le thread suspendu avant de quitter. Notez que la définition de la structure CONTEXT doit être placée dans la section de données non initialisées (.data?) sinon GetThreadContext renverra une erreur, tandis que les structures StartupInfo et ProcessInfo ne sont pas pointilleuses et peuvent aller dans .data ou .data?
|
Copyright (C)- xtx Team (2021)