Search code examples
c++windowswinapiregistry

How to load registry hive for all users in a loop


With admin privileges, I need to enumerate all users on a Windows 7+ system (even ones that are logged off). Then I need to load the registry hive for each user and set a key.

NetUserEnum gives me the SID (I guess LsaEnumerateLogonSessions would as well). WTSEnumerateSessions followed by WTSQueryUserToken (to get a token) would be nice but it does not work for users who are not actively logged on.

So, my question, after calling NetUserEnum, how do I use the SID to load the registry for that user? Any recommended way of doing this?


Solution

  • Information about local user profiles is stored in this Registry key:

    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList
    

    It is possible to enumerate it subkeys, where each subkey has a ProfileImagePath that points to the folder where ntuser.dat is located.

    But, directly loading a user profile by RegLoadKey() is very bad. First, the profile may already be loaded. Second, it is possible that after you load the profile yourself, the system may also try loading the profile. Note the RefCount value. The system uses that value to load the profile if it is not already loaded, incrementing RefCount. And UnloadUserProfile() decrements RefCount and unloads the profile only when it become 0 by calling RegUnLoadKey(). So all profile load/unload operations must be synchronized.

    There is only one correct way to load a profile - call LoadUserProfile(). (internally it performs a RPC call to profsvc.LoadUserProfileServer in svchost.exe -k netsvcs, where all synchronization is done).

    So how do you get the user token for LoadUserProfile() ? I guess call LogonUser(), which you said you do not want to do (and cannot unless you have the user's password).

    But, there does exist another way that works (I tested this), but it is undocumented. LoadUserProfile used only the user Sid from token (query for TOKEN_USER information with TokenUser iformation class) and then work with

    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\<Sid>
    

    key

    It is possible to create a token by calling ZwCreateToken() with any given SID, but for this call you need SE_CREATE_TOKEN_PRIVILEGE. This priviledge exists only in the lsass.exe process. So a possible solution is:

    1. open lsass.exe and get its token, or impersonate its thread.
    2. enable SE_CREATE_TOKEN_PRIVILEGE in the token, after impersonation
    3. enumerate HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList, and for each subkey query its Sid value, or (if Sid does not exist) convert the subkey name to a SID using ConvertStringSidToSid()
    4. create a token with that SID
    5. and finally call LoadUserProfile()

    -------------- EDIT code example by request ----------------------------

    code used ntdll export (which somebody here very not like) but as is

    1. we need got SE_CREATE_TOKEN_PRIVILEGE to create token by yourself in future

    enum processes in the system, open token for every process, look are SE_CREATE_TOKEN_PRIVILEGE exist in token, if yes - duplicate this token and if need enable SE_CREATE_TOKEN_PRIVILEGE in it. finally impersonate with duplicated token

    BOOL g_IsXP;// true if we on winXP, false otherwise
    static volatile UCHAR guz;
    static OBJECT_ATTRIBUTES zoa = { sizeof(zoa) };
    
    NTSTATUS ImpersonateIfConformToken(HANDLE hToken)
    {
        ULONG cb = 0, rcb = 0x200;
        PVOID stack = alloca(guz);
    
        union {
            PVOID buf;
            PTOKEN_PRIVILEGES ptp;
        };
    
        NTSTATUS status;
        do 
        {
            if (cb < rcb)
            {
                cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
            }
    
            if (0 <= (status = ZwQueryInformationToken(hToken, TokenPrivileges, buf, cb, &rcb)))
            {
                if (ULONG PrivilegeCount = ptp->PrivilegeCount)
                {
                    PLUID_AND_ATTRIBUTES Privileges = ptp->Privileges;
                    do 
                    {
                        if (Privileges->Luid.LowPart == SE_CREATE_TOKEN_PRIVILEGE && !Privileges->Luid.HighPart)
                        {
                            static SECURITY_QUALITY_OF_SERVICE sqos = {
                                sizeof sqos, SecurityImpersonation, SECURITY_DYNAMIC_TRACKING, FALSE
                            };
    
                            static OBJECT_ATTRIBUTES soa = { sizeof(soa), 0, 0, 0, 0, &sqos };
    
                            if (0 <= (status = ZwDuplicateToken(hToken, TOKEN_ADJUST_PRIVILEGES|TOKEN_IMPERSONATE, &soa, FALSE, TokenImpersonation, &hToken)))
                            {
                                if (Privileges->Attributes & SE_PRIVILEGE_ENABLED)
                                {
                                    status = STATUS_SUCCESS;
                                }
                                else
                                {
                                    static TOKEN_PRIVILEGES tp = {
                                        1, { { { SE_CREATE_TOKEN_PRIVILEGE }, SE_PRIVILEGE_ENABLED } }
                                    };
    
                                    status = ZwAdjustPrivilegesToken(hToken, FALSE, &tp, 0, 0, 0);
                                }
    
                                if (status == STATUS_SUCCESS)
                                {
                                    status = ZwSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, &hToken, sizeof(HANDLE));
                                }
    
                                ZwClose(hToken);
                            }
    
                            return status;
                        }
                    } while (Privileges++, --PrivilegeCount);
                }
    
                return STATUS_PRIVILEGE_NOT_HELD;
            }
    
        } while (status == STATUS_BUFFER_TOO_SMALL);
    
        return status;
    }
    
    NTSTATUS GetCreateTokenPrivilege()
    {
        BOOLEAN b;
        RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, &b);
    
        ULONG cb = 0, rcb = 0x10000;
        PVOID stack = alloca(guz);
    
        union {
            PVOID buf;
            PBYTE pb;
            PSYSTEM_PROCESS_INFORMATION pspi;
        };
    
        NTSTATUS status;
        do 
        {
            if (cb < rcb)
            {
                cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
            }
    
            if (0 <= (status = ZwQuerySystemInformation(SystemProcessInformation, buf, cb, &rcb)))
            {
                status = STATUS_UNSUCCESSFUL;
    
                ULONG NextEntryOffset = 0;
                do 
                {
                    pb += NextEntryOffset;
    
                    if (pspi->InheritedFromUniqueProcessId && pspi->UniqueProcessId)
                    {
                        CLIENT_ID cid = { pspi->UniqueProcessId };
    
                        NTSTATUS s = STATUS_UNSUCCESSFUL;
                        HANDLE hProcess, hToken;
    
                        if (0 <= ZwOpenProcess(&hProcess, g_IsXP ? PROCESS_QUERY_INFORMATION : PROCESS_QUERY_LIMITED_INFORMATION, &zoa, &cid))
                        {
                            if (0 <= ZwOpenProcessToken(hProcess, TOKEN_DUPLICATE|TOKEN_QUERY, &hToken))
                            {
                                s = ImpersonateIfConformToken(hToken);
    
                                NtClose(hToken);
                            }
    
                            NtClose(hProcess);
                        }
    
                        if (s == STATUS_SUCCESS)
                        {
                            return STATUS_SUCCESS;
                        }
                    }
    
                } while (NextEntryOffset = pspi->NextEntryOffset);
    
                return status;
            }
    
        } while (status == STATUS_INFO_LENGTH_MISMATCH);
    
        return STATUS_UNSUCCESSFUL;
    }
    

    if we have SE_CREATE_TOKEN_PRIVILEGE - we can create token !

    NTSTATUS CreateUserToken(PHANDLE phToken, PSID Sid)
    {
        HANDLE hToken;
        TOKEN_STATISTICS ts;
        NTSTATUS status = ZwOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &hToken);
    
        if (0 <= status)
        {
            if (0 <= (status = ZwQueryInformationToken(hToken, TokenStatistics, &ts, sizeof(ts), &ts.DynamicCharged)))
            {
                ULONG cb = 0, rcb = 0x200;
                PVOID stack = alloca(guz);
    
                union {
                    PVOID buf;
                    PTOKEN_PRIVILEGES ptp;
                };
    
                do 
                {
                    if (cb < rcb)
                    {
                        cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
                    }
    
                    if (0 <= (status = ZwQueryInformationToken(hToken, TokenPrivileges, buf, cb, &rcb)))
                    {
                        TOKEN_USER User = { { Sid } };
    
                        static TOKEN_SOURCE Source = { {' ','U','s','e','r','3','2', ' '} };
    
                        static TOKEN_DEFAULT_DACL tdd;// 0 default DACL
                        static TOKEN_GROUPS Groups;// no groups
    
                        static SECURITY_QUALITY_OF_SERVICE sqos = {
                            sizeof sqos, SecurityImpersonation, SECURITY_DYNAMIC_TRACKING
                        };
    
                        static OBJECT_ATTRIBUTES oa = { 
                            sizeof oa, 0, 0, 0, 0, &sqos
                        };
    
                        status = ZwCreateToken(phToken, TOKEN_ALL_ACCESS, &oa, TokenPrimary, 
                            &ts.AuthenticationId, &ts.ExpirationTime, &User, &Groups, ptp, (PTOKEN_OWNER)&Sid,
                            (PTOKEN_PRIMARY_GROUP)&Sid, &tdd, &Source);
    
                        break;
                    }
    
                } while (status == STATUS_BUFFER_TOO_SMALL);
            }
    
            ZwClose(hToken);
        }
    
        return status;
    }
    

    and finally enumerate and load/unload user profiles

    void EnumProf()
    {
        PROFILEINFO pi = { sizeof(pi), PI_NOUI };
        pi.lpUserName = L"*";
    
        STATIC_OBJECT_ATTRIBUTES(soa, "\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList");
    
        HANDLE hKey;
        if (0 <= ZwOpenKey(&hKey, KEY_READ, &soa))
        {
            PVOID stack = alloca(sizeof(WCHAR));
    
            union
            {
                PVOID buf;
                PKEY_BASIC_INFORMATION pkbi;
                PKEY_VALUE_PARTIAL_INFORMATION pkvpi;
            } u = {};
    
            DWORD cb = 0, rcb = 64;
            NTSTATUS status;
            ULONG Index = 0;
    
            do 
            {
                do 
                {
                    if (cb < rcb)
                    {
                        cb = RtlPointerToOffset(u.buf = alloca(rcb - cb), stack);
                    }
    
                    if (0 <= (status = ZwEnumerateKey(hKey, Index, KeyBasicInformation, u.buf, cb, &rcb)))
                    {
                        *(PWSTR)RtlOffsetToPointer(u.pkbi->Name, u.pkbi->NameLength) = 0;
    
                        PSID Sid;
                        if (ConvertStringSidToSidW(u.pkbi->Name, &Sid))
                        {
                            HANDLE hToken;
    
                            if (0 <= CreateUserToken(&hToken, Sid))
                            {
                                if (LoadUserProfile(hToken, &pi))
                                {
                                    UnloadUserProfile(hToken, pi.hProfile);
                                }
    
                                NtClose(hToken);
                            }
                            LocalFree(Sid);
                        }
                    }
    
                } while (status == STATUS_BUFFER_OVERFLOW);
    
                Index++;
    
            } while (0 <= status);
    
            ZwClose(hKey);
        }
    }