Форум программистов, компьютерный форум, киберфорум
_lunar_
Войти
Регистрация
Восстановить пароль
Оценить эту запись

Исследование сервиса "Обнаружение интерактивных служб" (UI0Detect)

Запись от _lunar_ размещена 06.11.2020 в 12:15
Обновил(-а) _lunar_ 18.11.2020 в 10:58

После насквозь "дырявой" Windows XP в Microsoft прикинули расклад и задались вопросом - как же изолировать систему от пользователя?
Ведь в XP пользователь работал в той же среде, что и ядро операционной системы.
Поэтому с выходом Windows Vista компания представила новый механизм изоляции сессии, в которой загружается операционная система.

Как же это работает?
После того, как система получает управление от BIOS (UEFI) winload.efi, происходит загрузка ядра ОС, процесс которой называется winload.exe (OS Loader).
Сперва система создаёт сессию, в окружении которой создаётся рабочая станция. Затем, внутри рабочей станции, создаются рабочие столы.
Рабочие станции могут быть интерактивные (к примеру WinSta0), т.е. имеют возможность оконного взаимодействия.
И могут быть не интерактивные (например Service-0x0-3e7$ - сервисная служба NT AUTHORITY\СИСТЕМА), без каких-либо оконных процедур.
Взаимодействие со службами, запущенными на таких рабочих станциях, происходит путём передачи данных через каналы (пайпы, события и т.д.).
Рабочих столов также есть несколько видов:
- Default основной рабочий стол, куда загружается Проводник системы (explorer.exe)
- Winlogon, рабочий стол приветствия входа в систему (запрос логина и пароля)
- Disconnect малоизученный рабочий стол (процесс создаётся в нём, но где он отображается, мне найти пока не удалось)
- кроме того есть ещё один рабочий стол - Secure Desktop for UAC (название не точное).
Обнаружить его также не удалось, но факт его присутствия на лицо: при запросе админских прав включается окно UAC, в этот момент делается скриншот фона рабочего стола и происходит затемнение.
Возможно Disconnect это и есть Secure Desktop for UAC.
Данная сессия имеет нумерацию 0 (Session Id 0). В этой сессии работают все минимальные системные процессы (Protected) и ProtectedLite процессы - System, Registry, wininit.exe, services.exe, smss.exe, csrss.exe, svchost.exe.
Более подробно о системных процессах читайте у Марка Руссиновича - Внутреннее устройство Windows, 7-е издание, Глава 3.
После того, как ядро полностью загружено, создаётся новая сессия с номером 1 (Session Id 1).
Вот это уже пользовательская сессия, в которой и работает первый залогинившийся юзер (winlogon.exe).

Сессия 0 изолирована от сессии 1, но взаимодействие между ними всё же происходит. Рассмотрим этот механизм взаимодействия.
Сервис "Обнаружение интерактивных служб" по умолчанию находится в режиме остановлен.
Но если служба не работает, как же она перехватывает окна, появляющиеся в сеансе 0?
Секрет кроется в малоизвестной библиотеке Wls0wndh.dll (Session0 Viewer Window Hook DLL).
Имя самой библиотеки можно расшифровать как - WinLogon Session 0 Window Hook.
Winlogon.exe на протяжении долгого времени накапливал все больше инструментария как загрузчик пользовательского режима, выполняя такие задачи, как настройка процесса локальной безопасности (LSASS), SCM, реестра, запуск процесса, отвечающего за извлечение файлов дампа из файла подкачки, и многое другое.
Теперь эту роль выполняет Wininit.exe, загружая в том числе библиотеку Wls0wndh.dll.

Одной из новых задач Wininit.exe является регистрация WinLogon Session 0 Window Hook, путем вызова функцией RegisterSession0ViewerWindowHookDll, которая является частью точки входа WinMain процесса Wininit.exe
Нажмите на изображение для увеличения
Название: 1.png
Просмотров: 30
Размер:	128.5 Кб
ID:	6564
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
DWORD ResilientSwitchDesktop(HDESK hDesktop)
{
    DWORD dwMilliseconds = NULL;
    DWORD deErr = NO_ERROR;
 
    while (!SwitchDesktop(hDesktop)) {
        deErr = GetLastError();
 
        if (dwMilliseconds >= 1000)
            return deErr;
 
        dwMilliseconds += 100;
        Sleep(dwMilliseconds);
    }
 
    return EXIT_SUCCESS;
}
 
typedef LRESULT(CALLBACK* _Session0ViewerWindowProcHook)(
    int nCode,
    WPARAM wParam,
    LPARAM lParam
    );
_Session0ViewerWindowProcHook Session0ViewerWindowProcHook;
 
