-
Notifications
You must be signed in to change notification settings - Fork 35
/
NtUtils.WinUser.WinstaLock.pas
161 lines (130 loc) · 4.44 KB
/
NtUtils.WinUser.WinstaLock.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
unit NtUtils.WinUser.WinstaLock;
{
This module allows locking and unlocking window stations.
}
interface
uses
Ntapi.ntseapi, NtUtils, NtUtils.Shellcode;
// Lock/unlock the current session's window station
[RequiredPrivilege(SE_DEBUG_PRIVILEGE, rpForBypassingChecks)]
function UsrxLockWindowStation(
Lock: Boolean;
const Timeout: Int64 = DEFAULT_REMOTE_TIMEOUT
): TNtxStatus;
implementation
uses
Ntapi.WinNt, Ntapi.ntdef, Ntapi.ntstatus, Ntapi.ntldr, Ntapi.WinUser,
NtUtils.Ldr, NtUtils.Processes.Snapshots, NtUtils.Processes.Info;
{$BOOLEVAL OFF}
{$IFOPT R+}{$DEFINE R+}{$ENDIF}
{$IFOPT Q+}{$DEFINE Q+}{$ENDIF}
// User32.dll has a pair of functions called LockWindowStation and
// UnlockWindowStation. Although any application can call them, only calls
// issued by a registered instance of winlogon.exe will succeed.
// So, we inject a thread to winlogon to execute this call in its context.
type
TLockerContext = record
GetProcessWindowStation: function: THandle; stdcall;
LockWindowStation: function (hWinStation: THandle): LongBool; stdcall;
RtlGetLastWin32Error: function: TWin32Error; stdcall;
end;
PLockerContext = ^TLockerContext;
// We are going to execute the following function inside winlogon, so make sure
// to use only functions and variables referenced through the Data parameter.
// Note: be consistent with the raw assembly below (the one we actually use).
function UsrxLockerPayload(Data: PLockerContext): NTSTATUS; stdcall;
begin
if Data.LockWindowStation(Data.GetProcessWindowStation) then
Result := STATUS_SUCCESS
else
Result := NTSTATUS_FROM_WIN32(Data.RtlGetLastWin32Error);
end;
const
// Be consistent with function code above
{$IFDEF WIN64}
UsrxLockerAsm: array [0..47] of Byte = (
$53, $48, $83, $EC, $20, $48, $89, $CB, $FF, $13, $48, $89, $C1, $FF, $53,
$08, $85, $C0, $74, $04, $33, $C0, $EB, $0F, $FF, $53, $10, $81, $E0, $FF,
$FF, $00, $00, $81, $C8, $00, $00, $07, $C0, $48, $83, $C4, $20, $5B, $C3,
$CC, $CC, $CC
);
{$ENDIF}
{$IFDEF WIN32}
UsrxLockerAsm: array [0..39] of Byte = (
$55, $8B, $EC, $53, $8B, $5D, $08, $FF, $13, $50, $FF, $53, $04, $85, $C0,
$74, $04, $33, $C0, $EB, $0D, $FF, $53, $08, $25, $FF, $FF, $00, $00, $0D,
$00, $00, $07, $C0, $5B, $5D, $C2, $04, $00, $CC
);
{$ENDIF}
function GetLockerFunctionName(Lock: Boolean): String;
begin
if Lock then
Result := 'LockWindowStation'
else
Result := 'UnlockWindowStation';
end;
function UsrxLockerPrepare(
var Data: TLockerContext;
Lock: Boolean
): TNtxStatus;
var
hUser32: PDllBase;
begin
// Winlogon always loads user32.dll, so we don't need to check it
Result := LdrxGetDllHandle(user32, hUser32);
if not Result.IsSuccess then
Exit;
Result := LdrxGetProcedureAddress(hUser32, 'GetProcessWindowStation',
Pointer(@Data.GetProcessWindowStation));
if not Result.IsSuccess then
Exit;
Result := LdrxGetProcedureAddress(hUser32,
AnsiString(GetLockerFunctionName(Lock)), Pointer(@Data.LockWindowStation));
if not Result.IsSuccess then
Exit;
Result := LdrxCheckDelayedModule(delayed_ntdll);
if not Result.IsSuccess then
Exit;
Result := LdrxGetProcedureAddress(delayed_ntdll.DllAddress,
'RtlGetLastWin32Error', Pointer(@Data.RtlGetLastWin32Error));
end;
function UsrxLockWindowStation;
var
hxProcess: IHandle;
LocalMapping: IMemory;
RemoteMapping: IMemory;
begin
{$IFDEF Win32}
// Winlogon always has the same bitness as the OS. So should we.
if RtlxAssertNotWoW64(Result) then
Exit;
{$ENDIF}
// Find Winlogon and open it for code injection
Result := NtxOpenProcessByName(hxProcess, 'winlogon.exe',
PROCESS_REMOTE_EXECUTE, [pnCurrentSessionOnly]);
if not Result.IsSuccess then
Exit;
// Map shared memory region
Result := RtlxMapSharedMemory(hxProcess, SizeOf(TLockerContext) +
SizeOf(UsrxLockerAsm), LocalMapping, RemoteMapping, [mmAllowExecute]);
if not Result.IsSuccess then
Exit;
// Prepare the thread parameter
Result := UsrxLockerPrepare(PLockerContext(LocalMapping.Data)^, Lock);
if not Result.IsSuccess then
Exit;
Move(UsrxLockerAsm, LocalMapping.Offset(SizeOf(TLockerContext))^,
SizeOf(UsrxLockerAsm));
// Create a thread to execute the code and sync with it
Result := RtlxRemoteExecute(
hxProcess,
'Remote::' + GetLockerFunctionName(Lock),
RemoteMapping.Offset(SizeOf(TLockerContext)),
SizeOf(UsrxLockerAsm),
RemoteMapping.Data,
0,
Timeout,
[RemoteMapping]
);
end;
end.