Handle LSA Account Rights

Hi !

How assign to a service account  (local or domain) local rights ? Obviously multi scenarios ! This is the cool. The first one is to make it manually, but let be clear, this is not what you/me are looking for, our goal in life is to automatize EVERYTHING possible.

No need to speicify but many privileges can be granted to services running on close systems, this is a little list about those rights:

  • SeAssignPrimaryTokenPrivilege
  • SeAuditPrivilege
  • SeBackupPrivilege
  • SeBatchLogonRight
  • SeChangeNotifyPrivilege
  • SeCreatePagefilePrivilege
  • SeCreatePermanentPrivilege
  • SeCreateTokenPrivilege
  • SeDebugPrivilege
  • SeIncreaseBasePriorityPrivilege
  • SeIncreaseQuotaPrivilege
  • SeInteractiveLogonRight
  • SeLoadDriverPrivilege
  • SeLockMemoryPrivilege
  • SeMachineAccountPrivilege
  • SeNetworkLogonRight
  • SeProfileSingleProcessPrivilege
  • SeRemoteShutdownPrivilege
  • SeRestorePrivilege
  • SeSecurityPrivilege
  • SeServiceLogonRight
  • SeShutdownPrivilege
  • SeSystemEnvironmentPrivilege
  • SeSystemProfilePrivilege
  • SetSystemtimePrivilege
  • SeTakeOwnershipPrivilege
  • SeTcbPrivilege
  • SeUnsolicitedInputPrivilege

 

