标题: 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=
怎么知道目标进程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配置,不必关心它。