DWORD RegisterSession0ViewerWindowHookDll(VOID)
{
    int Data = NULL;
    DWORD cbData = sizeof(DWORD);
    DWORD Type = REG_NONE;
    HKEY hKey = nullptr;
 
    HDESK hdeskApplication = OpenDesktop(_TEXT("Default"), DF_ALLOWOTHERACCOUNTHOOK, FALSE, DESKTOP_SWITCHDESKTOP);
 
    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _TEXT("SYSTEM\\CurrentControlSet\\Control\\WinInit"), REG_OPTION_RESERVED, KEY_QUERY_VALUE, &hKey)
        || (RegQueryValueEx(hKey, _TEXT("DisableS0Viewer"), nullptr, &Type, (LPBYTE)&Data, &cbData), RegCloseKey(hKey), !Data)) {
        HMODULE hModule = LoadLibrary(_TEXT("WlS0WndH"));
        if (hModule) {
            Session0ViewerWindowProcHook = (_Session0ViewerWindowProcHook)GetProcAddress(hModule, "Session0ViewerWindowProcHook");
            if (Session0ViewerWindowProcHook) {
                if (SetThreadDesktop(hdeskApplication)) {
                    SetWindowsHookEx(WH_CALLWNDPROC, Session0ViewerWindowProcHook, hModule, NULL);
                    ResilientSwitchDesktop(hdeskApplication);
                }
            }
        }
 
        if (hModule)
            FreeLibrary(hModule);
    }
 
    return EXIT_SUCCESS;
}
Если недокументированное значение DisableS0Viewer отсутствует в разделе реестра HKLM\System\CurrentControlSet\Control\WinInit, он пытается загрузить Wls0wndh.dll, а затем выполняет запрос функции Session0ViewerWindowProcHook внутри Wls0wndh.dll
Если все прошло успешно, он переключаетcя на рабочий стол Default и регистрирует процедуру как обработчик окна с помощью SetWindowsHookEx.
Теперь у нас есть механизм, позволяющий понять, как сервис "Обнаружение интерактивных служб" может автоматически запускаться - за это отвечает перехват обратного вызова (callback hook).
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
typedef BOOLEAN(NTAPI* _RtlTimeToSecondsSince1980)(
    _In_ PLARGE_INTEGER Time,
    _Out_ PULONG ElapsedSeconds
    );
_RtlTimeToSecondsSince1980 RtlTimeToSecondsSince1980;
 
LRESULT WINAPI Session0ViewerWindowProcHook(int nCode, WPARAM wParam, LPARAM lParam)
{
    HMODULE hModule = GetModuleHandle(TEXT("ntdll"));
 
    if (hModule)
        RtlTimeToSecondsSince1980 = (_RtlTimeToSecondsSince1980)GetProcAddress(hModule, "RtlTimeToSecondsSince1980");
 
    BOOL UserWorkItem = FALSE;
    DWORD LastSecondsStart = NULL;
    ULONG SecondsSince1980 = NULL;
    FILETIME SystemTimeAsFileTime{};
 
    if (!nCode && *(PDWORD)(lParam + 16) == 24) {
        if (*(PDWORD64)(lParam + 8)) {
            if (!FindWindow(TEXT("$$$UI0Background"), nullptr) && !GetParent(*(HWND*)(lParam + 24)) && !InterlockedCompareExchange(&g_lWorkItemGuard, 1, 0)) {
                GetSystemTimeAsFileTime(&SystemTimeAsFileTime);
                if (RtlTimeToSecondsSince1980((PLARGE_INTEGER)&SystemTimeAsFileTime, &SecondsSince1980)) {
                    if (SecondsSince1980 > g_dwElapsedSinceLastStart && SecondsSince1980 - g_dwElapsedSinceLastStart >= 0x12C) {
                        UserWorkItem = QueueUserWorkItem(StartUI0DetectThreadProc, nullptr, WT_EXECUTEDEFAULT);
                        LastSecondsStart = g_dwElapsedSinceLastStart;
                        if (UserWorkItem)
                            LastSecondsStart = SecondsSince1980;
                        g_dwElapsedSinceLastStart = LastSecondsStart;
                    }
                }
            }
        }
    }
 
    InterlockedCompareExchange(&g_lWorkItemGuard, 0, 1);
 
    return CallNextHookEx(nullptr, nCode, wParam, lParam);
}
Внутри Session0ViewerWindowProcHook довольно простая логика: выполняется проверка является ли вызов оконным сообщением WM_SHOWWINDOW, которое сигнализирует о появлении нового окна на рабочем столе. Если это так, DLL сначала проверяет имя окна $$$UI0Background и отключается, если это окно уже существует.
Вторая проверка - это сколько времени прошло с момента последней попытки запустить службу. Если прошло менее 300 (0x12C) секунд, DLL не запустит службу снова.
Наконец, если все проверки выполнены успешно, запрос ставится в очередь с использованием API пула потоков с обратным вызовом StartUI0DetectThreadProc в качестве начального адреса потока
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DWORD WINAPI StartUI0DetectThreadProc(PVOID Parameter)
{
    SC_HANDLE hService = nullptr;
 
    SC_HANDLE hSCManager = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
 
    if (!hSCManager || (hService = OpenService(hSCManager, TEXT("UI0Detect"), SERVICE_START), hService == nullptr) || !StartService(hService, NULL, nullptr))
        return GetLastError();
 
    if (hService)
        CloseServiceHandle(hService);
 
    if (hSCManager)
        CloseServiceHandle(hSCManager);
 
    return EXIT_SUCCESS;
}
Эта процедура выполняет очень простую задачу: открывает дескриптор для SCM, затем открывает дескриптор для службы UI0Detect и вызвать StartService, чтобы дать команду SCM запустить ее.
На этом завершается вся работа, выполняемая Wls0wndh.dll.
DLL просто подтверждает перехваты обратных вызовов оконных процедур, когда они поступают.

