Разработка под Windows
28 декабря 2008

Multi-user режим для Terminal Server в Windows XP x64

Перевод
Оригинал:
daNIL
На волне недавнего апгрейда, довелось мне столкнуться с неприятным ограничением Windows XP x64 на количество одновременно подключенных через Remote Desktop пользователей. А именно, в каждый момент времени работать с компьютером может не более одного пользователя. При соединении через RDP, локальный пользователь отключается; при локальном входе в систему — отключается удалённый. Досадно, учитывая, что ресурсов машины с лихвой хватило бы на несколько клиентов.

Насколько мне известно, проблема эта присуща всем десктопным (не-серверным) вариантам Windows. Для 32-битной версии Windows XP существует решение в виде патча TS-Free, который заменяет несколько системных библиотек на более старые, зато неограниченные, версии. Для 64-битных же систем решение обычно сводится либо к переходу на серверный вариант ОС (соответственно, немалые затраты), либо к использованию сторонних программ типа WinConnect Server VS (впрочем, WinXP x64 всё равно не поддерживается). Я уже успел отчаяться, когда в одном голландском блоге случайно набрёл на статью "Windows XP x64 Terminal Server patch". В отличие от TS-Free, на которого ругаются все антивирусы, в этой статье автор подробно описывает какие байты меняются и зачем, так что читатель может самостоятельно убедиться в безопасности патча.
Ниже — вольный перевод статьи.

Windows XP x64 основана на тех же файлах, что и Windows 2003 x64, однако Terminal Server в XP имеет некоторые ограничения. Эта статья демонстрирует как от них избавиться. В основу легли идеи cw2k из оригинального патча для Windows XP Terminal Server.

1) Winlogon.exe содержит функцию EnumerateMatchingUsers, которая вызывает IsProfessionalTerminalServer. Последнюю нужно исправить, чтобы она возвращала ноль (FALSE):
.text:0000000100042F77 IsProfessionalTerminalServer proc near ; CODE XREF: EnumerateMatchingUsers:loc_10002B44Bp
.text:0000000100042F77 ; DATA XREF: .pdata:00000001000D01DCo …
.text:0000000100042F77
.text:0000000100042F77 VersionInformation= _OSVERSIONINFOW ptr -138h
.text:0000000100042F77 var_20 = word ptr -20h
.text:0000000100042F77 var_ 1E = byte ptr -1Eh
.text:0000000100042F77 var_18 = qword ptr -18h
.text:0000000100042F77
.text:0000000100042F77 48 81 EC 58 01 00 00 sub rsp, 158h => 31 C0 C3 xor eax, eax; retn
.text:0000000100042F7E 48 8B 05 F3 3A 08 00 mov rax, cs:__security_cookie
.text:0000000100042F85 48 89 84 24 40 01 00 00 mov [rsp+158h+var_18], rax
.text:0000000100042F8D 48 8D 4C 24 20 lea rcx, [rsp+158h+VersionInformation] ; void *
.text:0000000100042F92 33 D2 xor edx, edx ; int

Но это ещё не всё. Ещё есть функция IsPerOrProTerminalServer; она используется в нескольких местах, однако править мы будем только её вызовы из MultiUserLogonAttempt:
.text:0000000100044A91 E8 71 E5 FF FF call IsPerOrProTerminalServer
.text:0000000100044A96 85 C0 test eax, eax
.text:0000000100044A98 0F 84 C9 00 00 00 jz loc_100044B67 => 0F 8D C9 00 00 00 jmp loc_100044B67
.text:0000000100044B13 E8 EF E4 FF FF call IsPerOrProTerminalServer
.text:0000000100044B18 85 C0 test eax, eax
.text:0000000100044B1A 74 0C jz short loc_100044B28 => EB 0C jmp short loc_100044B28 

