Первая часть блога по исследованию сервиса UI0Detect в больше степени касается таких операционных систем, как Windows Vista и Windows 7.
Механизм переключения на Рабочий стол нулевой сессии в этих ОС одинаков (за исключением некоторых неважных мелочей).
Но в Windows 8/8.1 и Windows 10 ядро было переписано в угоду "безопасности". Коснулись эти изменения и сервиса Обнаружение интерактивных служб.
Немного освежим память и посмотрим как UI0Detect работает в Vista и 7.
Как мы знаем, механизм переключения в сессию 0 перекочевал из подпрограммы Winlogon в подпрограмму Wininit.
В точке входа Wininit (функция WinMain) определены две internal функции RegisterSession0ViewerWindowHookDll и UnRegisterSession0ViewerWindowHookDll (вторая функция не представляет особого интересна и рассматриваться не будет).
Описание кода функции RegisterSession0ViewerWindowHookDll можете найти в первой части, но для наглядности я приведу её прототип
C++ | 1
2
3
| DWORD RegisterSession0ViewerWindowHookDll(
VOID
); |
|
Как видно, функция пустая и не принимает никаких аргументов.
Суть в том, что в Vista и 7 инструмент перехода в сессию 0 находился прям в Wininit.exe и не требовалось никаких дополнительных действий чтобы его задействовать.
Но в Microsoft посчитали, что это небезопасно и решили убрать функции RegisterSession0ViewerWindowHookDll и UnRegisterSession0ViewerWindowHookDll из ядра системы. Что же они сделали?
Гуляя в дизассемблере по Wininit.exe можно наткнуться на массивную функцию PrimaryTerminalAndHookWorker