Всё сервис запущен, теперь мы обратим внимание на отвечающий за нее модуль - UI0Detect.exe.
Поскольку UI0Detect.exe обрабатывает как пользовательскую (клиентскую), так и служебную (системную) часть процесса, он работает в двух режимах. В первом режиме он ведет себя как типичная служба Windows и регистрируется в SCM. Во втором режиме служба обнаруживает, что была запущена в сессии входа в систему, и делает обратный переход в режим клиента.

Глобальные переменные сервиса UI0Detect
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
HINSTANCE g_hInstance = nullptr;
HWND g_hWndS0Dlg = nullptr;
LPCVOID g_pxSharedInfo = nullptr;
HANDLE g_hSharedInfo = nullptr, g_hFullSharedInfo = nullptr, g_hEventSource = nullptr;
LPVOID g_TopLevelWindowList = nullptr;
SERVICE_STATUS_HANDLE hssService = nullptr;
 
GUID g_guidService{}, g_hSessionService{}, g_guidClient{};
FILETIME g_qwFTAtStart{};
LASTINPUTINFO g_xTCAtLastInput{};
SERVICE_STATUS ssService{};
 
BOOL g_fSqmIsOptedIn = NULL;
__int64 qword_140008620 = NULL;
const WCHAR word_1400059D8 = NULL;
__int16 word_140008752 = NULL;
DWORD g_dwTopLevelWindowCount = NULL, g_dwTCAtLastUserPing = NULL;
WCHAR near* g_EmptyString = nullptr;
WCHAR g_wszPath = NULL;
WCHAR g_wszWindowsPath[68]{}, g_wszProgramFilesPath[72]{};
Служба UI0Detect продолжает вызывать StartServiceCtrlDispatcher
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
BOOL IsSystem(VOID)
{
    BOOL CheckToken = FALSE, IsSidInToken = FALSE, IsMember = FALSE;
    PSID SidToCheck =  nullptr;
    SID_IDENTIFIER_AUTHORITY IdentifierAuthority{};
 
    *(PWORD)&IdentifierAuthority.Value[4] = 1280;
 
    if (AllocateAndInitializeSid(&IdentifierAuthority, 1, 0x12, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &SidToCheck) >= 0) {
        CheckToken = CheckTokenMembership(nullptr, SidToCheck, &IsMember);
        
        IsSidInToken = IsMember;
        if (!CheckToken)
            IsSidInToken = FALSE;
        IsMember = IsSidInToken;
    }
 
    if (SidToCheck)
        FreeSid(SidToCheck);
 
    return IsMember;
}
}
 
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nShowCmd)
{
    int v6 = 1;
    SERVICE_TABLE_ENTRY ServiceStartTable{};
    __int64 v9 = NULL;
    __int64 v10 = NULL;
    FILETIME SystemTimeAsFileTime{};
 
    HeapSetInformation(nullptr, HeapEnableTerminationOnCorruption, nullptr, NULL);
    g_hInstance = hInstance;
    g_fSqmIsOptedIn = WinSqmIsOptedIn();
    GetSystemTimeAsFileTime(&g_qwFTAtStart);
    
    if (lpCmdLine && *lpCmdLine) {
        v6 = ClientsWinMain(lpCmdLine);
    }
 
    else {
        ServiceStartTable.lpServiceName = (LPWSTR)TEXT("UI0Detect");
        ServiceStartTable.lpServiceProc = ServiceStart;
 
        if (g_fSqmIsOptedIn) {
            g_hSessionService.Data1 = (ULONG)WinSqmStartSession(&g_guidService, 984067, 0);
            WinSqmSetString(&g_hSessionService.Data1, 1411, TEXT("Session 0 viewer service"));
        }
 
        if (IsSystem())
            StartServiceCtrlDispatcher(&ServiceStartTable);
 
        else
            v6 = -2;
 
        if (g_fSqmIsOptedIn) {
            GetSystemTimeAsFileTime(&SystemTimeAsFileTime);
 
            if (!((*(PSIZE_T)&SystemTimeAsFileTime - *(PSIZE_T)&g_qwFTAtStart) / 0x271000000000))
                WinSqmSetDWORD(&g_hSessionService.Data1, 1790, (UINT)((*(PSIZE_T)&SystemTimeAsFileTime - *(PSIZE_T)&g_qwFTAtStart) / 0x2710));
            
            WinSqmEndSession(&g_hSessionService.Data1);
            *(PSIZE_T)&g_hSessionService.Data1 = NULL;
        }
    }
 
    if (g_pxSharedInfo) {
        UnmapViewOfFile(g_pxSharedInfo);
        g_pxSharedInfo = nullptr;
    }
 
    if (g_hSharedInfo) {
        CloseHandle(g_hSharedInfo);
        g_hSharedInfo = nullptr;
    }
 
    if (g_hFullSharedInfo) {
        CloseHandle(g_hFullSharedInfo);
        g_hFullSharedInfo = nullptr;
    }
 
    return v6;
}
со специальной подпрограммой ServiceStart
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
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
VOID WINAPI ServiceCtrlHandler(DWORD dwControl)
{
    switch (dwControl)
    {
    case SERVICE_CONTROL_STOP:
        ssService.dwWin32ExitCode = NO_ERROR;
        ssService.dwCurrentState = SERVICE_STOP_PENDING;
        break;
 
    case SERVICE_CONTROL_PAUSE:
        ssService.dwCurrentState = SERVICE_PAUSED;
        ssService.dwControlsAccepted = SERVICE_CONTROL_NETBINDADD;
        break;
 
    case SERVICE_CONTROL_CONTINUE:
        ssService.dwCurrentState = SERVICE_RUNNING;
        ssService.dwControlsAccepted = SERVICE_CONTROL_NETBINDADD;
        break;
 
    case SERVICE_CONTROL_SHUTDOWN:
        ssService.dwWin32ExitCode = NO_ERROR;
        ssService.dwCurrentState = SERVICE_STOP_PENDING;
        break;
 
    default:
        break;
    }
 
    SetServiceStatus(hssService, &ssService);
 
    if (((dwControl - 1) & 0xFFFFFFFB) == 0) {
        WTSDisconnectSession(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, FALSE);
        
        if (g_hWndS0Dlg)
            SendMessage(g_hWndS0Dlg, 0x466, 2, NULL);
 
        HWND hWnd = FindWindow(TEXT("$$$UI0Background"), nullptr);
        
        if (hWnd)
            SendMessage(hWnd, WM_APP, NULL, NULL);
    }
}
 
