基于SSH隧道的RDP Session远程桌面连接
2022-12-08符明
符 明
(湖南食品药品职业学院图书馆,长沙 410004)
0 引言
Windows家庭版以上系统自带远程桌面服务。MSTSC远程桌面服务被广泛用于小型局域网的维护工作中。但这个程序需要知道对方计算机的用户名和密码,并且只能在同一网段下使用。一般情况下,系统维护操作员会安装第三方软件TeamViewer等来翻过NAT路由来代替原有的Windows远程桌面服务。
1 网络拓扑环境描述
典型的局域网拓扑环境,计算机1位于NAT1后,计算机2位于NAT2后,计算机1、计算机2均可以直接和服务器通讯。但是服务器并不能直接访问计算机1、计算机2,且计算机1和计算机2之间也无法通讯。
2 RDP Session介绍
RDP Session是基于RdpEncom.dll的Windows远 程 桌 面COM控 件[1]。该DLL文 件 位 于System32和sysWOW64目录下,此COM控件存在于从Windows7至Windows10的系统自带COM控件库中。此COM的优点是:控制端用RDP连接字符串包含的密钥,无需输入被控制端的用户名及密码即可接入被控制端计算机的远程桌面。在同一网段的局域网下,该控件提供了一种优异的系统级远程桌面连接方法,可以轻松实现对远程计算机进行实时监控、控制、发送文本等功能。缺点是:只能用于同一网段之内的计算机远程桌面服务,无法跨NAT网段连接。以下为一个标准的RDP连接字符串,里面包含了连接密钥和连接地址与端口号。
<E><A KH="密钥字符串(略)"KH2="密钥字符串(略)"
CE="密钥字符串(略)"ID="连接ID(略)"/><C><T ID="1"SID="0"><L
P="41000"N="fe80::2955:f79a:f465:3c75%2"/><L P="41000"
N="fe80::f997:d009:53b7:50f7%8"/><L P="41000"N="192.168.56.1"/><L
P="41000"N="192.168.0.103"/></T></C></E>
3 跨网段远程桌面实现原理
3.1 在跨NAT段建立计算机之间可直接通讯的SSH隧道[2]
控制端计算机采用本地端口转发,将本地端口映射到服务器,相当于执行命令:ssh-L port:localhost:port user@server,通过访问本地端口交由服务器转发;被控制端PC采用远程转发,相当于执行命令:ssh-R port:localhost:port user@server,将服务器端口映射到本地端口,访问服务器端口转发至本地端口。通过中间OPENSSH服务器,两台计算机之间建立绕过NAT限制,能直接通讯的加密SSH通道。
3.2 修改RDP连接字符串,在原字符串中添加127.0.0.1本地监听地址及端口字符串
提供RDP远程桌面服务的计算机创建RDP远程连接字符串。原始RDP字符串中包含明文的基于本机生成的连接密钥及本机IP地址、RDP访问端口等信息。控制端计算机接收到该字符串后,根据SSH隧道要求,必须要使用修改后的RDP连接字符串,添加本地127.0.0.1及端口信息。控制端计算机就可以利用修改过的RDP连接字符串,通过已经建立好的SSH隧道连接到远程计算机的桌面。
4 实现步骤
本方法采用NET Framework 4.5环境,Windows10下Visual Studio 2019 C#社区版开发,分为控制端、被控制端、服务器端三个应用程序。服务器安装OPENSSH服务环境。控制端与被控制端均添加引用SSH.NET。
4.1 建立SSH隧道
客户机之间建立SSH隧道,SSH隧道监听的端口就是RDP监听的端口,这个端口可以自定义。
控制端代码建立SSH本地端口转发,将本地端口映射到服务器,访问本地127.0.0.1:Port,即访问Server:Port。
Renci.SshNet.SshClient sshClient=new Renci.SshNet.SshClient(serverIP,"username","password");
sshClient.Connect();
Renci.SshNet.ForwardedPortLocal localForwardPort=new ForwardedPortLocal("127.0.0.1",(uint)RDP_TCP_Port,"127.0.0.1",(uint)RDP_TCP_Port);
sshClient.AddForwardedPort(localForwardPort);
localForwardPort.Start();
被控制端代码建立远程端口转发,将服务器端口映射到本地,访问Server:Port,即访问
local:Port。
Renci.SshNet.SshClient sshClient=new Renci.SshNet.SshClient(serverIP,"username","password");
sshClient.Connect();
Renci.SshNet.ForwardedPortRemote remoteForward-Port=new
ForwardedPortRemote((uint)RDP_TCP_Port,localIP,(uint)RDP_TCP_Port);
sshClient.AddForwardedPort(remoteForwardPort);
remoteForwardPort.Start();
4.2 计算机之间RDP Session请求的接收和发送
控制端添加ActiveX视窗控件AxRDPCOMAPILib及RdpEncom.dll引用。控制端从服务器通过Socket方式接收RDP连接字符串,并通过此连接字符串进行RDP连接:
//通过Socket形式接收RDP连接字符串过程略
Form frm=new Form();
frm.Text="远程桌面";
frm.Size=new ystem.Drawing.Size(Screen.PrimaryScreen.Bounds.Width,Screen.PrimaryScreen.Bounds.Height);
frm.MaximizeBox=false;
frm.MinimizeBox=false;
frm.FormClosing+=new FormClosingEventHandler(frm_FormClosing);
axRDPViewer.Dock=System.Windows.Forms.Dock-Style.Fill;
axRDPViewer.Size=new System.Drawing.Size(frm.Width,frm.Height);
axRDPViewer.OnConnectionTerminated+=AxRDPViewer_OnConnectionTerminated;//定义远程桌面被控制端断线以后的事件
((System.ComponentModel.ISupportInitialize)axRDPViewer).BeginInit();
frm.Controls.Add(axRDPViewer);
((System.ComponentModel.ISupportInitialize)axRDPViewer).EndInit();
axRDPViewer.SmartSizing=true;//定义远程桌面窗口信息,并初始化
axRDPViewer.Connect(rdpConnectString,System.Guid.NewGuid().ToString(),"");//用接收的RDP连接字符串进行连接
frm.ShowDialog();
被控制端添加RdpEncom.dll引用建立RDP连接请求,并将修改后的RDP连接字符串通过Socket方式发送给服务器,等待RDP的远程连接。在RDP连接建立时,可以自定义RDP监听端口:
RDPCOMAPILib.RDPSession rdp=new RDPCOMAPILib.RDPSession();
rdp.OnAttendeeConnected+=new
RDPCOMAPILib._IRDPSessionEvents_OnAttendee ConnectedEventHandler(rdp_OnAttendeeConnected);//定义连接等级
rdp.OnAttendeeDisconnected+=Rdp_OnAttendeeDisconnected;//定义断开以后的事件
rdp.SetDesktopSharedRect(0,0,System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width,System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height);
rdp.Properties["PortId"]=RDP_TCP_Port;//自 定 义在41000端口上进行RDP监听
rdp.Open();//打开RDP监听
RDPCOMAPILib.IRDPSRAPIInvitation invitation=rdp.Invitations.CreateInvitation(null,System.Guid.NewGuid().ToString(),"",1);
string rdpConnStr=invitation.ConnectionString;//创建RDP连接字符串,同时连接数=1
using(System.Net.Sockets.Socket skt=objSocket as System.Net.Sockets.Socket)
{
//向服务器发送连接字符串,代码略
}
被控制端定义连接等级:
static void rdp_OnAttendeeConnected(object pAttendee)
{
RDPCOMAPILib.RDPSRAPIAttendee att=pAttendee as RDPCOMAPILib.RDPSRAPIAttendee;
att.ControlLevel=RDPCOMAPILib.CTRL_LEVEL.CTRL_LEVEL_VIEW;//连接等级为仅浏览
}
修改RDP字符串,在原字符串上根据格式直接进行添加:
string insertStr="<L P=""+RDP_TCP_Port+""N="127.0.0.1"/>";
rdpConnectString=rdpConnectString.Insert(rdpConnectString.LastIndexOf("</T>"),insertStr);
控制端计算机和被控制端计算机分别位于D-LINK DIR-629及TPLINK TL-WR886N家用普通路由器后,SSH服务器在路由器前面,此方案测试通过。
5 结语
(1)RdpEncom.dll为系统自带服务,提供了多种连接等级,包括仅浏览到全局控制多个控制等级。可无视UAC控制窗口对系统进行设置,并提供系统级远程桌面服务。
(2)RdpEncom.dll同时也可以提供Virtual Channel功能,这个功能可以在RDP通讯中提供简单的文本传输服务,即不通过额外的Socket通道实现即时文本通讯。
(3)市场上常见的路由器、防火墙、通讯运营商一般不禁止SSH端口及服务。这个方案可以用在Internet广域网中,具有一定的实用性,但需要在Internet上暴露OPENSSH服务器,因此对此服务器安全设置必须十分谨慎。
(4)如果要建立稳定的远程桌面服务,那么必须要提高SSH加密隧道的稳定性,这是后续需要进一步研究的课题。