I am trying to launch a process in a context of a user that I log on using LogonUserExW.
In order to do that, I need to modify DACL of Winstation "Winsta0" and Desktop "Default". What I do is get the DACL and Security Descriptor (which I don't actually need nor use) using GetSecurityInfo. Then I call GetAclInformation twice to get information about the size and revision of the ACL.
ACL_SIZE_INFORMATION aclInfoSizeOut = new ACL_SIZE_INFORMATION();
GetAclInformation(daclWinsta, out aclInfoSizeOut, Marshal.SizeOf(aclInfoSizeOut), ACL_INFORMATION_CLASS.AclSizeInformation);
ACL_REVISION_INFORMATION aclInfoRevisionOut = new ACL_REVISION_INFORMATION();
GetAclInformation(daclWinsta, out aclInfoRevisionOut, Marshal.SizeOf(aclInfoRevisionOut), ACL_INFORMATION_CLASS.AclRevisionInformation);
Once I have the data, I calculate the required size of the new ACL:
int cbNewACL = Convert.ToInt32(aclInfoSizeOut.AclBytesInUse Marshal.SizeOf(allowedAce) GetLengthSid(userpSid) - sizeof(UInt32));
Then as per https://web.archive.org/web/20121231022835/http://support.microsoft.com/kb/102102 I allocate memory for the new ACL using
IntPtr pNewAcl = LocalAlloc(LocalAllocFlags.LPTR, cbNewACL);
This is where it starts getting confusing for me BIG time. I fail to understand how this actually works. Using the LocalAlloc I now have a pointer to a memory of the size I specified. Then I initialize the new ACL which is supposed to be in that memory block, BUT how? Because the pointer is actually rewritten once I use the function below. Interestingly enough, it always has the same value every time I call the function. Whereas the pointer returned by Locallloc is always different.
InitializeAcl(out pNewAcl, cbNewACL, aclInfoRevisionOut.AclRevision);
The pAcl in InitializeAcl function is defnied as "[out] pAcl A pointer to an ACL structure to be initialized by this function. Allocate memory for pAcl before calling this function." I would understand if it was passed as REF for the function to know where to actually initialize the ACL, but REF doesn't change it at all.
Another thing is when I call the InitializeAcl, the cbNewAcl gets set to 0. This goes absolutely beyond my head. How and why does it change the value?
Then when I call AddAccessAllowedAce it completely messes up the structures I have set before I get the data from the existing ACL- aclInfoSizeOut and aclInfoRevisionOut which get set to nonsense values. Such as count 0, bytes in use 78548557 (some high number). And cbNewAcl gets changed to 1. I have no idea why it does that.
AddAccessAllowedAce(ref pNewAcl, aclInfoRevisionOut.AclRevision, ACCESS_MASK.READ_CONTROL | ACCESS_MASK.WINSTA_ALL_ACCESS, userpSid);
Below is my full code which "works" - I get the user's token and psid, I query the existing DACL, I query the members of the DACL and get their string SIDs, but that is about it. I can't create a new DACL. Posting it with my comments as well. I hope I did not forget any signatures as the code is a bit longer in VS than what I am actually posting.
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUserExW(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out IntPtr phToken,
out IntPtr ppLogonSid,
out IntPtr ppProfileBuffer,
out IntPtr pdwProfileLength,
out QUOTA_LIMITS pQuotaLimits
);
[DllImport("Advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int ConvertSidToStringSidW(
IntPtr SidPtr,
out IntPtr SidString
);
[DllImport("Advapi32.dll", SetLastError = true)]
public static extern int GetSecurityInfo
(
IntPtr handle,
SE_OBJECT_TYPE ObjectType,
SECURITY_INFORMATION SecurityInfo,
out IntPtr ppsidOwner,
out IntPtr ppsidGroup,
out IntPtr ppDacl,
out IntPtr ppSacl,
out IntPtr ppSecurityDescriptor
);
[DllImport("User32.dll", SetLastError = true)]
public static extern IntPtr GetProcessWindowStation();
[DllImport("Advapi32.dll", SetLastError = true)]
public static extern bool GetAclInformation(
IntPtr pAcl,
out ACL_SIZE_INFORMATION pAclInformation,
int nAclInformationLength,
ACL_INFORMATION_CLASS aclInformationClass
);
public struct ACL_SIZE_INFORMATION
{
public uint AceCount;
public uint AclBytesInUse;
public uint AclBytesFree;
}
public struct ACL_REVISION_INFORMATION
{
public int AclRevision;
}
public enum ACL_INFORMATION_CLASS
{
AclRevisionInformation = 1,
AclSizeInformation
}
public struct QUOTA_LIMITS
{
public int PagedPoolLimit;
public int NonPagedPoolLimit;
public int MinimumWorkingSetSize;
public int MaximumWorkingSetSize;
public int PagefileLimit;
public Int64 TimeLimit;
}
ublic struct ACCESS_ALLOWED_ACE
{
public ACE_HEADER Header;
public ACCESS_MASK Mask;
public UInt16 SidStart;
}
ublic enum LocalAllocFlags : uint
{
LHND = 0x0042,
LMEM_FIXED = 0x0000,
LMEM_MOVEABLE = 0x0002,
LMEM_ZEROINIT = 0x0040,
LPTR = 0x0040
}
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern IntPtr LocalAlloc(LocalAllocFlags flags, int bytes);
DllImport("Advapi32.dll", SetLastError = true)]
public static extern bool InitializeAcl(
out IntPtr pAcl,
int nAclLength,
int dwAclRevision
);
public static extern int AddAccessAllowedAce
(
ref IntPtr pAcl,
/* ACL_REVISION 2
ACL_REVISION_DS 4
*/
int dwAceRevision,
ACCESS_MASK AccessMask,
IntPtr pSid
);
LogonUserExW("XXXXX", "FFFFF", "BBBBB", 2, 0, out IntPtr userToken, out IntPtr userpSid, out IntPtr profileBuffer, out IntPtr profileLength, out QUOTA_LIMITS profQuotaLimits);
ConvertSidToStringSidW(userpSid, out IntPtr ptrSid);
string stringSid = Marshal.PtrToStringUni(ptrSid);
IntPtr winstaHandle = GetProcessWindowStation();
//get security descriptor for the station
int getDescriptorResult = GetSecurityInfo(winstaHandle, SE_OBJECT_TYPE.SE_WINDOW_OBJECT, SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | SECURITY_INFORMATION.PROTECTED_DACL_SECURITY_INFORMATION, out IntPtr owner, out IntPtr group, out IntPtr daclWinsta, out IntPtr Sacl, out IntPtr secDescriptor);
//this actually works but shows different values than ACL_SIZE_INFORMATION and ACL_REVISION_INFORMATION
//maybe DACL is in different structure than ACL struct? could not find any other struct in the documentation
ACL aclStruct = (ACL)Marshal.PtrToStructure(daclWinsta, typeof(ACL));
ACL_SIZE_INFORMATION aclInfoSizeOut = new ACL_SIZE_INFORMATION();
GetAclInformation(daclWinsta, out aclInfoSizeOut, Marshal.SizeOf(aclInfoSizeOut), ACL_INFORMATION_CLASS.AclSizeInformation);
ACL_REVISION_INFORMATION aclInfoRevisionOut = new ACL_REVISION_INFORMATION();
GetAclInformation(daclWinsta, out aclInfoRevisionOut, Marshal.SizeOf(aclInfoRevisionOut), ACL_INFORMATION_CLASS.AclRevisionInformation);
//count:17 bytes:436
//create a new ACL just to get its size for the cbNewACL value
ACCESS_ALLOWED_ACE allowedAce = new ACCESS_ALLOWED_ACE();
int cbNewACL = Convert.ToInt32(aclInfoSizeOut.AclBytesInUse Marshal.SizeOf(allowedAce) GetLengthSid(userpSid) - sizeof(UInt32));
//??? for some reason cbNewACL gets cleared after calling InitializeAcl
IntPtr pNewAcl = LocalAlloc(LocalAllocFlags.LPTR, cbNewACL);
InitializeAcl(out pNewAcl, cbNewACL, aclInfoRevisionOut.AclRevision);
//just for test try to add it right away
//AddAccessAllowedAce(ref pNewAcl, aclInfoRevisionOut.AclRevision, ACCESS_MASK.READ_CONTROL | ACCESS_MASK.WINSTA_ALL_ACCESS, userpSid);
//after calling AddAccessAllowedAce the information is structs aclInfoSizeOut and aclInfoRevisionOut gets destroyed
//cbNewAcl gets set to 1
//???
bool newAceAdded = false;
bool userSidAlreadyExists = false;
//copy old ACEs into the new ACL
if (aclInfoSizeOut.AceCount != 0)
{
int indexInNewAcl = 0;
for (int i=0;i<aclInfoSizeOut.AceCount;i )
{
//get ace and add it to the new ACL
GetAce(daclWinsta, i, out IntPtr existingAce);
if (existingAce != IntPtr.Zero)
{
//get ace header from the ACE pointer to identify the ACE type
//get first 4 bytes from the pointer starting at 0 since ACE_HEADER struct is of size 4
//once we have it in the byte array, create a GC handle with the bytes - this allocates them in a managed memory
//then read it to the structure using Marshal.PtrToStructure
ACE_HEADER aceHeader = new ACE_HEADER();
byte[] aceHeaderBytes = new byte[Marshal.SizeOf(aceHeader)];
Marshal.Copy(existingAce, aceHeaderBytes, 0, aceHeaderBytes.Length);
GCHandle aceHeaderBytesHandle = GCHandle.Alloc(aceHeaderBytes, GCHandleType.Pinned);
try
{
aceHeader = (ACE_HEADER)Marshal.PtrToStructure(aceHeaderBytesHandle.AddrOfPinnedObject(), typeof(ACE_HEADER));
switch (aceHeader.AceType)
{
case 0:
case 1:
//0 allowed - ACCESS_ALLOWED_ACE, 1 denied - ACCESS_DENIED_ACE
//the two have the same members definied in their structure.
//the declaration below is not explicitly needed in our case, but might be useful for some other things
//ACCESS_ALLOWED_ACE existingDeniedAllowedACE = (ACCESS_ALLOWED_ACE)Marshal.PtrToStructure(existingAce, typeof(ACCESS_ALLOWED_ACE));
//0 and 1 in this context are together in one clause since they are definied by the same members in the same order, thus allowing us
//to get SidStart from the same offset from the ACE IntPtr
//GUESSING: SidStart starts at 8th byte in the struct, therefore an offset of 8 bytes - size of ACE_HEADER and ACCESS_MASK (Uint16)
//if for example the ACE type would be definied by more members with SidStart at the end, we would have to calculate the offset by
//the sum of all members except for SidStart
IntPtr sidPtrInAce = IntPtr.Zero;
try
{
sidPtrInAce = IntPtr.Add(existingAce, 8);
ConvertSidToStringSidW(sidPtrInAce, out IntPtr pSidStringInAce);
if (pSidStringInAce == IntPtr.Zero) { throw new Exception("failed to get pSidString"); }
string sidStringInAce = Marshal.PtrToStringUni(pSidStringInAce);
Console.WriteLine(sidStringInAce);
if (sidStringInAce == stringSid) { userSidAlreadyExists = true; }
} catch { }
break;
}
if (userSidAlreadyExists) { break; }
//if the ACE was not added AND the processing ACE is not NON-INHERITED DENIED and is not DENIED for Object AND is not Enable for Object
//then add our ADE to ensure it is in the correct order
//Windows 2000 and later ACE ordering in ACL:
//Non-Inherited -> Inherited
//withing each of the two groups the ACEs are also in the following order
//Disable for the object
//Disable for the subject of the object
//Enable for the object
//Enable for the subject of the object
//So basically if we hit anything but NON-Inherited disable for the object, NON-Inherited Disable for the subject of the object and NON-Inherited Enable for the object
//then we should add our ACE - this will ensure correct order
//^ could not figure out how to distinguish between Enable for the object and Enable for the subject of the object
//instead, add the ACE the moment we find an inherited ACE
if (!newAceAdded && isAceInherited(aceHeader.AceFlags))
{
//AddAccessAllowedAce(ref pNewAcl, aclInfoRevisionOut.AclRevision, ACCESS_MASK.READ_CONTROL | ACCESS_MASK.WINSTA_ALL_ACCESS, userpSid);
newAceAdded = true;
//indexInNewAcl ;
}
//AddAce(ref pNewAcl, aclInfoRevisionOut.AclRevision, indexInNewAcl, existingAce, aceHeader.AceSize);
indexInNewAcl ;
}
catch (Exception ex)
{
aceHeaderBytesHandle.Free();
}
} else
{
Console.WriteLine("failed obtaining existing ACE in ACL. quitting"); Console.Read(); return;
}
}
}
if (userSidAlreadyExists) { Console.WriteLine("user already exists in the ACL. quit"); Console.Read(); return; }
if (!newAceAdded && !userSidAlreadyExists)
{
//we did not hit an inherited ACE - probably none exists in the ACL OR AceCount was 0 - no going through ACEs
//therefore user was not added
//AddAccessAllowedAce(ref pNewAcl, aclInfoRevisionOut.AclRevision, ACCESS_MASK.READ_CONTROL | ACCESS_MASK.WINSTA_ALL_ACCESS, userpSid);
newAceAdded = true;
}
if (!newAceAdded)
{
Console.WriteLine("failed to add new ACE. quit"); Console.Read(); return;
}
CloseHandle(userToken);
LocalFree(userpSid);
Console.WriteLine("ready to quit");
Console.Read();
CodePudding user response:
As per Selvin's comment:
The problem was with InitializeAcl signature. It is not out, but in and the function initialized the acl in the address pointed by the IntPtr. In addition to that, I had to change AddAce and AddAccessAllowedAce signatures as well - ref IntPtr pAcl to just in IntPtr.