NtAdjustGroupsToken
Active ou désactive des groupes (SIDs) dans un token d'accès, ou réinitialise les attributs aux valeurs par défaut.
Prototype
NTSTATUS NtAdjustGroupsToken( HANDLE TokenHandle, BOOLEAN ResetToDefault, PTOKEN_GROUPS NewState, ULONG BufferLength, PTOKEN_GROUPS PreviousState, PULONG ReturnLength );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| TokenHandle | HANDLE | in | Handle de token ouvert avec l'accès TOKEN_ADJUST_GROUPS (et TOKEN_QUERY si PreviousState est demandé). |
| ResetToDefault | BOOLEAN | in | TRUE restaure les attributs de chaque groupe à leur défaut (NewState ignoré) ; FALSE applique NewState. |
| NewState | PTOKEN_GROUPS | in | Tableau de SID_AND_ATTRIBUTES à appliquer. Les attributs basculent typiquement SE_GROUP_ENABLED / SE_GROUP_ENABLED_BY_DEFAULT. |
| BufferLength | ULONG | in | Taille du buffer PreviousState en octets. Passer 0 si PreviousState est NULL. |
| PreviousState | PTOKEN_GROUPS | out | Buffer optionnel qui reçoit l'ensemble précédent d'attributs de groupes pour permettre un revert. |
| ReturnLength | PULONG | out | Reçoit le nombre d'octets écrits dans PreviousState (ou requis si STATUS_BUFFER_TOO_SMALL). |
IDs de syscalls par version de Windows
| Version de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x6A | win10-1507 |
| Win10 1607 | 0x6A | win10-1607 |
| Win10 1703 | 0x6B | win10-1703 |
| Win10 1709 | 0x6B | win10-1709 |
| Win10 1803 | 0x6B | win10-1803 |
| Win10 1809 | 0x6B | win10-1809 |
| Win10 1903 | 0x6B | win10-1903 |
| Win10 1909 | 0x6B | win10-1909 |
| Win10 2004 | 0x6C | win10-2004 |
| Win10 20H2 | 0x6C | win10-20h2 |
| Win10 21H1 | 0x6C | win10-21h1 |
| Win10 21H2 | 0x6C | win10-21h2 |
| Win10 22H2 | 0x6C | win10-22h2 |
| Win11 21H2 | 0x6C | win11-21h2 |
| Win11 22H2 | 0x6C | win11-22h2 |
| Win11 23H2 | 0x6C | win11-23h2 |
| Win11 24H2 | 0x6C | win11-24h2 |
| Server 2016 | 0x6A | winserver-2016 |
| Server 2019 | 0x6B | winserver-2019 |
| Server 2022 | 0x6C | winserver-2022 |
| Server 2025 | 0x6C | winserver-2025 |
Module noyau
APIs liées
Stub du syscall
4C 8B D1 mov r10, rcx B8 6C 00 00 00 mov eax, 0x6C F6 04 25 08 03 FE 7F 01 test byte ptr [0x7FFE0308], 1 75 03 jne short +3 0F 05 syscall C3 ret CD 2E int 2Eh C3 ret
Notes non documentées
Le côté noyau de l'API Win32 `AdjustTokenGroups`. Frère de `NtAdjustPrivilegesToken` — les privilèges vivent dans TOKEN_PRIVILEGES, l'appartenance aux groupes dans TOKEN_GROUPS. `NtAdjustGroupsToken` ne peut pas *ajouter* un SID à un token ; il peut seulement activer, désactiver ou restaurer-par-défaut les SIDs déjà présents (qu'ils soient activés, désactivés, deny-only ou restricted). Pour désactiver l'attribut SE_GROUP_USE_FOR_DENY_ONLY d'un SID, il faut typiquement passer d'abord par `NtFilterToken` pour produire une variante non-deny. Chaque SID dans NewState se couple avec un masque Attributes (SE_GROUP_ENABLED, SE_GROUP_ENABLED_BY_DEFAULT, SE_GROUP_OWNER, SE_GROUP_USE_FOR_DENY_ONLY) ; fournir SE_GROUP_ENABLED sur un SID précédemment désactivé l'active.
Usage courant par les malwares
**Réactivation de SID de groupe pour chaînes d'escalade de privilège** : la configuration canonique est un attaquant qui a volé un token (via SeImpersonatePrivilege, Rotten/Juicy Potato, PrintSpoofer, etc.) contenant BUILTIN\Administrators en SID deny-only (état typique sous UAC split-token IL Medium). L'attaquant appelle NtFilterToken ou duplique le token pour produire une variante non-deny, puis NtAdjustGroupsToken pour réactiver SE_GROUP_ENABLED sur le SID Administrators — donnant instantanément l'appartenance admin sur le thread live sans prompt UAC interactif. Plus rarement, sert à supprimer un SID que l'attaquant ne veut pas voir évalué (désactiver un SID de groupe de logging qui déclenche un audit supplémentaire à l'accès fichier). Documenté dans les chaînes offensives autour des familles AlwaysInstallElevated et SeImpersonatePrivilege.
Opportunités de détection
Microsoft-Windows-Security-Auditing event ID 4672 (privilèges spéciaux assignés) et 4673 (service privilégié appelé) aident côté privilèges ; le côté ajustement de groupes n'est couvert qu'indirectement par 4670 (permissions sur un objet modifiées). Les EDR qui suivent les primitives de modification de token exposent une séquence comme (NtOpenProcessToken sur un processus plus privilégié → NtDuplicateToken → NtFilterToken → NtAdjustGroupsToken → NtSetInformationThread(ImpersonationToken)) comme une chaîne d'abus de token. L'invariant défensif utile : les processus légitimes n'appellent quasi jamais NtAdjustGroupsToken pour *activer* un SID haut-privilège précédemment désactivé ; des auditeurs qui snapshottent l'état du token à la création de processus et comparent à l'état runtime peuvent flagger le delta. ETW Microsoft-Windows-Kernel-Audit-API-Calls expose le syscall à un volume modéré.
Exemples de syscalls directs
asmx64 direct stub (Win11 24H2)
; Direct syscall stub for NtAdjustGroupsToken (SSN 0x6C on Win10 2004+/Win11)
NtAdjustGroupsToken PROC
mov r10, rcx ; syscall convention
mov eax, 6Ch ; SSN — stable across modern builds
syscall
ret
NtAdjustGroupsToken ENDPcPrivesc — re-enable BUILTIN\Administrators in a stolen token
// Assumes hTok came from NtDuplicateToken + NtFilterToken (non-deny variant).
// Re-enable SE_GROUP_ENABLED on the Administrators SID in-place.
#include <windows.h>
#include <winternl.h>
static VOID EnableAdminsGroup(HANDLE hTok) {
SID_IDENTIFIER_AUTHORITY nt = SECURITY_NT_AUTHORITY;
PSID adminSid = NULL;
AllocateAndInitializeSid(&nt, 2,
SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0, &adminSid);
BYTE buf[sizeof(TOKEN_GROUPS) + sizeof(SID_AND_ATTRIBUTES)];
TOKEN_GROUPS* tg = (TOKEN_GROUPS*)buf;
tg->GroupCount = 1;
tg->Groups[0].Sid = adminSid;
tg->Groups[0].Attributes = SE_GROUP_ENABLED; // flip the bit
// Win32 wrapper -> NtAdjustGroupsToken
AdjustTokenGroups(hTok, FALSE, tg, 0, NULL, NULL);
FreeSid(adminSid);
}cSnapshot then restore previous group state
// Defensive / red-team utility: change group state, do work, restore exactly.
#include <windows.h>
#include <winternl.h>
typedef NTSTATUS (NTAPI *pNtAdjustGroupsToken)(
HANDLE, BOOLEAN, PTOKEN_GROUPS, ULONG, PTOKEN_GROUPS, PULONG);
VOID WithToggledGroup(HANDLE hTok, TOKEN_GROUPS* desired) {
pNtAdjustGroupsToken NtAdjustGroupsToken = (pNtAdjustGroupsToken)
GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtAdjustGroupsToken");
BYTE prev[4096]; ULONG ret = 0;
NtAdjustGroupsToken(hTok, FALSE, desired,
sizeof(prev), (PTOKEN_GROUPS)prev, &ret);
DoWorkWithToggledGroups();
NtAdjustGroupsToken(hTok, FALSE, (PTOKEN_GROUPS)prev,
0, NULL, NULL); // revert exact prior state
}Mappings MITRE ATT&CK
Last verified: 2026-05-20