Skip to content

标题: 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配置,不必关心它。