VOID WINAPI ServiceStart(DWORD dwNumServicesArgs, LPWSTR* lpServiceArgVectors)
{
    DWORD nLengthNeeded = NULL;
    PWCHAR wszPath = nullptr;
    HWND hWnd = nullptr;
    __int64 v8[5]{};
    MSG Msg{};
    WCHAR v10 = NULL;
 
    ssService.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
    ssService.dwCurrentState = SERVICE_START_PENDING;
    ssService.dwControlsAccepted = SERVICE_CONTROL_NETBINDADD;
    ssService.dwWin32ExitCode = NO_ERROR;
    ssService.dwCheckPoint = NULL;
 
    SERVICE_STATUS_HANDLE hSvcStatus = RegisterServiceCtrlHandler(TEXT("UI0Detect"), ServiceCtrlHandler);
 
    hssService = hSvcStatus;
    
    if (hSvcStatus) {
        ssService.dwCurrentState = SERVICE_RUNNING;
        SetServiceStatus(hSvcStatus, &ssService);
        HWINSTA hWinsta = GetProcessWindowStation();
        HDESK hDesk = GetThreadDesktop(GetCurrentThreadId());
 
        if (hWinsta && hDesk && GetUserObjectInformation(hWinsta, UOI_NAME, &g_wszPath, 0x208, &nLengthNeeded)
            && (wszPath = &g_wszPath + (nLengthNeeded >> 1), *(wszPath - 1) = 92, GetUserObjectInformation(hDesk, UOI_NAME, wszPath, 520 - nLengthNeeded, &nLengthNeeded))
            && wcscmp(&g_wszPath, TEXT("WinSta0\\Default")))
        {
            ssService.dwWin32ExitCode = ERROR_INVALID_FUNCTION;
        }
 
        else {
            HeapSetInformation(nullptr, HeapEnableTerminationOnCorruption, nullptr, NULL);
            
            if (!SetupMainWindows(&v10, &hWnd))
            {
                v8[0] = (__int64)&S0DialogService::vftable;
                S0DialogBase::ShowDialog((S0DialogBase*)v8, (unsigned __int16*)0x3F2, (unsigned __int16*)0x3F3, 0i64);
                
                while ((GetMessage(&Msg, nullptr, RIM_INPUT, RIM_INPUT) + 1) > 1)
                    DispatchMessage(&Msg);
            }
        }
 
        if (v10)
            UnregisterClass(TEXT("$$$UI0Background"), g_hInstance);
        
        if (g_hEventSource)
            DeregisterEventSource(g_hEventSource);
        
        ssService.dwCurrentState = SERVICE_STOPPED;
        SetServiceStatus(hssService, &ssService);
    }
}
Служба сначала выполняет проверку, на предмет того, что она работает на правильной оконной станции и рабочем столе (WinSta0\\Default), а затем уведомляет SCM об успехе или неудаче.
После завершения взаимодействия с SCM она вызывает внутреннюю функцию SetupMainWindows, которая регистрирует класс $$$UI0Background и окно Shell0 Window. Поскольку предполагается, что это главное окно «оболочки», с которым пользователь будет взаимодействовать на рабочем столе службы, это окно также регистрируется как текущее окно оболочки и панели задач через SetShellWindow и SetTaskmanWindowAPI
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
typedef LRESULT(WINAPI* _SetTaskmanWindow)(
    HWND hWnd
    );
