标题: x64/Win10远程桌面多开(Inside RDP Wrapper) 创建: 2018-04-12 15:54 更新: 2018-04-18 11:15 链接: https://scz.617.cn/windows/201804121554.txt 假设你在VMware里有个x64/Win10,已经配置好远程桌面,允许Administrator远程登 录。然后你从Host远程登录Guest,发现Guest的Console被登出,所有界面切换到RDP。 而你可能想要的结果是,Console保持不变,可以用Adminitrator这个帐号开多个RDP。 很多人在很多年前就存在这个需求,有很多解决方案,现在有一个傻瓜式解决方案: RDP Wrapper https://github.com/stascorp/rdpwrap/ 有兴趣者直接看这个文件: https://github.com/stascorp/rdpwrap/blob/master/res/rdpwrap-ini-kb.txt 从Vista到Win10,全支持。这可能是一个不错的选择,但有洁癖的程序员们或许不太 愿意安装它。本文介绍"RDP Wrapper"的核心Patch方案,使得有逆向能力的程序员们 可以自行Patch。 用IDA分析termsrv.dll,总共有3处Patch。 以64位10.0.16299.15版termsrv.dll为例,假设加载基址是0x7FFD5F9B0000。 ========================================================================== 1) CDefPolicy::Query -------------------------------------------------------------------------- unsigned int CDefPolicy::Query ( CDefPolicy *this, // rcx unsigned int *out // rdx ) { unsigned int ret = 0; /* * ( unsigned char *)this + 0x644 */ *out = *( ( unsigned int * )this + 0x191 ); if ( *( ( unsigned int *)this + 0x18F ) == *( ( unsigned int * )this + 0x18E ) ) { /* * 查看这个字符串的交叉引用 */ _DbgPrintMessage( 8, "CDefPolicy::Query FAILED - License not available", 0 ); ret = 0xD00A0013; } return( ret ); } -------------------------------------------------------------------------- 00007FFD5F9C2D70 public: virtual CDefPolicy::Query 00007FFD5F9C2D70 00007FFD5F9C2D70 48 83 EC 28 sub rsp, 28h 00007FFD5F9C2D74 8B 81 44 06 00 00 mov eax, [rcx+644h] 00007FFD5F9C2D7A 45 33 C0 xor r8d, r8d 00007FFD5F9C2D7D 89 02 mov [rdx], eax 00007FFD5F9C2D7F 8B 81 38 06 00 00 mov eax, [rcx+638h] /* * 改这里,不与[rcx+63Ch]比较,直接给[rcx+638h]赋值256 */ 00007FFD5F9C2D85 39 81 3C 06 00 00 cmp [rcx+63Ch], eax 00007FFD5F9C2D8B 0F 84 B1 7D 02 00 jz loc_7FFD5F9EAB42 00007FFD5F9C2D91 00007FFD5F9C2D91 loc_7FFD5F9C2D91: 00007FFD5F9C2D91 41 8B C0 mov eax, r8d 00007FFD5F9C2D94 48 83 C4 28 add rsp, 28h 00007FFD5F9C2D98 C3 retn -------------------------------------------------------------------------- 改成: -------------------------------------------------------------------------- unsigned int CDefPolicy::Query ( CDefPolicy *this, // rcx unsigned int *out // rdx ) { *out = *( ( unsigned int * )this + 0x191 ); /* * 允许256个并发RDP */ *( ( unsigned int * )this + 0x18E = 256; return( 0 ); } -------------------------------------------------------------------------- 00007FFD5F9C2D70 public: virtual CDefPolicy::Query 00007FFD5F9C2D70 00007FFD5F9C2D70 48 83 EC 28 sub rsp, 28h 00007FFD5F9C2D74 8B 81 44 06 00 00 mov eax, [rcx+644h] 00007FFD5F9C2D7A 45 33 C0 xor r8d, r8d 00007FFD5F9C2D7D 89 02 mov [rdx], eax 00007FFD5F9C2D7F 8B 81 38 06 00 00 mov eax, [rcx+638h] /* * 改这里,不与[rcx+63Ch]比较,直接给[rcx+638h]赋值256 */ 00007FFD5F9C2D85 B8 00 01 00 00 mov eax, 100h 00007FFD5F9C2D8A 89 81 38 06 00 00 mov [rcx+638h], eax 00007FFD5F9C2D90 90 nop 00007FFD5F9C2D91 00007FFD5F9C2D91 loc_7FFD5F9C2D91: 00007FFD5F9C2D91 41 8B C0 mov eax, r8d 00007FFD5F9C2D94 48 83 C4 28 add rsp, 28h 00007FFD5F9C2D98 C3 retn -------------------------------------------------------------------------- 从内存地址0x7FFD5F9C2D85(文件偏移0x12185)处开始Patch: old - 39 81 3C 06 00 00 0F 84 B1 7D 02 00 new - B8 00 01 00 00 89 81 38 06 00 00 90 2) CSessionArbitrationHelper::IsSingleSessionPerUserEnabled 在IDA里不看交叉引用,只凭全称函数名可唯一定位它。也可以查看如下字符串的交 叉引用: "Software\\Policies\\Microsoft\\Windows NT\\Terminal Services" "fSingleSessionPerUser" "System\\CurrentControlSet\\Control\\Terminal Server" "Registry.ReadRegDWord failed: 0x%x in %s" "Registry.OpenKey failed: 0x%x in %s" "CSessionArbitrationHelper::IsSingleSessionPerUserEnabled" -------------------------------------------------------------------------- unsigned int CSessionArbitrationHelper::IsSingleSessionPerUserEnabled ( CSessionArbitrationHelper *this, // rcx unsigned int *out // rdx ) { /* * 函数入口附近 */ memset( &VersionInformation.dwMajorVersion, 0, 0x118 ); VersionInformation.dwOSVersionInfoSize = 0x11C; *out = 1; ... } -------------------------------------------------------------------------- 00007FFD5F9CC730 public: virtual CSessionArbitrationHelper::IsSingleSessionPerUserEnabled 00007FFD5F9CC730 ... 00007FFD5F9CC761 48 8D 4C 24 64 lea rcx, [rsp+1A0h+VersionInformation.dwMajorVersion] 00007FFD5F9CC766 33 D2 xor edx, edx 00007FFD5F9CC768 41 B8 18 01 00 00 mov r8d, 118h 00007FFD5F9CC76E E8 03 74 01 00 call memset /* * 改这里,从1改成0 */ 00007FFD5F9CC773 BB 01 00 00 00 mov ebx, 1 00007FFD5F9CC778 C7 44 24 60 1C 01 00 00 mov [rsp+1A0h+VersionInformation.dwOSVersionInfoSize], 11Ch 00007FFD5F9CC780 48 8D 4C 24 60 lea rcx, [rsp+1A0h+VersionInformation] 00007FFD5F9CC785 89 1E mov [rsi], ebx 00007FFD5F9CC787 FF 15 EB B4 09 00 call cs:__imp_GetVersionExW -------------------------------------------------------------------------- 改成: -------------------------------------------------------------------------- unsigned int CSessionArbitrationHelper::IsSingleSessionPerUserEnabled ( CSessionArbitrationHelper *this, // rcx unsigned int *out // rdx ) { memset( &VersionInformation.dwMajorVersion, 0, 0x118 ); VersionInformation.dwOSVersionInfoSize = 0x11C; /* * 从1改成0 */ *out = 0; ... } -------------------------------------------------------------------------- 00007FFD5F9CC730 public: virtual CSessionArbitrationHelper::IsSingleSessionPerUserEnabled 00007FFD5F9CC730 ... 00007FFD5F9CC761 48 8D 4C 24 64 lea rcx, [rsp+1A0h+VersionInformation.dwMajorVersion] 00007FFD5F9CC766 33 D2 xor edx, edx 00007FFD5F9CC768 41 B8 18 01 00 00 mov r8d, 118h 00007FFD5F9CC76E E8 03 74 01 00 call memset /* * 改这里,从1改成0 */ 00007FFD5F9CC773 BB 00 00 00 00 mov ebx, 0 00007FFD5F9CC778 C7 44 24 60 1C 01 00 00 mov [rsp+1A0h+VersionInformation.dwOSVersionInfoSize], 11Ch 00007FFD5F9CC780 48 8D 4C 24 60 lea rcx, [rsp+1A0h+VersionInformation] 00007FFD5F9CC785 89 1E mov [rsi], ebx 00007FFD5F9CC787 FF 15 EB B4 09 00 call cs:__imp_GetVersionExW -------------------------------------------------------------------------- 从内存地址0x7FFD5F9CC774(文件偏移0x1BB74)处开始Patch: old - 01 new - 00 3) CEnforcementCore::GetInstanceOfTSLicense 在IDA里不看交叉引用,只凭全称函数名可唯一定位它。也可以查看如下字符串的交 叉引用: "CEnforcementCore::GetInstanceOfTSLicense FAILED - License type meant only for local sessions" "ptrLicenseManager->GetInstanceOfLicense failed: 0x%x in %s" "CEnforcementCore::GetInstanceOfTSLicense" "CEnforcementCore::GetInstanceOfTSLicense FAILED - error in StartEnfRequest %x" -------------------------------------------------------------------------- CEnforcementCore::GetInstanceOfTSLicense ( CEnforcementCore *this, GUID *a2, ITSLicense **a3 ) { ... /* * 可以先定位CSLQuery::IsLicenseTypeLocalOnly,然后交叉引用主调函数直 * 接定位这个Patch点 */ if ( CSLQuery::IsLicenseTypeLocalOnly( a2, &x ) >= 0 && x ) { /* * 旁路这段代码 */ ret = 0x80070005; _DbgPrintMessage ( 8, "CEnforcementCore::GetInstanceOfTSLicense FAILED - License type meant only for local sessions" ); } else { /* * 修改前述判断语句,让流程死活到这儿来 */ ... } ... } -------------------------------------------------------------------------- 00007FFD5FA3FCF3 E8 74 FD 00 00 call CSLQuery::IsLicenseTypeLocalOnly(_GUID &,int *) 00007FFD5FA3FCF8 85 C0 test eax, eax 00007FFD5FA3FCFA 78 1F js short loc_7FFD5FA3FD1B 00007FFD5FA3FCFC 83 7C 24 68 00 cmp [rsp+48h+arg_18], 0 /* * 改这里,从条件跳转改成无条件跳转 */ 00007FFD5FA3FD01 74 18 jz short loc_7FFD5FA3FD1B 00007FFD5FA3FD03 48 8D 15 56 7B 04 00 lea rdx, aCenforcementco_0 ; "CEnforcementCore::GetInstanceOfTSLicens"... 00007FFD5FA3FD0A B9 08 00 00 00 mov ecx, 8 00007FFD5FA3FD0F BB 05 00 07 80 mov ebx, 80070005h 00007FFD5FA3FD14 E8 57 66 F7 FF call _DbgPrintMessage(int,char const *,...) 00007FFD5FA3FD19 EB 48 jmp short loc_7FFD5FA3FD63 00007FFD5FA3FD1B 00007FFD5FA3FD1B loc_7FFD5FA3FD1B: -------------------------------------------------------------------------- 改成: -------------------------------------------------------------------------- CEnforcementCore::GetInstanceOfTSLicense ( CEnforcementCore *this, GUID *a2, ITSLicense **a3 ) { ... /* * 可以先定位CSLQuery::IsLicenseTypeLocalOnly,然后交叉引用主调函数直 * 接定位这个Patch点 */ CSLQuery::IsLicenseTypeLocalOnly( a2, &x ); /* * 修改前述判断语句,让流程死活到这儿来 */ ... ... } -------------------------------------------------------------------------- 00007FFD5FA3FCF3 E8 74 FD 00 00 call CSLQuery::IsLicenseTypeLocalOnly(_GUID &,int *) 00007FFD5FA3FCF8 85 C0 test eax, eax 00007FFD5FA3FCFA 78 1F js short loc_7FFD5FA3FD1B 00007FFD5FA3FCFC 83 7C 24 68 00 cmp [rsp+48h+arg_18], 0 /* * 改这里,从条件跳转改成无条件跳转 */ 00007FFD5FA3FD01 EB 18 jmp short loc_7FFD5FA3FD1B 00007FFD5FA3FD03 48 8D 15 56 7B 04 00 lea rdx, aCenforcementco_0 ; "CEnforcementCore::GetInstanceOfTSLicens"... 00007FFD5FA3FD0A B9 08 00 00 00 mov ecx, 8 00007FFD5FA3FD0F BB 05 00 07 80 mov ebx, 80070005h 00007FFD5FA3FD14 E8 57 66 F7 FF call _DbgPrintMessage(int,char const *,...) 00007FFD5FA3FD19 EB 48 jmp short loc_7FFD5FA3FD63 00007FFD5FA3FD1B 00007FFD5FA3FD1B loc_7FFD5FA3FD1B: -------------------------------------------------------------------------- 从内存地址0x7FFD5FA3FD01(文件偏移0x8F101)处开始Patch: old - 74 new - EB ========================================================================== 一般是静态Patch后测试,我当时直接用windbg远程调试、动态Patch: dbgsrv.exe -t tcp:port=,password= cdb.exe -noinh -snul -hd -o -premote tcp:server=,port=,password= -p 怎么知道目标进程PID呢?用Process Explorer搜termsrv.dll,包含它的那个 svchost.exe就是目标进程。更直接点的办法: tasklist /svc /fi "services eq TermService" 或许这些Patch能允许多个不同帐号开多个RDP,但实测表明,只靠这些Patch并不能 实现后面这个效果,"Console保持不变,可以用Adminitrator这个帐号开多个RDP"。 远程登录时仍会碰上错误提示: -------------------------------------------------------------------------- 远程桌面服务会话已结束。 另一用户已连接到此远程计算机,因此你的连接已丢失。请尝试再次连接,或联系网 络管理员或技术支持小组。 -------------------------------------------------------------------------- 为此还需要修改组策略: Local Computer Policy Computer Configuration Administrative Templates Windows Components Remote Desktop Services Remote Desktop Session Host Connections Restrict Remote Desktop Services users to a single Remote Desktop Services session Disabled If you disable this policy setting, users are allowed to make unlimited simultaneous remote connections by using Remote Desktop Services. 此处缺省是"Not Configured"。为了强制组策略不重启而热生效,可能需要: gpupdate.exe /force -------------------------------------------------------------------------- Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services] "fSingleSessionPerUser"=dword:00000000 -------------------------------------------------------------------------- reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" /v "fSingleSessionPerUser" /t REG_DWORD /d 0 /f reg query "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" /v "fSingleSessionPerUser" 缺省没有这个键值,Disabled之后创建这个键值,并且键值数据为0。 至此,我的x64/Win10 Pro已经可以管理员帐号多开RDP,不影响Console。 后来我又测了一下,第2个Patch不能少,第3个Patch不做也没事。话说回来,我没有 深究这些Patch点,仅仅是介绍"RDP Wrapper"做了啥。此外,"RDP Wrapper"还Hook 了CSLQuery::Initialize(),估计是为了提供GUI配置,不必关心它。