At first glance, this is not possible by default in all PowerShell version. All bing search send me in only 3 ways:

  • ntrights.exe
  • Set-Privilege (from PSCX module)
  • LsaAddAccountRights (C# API)

Let’s have a look about how it works

 

 

NTRights.exe

The simplest solution, this legacy utility was firstly publied in Windows Ressource Kit, but it works perfectly in Windows 2008 R2… didn’t know about 2012.. but why not.

Microsoft says that you use it from cmd line like this

ntrights +r user_right -u "nom_compte" [-m\nom_ordinateur]

 

In PowerShell it could be something like this

$Accounts @('Test1','Test2')
$Rights = @("SeTcbPrivilege","SeIncreaseQuotaPrivilege","SeCreateTokenPrivilege",`"SeImpersonatePrivilege","SeBatchLogonRight","SeServiceLogonRight", "SeAssignPrimaryTokenPrivilege","SeManageVolumeNamePrivilege","SeLockMemoryPrivilege","SeChangeNotifyPrivilege")

Foreach ($Account in $Accounts) {
    Foreach ($Right in $Rights) {
        $cmd = "ntrights.exe +r $Right -u $Account"
        Invoke-Expression $cmd
    }
}

 

Honestly this method is really simple, but who wants to use third party legacy utilities in their script if it’s not mandatory ?

 

PSCX / Set-Privilege

Module Powershell everyone should have ! it adds many powerfull features to your PowerShell. You can download it here. This is a module, so again you need to add it to your script… But let’s see how it works… nothing more simple !

$p = Get-Privilege
$p.Enable('SeTimeZonePrivilege')
Set-Privilege $p
Get-Privilege | ft Name, Status -a

We gather privileges for current user, we enable a strict privilege and we assign privileges. The is always the WindowsIdentity parameter we can be use to provide to specific user as a target ! But there is a cooler method 🙂

 

LsaAddAccountRights

For me the best ! This Windows API contains multiple functions to handle accounts privileges and by default it’s a part of advapi32.dll que l’on trouve au minimum sur Windows 2008 R2 (not tested on 2012+) ! I’m not gonna make a training about how use Windows API, but i’ll show you about how compile an assembly to use it on your scripts 🙂

 

$LSAType = @'
using System;
using System.Collections.Generic;
using System.Text;

namespace MyLsaWrapper
{
using System.Runtime.InteropServices;
using System.Security;
using System.Management;
using System.Runtime.CompilerServices;
using System.ComponentModel;

using LSA_HANDLE = IntPtr;

[StructLayout(LayoutKind.Sequential)]
struct LSA_OBJECT_ATTRIBUTES
{
internal int Length;
internal IntPtr RootDirectory;
internal IntPtr ObjectName;
internal int Attributes;
internal IntPtr SecurityDescriptor;
internal IntPtr SecurityQualityOfService;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct LSA_UNICODE_STRING
{
internal ushort Length;
internal ushort MaximumLength;
[MarshalAs(UnmanagedType.LPWStr)]
internal string Buffer;
}
sealed class Win32Sec
{
[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true),
SuppressUnmanagedCodeSecurityAttribute]
internal static extern uint LsaOpenPolicy(
LSA_UNICODE_STRING[] SystemName,
ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,
int AccessMask,
out IntPtr PolicyHandle
);

[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true),
SuppressUnmanagedCodeSecurityAttribute]
internal static extern uint LsaAddAccountRights(
LSA_HANDLE PolicyHandle,
IntPtr pSID,
LSA_UNICODE_STRING[] UserRights,
int CountOfRights
);

[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true),
SuppressUnmanagedCodeSecurityAttribute]
internal static extern int LsaLookupNames2(
LSA_HANDLE PolicyHandle,
uint Flags,
uint Count,
LSA_UNICODE_STRING[] Names,
ref IntPtr ReferencedDomains,
ref IntPtr Sids
);

[DllImport("advapi32")]
internal static extern int LsaNtStatusToWinError(int NTSTATUS);

[DllImport("advapi32")]
internal static extern int LsaClose(IntPtr PolicyHandle);

[DllImport("advapi32")]
internal static extern int LsaFreeMemory(IntPtr Buffer);

}
///

/// This class is used to grant "Log on as a service", "Log on as a batchjob", "Log on localy" etc.
/// to a user.
///

public sealed class LsaWrapper : IDisposable
{
[StructLayout(LayoutKind.Sequential)]
struct LSA_TRUST_INFORMATION
{
internal LSA_UNICODE_STRING Name;
internal IntPtr Sid;
}
[StructLayout(LayoutKind.Sequential)]
struct LSA_TRANSLATED_SID2
{
internal SidNameUse Use;
internal IntPtr Sid;
internal int DomainIndex;
uint Flags;
}

[StructLayout(LayoutKind.Sequential)]
struct LSA_REFERENCED_DOMAIN_LIST
{
internal uint Entries;
internal LSA_TRUST_INFORMATION Domains;
}

enum SidNameUse : int
{
User = 1,
Group = 2,
Domain = 3,
Alias = 4,
KnownGroup = 5,
DeletedAccount = 6,
Invalid = 7,
Unknown = 8,
Computer = 9
}

enum Access : int
{
POLICY_READ = 0x20006,
POLICY_ALL_ACCESS = 0x00F0FFF,
POLICY_EXECUTE = 0X20801,
POLICY_WRITE = 0X207F8
}
const uint STATUS_ACCESS_DENIED = 0xc0000022;
const uint STATUS_INSUFFICIENT_RESOURCES = 0xc000009a;
const uint STATUS_NO_MEMORY = 0xc0000017;

IntPtr lsaHandle;

public LsaWrapper()
: this(null)
{ }
// // local system if systemName is null
public LsaWrapper(string systemName)
{
LSA_OBJECT_ATTRIBUTES lsaAttr;
lsaAttr.RootDirectory = IntPtr.Zero;
lsaAttr.ObjectName = IntPtr.Zero;
lsaAttr.Attributes = 0;
lsaAttr.SecurityDescriptor = IntPtr.Zero;
lsaAttr.SecurityQualityOfService = IntPtr.Zero;
lsaAttr.Length = Marshal.SizeOf(typeof(LSA_OBJECT_ATTRIBUTES));
lsaHandle = IntPtr.Zero;
LSA_UNICODE_STRING[] system = null;
if (systemName != null)
{
system = new LSA_UNICODE_STRING[1];
system[0] = InitLsaString(systemName);
}

uint ret = Win32Sec.LsaOpenPolicy(system, ref lsaAttr,
(int)Access.POLICY_ALL_ACCESS, out lsaHandle);
if (ret == 0)
return;
if (ret == STATUS_ACCESS_DENIED)
{
throw new UnauthorizedAccessException();
}
if ((ret == STATUS_INSUFFICIENT_RESOURCES) || (ret == STATUS_NO_MEMORY))
{
throw new OutOfMemoryException();
}
throw new Win32Exception(Win32Sec.LsaNtStatusToWinError((int)ret));
}

public void AddPrivileges(string account, string privilege)
{
IntPtr pSid = GetSIDInformation(account);
LSA_UNICODE_STRING[] privileges = new LSA_UNICODE_STRING[1];
privileges[0] = InitLsaString(privilege);
uint ret = Win32Sec.LsaAddAccountRights(lsaHandle, pSid, privileges, 1);
if (ret == 0)
return;
if (ret == STATUS_ACCESS_DENIED)
{
throw new UnauthorizedAccessException();
}
if ((ret == STATUS_INSUFFICIENT_RESOURCES) || (ret == STATUS_NO_MEMORY))
{
throw new OutOfMemoryException();
}
throw new Win32Exception(Win32Sec.LsaNtStatusToWinError((int)ret));
}

public void Dispose()
{
if (lsaHandle != IntPtr.Zero)
{
Win32Sec.LsaClose(lsaHandle);
lsaHandle = IntPtr.Zero;
}
GC.SuppressFinalize(this);
}
~LsaWrapper()
{
Dispose();
}
// helper functions

IntPtr GetSIDInformation(string account)
{
LSA_UNICODE_STRING[] names = new LSA_UNICODE_STRING[1];
LSA_TRANSLATED_SID2 lts;
IntPtr tsids = IntPtr.Zero;
IntPtr tdom = IntPtr.Zero;
names[0] = InitLsaString(account);
lts.Sid = IntPtr.Zero;
Console.WriteLine("String account: {0}", names[0].Length);
int ret = Win32Sec.LsaLookupNames2(lsaHandle, 0, 1, names, ref tdom, ref tsids);
if (ret != 0)
throw new Win32Exception(Win32Sec.LsaNtStatusToWinError(ret));
lts = (LSA_TRANSLATED_SID2)Marshal.PtrToStructure(tsids,
typeof(LSA_TRANSLATED_SID2));
Win32Sec.LsaFreeMemory(tsids);
Win32Sec.LsaFreeMemory(tdom);
return lts.Sid;
}

static LSA_UNICODE_STRING InitLsaString(string s)
{
// Unicode strings max. 32KB
if (s.Length > 0x7ffe)
throw new ArgumentException("String too long");
LSA_UNICODE_STRING lus = new LSA_UNICODE_STRING();
lus.Buffer = s;
lus.Length = (ushort)(s.Length * sizeof(char));
lus.MaximumLength = (ushort)(lus.Length + sizeof(char));
return lus;
}
}
public class LsaWrapperCaller
{
public static void AddPrivileges(string account, string privilege)
{
using (LsaWrapper lsaWrapper = new LsaWrapper())
{
lsaWrapper.AddPrivileges(account, privilege);
}
}
}
}
'@

Add-Type $LSAType -PassThru

[MyLsaWrapper.LsaWrapperCaller]::AddPrivileges("andy", "SeServiceLogonRight")

Here we go! For me this is the best solution to assign LSA rights without any thirt party or legacy utilities. 

Regards