Search code examples
c#cross-domainaccess-tokenimpersonationprivileges

Impersonate Access Token with Backup And Restore Privilege for Copying File Across Domain


  • Computer A is on Domain A
  • Computer B is on Domain B
  • Domain A and Domain B have a trust allowing Accounts to connect to the other domain's computers
  • Account A is Admin on Computer A and has Backup&Restore Privileges
  • Account B is Admin on Computer B and has Backup&Restore Privileges
  • Account A IS NOT Admin on Computer B and does not have Backup&Restore Privileges
  • Account B IS NOT Admin on Computer A and does not have Backup&Restore Privileges

A person owns both Account A and Account B.
They want to copy a file from Computer A to Computer B.
They wish to copy this file from and into a folder they do not have permissions for. Therefore, they must both enable Backup&Restore Privileges on their Access Token they got from LogonUser

It is not allowed to change the DACL to grant these permissions on any folders or files and instead both Account A and Account B must enable B&R privileges which is recognized by the Computer, only then will they be able to copy the file without Access Denied.


The problem is I have tried using NETWORK, INTERACTIVE, and NEW_CREDENTIALS Access Tokens and enabling privileges on them, but only NETWORK contains the privileges and they are already enabled by default. Then when I try to WindowsIdentity.Impersonate the NETWORK token and then call "CreateFile" to open the file with privileges, it fails and returns an invalid file handle of -1. I can use INTERACTIVE to read an unrestricted file but it didn't have Access Token Privileges needed ( SeBackupPrivilege & SeRestorePrivilege ) when it was returned from LogonUser. I assume once Impersonate happens it generates a Token that "might" have those privileges, but I assume that's based on the machine the code is running on.

Is there a way to Impersonate Access Token -> Enable Access Token B&R Privileges on the remote computers they would have normally when at that machine running as administrator which could be enabled.

OR

Is there a way to use the NETWORK Token with Impersonation to successfully copy the file from Computer on Domain A to Computer on Domain B. If I run the program as Account B who isn't admin trying to impersonate Account A with network credential, it appears to not work on impersonation

Below is demo code in a Console Application that emulates the situation. You must change parts of it to test it accordingly: You must create the file paths and the file to be read. You must edit permissions on "DENYTHEIMPERSONATINGUSERHERE" folder so the impersonating user account is denied and must use privileges. You must enter an actual account credentials to get an AccessToken.