_SetTaskmanWindow SetTaskmanWindow;
 
typedef LRESULT(WINAPI* _SetShellWindow)(
    HWND hWnd
    );
_SetShellWindow SetShellWindow;
 
BOOL WINAPI SetupMainWindows(PWCHAR a1, HWND* phWnd)
{
    WCHAR v6;
    MINIMIZEDMETRICS pvParam{ sizeof(MINIMIZEDMETRICS) };
    int v13 = NULL;
 
    WNDCLASS WndClass{};
    WndClass.style = CS_NOCLOSE;
    WndClass.lpfnWndProc = (WNDPROC)BackgroundWndProc;
    WndClass.hInstance = g_hInstance;
    WndClass.cbWndExtra = 8;
    WndClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
    WndClass.hbrBackground = CreateSolidBrush(0xEEDAC8);
    WndClass.lpszClassName = TEXT("$$$UI0Background");
    
    v6 = RegisterClass(&WndClass);
    *a1 = v6;
    
    if (!v6)
        return GetLastError();
 
    HWND hWnd = CreateWindowEx(WS_EX_NOACTIVATE, TEXT("$$$UI0Background"), TEXT("Shell0 Window"), WS_POPUP | WS_CLIPCHILDREN,
        NULL, NULL, GetSystemMetrics(0), GetSystemMetrics(1), nullptr, nullptr, nullptr, nullptr);
    
    if ((*phWnd = hWnd) == nullptr)
        return GetLastError();
 
    ShowWindow(hWnd, SW_SHOW);
    SystemParametersInfo(SPI_GETMINIMIZEDMETRICS, sizeof(MINIMIZEDMETRICS), &pvParam, 0);
    v13 |= 8;
    SystemParametersInfo(SPI_SETMINIMIZEDMETRICS, sizeof(MINIMIZEDMETRICS), &pvParam, 0);
 
    HMODULE hModule = GetModuleHandle(TEXT("user32"));
    if (hModule) {
        SetTaskmanWindow = (_SetTaskmanWindow)GetProcAddress(hModule, "SetTaskmanWindow");
        SetShellWindow = (_SetShellWindow)GetProcAddress(hModule, "SetShellWindow");
    }
 
    SetTaskmanWindow(*phWnd);
    SetShellWindow(*phWnd);
 
    return TRUE;
}
Теперь обратим внимание на BackgroundWndProc, поскольку именно здесь будут выполняться основные задачи инициализации и поведения службы.
Как только приходит сообщение WM_CREATE, UI0Detect будет использовать API RegisterWindowMessage со специальным параметром SHELLHOOK, чтобы он мог получать определенные сообщения уведомления оболочки. Затем он инициализирует свой список окон верхнего уровня и регистрируется для получения уведомлений о создании новой сессии от службы терминалов. Наконец, он вызывает SetupSharedMemory для инициализации раздела, который он будет использовать для связи с процессами UI0Detect в клиентском режиме, и вызывает EnumWindows для перечисления всех окон на рабочем столе сессии 0.
Другое сообщение, которое получает этот обработчик окна - это WM_DESTROY, которое, как следует из его названия, отменяет регистрацию уведомления сессии, уничтожает списки окон и завершает работу.
Эта процедура также получает сообщения WM_WTSSESSION_CHANGE, которые она зарегистрировала для уведомлений о сеансе. Служба ожидает создания удаленных или локальных сеансов или отключений. Когда создается новый сеанс, он запрашивает разрешение нового виртуального экрана, чтобы знать, где и как отображать собственное окно. Эта функция существует для обнаружения фактического переключения на сеанс 0.
Помимо этих трех сообщений, основным «рабочим» сообщением для обработчика окна является WM_TIMER. Все события, которые мы видели до сих пор, вызывают SetTimer с другим параметром, чтобы сгенерировать какое-то уведомление. Эти уведомления затем анализируются оконной процедурой либо для возврата в сессию пользователя, либо для измерения того, был ли в последнее время какой-либо вход в сессию 0, а также для обработки операций создания и уничтожения динамического окна.
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
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
LRESULT CALLBACK BackgroundWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, HWND* lParam)
{
    HWND* v4 = lParam;
    __int64 v7 = NULL;
    PDWORD v8;
    int v9;
    int nHeight;
    int v11;
    int v12;
    int v13;
    PDWORD v14;
    int v15;
    WPARAM v16;
    WPARAM v17;
    UINT_PTR v18;
    UINT v19;
    DWORD v20;
    SIZE_T v21 = NULL;
    UINT v22;
    LPVOID* v23;
    LPVOID* v24;
    PDWORD v25;
    UINT dwNewLong;
    HWND v27;
    LASTINPUTINFO LastInputInfo{ sizeof(LASTINPUTINFO) };
    HWND* v31 = lParam;
 
    switch (uMsg)
    {
    case WM_CREATE:
    {
        dwNewLong = RegisterWindowMessage(TEXT("SHELLHOOK"));
 
        if (!dwNewLong)
            return -1;
 
        SetLastError(NO_ERROR);
 
        if (!SetWindowLongPtr(hWnd, 0, dwNewLong)) {
            if (GetLastError())
                return -1;
        }
 
        if (!RegisterShellHookWindow(hWnd))
            return -1;
 
        qword_140008620 = (__int64)&g_TopLevelWindowList;
        g_TopLevelWindowList = &g_TopLevelWindowList;
        g_dwTopLevelWindowCount = NULL;
 
        if (g_fSqmIsOptedIn)
            SetTimer(hWnd, 1, 0x5265C00, nullptr);
 
        WTSRegisterSessionNotification(hWnd, NOTIFY_FOR_THIS_SESSION);
        SetupSharedMemory();
        InitializeAllowedSQMPaths();
        EnumWindows(EnumWindowsCallbackProc, NULL);
        v27 = hWnd;
        
        if (!g_dwTopLevelWindowCount) {
            v18 = 2;
            v19 = 28800000;
        LABEL_63:
            SetTimer(v27, v18, v19, nullptr);
            return v7;
        }
 
        PostMessage(hWnd, WM_TIMER, 4, NULL);
        goto LABEL_61;
    }
 
    case WM_DESTROY:
    {
        v25 = (PDWORD)g_pxSharedInfo;
        
        if (g_pxSharedInfo) {
            *((PDWORD)g_pxSharedInfo + 1) |= 1;
            ++* v25;
        }
 
        WTSUnRegisterSessionNotification(hWnd);
        EmptyListOfTrackedWindow();
        PostQuitMessage(0);
 
        return v7;
    }
 
    case WM_TIMER:
    {
        switch (wParam)
        {
        case 1:
        {
            WinSqmEndSession(&g_hSessionService.Data1);
            *(PSIZE_T)&g_hSessionService.Data1 = (ULONG)WinSqmStartSession(&g_guidService, 984067, 0);
            WinSqmSetString(&g_hSessionService.Data1, 1411, TEXT("Session 0 viewer service"));
            
            return v7;
        }
 
        case 2:
        {
            goto LABEL_30;
        }
 
        case 3:
        {
            GetLastInputInfo(&LastInputInfo);
            
            if (LastInputInfo.dwTime == g_xTCAtLastInput.dwTime) {
                WTSDisconnectSession(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, FALSE);
                return v7;
            }
 
            g_xTCAtLastInput.dwTime = LastInputInfo.dwTime;
            v20 = GetTickCount() - g_xTCAtLastInput.dwTime;
            v19 = 60000;
 
            if (v20 <= 0xEA60)
                v19 = 60000 - v20;
            
            LastInputInfo.dwTime = v19;
            v18 = 3;
            goto LABEL_62;
        }
        }
 
        if (wParam != 4)
            return 1;
 
        if (GetTickCount() - g_dwTCAtLastUserPing < 0x7530)
            return v7;
 
        g_dwTCAtLastUserPing = GetTickCount();
        v22 = g_dwTopLevelWindowCount;
 
        if (!g_dwTopLevelWindowCount)
            goto LABEL_23;
 
        v23 = (LPVOID*)g_TopLevelWindowList;
 
        if (g_TopLevelWindowList != &g_TopLevelWindowList) {
            do {
                v24 = v23;
                v23 = (LPVOID*)*v23;
                if (!IsWindow((HWND)v24[2]))
                    OnTopLevelWindowDestruction((HWND)v24[2]);
            } while (v23 != &g_TopLevelWindowList);
            
            v22 = g_dwTopLevelWindowCount;
        }
 
        if (!v22) {
        LABEL_23:
            SendMessage(g_hWndS0Dlg, 0x46C, NULL, 1011);
            SendMessage(g_hWndS0Dlg, 0x46C, 3, 1010);
            v18 = 2;
            v19 = 28800000;
 
        LABEL_62:
            v27 = hWnd;
            goto LABEL_63;
        }
 
        CreateClients(v21);
 
    LABEL_61:
        v18 = 4;
        v19 = 300000;
        goto LABEL_62;
    }
 
    case WM_WTSSESSION_CHANGE:
    {
        if (!lParam) {
            if (((wParam - 1) & 0xFFFFFFFFFFFFFFFD) != 0) {
                if (((wParam - 2) & 0xFFFFFFFFFFFFFFFD) == 0) {
                    KillTimer(hWnd, 3);
                    v8 = (PDWORD)g_pxSharedInfo;
                    g_dwTCAtLastUserPing = 0;
                    
                    if (g_pxSharedInfo) {
                        v9 = *((PDWORD)g_pxSharedInfo + 1);
                        
                        if ((*((PBYTE)g_pxSharedInfo + 4) & 1) == 0) {
                            ++* (PDWORD)g_pxSharedInfo;
                            v8[1] = v9 & 0xFFFFFFFD;
                        }
                    }
                }
            }
 
            else {
                nHeight = GetSystemMetrics(79);
                v11 = GetSystemMetrics(78);
                v12 = GetSystemMetrics(77);
                v13 = GetSystemMetrics(76);
                MoveWindow(hWnd, v13, v12, v11, nHeight, TRUE);
                g_xTCAtLastInput.cbSize = sizeof(LASTINPUTINFO);
                GetLastInputInfo(&g_xTCAtLastInput);
                SetTimer(hWnd, 3, 0xEA60, nullptr);
                v14 = (PDWORD)g_pxSharedInfo;
                v4 = v31;
 
                if (g_pxSharedInfo) {
                    v15 = *((PDWORD)g_pxSharedInfo + 1);
                   
                    if ((*((PBYTE)g_pxSharedInfo + 4) & 1) == 0) {
                        ++* (PDWORD)g_pxSharedInfo;
                        v14[1] = v15 | 2;
                    }
                }
            }
        }
        break;
    }
 
    case WM_APP:
    {
    LABEL_30:
        DestroyWindow(hWnd);
        return v7;
    }
    }
 
    if (uMsg != (UINT)GetWindowLongPtr(hWnd, 0) || !v4)
        return DefWindowProc(hWnd, uMsg, wParam, (LPARAM)v4);
 
    v16 = wParam - 1;
 
    if (v16) {
        v17 = v16 - 1;
        if (v17) {
            if (v17 == 3)
                PostMessage(*v4, WM_SYSCOMMAND, 0xF120, NULL);
            return v7;
        }
 
        v7 = OnTopLevelWindowDestruction((HWND)v4);
        
        if (g_dwTopLevelWindowCount)
            return v7;
       
        goto LABEL_23;
    }
 
    v7 = OnTopLevelWindowCreation(hWnd, (HWND)v4);
    
    if (g_dwTopLevelWindowCount)
        KillTimer(hWnd, 2);
 
    return v7;
}
Посмотрим, что происходит во время этих двух операций. Первое действие по созданию окна происходит во время вышеупомянутого вызова EnumWindows, который генерирует начальный список окон. Функция ищет только окна без родителя, то есть окна верхнего уровня, и вызывает OnTopLevelWindowCreation для анализа окна. Этот анализ состоит из запроса владельца окна, получения его PID и последующего запроса информации модуля об этом процессе. Информация о версии также извлекается, чтобы получить название компании. Наконец, окно добавляется в список отслеживаемых окон, а глобальная переменная счетчика увеличивается.
Информация об этом модуле и версии попадает в раздел общей памяти. Он создается SetupSharedMemory и фактически генерирует два дескриптора. Первый дескриптор создается с доступом SECTION_MAP_WRITE и сохраняется внутри для доступа на запись. Затем дескриптор дублируется с доступом SECTION_MAP_READ, и этот дескриптор будет использоваться клиентом.
Структура UI0_SHARED_INFO содержит несколько информационных полей: некоторые флаги, количество окон, обнаруженных на рабочем столе сессии 0, и, что более важно, информацию о модуле, окне и версии для каждого из этих окон. Функция OnTopLevelWindowCreation использует эту структуру для сохранения всей обнаруженной информации, а OnTopLevelWindowDestroy делает уничтожает эту информацию.