2) Termsrv.dll. Заставим Terminal Server думать, что он запущен на серверной ОС. В ServiceMain termsrv.dll инициализирует глобальную переменную с названием gbServer. Нам нужно, чтобы она была равна TRUE (1):
.text:000007FF7B877F77 33 C9 xor ecx, ecx ; ConditionMask
.text:000007FF7B877F79 C7 05 9D 2C 05 00 1C 01 00 00 mov cs:gOsVersion.dwOSVersionInfoSize, 11Ch
.text:000007FF7B877F83 C6 05 B0 2D 05 00 01 mov cs:gOsVersion.wProductType, 1
.text:000007FF7B877F8A FF 15 F8 9A FF FF call cs:__imp_VerSetConditionMask
.text:000007FF7B877F90 48 8D 0D 89 2C 05 00 lea rcx, gOsVersion ; lpVersionInformation
.text:000007FF7B877F97 BA 80 00 00 00 mov edx, 80h ; dwTypeMask
.text:000007FF7B877F9C 4C 8B C0 mov r8, rax ; dwlConditionMask
.text:000007FF7B877F9F FF 15 73 95 FF FF call cs:__imp_VerifyVersionInfoW
.text:000007FF7B877FA5 8B CF mov ecx, edi => 31 C9 xor ecx, ecx
.text:000007FF7B877FA7 48 8D 15 B2 AE FF FF lea rdx, SubKey ; "System\\CurrentControlSet\\Control\\Termin"…
.text:000007FF7B877FAE 85 C0 test eax, eax
.text:000007FF7B877FB0 48 8D 44 24 60 lea rax, [rsp+78h+hKey]
.text:000007FF7B877FB5 41 B9 19 00 02 00 mov r9d, 20019h ; samDesired
.text:000007FF7B877FBB 0F 94 C1 setz cl => FF C1 90 inc ecx; nop
.text:000007FF7B877FBE 45 33 C0 xor r8d, r8d ; ulOptions
.text:000007FF7B877FC1 48 89 44 24 20 mov [rsp+78h+var_58], rax
.text:000007FF7B877FC6 89 0D 30 B0 04 00 mov cs:gbServer, ecx

Итак, мы «проапгрейдились» до сервера. Однако, на серверных ОС запрещено отключать консоль (STATUS_CTX_CONSOLE_DISCONNECT = $C00A0027), так что нам нужно поправить ещё два места, где используется этот код:
.text:000007FF7B889D99 85 C0 test eax, eax
.text:000007FF7B889D9B 75 21 jnz short loc_7FF7B889DBE => EB 21 jmp short loc_7FF7B889DBE
.text:000007FF7B889D9D 48 8D 4B 18 lea rcx, [rbx+18h]
.text:000007FF7B889DA1 BF 27 00 0A C0 mov edi, 0C00A0027h 
и ещё раз:
.text:000007FF7B88AA1B 45 85 E4 test r12d, r12d
.text:000007FF7B88AA1E 74 0A jz short loc_7FF7B88AA2A => EB 21 jmp short loc_7FF7B88AA2A
.text:000007FF7B88AA20 BB 27 00 0A C0 mov ebx, 0C00A0027h

На этом с патчами всё! Чтобы применить их, нужно сделать следующее:
  • Во-первых, убедитесь, что вы делаете это из 64-битного процесса (например, 64-битного explorer.exe)! 32-битные процессы перенаправляются в директорию %windir%\SysWOW64 (см. File System Redirector).
  • Сделайте резервные копии termsrv.dll и winlogon.exe из %windir%\system32. В случае, если система не загрузится, они вам понадобятся :)
  • Удалите (или переименуйте) все файлы termsrv.dll и winlogon.exe (обычно они находятся в %windir%\system32\DllCache, в %windir%\ServicePackFiles, или где-то ещё). Также нужно запретить системе доступ к её собственному дистрибутиву. Если всё сделано правильно, вы увидите сообщения вроде этого:
    Windows File Protection
    [Прим.: я делал из Safe Mode, и таких сообщений не было.]
  • Используя Windows XP x64 Terminal Server Mandatory Patch и DUP2, примените патч к termsrv.dll и winlogon.exe.
  • Скопируйте пропатченные файлы обратно в %windir%\system32. Снова появится предупреждение об изменении системных файлов.
  • Перегрузитесь.

Правка реестра: убедитесь, что следующие ключи имеют указанные значения.
  • HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\AllowMultipleTSSessions = 1
  • HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\WinStationsDisabled = 0
  • HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\Console\fEnableWinStation = 1
  • HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp\fEnableWinStation = 1

Некоторые нюансы ещё остаются: нельзя установить соединение с localhost (решается подключением к 127.0.0.2), и если вы заблокируете ваш компьютер из RDP-сессии (не консоли), вас просто отсоединит (в случае Fast User Switching, быстрого переключения пользователей). Решению этих проблем будет посвящена следующая статья.

P.S. Кнопка «Donate» оказалась как раз кстати, чтобы на радостях выразить автору свою благодарность :)
P.P.S. Нет, мы с ним не в доле :)
+17
8,9k 44
Комментарии 75
Похожие публикации
Популярное за сутки