class Program
{
static void Main(string[] args)
{
    //Credentials of Account A
    string usernameA = "AccountA";
    string DomainA = "DomainA.com";
    SecureString passwordA = new SecureString();
    passwordA.AppendChar('P');
    passwordA.AppendChar('W');
    passwordA.AppendChar('D');
    passwordA.AppendChar('A');


    IntPtr AccessToken = IntPtr.Zero;

    //Getting Network Access Token. (Network is an Impersonation Token, most other types are returned as Primary Tokens)
    AccessTokenHelper.LOGON32_LOGONERROR lgnCode = AccessTokenHelper.GetAccessToken(DomainA, usernameA, passwordA, AccessTokenHelper.LOGON32_LOGONTYPE.LOGON32_LOGON_NETWORK, AccessTokenHelper.LOGON32_LOGONPROVIDER.LOGON32_PROVIDER_WINNT50, out AccessToken);



    /*
        //Getting INTERACTIVE Access Token. (returns Primary Token)
        //Impersonation will work but won't have enabled privileges so will error when trying to write file.
        AccessTokenHelper.LOGON32_LOGONERROR lgnCode = AccessTokenHelper.GetAccessToken(DomainA, usernameA, passwordA, AccessTokenHelper.LOGON32_LOGONTYPE.LOGON32_LOGON_INTERACTIVE, AccessTokenHelper.LOGON32_LOGONPROVIDER.LOGON32_PROVIDER_WINNT50, out AccessToken);

    */


    if(AccessToken == IntPtr.Zero || AccessToken.ToInt32() == -1)
    {
        //Not valid creds
        System.Diagnostics.Debug.WriteLine(lgnCode.ToString());
        return;
    }


    //Enable Token Security Privileges
    AccessTokenHelper.ElevateSecurityPrivileges(AccessToken);

    //List Enabled Token Privileges
    List<string> tokenEnabledPrivileges = AccessTokenHelper.ListEnabledPrivileges(AccessToken);
    foreach(string s in tokenEnabledPrivileges)
    {
        System.Diagnostics.Debug.WriteLine(s);
    }

    string sourceFile = @"C:\Temp\Test1\TestData.txt";
    string destFile = @"C:\Temp\Test1\DENYTHEIMPERSONATINGUSERHERE\PrivCopy.txt";

    bool bCopied = PrivilegedEnterpriseCopyFile(sourceFile, destFile, AccessToken, AccessToken);
    System.Diagnostics.Debug.WriteLine("DID THE COPY WORK? " + bCopied.ToString().ToUpper());


}

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr CreateFile(
                                string lpFileName,
                                uint dwDesiredAccess,
                                OPENFILE_SHAREACCESS dwShareMode, //If you are reading folders you better have OPENFILE_SHAREACCESS.FILE_SHARE_READ
                                IntPtr SecurityAttributes,
                                uint dwCreationDisposition,
                                uint dwFlagsAndAttributes,
                                IntPtr hTemplateFile
                                );

private enum OPENFILE_SHAREACCESS : uint
{
    NO_SHARING = 0x00000000,
    FILE_SHARE_READ = 0x00000001,
    FILE_SHARE_WRITE = 0x00000002,
    FILE_SHARE_DELETE = 0x00000004
};

[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern bool CloseHandle(IntPtr hObject);

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool GetFileSizeEx(IntPtr hFile, out long lpFileSize);

/// <summary>
/// Goal:   Enterprise Level Copy.... can copy file data between remote machines between domains.
/// </summary>
public static bool PrivilegedEnterpriseCopyFile(string sourceAbsoluteFileName, string destAbsoluteFileName, IntPtr sourceAccessToken, IntPtr destAccessToken, bool bOverwrite = false)
{
    const uint GENERIC_READ = 0x80000000;
    const uint GENERIC_WRITE = 0x40000000; //If you are writing to a file, best allow both READ and WRITE because Windows Performance suffers without.
    const uint CREATE_ALWAYS = 2; //Create and overwrite if necesarry
    const uint OPEN_EXISTING = 3; //Open only if exists
    const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; //This makes it take enabled Backup&Restore privileges into account
    const uint FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000; //This helps speed up reading.
    const uint FILE_FLAG_WRITE_THROUGH = 0x80000000;  //This helps speed up writing!
    //We pick a value that is the largest multiple of 4096 that is still smaller than the large object heap threshold (85K).
    // The CopyTo/CopyToAsync buffer is short-lived and is likely to be collected at Gen0, and it offers a significant
    // improvement in Copy performance.
    const int DefaultCopyBufferSize = 81920; //80kb

    bool bSuccess = false;

    //Get File Ptr Handles. DO NOT ALLOW DELETE ON SOURCE OR DEST WHILE THIS IS HAPPENING.
    //Get the file pointer by using an access token with access.
    IntPtr pSource = IntPtr.Zero;
    IntPtr pDest = IntPtr.Zero;

    //As source user, connect for optimized READING
    using (WindowsImpersonationContext impersonatedUser = WindowsIdentity.Impersonate(sourceAccessToken))
    {
        pSource = CreateFile(sourceAbsoluteFileName, GENERIC_READ, OPENFILE_SHAREACCESS.FILE_SHARE_READ,
                                IntPtr.Zero, OPEN_EXISTING,
                                FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN, IntPtr.Zero);
    }

    if (pSource == IntPtr.Zero || pSource.ToInt32() == -1)
    {
        //Failed to get Source, return false
        return bSuccess;
    }

    //Get Dest Path
    string DestPath = Path.GetDirectoryName(destAbsoluteFileName);
    string DestFileName = Path.GetFileName(destAbsoluteFileName);


    //As dest user
    using (WindowsImpersonationContext impersonatedUser = WindowsIdentity.Impersonate(destAccessToken))
    {
        try
        {
            bool bProceed = true;
            if (!bOverwrite)
            {
                //We don't want to overwrite existing file, ensure it doesn't exist.
                List<string> files = Directory.EnumerateFiles(DestPath).ToList();
                if (files.Any(s => s.Equals(DestFileName, StringComparison.OrdinalIgnoreCase)))
                {
                    //File exists, do not proceed
                    bProceed = false;
                }
            }
            //Do we proceed?
            if (bProceed)
            {
                //Create/Overwrite existing File
                pDest = CreateFile(destAbsoluteFileName, GENERIC_READ | GENERIC_WRITE, OPENFILE_SHAREACCESS.NO_SHARING,
                                    IntPtr.Zero, CREATE_ALWAYS,
                                    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_WRITE_THROUGH, IntPtr.Zero);
            }
        }
        catch(Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
        }
    }


    //If we successfully have both File Handles, we can proceed with Reading from source and writing to Dest.
    //If valid file pointers!!!
    if (pSource != IntPtr.Zero && pSource.ToInt32() != -1 &&
        pDest != IntPtr.Zero && pDest.ToInt32() != -1)
    {
        //Put Handle into more supported SafeFileHandle Ptr.
        SafeFileHandle safeSourcePtr = new SafeFileHandle(pSource, false); //We will close handle manually
        SafeFileHandle safeDestPtr = new SafeFileHandle(pDest, false);  //We will close handle manually

        try
        {
            using (FileStream fsSource = new FileStream(safeSourcePtr, FileAccess.Read, DefaultCopyBufferSize))
            {
                using (FileStream fsDest = new FileStream(safeDestPtr, FileAccess.ReadWrite, DefaultCopyBufferSize))
                {
                    //Here we read X bytes up to limit from fsSource and write to fsDest, until no more bytes!
                    // Read the source file into a byte array.

                    byte[] buffer = new byte[DefaultCopyBufferSize];
                    int bytesRead = -1;
                    while ((bytesRead = fsSource.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        fsDest.Write(buffer, 0, bytesRead);
                    }
                    //Force data to be flushed to harddisk.
                    fsDest.Flush(true);
                }
            }
        }
        catch { }

        //Compare File Size to know if we successfully wrote bytes to destination!
        //We'll assume it'd error out if it didn't.
        GetFileSizeEx(pSource, out long sourceSize);
        GetFileSizeEx(pDest, out long destSize);
        if (sourceSize == destSize)
        {
            //consider it a success
            bSuccess = true;
        }
    }

    if (pSource != IntPtr.Zero && pSource.ToInt32() != -1)
    {
        //Close file handle manually
        CloseHandle(pSource);
    }

    if (pDest != IntPtr.Zero && pDest.ToInt32() != -1)
    {
        //Close file handle manually
        CloseHandle(pDest);
    }

    return bSuccess;
}



}

public class AccessTokenHelper
{
public enum LOGON32_LOGONTYPE : int
{
    LOGON32_LOGON_INTERACTIVE = 2,
    LOGON32_LOGON_NETWORK = 3,
    LOGON32_LOGON_BATCH = 4,
    LOGON32_LOGON_SERVICE = 5,
    LOGON32_LOGON_UNLOCK = 7,
    LOGON32_LOGON_NETWORK_CLEARTEXT = 8,
    LOGON32_LOGON_NEW_CREDENTIALS = 9,
};

public enum LOGON32_LOGONPROVIDER : int
{
    LOGON32_PROVIDER_DEFAULT = 0,
    LOGON32_PROVIDER_WINNT40 = 2,
    LOGON32_PROVIDER_WINNT50 = 3,
};

public enum LOGON32_LOGONERROR : int
{
    ERROR_SUCCESS = 0,
    ERROR_NO_LOGON_SERVERS = 1311,
    ERROR_INVALID_ACCOUNT_NAME = 1315,
    ERROR_LOGON_FAILURE = 1326,
    ERROR_ACCOUNT_RESTRICTION = 1327,
    ERROR_INVALID_LOGON_HOURS = 1328,
    ERROR_INVALID_WORKSTATION_LOGONDENIED = 1329,
    ERROR_PASSWORD_EXPIRED = 1330,
    ERROR_ACCOUNT_DISABLED = 1331,
    ERROR_INVALID_LOGON_TYPE = 1367,
    ERROR_LOGON_NOT_GRANTED = 1380,
    ERROR_NETLOGON_NOT_STARTED = 1792,
    ERROR_ACCOUNT_EXPIRED = 1793,
    ERROR_PASSWORD_MUST_CHANGE = 1907,
    ERROR_ACCOUNT_LOCKED_OUT = 1909,
};

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private extern static bool LogonUser(string username, string domain, IntPtr password, LOGON32_LOGONTYPE logonType, LOGON32_LOGONPROVIDER logonProvider, out IntPtr token);

/// <summary>
/// Attempts to create Access token from information given.
/// </summary>
public static LOGON32_LOGONERROR GetAccessToken(string domain, string username, SecureString securepassword, LOGON32_LOGONTYPE eLOGONTYPE, LOGON32_LOGONPROVIDER eLOGONPROVIDER, out IntPtr token)
{
    token = IntPtr.Zero;
    // Marshal the SecureString to unmanaged memory.  I hate doing this but it's currently most secure way offered by Microsoft to get Access Token from Credentials.
    IntPtr passwordPtr = Marshal.SecureStringToGlobalAllocUnicode(securepassword);

    bool bSuccess = LogonUser(username,
                                domain,
                                passwordPtr,
                                eLOGONTYPE,
                                eLOGONPROVIDER,
                                out token);
    //return the error code, useful if not successful.
    int errResult = Marshal.GetLastWin32Error();

    // Zero-out and free the unmanaged string reference.
    Marshal.ZeroFreeGlobalAllocUnicode(passwordPtr);

    return (LOGON32_LOGONERROR)errResult;
}






[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool LookupPrivilegeName(string lpSystemName, IntPtr lpLuid, StringBuilder lpName, ref int cchName);

[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool LookupPrivilegeDisplayName(string systemName, string privilegeName, StringBuilder displayName, ref int cchDisplayName, out uint languageId);

[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out long lpLuid);

[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool AdjustTokenPrivileges(
IntPtr TokenHandle,
[MarshalAs(UnmanagedType.Bool)]bool DisableAllPrivileges,
ref TOKEN_PRIVILEGES NewState,
uint prevStateBuffer,
IntPtr prevStateNA,
IntPtr prevBufferNA);

[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool GetTokenInformation(
IntPtr hToken,
uint TokenInformationClass,
IntPtr TokenInformation,
int TokenInformationLength,
out int ReturnLength);

[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
    public uint LowPart;
    public uint HighPart;
};

[StructLayout(LayoutKind.Sequential)]
public struct LUID_AND_ATTRIBUTES
{
    public LUID Luid;
    public uint Attributes;
}

[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_PRIVILEGES
{
    public int PrivilegeCount;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
    public LUID_AND_ATTRIBUTES[] Privileges;
};

[Flags]
public enum SE_PRIVILEGE_STATE : uint
{
    SE_PRIVILEGE_DISABLED = 0x00,
    SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x01,
    SE_PRIVILEGE_ENABLED = 0x02,
    SE_PRIVILEGE_REMOVED = 0x04,
    SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000
};

/// <summary>
/// Will elevate Backup and Restore Privileges of Access Token (be it Primary Type or Impersonation Type) if Access Token has Privilege
///    1) SeBackupPrivilege -- this grants read access regardless of DACL entries.
///    2) SeRestorePrivilege -- this grants write access regardless of DACL entries.
///    3) SeSecurityPrivilege -- this grants access to SACL for audits/security log.
/// </summary>
public static void ElevateSecurityPrivileges(IntPtr hToken, bool bEnabled = true)
{
    SE_PRIVILEGE_STATE privSTATE = bEnabled ? SE_PRIVILEGE_STATE.SE_PRIVILEGE_ENABLED : SE_PRIVILEGE_STATE.SE_PRIVILEGE_DISABLED;
    List<string> SecurityPrivNames = new List<string>() { "SeBackupPrivilege", "SeRestorePrivilege", "SeSecurityPrivilege" };
    if (hToken != IntPtr.Zero)
    {
        AdjustAccessTokenPrivileges(hToken, SecurityPrivNames, privSTATE);
    }
}

public static void AdjustAccessTokenPrivileges(IntPtr TokenHandle, List<string> PrivilegeNames, SE_PRIVILEGE_STATE eNewPrivilegeState, string remoteMachineName = null)
{
    if (TokenHandle != IntPtr.Zero && PrivilegeNames != null && PrivilegeNames.Count > 0)
    {
        DataTable privDT = GetAccessTokenPrivilegesAsDataTable(TokenHandle, remoteMachineName);
        if (privDT != null && privDT.Rows.Count > 0)
        {
            //If we have privileges, try to set state!
            foreach (string privName in PrivilegeNames)
            {
                DataRow row = privDT.Select(string.Format("[{0}]='{1}'", privDT.Columns[1].ColumnName, privName)).FirstOrDefault();
                if (row != null)
                {
                    UpdateExistingTokenPrivilege(TokenHandle, row.Field<LUID>(0), eNewPrivilegeState);
                }
            }
        }
    }
}

private static bool UpdateExistingTokenPrivilege(IntPtr tokenHandle, LUID privLuid, SE_PRIVILEGE_STATE eNewPrivilegeState)
{
    //Create our updated tokenPriv to send what privs we want updated.
    TOKEN_PRIVILEGES tokenPrivs = new TOKEN_PRIVILEGES();
    tokenPrivs.PrivilegeCount = 1;
    tokenPrivs.Privileges = new LUID_AND_ATTRIBUTES[] { new LUID_AND_ATTRIBUTES() { Luid = privLuid, Attributes = (uint)eNewPrivilegeState } };
    //Adjust Token Privilege!
    bool bSuccess = AdjustTokenPrivileges(tokenHandle, false, ref tokenPrivs, 0, IntPtr.Zero, IntPtr.Zero);
    //Return result of trying to adjust token privilege.
    return bSuccess;
}

public static DataTable GetAccessTokenPrivilegesAsDataTable(IntPtr TokenHandle, string remoteMachineName = null)
{
    TOKEN_PRIVILEGES tokenPrivData = GetAccessTokenPrivileges(TokenHandle);

    DataTable privDT = new DataTable();
    privDT.Columns.Add("PrivilegeLUID", typeof(LUID));
    privDT.Columns.Add("PrivilegeName");
    privDT.Columns.Add("PrivilegeState", typeof(SE_PRIVILEGE_STATE));

    foreach (LUID_AND_ATTRIBUTES privData in tokenPrivData.Privileges)
    {
        string PrivilegeName = LookupPrivilegeName(privData.Luid, remoteMachineName);
        if (!string.IsNullOrEmpty(PrivilegeName))
        {
            DataRow row = privDT.NewRow();
            row[0] = privData.Luid;
            row[1] = PrivilegeName;
            row[2] = (SE_PRIVILEGE_STATE)privData.Attributes;
            //Add Row
            privDT.Rows.Add(row);
        }
    }
    return privDT;
}

private static TOKEN_PRIVILEGES GetAccessTokenPrivileges(IntPtr TokenHandle)
{
    uint TOKEN_INFORMATION_CLASS_TokenPrivileges = 3;

    if (TokenHandle != IntPtr.Zero)
    {
        int nBufferSize = 0;
        bool TokenInfoResult;
        //First call is to get buffer size!
        TokenInfoResult = GetTokenInformation(TokenHandle, TOKEN_INFORMATION_CLASS_TokenPrivileges, IntPtr.Zero, nBufferSize, out nBufferSize);
        //Allocate Token Info correctly.
        IntPtr pTokenInfo = Marshal.AllocHGlobal(nBufferSize);
        //Get our Token Info Data
        TokenInfoResult = GetTokenInformation(TokenHandle, TOKEN_INFORMATION_CLASS_TokenPrivileges, pTokenInfo, nBufferSize, out nBufferSize);

        if (TokenInfoResult)
        {
            TOKEN_PRIVILEGES returnedPrivilegeSet = (TOKEN_PRIVILEGES)Marshal.PtrToStructure(pTokenInfo, typeof(TOKEN_PRIVILEGES));
            int PrivilegeCount = returnedPrivilegeSet.PrivilegeCount;

            //lets create the array we should have had returned in the first place
            LUID_AND_ATTRIBUTES[] AllPrivs = new LUID_AND_ATTRIBUTES[PrivilegeCount]; //initialize an array to the right size, including 0!
            if (PrivilegeCount > 0)
            {
                LUID_AND_ATTRIBUTES currentPriv = new LUID_AND_ATTRIBUTES();
                //pPrivileges will hold our new location to read from by taking the last pointer plus the size of the last structure read
                IntPtr pPrivilege = new IntPtr(pTokenInfo.ToInt32() + sizeof(int)); //pointer math, we point to the first element of Privileges array in the struct.
                currentPriv = (LUID_AND_ATTRIBUTES)Marshal.PtrToStructure(pPrivilege, typeof(LUID_AND_ATTRIBUTES)); //Get Privilege from pointer
                AllPrivs[0] = currentPriv; //We'll add the first element to our array.

                //After getting our first structure we can loop through the rest since they will all be the same
                for (int i = 1; i < PrivilegeCount; ++i)
                {
                    pPrivilege = new IntPtr(pPrivilege.ToInt32() + Marshal.SizeOf(currentPriv)); //This will point to the next Privilege element in the array
                    currentPriv = (LUID_AND_ATTRIBUTES)Marshal.PtrToStructure(pPrivilege, typeof(LUID_AND_ATTRIBUTES)); //Get Privilege from pointer
                    AllPrivs[i] = currentPriv; //Add element to the array
                }
            }
            //Create our complete struct of TOKEN_PRIVILEGES
            TOKEN_PRIVILEGES completePrivilegeSet = new TOKEN_PRIVILEGES();
            completePrivilegeSet.PrivilegeCount = PrivilegeCount;
            completePrivilegeSet.Privileges = AllPrivs;

            //We can get release all the pointers now, we got what we wanted!
            Marshal.FreeHGlobal(pTokenInfo);  //Free up the reserved space in unmanaged memory (Should be done any time AllocHGlobal is used)

            //Return our completePrivilegeSet!
            return completePrivilegeSet;
        }
    }
    return new TOKEN_PRIVILEGES() { PrivilegeCount = 0, Privileges = new LUID_AND_ATTRIBUTES[] { } };
}

private static string LookupPrivilegeName(LUID privLuid, string remoteMachineName = null)
{
    string PrivilegeName = null;
    StringBuilder sb = new StringBuilder();
    int cchName = 0; //Holds the length of structure we will be receiving LookupPrivilagename
    IntPtr ipLuid = Marshal.AllocHGlobal(Marshal.SizeOf(privLuid)); //Allocate a block of memory large enough to hold the structure
    Marshal.StructureToPtr(privLuid, ipLuid, true); //Write the structure into the reserved space in unmanaged memory
    LookupPrivilegeName(remoteMachineName, ipLuid, null, ref cchName); // call once to get the name length we will be receiving
    sb.Capacity = cchName; //Our string builder is buffered for the name!
    if (LookupPrivilegeName(remoteMachineName, ipLuid, sb, ref cchName))
    {
        // Successfully retrieved name!
        PrivilegeName = sb.ToString();
    }
    Marshal.FreeHGlobal(ipLuid); //Free up the reserved space in unmanaged memory
    return PrivilegeName;
}

public static List<string> ListEnabledPrivileges(IntPtr TokenHandle, string remoteMachineName = null)
{
    List<string> enabledPrivs = null;

    DataTable dt = GetAccessTokenPrivilegesAsDataTable(TokenHandle, remoteMachineName);

    if (dt != null && dt.Rows.Count > 0)
    {
        uint nEnabled = (uint)SE_PRIVILEGE_STATE.SE_PRIVILEGE_ENABLED;
        uint nEnabledAndDefault = (uint)(SE_PRIVILEGE_STATE.SE_PRIVILEGE_ENABLED_BY_DEFAULT | SE_PRIVILEGE_STATE.SE_PRIVILEGE_ENABLED);



        string query = string.Format("[{0}] IN ( {1}, {2} )"
                                                        , dt.Columns[2].ColumnName
                                                        , nEnabled
                                                        , nEnabledAndDefault);
        IEnumerable<DataRow> rows = dt.Select(query);
        if (rows != null && rows.Count() > 0)
        {
            enabledPrivs = dt.Select(query).Select(r => r.Field<string>(1)).ToList();
        }

    }
    return enabledPrivs;
}

}

Solution

  • So after much research it turns out this is locked down in Windows. Unless the current process token has SeTcbPrivilege (Act as Part of Operating System) and it's enabled, you cannot get an unrestricted/elevated token when deciding to impersonate from a non-elevated token.

    Also if you then try to get the LinkedToken/Unrestricted Token of an Access Token but do not have the SeTcbPrivilege enabled, you will end up with a Token that has a SECURITY_IMPERSONATION_LEVEL of SecurityIdentification which cannot be used for impersonation. Without SECURITY_IMPERSONATION_LEVEL being SecurityImpersonation (Local Access) or SecurityDelegation (Local+Remote Access), you cannot impersonate the Unrestricted Access Token and will result in failure with LastError being ERROR_BAD_IMPERSONATION_LEVEL (Either a required impersonation level was not provided, or the provided impersonation level is invalid).

    This means if you do not have SeTcbPrivilege and it is not enable on the token, it is required to have an elevated process token (ex. Run As Admin was used to start the program manually or forcefully via requestedExecutionLevel level="requireAdministrator") then Windows will not strip the privileges on Access Token when you decide to impersonate the Access Token.

    By using a local administrator account that runs the program as administrator, and then using domain account credentials that are an admin on another machine to get an access token using the params of LOGON32_LOGONTYPE.LOGON32_LOGON_NEW_CREDENTIALS and LOGON32_LOGONPROVIDER.LOGON32_PROVIDER_WINNT50. It is then possible to elevate privileges on the NEWCRED token, impersonate the NEWCRED token which will not strip away the privileges, and have it successfully connect to another computer and read from a file and write to a file on another computer to which it has no permissions or is even explicitly denied, proving it was using the token backup and restore privileges!

    Helpful Sources: