Search code examples
windowsntdll

What is RtlQueryEnvironmentVariable for?


Lately I've been messing with Windows, and I came across this function in ntdll.dll.

I tried searching, but there seems to be no documentation at all on it.

Could anybody hint me what kind of environment variables does it query?


Solution

  • Seeing that most probably I'd get little help on something weird like this (or rather, I'm impatient), I decided to do some research on it myself.

    Here's some pseudocode I made up to understand the function: (scroll down for TL;DR)

    // Credits to:
    // -> http://filelog.net/func/RtlQueryEnvironmentVariable
    //    For helping with the arguments' names and types
    // -> IDA Pro
    // -> NirSoft for the RTL_CRITICAL_SECTION structure
    //    http://www.nirsoft.net/kernel_struct/vista/RTL_CRITICAL_SECTION.html
    
    // https://msdn.microsoft.com/en-us/library/cc704588.aspx
    #ifndef STATUS_VARIABLE_NOT_FOUND
    #define STATUS_VARIABLE_NOT_FOUND 0xC0000100
    #endif
    
    NTSTATUS __stdcall RtlQueryEnvironmentVariable(PVOID Environment, PWSTR Name, size_t NameLength, PWSTR Value, size_t ValueLength, PSIZE_T ReturnLength){
        // Here happens some exception stuff
        // ...
    
        // Return variable
        NTSTATUS ret;
    
        // PEB environment
        PVOID pEnv = &teb->ProcessEnvironmentBlock->ProcessParameters->Environment;
    
        // mov     ebx, [ebp+ReturnLength]
        // xor     esi, esi
        // mov     [ebx], esi
        *ReturnLength = 0;
    
        // Sanity check
        if ( !NameLength )
            return STATUS_VARIABLE_NOT_FOUND;
    
        // Here happens some exception stuff
        // ...
    
        // Check the variable
        NTSTATUS envVar = RtlpCheckPseudoEnvironmentVariable(Name, NameLength, Value, ValueLength, ReturnLength);
    
        // If the variable exists, fail.
        if ( envVar >= 0 ) {
            goto sehReturn;
        }
    
        /* Only process variables that haven't been set as pseudo.
           I could've joined this with the previous if () with ||,
           but I'd rather leave it as is for easier comprehension.
           Do note that this is a signed comparison, and it's why
           this doesn't always simply jump to 'sehReturn'.
           P.S. typedef long NTSTATUS; 'long' is signed by default */
        if ( envVar != (signed int) STATUS_VARIABLE_NOT_FOUND ) {
            goto sehReturn;
        }
    
        // In case there's no environment supplied, just take it from TEB->PEB->ProcessParameters->Environment
        if ( !Environment ) {
            // Get a pointer to TEB
            TEB* teb = __readfsdword(0x18);
    
            // Wait till we get thread-safe access to PEB
            RtlEnterCriticalSection(&FastPebLock);
    
            // Exception handling stuff
            // ...
    
            // Try to load it from cache
            NTSTATUS varFromCache = RtlpQueryEnvironmentCache(
                pEnv,
                Name, NameLength,
                Value, ValueLength,
                ReturnLength
            );
    
            // Get it from the actual environment if it isn't in the cache
            if ( varFromCache == STATUS_VARIABLE_NOT_FOUND ) {
                varFromCache = RtlpScanEnvironment(
                    pEnv,
                    Name, NameLength,
                    Value, ValueLength,
                    ReturnLength,
                    // I'm not really sure what this argument is for,
                    // but it's set to FALSE when the environment is not
                    // the same as the current process or the critical
                    // section is locked by a thread. mainly corner cases.
                    TRUE
                );
            }
    
            // Save return value
            ret = varFromCache;
    
            // Exception handling stuff
            // ...
    
            RtlLeaveCriticalSection(&FastPebLock);
    
            goto sehReturn;
        }
    
        // Try to determine if environment is valid
        if ( !*PWORD(Environment) ) {
            ret = STATUS_VARIABLE_NOT_FOUND;
            goto sehReturn;
        }
    
        RTL_CRITICAL_SECTION* pCriticalSection = teb->ProcessEnvironmentBlock->FastPebLock;
        BOOL cornerCase;
    
        // Set 'cornerCase' to FALSE if the environment is not our process'
        // environment or if it is locked by a thread.
        if ( pEnv != Environment || (pCriticalSection != NULL && !RtlIsCriticalSectionLockedByThread(pCriticalSection)) ) {
            cornerCase = FALSE;
        } else {
            // If the variable is in the current process, try to take it from the cache.
            NTSTATUS varFromCache = RtlpQueryEnvironmentCache(
                Environment,
                Name, NameLength,
                Value, ValueLength,
                ReturnLength
            );
    
            if ( varFromCache != STATUS_VARIABLE_NOT_FOUND ) {
                ret = varFromCache;
                goto sehReturn;
            }
    
            cornerCase = TRUE;
        }
    
        // Take the variable from the environment
        RtlpScanEnvironment(
            Environment,
            Name, NameLength,
            Value, ValueLength,
            ReturnLength,
            cornerCase
        );
    
        // Structured Exception Handler return; does some SEH stuff and returns.
    sehReturn:
        ms_exc.registration.TryLevel = -2;
        return ret;
    }
    

    TL;DR This basically checks for environment variables in PEB->ProcessParameters->Reserved2 (Environment), and returns it in the passed pointers.

    It checks the cache, and normalizes the case.