Одно из связанных с таймером сообщений, за которое отвечает оконная процедура, будет проверять количество окон верхнего уровня (больше ли оно чем 0). Если это так, то выполняется вызов CreateClients вместе с дескриптором для раздела с отображенной памятью только для чтения
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
VOID WINAPI CreateClients(DWORD a1)
{
    HANDLE v0;
    int v1;
    BYTE v2;
    int v3;
    ULONG v4;
    int v5;
    ULONG SessionId[2]{};
    int v7;
    int v8;
 
    v0 = g_hSharedInfo;
    *(PDWORD)SessionId = 0;
    v7 = 0;
    v8 = 0;
    v1 = 5;
    v5 = 5;
    v2 = WinStationGetSessionIds(0, SessionId, &v5);
    v3 = v2;
    
    if (v2 || GetLastError() != ERROR_MORE_DATA)
        v1 = v5;
   
    else
        v3 = 1;
    
    if (v3) {
        while (v1)
        {
            v5 = v1 - 1;
            v4 = SessionId[v1 - 1];
            if (WTSGetServiceSessionId() != v4)
            {
                CreateClient(v4, (DWORD)v0);
                Sleep(100);
            }
            v1 = v5;
        }
    }
}
Эта процедура вызывает API WinStationGetSessionIds и перечисляет каждый идентификатор, вызывая процедуру CreateClient
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
DWORD WINAPI CreateClient(ULONG SessionId, int a2)
{
    DWORD dwErrCode = ERROR_NO_TOKEN;
    BOOL IsImpersonate = FALSE;
    HANDLE phToken = nullptr;
    PROCESS_INFORMATION ProcessInformation{};
    STARTUPINFOW StartupInfo{ sizeof(STARTUPINFOW) };
    WCHAR CommandLine[56];
 
    memset(CommandLine, 0, 0x64);
    memset(&StartupInfo.lpReserved, 0, 0x60);
 
    if (SessionId != -1) {
        if (WTSQueryUserToken(SessionId, &phToken) && ImpersonateLoggedOnUser(phToken)) {
            IsImpersonate = TRUE;
            StringCchPrintfW(CommandLine, 0x32, L"UI0Detect.exe %Iu", a2);
            StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
            StartupInfo.wShowWindow = SW_SHOWMINNOACTIVE;
 
            dwErrCode = CreateProcessAsUserW(phToken, nullptr, CommandLine, nullptr, nullptr, TRUE, NULL, nullptr, nullptr, &StartupInfo, &ProcessInformation);
 
            if (dwErrCode) {
                CloseHandle(ProcessInformation.hThread);
                CloseHandle(ProcessInformation.hProcess);
            }
        }
 
        else {
            dwErrCode = GetLastError();
        }
    }
 
    if (IsImpersonate)
        RevertToSelf();
 
    if (phToken)
        CloseHandle(phToken);
 
    return dwErrCode;
}
Наконец, то, что происходит в CreateClient, довольно просто: API-интерфейсы WTSQueryUserToken и ImpersonateLoggedOnUser используются для получения токена входа в систему олицетворения, соответствующего пользователю по указанному идентификатору сессии, и создается командная строка для UI0Detect, которая содержит дескриптор чтения только раздела с отображением памяти. В конечном итоге вызывается CreateProcessAsUser, в результате чего для этого идентификатора сеанса создается процесс UI0Detect, выполняющийся с учетными данными пользователя.