По косвенным признакам и вспоминая из первой части, что для перехода на рабочий стол нужен дескриптор этого рабочего стола (s_hdeskApplication), который функция RegisterSession0ViewerWindowHookDll получала из другой internal функции CreatePrimaryTerminal
C++ | 1
| CreatePrimaryTerminal(s_hWinsta, lpMem, s_hdeskWinlogon, s_hdeskApplication, g_Context); |
|
сразу становится ясно, что функция PrimaryTerminalAndHookWorker оперирует нужными дескрипторами для перехода на другие рабочие столы других сессий.
Вот её прототип (от версии к версии ОС функция постоянно меняется, я приведу код для Windows 10 build 1709)
C++ | 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
| __int64 g_Context;
unsigned short g_usState = 5;
DWORD Period = 6;
__int16 v57;
DWORD PrimaryTerminalAndHookWorker(
&s_hWinsta,
&s_hdeskWinlogon,
&s_hdeskApplication,
&g_Context,
&g_usState,
Period,
&v57,
g_pSidSystem,
g_pSidLocalService,
g_pSidLocal,
g_pSidWorld,
g_pSidAdmin,
g_pSidPowerUser,
g_pSidCreator,
g_pSidRestricted,
g_pSidInteractive,
g_pSidService,
g_pSidWindowManager,
g_pSidFontDriverHost,
g_pSidAnyPackage,
g_pSidAnyRestrictedPackage
); |
|
Но функция PrimaryTerminalAndHookWorker не является внутренней (internal), она экспортируемая.
И экспортируется она из библиотеки wininitext.dll (wininitext.dll появилась именно в Windows 8 и последующих ОС, в Windows 7 и предыдущих ОС её не было).
Посмотрим на мнемонику функции PrimaryTerminalAndHookWorker в дизассемблере (код для Windows 10 build 1709)
C++ | 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
| __int64 __fastcall PrimaryTerminalAndHookWorker(__int64 a1, __int64 a2, HDESK *a3, __int64 a4, _WORD *a5, __int16 a6, _WORD *a7, __int64 a8, __int64 a9, __int64 a10, __int64 a11, __int64 a12, __int64 a13, __int64 a14, __int64 a15, __int64 a16, __int64 a17, __int64 a18, __int64 a19, __int64 a20, __int64 a21)
{
bool v21; // zf
__int64 result; // rax
unsigned int v27; // esi
HANDLE v28; // rax
LPVOID lpMem[5]; // [rsp+30h] [rbp-28h] BYREF
v21 = dword_18000970C == 0;
lpMem[0] = 0i64;
*a7 = 0;
if ( v21 )
{
dword_180009710 = GetRespecializeFlag();
dword_180009708 = GetSetupType();
dword_18000970C = 1;
if ( WPP_GLOBAL_Control != &WPP_GLOBAL_Control
&& (*((_BYTE *)WPP_GLOBAL_Control + 28) & 1) != 0
&& *((_BYTE *)WPP_GLOBAL_Control + 25) >= 4u )
{
WPP_SF_dd(*((_QWORD *)WPP_GLOBAL_Control + 2));
}
}
g_pSidSystem = a8;
g_pSidLocalService = a9;
g_pSidLocal = a10;
g_pSidWorld = a11;
g_pSidAdmin = a12;
g_pSidPowerUser = a13;
g_pSidCreator = a14;
g_pSidRestricted = a15;
g_pSidInteractive = a16;
g_pSidService = a17;
g_pSidWindowManager = a18;
g_pSidFontDriverHost = a19;
g_pSidAnyPackage = a20;
g_pSidAnyRestrictedPackage = a21;
result = CreatePrimaryTerminal(a1, lpMem, a2, a3, a4);
v27 = result;
if ( (_DWORD)result )
{
*a7 = 1;
}
else
{
*a5 = a6;
v28 = GetProcessHeap();
HeapFree(v28, 0, lpMem[0]);
UpdatePerUserSystemParameters(0i64);
if ( dword_180009708 || dword_180009710 != v27 )
{
result = v27;
}
else
{
result = RegisterSession0ViewerWindowHookDll(*a3); // вот она и попалась!!!
if ( (_DWORD)result )
*a7 = 2;
}
}
return result;
} |
|
И что же мы видим - функция RegisterSession0ViewerWindowHookDll перекочевала в функцию PrimaryTerminalAndHookWorker (библиотеки wininitext.dll) и вызывается ядром в подпрограмме Wininit.exe (функция UnRegisterSession0ViewerWindowHookDll теперь также располагается в библиотеке wininitext.dll).
При этом изменился её прототип
C++ | 1
2
3
| DWORD RegisterSession0ViewerWindowHookDll(
HDESK s_hdeskApplication
); |
|
RegisterSession0ViewerWindowHookDll принимает один аргумент - дескриптор рабочего стола, который далее передаётся в функции SetThreadDesktop и SwitchDesktop.
Есть ещё один важный момент - чтобы механизм перехода в Windows 8/8.1 и Windows 10 заработал, нужно изменить в реестре значение NoInteractiveServices на ноль (0), которое находится по следующему пути
Цитата:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Windows
по умолчанию значение NoInteractiveServices установлено в единицу (1).
Теперь я приведу мнемонику функции PrimaryTerminalAndHookWorker из ОС Windows 10 build 2009 (20H2)
C++ | 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
| __int64 __fastcall PrimaryTerminalAndHookWorker(__int64 a1, __int64 a2, __int64 a3, __int64 a4, _WORD *a5, __int16 a6, _WORD *a7, __int64 a8, __int64 a9, __int64 a10, __int64 a11, __int64 a12, __int64 a13, __int64 a14, __int64 a15, __int64 a16, __int64 a17, __int64 a18, __int64 a19, __int64 a20, __int64 a21)
{
bool v21; // zf
__int64 result; // rax
HANDLE v27; // rax
LPVOID lpMem[5]; // [rsp+30h] [rbp-28h] BYREF
v21 = dword_180009778 == 0;
lpMem[0] = 0i64;
*a7 = 0;
if ( v21 )
{
dword_18000977C = GetRespecializeFlag();
dword_180009774 = GetSetupType();
dword_180009778 = 1;
if ( WPP_GLOBAL_Control != &WPP_GLOBAL_Control
&& (*((_BYTE *)WPP_GLOBAL_Control + 28) & 1) != 0
&& *((_BYTE *)WPP_GLOBAL_Control + 25) >= 4u )
{
WPP_SF_dd(*((_QWORD *)WPP_GLOBAL_Control + 2));
}
}
g_pSidSystem = a8;
g_pSidLocalService = a9;
g_pSidLocal = a10;
g_pSidWorld = a11;
g_pSidAdmin = a12;
g_pSidPowerUser = a13;
g_pSidCreator = a14;
g_pSidRestricted = a15;
g_pSidInteractive = a16;
g_pSidService = a17;
g_pSidWindowManager = a18;
g_pSidFontDriverHost = a19;
g_pSidAnyPackage = a20;
g_pSidAnyRestrictedPackage = a21;
result = CreatePrimaryTerminal(a1, lpMem, a2, a3, a4);
if ( (_DWORD)result )
{
*a7 = 1;
}
else
{
*a5 = a6;
v27 = GetProcessHeap();
HeapFree(v27, 0, lpMem[0]);
UpdatePerUserSystemParameters(0i64);
result = 0i64;
}
return result;
} |
|
Как можно видеть функция RegisterSession0ViewerWindowHookDll просто отсутствует в коде PrimaryTerminalAndHookWorker (UnRegisterSession0ViewerWindowHookDll также удалена из библиотеки wininitext.dll).
На данный момент я ищу способ вернуть функциональность RegisterSession0ViewerWindowHookDll и Microsoft немного способствует этому (хукать библиотеку всё же проще, чем защищенный процесс).
И ещё один важный момент. В Windows 10 Microsoft переписала драйвер клавиатуры и мыши в сессии 0.
Перейдя в сессию 0 у вас не будет работать клава-мышь!
Они переписали драйвер win32kfull.sys, точнее 2 функции - RawInputThread (отвечающую за ввод клавиатуры) и xxxDesktopThread (отвечающую за ввод мыши).
Утрированно, в коде это выглядит следующим образом:
в Windows 8/8.1
C++ | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| mouseInputThread()
{
LODWORD(Object) = 0;
RegisterHotKey(struct tagWND *)1, (ULONG_PTR)Object);
SetDebugHotKeys();
SetWinlogonHotKeys();
SetWindowArrangementHotKeys();
SetPenHotKeys();
CreatePointerDeviceProcessEvents();
v10 = (struct _KEVENT *)v57[8];
EnterCrit(0, 1);
gpkeRITEvent = 0;
ObfDereferenceObject((PVOID)v57[8]);
UserSessionSwitchLeaveCrit();
} |
|
в Windows 10
C++ | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| mouseInputThread()
{
if (gSessionId != gServiceSessionId) // if (gSessionId != 0)
{
LODWORD(Object) = 0;
RegisterHotKey((struct tagWND *)1, (ULONG_PTR)Object);
SetDebugHotKeys();
SetWinlogonHotKeys();
SetWindowArrangementHotKeys();
SetPenHotKeys();
CreatePointerDeviceProcessEvents();
v10 = (struct _KEVENT *)v57[8];
EnterCrit(0, 1);
gpkeRITEvent = 0;
ObfDereferenceObject((PVOID)v57[8]);
UserSessionSwitchLeaveCrit();
}
} |
|
Так что переходя в сессию 0 в Windows 10 не забудьте поставить таймер на возврат, ну или напишите свой дров для клава-мыши  |