In the previous parts (part 1 part 2) i’ve described the theoretical part and implementation problems. So, now we can write the code:
- In case we login the user, we just call LsaLogonUser to get the token:
objectpascal
Download
function CopyString(const Source : WideString; const Buffer : Pointer; var CurrentPosition : Pointer; LengthInBytes: Cardinal = $FFFFFFFF) : Pointer;
begin
if (Source <> '' ) then
begin
Cardinal(Result) := Cardinal(CurrentPosition) - Cardinal(Buffer);
if (LengthInBytes = $FFFFFFFF) then
LengthInBytes := (Length(Source) + 1) * SizeOf(Source[1]);
CopyMemory(CurrentPosition, PWideChar(Source), LengthInBytes);
Cardinal(CurrentPosition) := Cardinal(CurrentPosition) + LengthInBytes;
end
else
Result := nil;
end;
procedure CopyUnicodeString(const Target : PUnicodeString; const Source : PUnicodeString; const Buffer : Pointer; var CurrentPosition : Pointer);
begin
Target^ := Source^;
Target^.Buffer := CopyString(Target^.Buffer, Buffer, CurrentPosition, Target^.MaximumLength);
end;
procedure CreateLsaLogonPackageBuffer(const UserName, Domain, Password : WideString;
out Result : PMSV1_0_INTERACTIVE_LOGON; out ResultLength : Cardinal);
var
LogonCurrentPointer : Pointer;
begin
ResultLength := SizeOf(Result^) + (Length(UserName) + Length(Domain) + Length(Password) + 3) * SizeOf(UserName[1]);
Result := GetMemory(ResultLength);
try
ZeroMemory(Result, ResultLength);
Cardinal(LogonCurrentPointer) := Cardinal(Result) + SizeOf(Result^);
Result^.MessageType := MsV1_0InteractiveLogon;
RtlInitUnicodeString(@Result^.UserName, PWideChar(UserName));
CopyUnicodeString(@Result^.UserName, @Result^.UserName, nil, LogonCurrentPointer);
RtlInitUnicodeString(@Result^.LogonDomainName, PWideChar(Domain));
CopyUnicodeString(@Result^.LogonDomainName, @Result^.LogonDomainName, nil, LogonCurrentPointer);
RtlInitUnicodeString(@Result^.Password, PWideChar(Password));
CopyUnicodeString(@Result^.Password, @Result^.Password, nil, LogonCurrentPointer);
except
FreeMemory(Result);
Raise;
end;
end;
procedure LoginUser;
var
LsaHandle : THandle;
LsaName : LSA_STRING;
PackageName : LSA_STRING;
AuthenticationPackage : ULONG;
LogonPackageBuffer : PMSV1_0_INTERACTIVE_LOGON ;
LogonPackageBufferLength : Cardinal;
TokenSource : JwaWindows.TTokenSource;
SubStatus : Integer;
SecurityMode : LSA_OPERATIONAL_MODE;
begin
RtlInitUnicodeString(@LsaName, 'AutoLogonXP login');
NtCheck(LsaRegisterLogonProcess(LsaName, LsaHandle, @SecurityMode));
try
RtlInitString(@PackageName, PCSZ(PChar(MSV1_0_PACKAGE_NAME)));
LsaLookupAuthenticationPackage(LsaHandle, PackageName, AuthenticationPackage);
JwaWindows.AllocateLocallyUniqueId(TokenSource.SourceIdentifier);
TokenSource.SourceName := 'AUTOLGON';
CreateLsaLogonPackageBuffer(UserName, Domain, Password, LogonPackageBuffer, LogonPackageBufferLength);
try
NtCheck(
LsaLogonUser(
LsaHandle,
LsaName,
Interactive,
AuthenticationPackage,
LogonPackageBuffer,
LogonPackageBufferLength,
TokenGroups,
@TokenSource,
Pointer(Profile),
ProfileLength,
JwaWindows.LUID(Result^.LogonId),
Result^.UserToken,
JwaWindows.QUOTA_LIMITS(Result^.Quotas),
SubStatus
));
finally
FreeLsaLogonPackageBuffer(LogonPackageBuffer);
end;
finally
LsaDeregisterLogonProcess(LsaHandle);
end;
end;- In case we need to create a token, we call NtCreateToken. If there are no groups, we create a single group with the Primary SID in this group :
objectpascal
Download
procedure CreateToken;
var
ExpirationTime : LARGE_INTEGER;
User : JwaWindows.TOKEN_USER;
TokenSource : TOKEN_SOURCE;
PrimaryGroup : JwaWindows.TOKEN_PRIMARY_GROUP;
EmptyTokenGroups : JwaWindows.TTokenGroups;
begin
ExpirationTime.QuadPart := 0;
User.User.Sid := PrimarySID;
User.User.Attributes := 0;
TokenSource.SourceName := 'AUTOLGON';
Win32Check(AllocateLocallyUniqueId(TokenSource.SourceIdentifier));
Result^.LogonId := SYSTEM_LUID;
PrimaryGroup.PrimaryGroup := PrimarySID;
if (nil = TokenGroups) then
begin
EmptyTokenGroups.GroupCount := 1;
EmptyTokenGroups.Groups[0].Sid := PrimarySID;
EmptyTokenGroups.Groups[0].Attributes := SE_GROUP_MANDATORY or SE_GROUP_ENABLED or SE_GROUP_ENABLED_BY_DEFAULT or SE_GROUP_LOGON_ID;
TokenGroups := @EmptyTokenGroups;
end;
EnableToken(SE_CREATE_TOKEN_NAME);
NtCheck(
NtCreateToken(
@Result^.UserToken,
TOKEN_ALL_ACCESS,
nil,
TokenPrimary,
@Result^.LogonId,
@ExpirationTime,
@User,
TokenGroups,
Privs,
nil,
@PrimaryGroup,
nil,
@TokenSource
));
try
Profile := AllocEmptyProfile(ProfileLength);
except
CloseHandle(Result^.UserToken);
Raise;
end;
end;- In case we want to use a “current” system token, we need to do some adjustments with it - we need to add the group with SE_GROUP_LOGON_ID flag to it:
objectpascal
Download
function GetFromToken(TokenHandle : THandle; Info : TOKEN_INFORMATION_CLASS) : Pointer;
var
Res : DWORD;
begin
Result := nil;
GetTokenInformation(TokenHandle, info, nil, 0, Res);
if GetLastError = ERROR_INSUFFICIENT_BUFFER then
begin
Result := GetMemory(Res);
Win32Check(GetTokenInformation(TokenHandle, info, Result, Res, Res));
end else
RaiseLastOSError;
end;
procedure AsSystem;
var
SystemToken : THandle;
Statistics : PTokenStatistics;
User : JwaWinNT.PTokenUser;
Priviledges : JwaWindows.PTokenPrivileges;
Owner : PTokenOwner;
Groups : JwaWindows.PTokenGroups;
PrimaryGroup : PTokenPrimaryGroup;
DefaultDacl : PTokenDefaultDacl;
TokenSrc : PTokenSource;
TokenNewGroups : JwaWindows.PTokenGroups;
I : Integer;
begin
Win32Check(OpenProcessToken(GetCurrentProcess, TOKEN_ALL_ACCESS, SystemToken));
EnableToken(SE_CREATE_TOKEN_NAME);
Statistics := nil;
User := nil;
Priviledges := nil;
Owner := nil;
PrimaryGroup := nil;
DefaultDacl := nil;
TokenSrc := nil;
Groups := nil;
TokenNewGroups := nil;
try
Statistics := GetFromToken(SystemToken, TokenStatistics);
User := GetFromToken(SystemToken, TokenUser);
Priviledges := GetFromToken(SystemToken, TokenPrivileges);
Owner := GetFromToken(SystemToken, TokenOwner);
PrimaryGroup := GetFromToken(SystemToken, TokenPrimaryGroup);
DefaultDacl := GetFromToken(SystemToken, TokenDefaultDacl);
TokenSrc := GetFromToken(SystemToken, TokenSource);
Groups := GetFromToken(SystemToken, JwaWinNT.TokenGroups);
TokenNewGroups := GetMemory(SizeOf(TokenNewGroups^) + SizeOf(TokenNewGroups^.Groups) * Groups^.GroupCount);
TokenNewGroups^.GroupCount := Groups^.GroupCount + 1;
for I := 0 to Groups^.GroupCount - 1 do
TokenNewGroups^.Groups[I] := Groups^.Groups[I];
TokenNewGroups^.Groups[Groups^.GroupCount].Sid := Pointer(User^.User.Sid);
TokenNewGroups^.Groups[Groups^.GroupCount].Attributes := SE_GROUP_MANDATORY or SE_GROUP_ENABLED or SE_GROUP_ENABLED_BY_DEFAULT or SE_GROUP_LOGON_ID;
NtCheck(
NtCreateToken(
@Result^.UserToken,
TOKEN_ALL_ACCESS,
nil,
Statistics^.TokenType,
@Statistics^.AuthenticationId,
@Statistics^.ExpirationTime,
User,
TokenNewGroups,
Priviledges,
Owner,
PrimaryGroup,
DefaultDacl,
TokenSrc
));
finally
if (Statistics <> nil) then
FreeMemory(Statistics);
if (User <> nil) then
FreeMemory(User);
if (Priviledges <> nil) then
FreeMemory(Priviledges);
if (Owner <> nil) then
FreeMemory(Owner);
If (PrimaryGroup <> nil) then
FreeMemory(PrimaryGroup);
if (DefaultDacl <> nil) then
FreeMemory(DefaultDacl);
if (TokenSrc <> nil) then
FreeMemory(TokenSrc);
if (Groups <> nil) then
FreeMemory(Groups);
if (TokenNewGroups <> nil) then
FreeMemory(TokenNewGroups);
end;
try
try
Profile := AllocEmptyProfile(ProfileLength);
except
CloseHandle(Result^.UserToken);
Raise;
end;
finally
CloseHandle(SystemToken);
end;
end;- Now we can create a buffer:
objectpascal
Download
function CreateAutoLogonBuffer32(
const UserName, Domain, Password : WideString;
WorkingMode : TWorkingMode;
const PrimarySID : JwaWindows.PSID; TokenGroups : JWaWindows.PTokenGroups;
const Privs : JwaWindows.PTokenPrivileges
) : PAutoLogonBuffer32;
var
Profile : PMSV1_0_INTERACTIVE_PROFILE;
ProfileLength : DWORD;
CurrentPosition : Pointer;
bDoFreeTokenHandle : Boolean;
begin
Result := GetMemory(SizeOf(Result^));
try
ZeroMemory(Result, SizeOf(Result^));
case WorkingMode of
wmNormalLogin : LoginUser;
wmCreateToken : CreateToken;
wmAsSystem : AsSystem;
end;
// PrintTokenSIDs(Result^.UserToken);
bDoFreeTokenHandle := True;
try
if Profile^.MessageType <> MsV1_0InteractiveProfile then
begin
Writeln('Profile message type is not MsV1_0InteractiveProfile, but is ', Ord(Profile^.MessageType));
Abort;
end;
Result^.SourceProcessId := GetCurrentProcessId;
CurrentPosition := @Result^.DynamicData[0];
if Is2003 then
begin
Result^.Credentials.WIN2K_MODE.UserName := CopyString(UserName, Result, CurrentPosition);
Result^.Credentials.WIN2K_MODE.Domain := CopyString(Domain, Result, CurrentPosition);
Result^.Credentials.WIN2K_MODE.DomainLength := Length(Domain);
Result^.Credentials.WIN2K_MODE.ProfileLength := ProfileLength;
end else
begin
Result^.Credentials.WINXP_MODE.UserName := CopyString(UserName, Result, CurrentPosition);
Result^.Credentials.WINXP_MODE.Domain := CopyString(Domain, Result, CurrentPosition);
Result^.Credentials.WINXP_MODE.ProfileLength := ProfileLength;
end;
Result^.MessageType := Ord(MsV1_0InteractiveProfile);
Result^.LogonCount := Profile^.LogonCount;
Result^.BadPasswordCount := Profile^.BadPasswordCount;
Result^.ProfileLogonTime := Profile^.LogonTime;
Result^.LogoffTime := Profile^.LogoffTime;
Result^.KickOffTime := Profile^.KickOffTime;
Result^.PasswordLastSet := Profile^.PasswordLastSet;
Result^.PasswordCanChange := Profile^.PasswordCanChange;
Result^.PasswordMustChange := Profile^.PasswordMustChange;
CopyUnicodeString(@Result^.LogonScript, @Profile^.LogonScript, Result, CurrentPosition);
CopyUnicodeString(@Result^.HomeDirectory, @Profile^.HomeDirectory, Result, CurrentPosition);
CopyUnicodeString(@Result^.FullName, @Profile^.FullName, Result, CurrentPosition);
CopyUnicodeString(@Result^.ProfilePath, @Profile^.ProfilePath, Result, CurrentPosition);
CopyUnicodeString(@Result^.HomeDirectoryDrive, @Profile^.HomeDirectoryDrive, Result, CurrentPosition);
CopyUnicodeString(@Result^.LogonServer, @Profile^.LogonServer, Result, CurrentPosition);
Result^.UserFlags := Profile^.UserFlags;
Result^.LogonTime := GetCurrentTime();
Result^.SmartCardLogon := False;
Result^.TotalLength.Length := Cardinal(CurrentPosition) - Cardinal(Result);
bDoFreeTokenHandle := False;
finally
if (bDoFreeTokenHandle) then
CloseHandle(Result^.UserToken);
LsaFreeReturnBuffer(Profile);
end;
except
FreeMemory(Result);
Raise;
end;
end;- In case of x64 systems we need to copy the whole structure into 64 bit structure:
objectpascal
Download
type
PWSTR_64 = record
Buffer : PWSTR;
Alignment : DWORD;
end;
THANDLE_64 = record
Handle : THandle;
Alignment : DWORD;
end;
PUNICODE_STRING_64 = ^UNICODE_STRING_64;
UNICODE_STRING_64 = record
Length: USHORT;
MaximumLength: USHORT;
Alignment : DWORD;
Buffer: PWSTR_64;
end;
ULONG_64 = record
Value : DWORD;
Alignment : DWORD;
end;
QUOTA_LIMITS_64 = record
PagedPoolLimit: ULONG_64;
NonPagedPoolLimit: ULONG_64;
MinimumWorkingSetSize: ULONG_64;
MaximumWorkingSetSize: ULONG_64;
PagefileLimit: ULONG_64;
TimeLimit: LARGE_INTEGER;
end;
PAutoLogonBuffer64 = ^TAutoLogonBuffer64;
TAutoLogonBuffer64 = record
TotalLength : TLengthBuffer;
SourceProcessId : DWORD;
UserToken : THANDLE_64;
LogonId : LUID;
Quotas : QUOTA_LIMITS_64;
Credentials : record
case Boolean of
True : (WINXP_MODE : record
UserName : PWSTR_64;
Domain : PWSTR_64;
ProfileLength : ULONG;
Unknown1 : DWORD;
end);
False : (WIN2K_MODE : record
UserName : PWSTR_64;
DomainLength : ULONG_64;
Domain : PWSTR_64;
ProfileLength : ULONG_64;
end);
end;
MessageType : DWORD;
LogonCount : USHORT;
BadPasswordCount: USHORT;
ProfileLogonTime: LARGE_INTEGER;
LogoffTime : LARGE_INTEGER;
KickOffTime : LARGE_INTEGER;
PasswordLastSet : LARGE_INTEGER;
PasswordCanChange: LARGE_INTEGER;
PasswordMustChange: LARGE_INTEGER;
LogonScript : UNICODE_STRING_64;
HomeDirectory : UNICODE_STRING_64;
FullName : UNICODE_STRING_64;
ProfilePath : UNICODE_STRING_64;
HomeDirectoryDrive : UNICODE_STRING_64;
LogonServer : UNICODE_STRING_64;
UserFlags : ULONG_64;
LogonTime : LARGE_INTEGER;
SmartCardLogon : BOOL;
PrivateDataLen : ULONG;
PrivateData : Pointer;
Unknown2 : DWORD;
DynamicData : array [0..7981] of BYTE;
end;
function CreateAutoLogonBuffer64(const Source : PAutoLogonBuffer32) : PAutoLogonBuffer64;
var
DynamicBufferDelta : Integer;
begin
Result := GetMemory(SizeOf(Result^));
try
ZeroMemory(Result, SizeOf(Result^));
DynamicBufferDelta := ((Cardinal(@Result^.DynamicData) - Cardinal(Result) - (Cardinal(@Source^.DynamicData) - Cardinal(Source))));
Result^.TotalLength.Length := Source^.TotalLength.Length + Cardinal(DynamicBufferDelta);
Result^.SourceProcessId := Source^.SourceProcessId;
Result^.UserToken.Handle := Source^.UserToken;
Result^.LogonId := Source^.LogonId;
Result^.Quotas.PagedPoolLimit.Value := Source^.Quotas.PagedPoolLimit;
Result^.Quotas.NonPagedPoolLimit.Value := Source^.Quotas.NonPagedPoolLimit;
Result^.Quotas.MinimumWorkingSetSize.Value := Source^.Quotas.MinimumWorkingSetSize;
Result^.Quotas.MaximumWorkingSetSize.Value := Source^.Quotas.MaximumWorkingSetSize;
Result^.Quotas.PagefileLimit.Value := Source^.Quotas.PagefileLimit;
Result^.Quotas.TimeLimit := Source^.Quotas.TimeLimit;
// In theory we need to do a check if it's windows 2003/xp x64, but actually there is no public version of XP (5.1) x64, so
// we can just skip it.
Result^.Credentials.WIN2K_MODE.UserName.Buffer := AdjustBuffer(Source^.Credentials.WIN2K_MODE.UserName, DynamicBufferDelta);
Result^.Credentials.WIN2K_MODE.DomainLength.Value := Source^.Credentials.WIN2K_MODE.DomainLength;
Result^.Credentials.WIN2K_MODE.Domain.Buffer := AdjustBuffer(Source^.Credentials.WIN2K_MODE.Domain, DynamicBufferDelta);
Result^.Credentials.WIN2K_MODE.ProfileLength.Value:= Source^.Credentials.WIN2K_MODE.ProfileLength;
Result^.MessageType := Source^.MessageType;
Result^.LogonCount := Source^.LogonCount;
Result^.BadPasswordCount := Source^.BadPasswordCount;
Result^.ProfileLogonTime := Source^.ProfileLogonTime;
Result^.LogoffTime := Source^.LogoffTime;
Result^.KickOffTime := Source^.KickOffTime;
Result^.PasswordLastSet := Source^.PasswordLastSet;
Result^.PasswordCanChange := Source^.PasswordCanChange;
Result^.PasswordMustChange := Source^.PasswordMustChange;
Result^.LogonScript := AdjustUnicodeString(Source^.LogonScript, DynamicBufferDelta);
Result^.HomeDirectory := AdjustUnicodeString(Source^.HomeDirectory, DynamicBufferDelta);
Result^.FullName := AdjustUnicodeString(Source^.FullName, DynamicBufferDelta);
Result^.ProfilePath := AdjustUnicodeString(Source^.ProfilePath, DynamicBufferDelta);
Result^.HomeDirectoryDrive:= AdjustUnicodeString(Source^.HomeDirectoryDrive, DynamicBufferDelta);
Result^.LogonServer := AdjustUnicodeString(Source^.LogonServer, DynamicBufferDelta);
Result^.UserFlags.Value := Source^.UserFlags;
Result^.LogonTime := Source^.LogonTime;
Result^.SmartCardLogon := Source^.SmartCardLogon;
Result^.PrivateDataLen := Source^.PrivateDataLen;
Result^.PrivateData := AdjustBuffer(Source^.PrivateData, DynamicBufferDelta);
Result^.Unknown2 := Source^.Unknown2;
CopyMemory(@Result^.DynamicData, @Source^.DynamicData, Source^.TotalLength.Length - (SizeOf(Source^) - SizeOf(Source^.DynamicData)));
except
FreeMemory(Result);
Raise;
end;
end;
function CreateAutoLogonBuffer(
const UserName, Domain, Password : WideString;
WorkingMode : TWorkingMode;
const PrimarySID : JwaWindows.PSID; TokenGroups : JWaWindows.PTokenGroups;
const Privs : JwaWindows.PTokenPrivileges
) : PLengthBuffer;
begin
Result := @CreateAutoLogonBuffer32(UserName, Domain, Password, WorkingMode, PrimarySID, TokenGroups, Privs)^.TotalLength;
if (IsWow64) then
try
Result := @CreateAutoLogonBuffer64(PAutoLogonBuffer32(Result))^.TotalLength;
except
FreeMemory(Result);
Raise;
end;
end;
procedure FreeAutoLogonBuffer(var Buffer : PLengthBuffer);
begin
if (Buffer <> nil) then
begin
if (IsWow64) then
CloseHandle(PAutoLogonBuffer64(Buffer)^.UserToken.Handle)
else
CloseHandle(PAutoLogonBuffer32(Buffer)^.UserToken);
FreeMemory(Buffer);
Buffer := nil;
end;
end;- Now we need to open the pipe, send the WLX_SAS_TYPE_AUTHENTICATED SAS to Winlogon and write the data to the pipe:
objectpascal
Download
function SendCredentialThreadProc(Parameter: PLengthBuffer): Integer;
var
PipeHandle : THandle;
begin
Result := 0;
try
PipeHandle := OpenPipe;
try
DoLogonNotifyMessage(WPARAM_AUTHENTICATED, 0);
ProcessPipe(PipeHandle, Parameter);
finally
CloseHandle(PipeHandle);
end;
except
on E : EOSError do
Result := EOsError(E).ErrorCode;
on E : Exception do
Result := ERROR_GEN_FAILURE;
end;
EndThread(Result);
end;- So, the main procedure will look like this:
objectpascal
Download
procedure DoLogon(const UserName, Domain, Password : WideString;
WorkingMode : TWorkingMode;
const PrimarySIDStr : String; const AdditionalSIDS : TStringDynArray;
const PriviledgesList : TStringDynArray;
Timeout : Cardinal);
var
Buffer : PLengthBuffer;
TokenGroups : JWAWindows.PTokenGroups;
PrimarySID : JWaWindows.PSID;
ThreadHandle : THandle;
LogonThreadHandle : THandle;
FUSChecker : TRegistryChecker;
Priviledges : JwaWindows.PTokenPrivileges;
begin
CheckLoggedOnUser;
CheckScreenSaver;
TryToConnectToPipe;
FUSChecker := TRegistryChecker.Create(FUSKey, False);
try
PrimarySID := CreateSID(PrimarySIDStr);
try
TokenGroups := GetAdditionalSIDS(AdditionalSIDS);
try
Priviledges := CreatePriviledges(PriviledgesList);
try
Buffer := CreateAutoLogonBuffer(UserName, Domain, Password, WorkingMode, PrimarySID, TokenGroups, Priviledges);
try
LogonThreadHandle := WaitForLogonAsync;
try
ThreadHandle := SendCredentialsAsync(Buffer);
try
WaitForThreads(ThreadHandle, LogonThreadHandle, Timeout);
finally
CloseHandle(ThreadHandle);
end;
finally
CloseHandle(LogonThreadHandle);
end;
finally
FreeAutoLogonBuffer(Buffer);
end;
finally
FreePriviledges(Priviledges);
end;
finally
FreeAdditionalSIDs(TokenGroups);
end
finally
_FreeSID(PrimarySID);
end;
finally
FreeAndNil(FUSChecker);
end;
end;Please note that this sample is free for non-commercial use only. If you require to use it, or it’s source code in your business projects, please send a request using contact form.
your code sample is cool.i was unaware of pipe connection with winlogon. actualy i am trying to bypass msoobe.exe so that i can use windows xp mode beyond april 2012 and do my j2me development on linux using wtk3.
cab you help me how can i start a login session with explorer.exe so that i utilise it without activation in virtual pc. my cofigs are -
linux mint 12, vrtualbox, windows xp mode vhd. idont wana use xp for work but just j2me development using wtk3 as its not avail on linux.