А теперь главное: UI0Detect работает начиная с Windows Vista и заканчивая Windows 10 1709 включительно.
Начиная с Windows 10 1803 UI0Detect и всё с ним связанное было жестко вырезано Microsoft из системы, поэтому инструмента перехода в сессию 0 у пользователей больше нет.

Если только использовать сторонние (платные) программы, на подобии FireDaemon Zero.

Вот так вкратце работает данный сервис
Кода конечно же больше, чем приведенные мною несколько функций.
Как только я его приведу в читабельный вид, прикреплю исходники WlS0WndH.dll и UI0Detect.exe в этом блоге.

Инструмент, который способен создавать процессы в сессии 0, и к которому я планирую прикрутить сервис перехода в сессию 0 можете оценить здесь (качать естественно последнюю версию из моих последних сообщений)
KernelExplorer without driver

PS: примерные прототипы WinSqm функций из ntdll.dll
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
HANDLE WINAPI WinSqmStartSession(GUID* pSessionGuid, DWORD dwSessionId, DWORD unknown1)
{
    return INVALID_HANDLE_VALUE;
}
 
#define STATUS_NOT_IMPLEMENTED 0xC0000002
NTSTATUS WINAPI WinSqmEndSession(HANDLE hSession)
{
    return STATUS_NOT_IMPLEMENTED;
}
 
BOOL WINAPI WinSqmIsOptedIn(VOID)
{
    return FALSE;
}
 
VOID WINAPI WinSqmSetDWORD(HANDLE hSession, DWORD dwDataPointId, DWORD dwDataPointValue)
{
    
}
 
VOID WINAPI WinSqmIncrementDWORD(HANDLE hSession, DWORD dwDataPointId, DWORD dwDataPointValue)
{
 
}
 
VOID WINAPI WinSqmAddToAverageDWORD(HANDLE hSession, DWORD dwDataPointId, DWORD dwDataPointValue)
{
 
}
 
VOID WINAPI WinSqmAddToStream(HANDLE hSession, DWORD dwDataPointId, DWORD sqmStreamEntries, PVOID streamEntries)
{
 
}
 
VOID WINAPI WinSqmSetString(HANDLE hSession, DWORD dwDataPointId, LPCWSTR unk3)
{
 
}
Размещено в Без категории
Просмотров 768 Комментарии 2
Всего комментариев 2
Комментарии
  1. Старый комментарий
    После насквозь "дырявой" Windows XP в Microsoft прикинули расклад и задались вопросом - как же изолировать систему от пользователя? Ведь в XP пользователь работал в той же среде, что и ядро...
    ...
    У меня Windows XP работает уже второе десятилетие. Все ОС, что появилось после XP - барахло! Каждая новая ОС всё хуже и хуже. А Майкрософт просто делает деньги. То ли ещё будет!
    Запись от wer1 размещена 09.11.2020 в 09:20 wer1 вне форума
  2. Старый комментарий
    Аватар для _lunar_
    Цитата:
    Сообщение от wer1 Просмотреть комментарий
    У меня Windows XP работает уже второе десятилетие. Все ОС, что появилось после XP - барахло! Каждая новая ОС всё хуже и хуже. А Майкрософт просто делает деньги. То ли ещё будет!
    Ты не понял моего посыла: работать то она работает, с этим вопросов нет. А вот то, что до ядра там рукой подать, это совсем уже другое.
    Microsoft не только деньги делает, но ещё всё больше отгораживает пользователя от системы. И вот с этим нужно бороться.
    Запись от _lunar_ размещена 09.11.2020 в 10:42 _lunar_ вне форума
    Обновил(-а) _lunar_ 09.11.2020 в 10:44
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2020, vBulletin Solutions, Inc.