我主要使用了xfreerdp这个工具, 已经在先前的博客中介绍过了.
周一来到公司, 发现自己平时使用的freerdp没法连接到公司的服务, 我介绍一下自己的排查以及解决方案, 不一定适用于所有情况, 仅在此与大家分享一下.
使用中出现的错误
linux下的错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| [INFO][com.freerdp.core] - freerdp_connect:freerdp_set_last_error_ex resetting error state [INFO][com.freerdp.client.common.cmdline] - loading channelEx rdpdr [INFO][com.freerdp.client.common.cmdline] - loading channelEx rdpsnd [INFO][com.freerdp.client.common.cmdline] - loading channelEx cliprdr [INFO][com.freerdp.client.common.cmdline] - loading channelEx drdynvc [INFO][com.freerdp.primitives] - primitives autodetect, using optimized 1f40] Failed to initialise VAAPI connection: -1 (unknown libva error). [ERROR][com.freerdp.codec] - Could not initialize hardware decoder, falling back to software: Input/output error [INFO][com.freerdp.core] - freerdp_tcp_is_hostname_resolvable:freerdp_set_last_error_ex resetting error state [INFO][com.freerdp.core] - freerdp_tcp_connect:freerdp_set_last_error_ex resetting error state [WARN][com.freerdp.crypto] - Certificate verification failure 'unable to get local issuer certificate (20)' at stack position 0 [WARN][com.freerdp.crypto] - CN = XXXXXX.xxx.xxx [ERROR][com.freerdp.core.nla] - SPNEGO failed with NTSTATUS: 0xC0000070 [ERROR][com.freerdp.core] - nla_recv_pdu:freerdp_set_last_error_ex ERRCONNECT_AUTHENTICATION_FAILED [0x00020009] [ERROR][com.freerdp.core.rdp] - rdp_recv_callback: CONNECTION_STATE_NLA - nla_recv_pdu() fail [ERROR][com.freerdp.core.transport] - transport_check_fds: transport->ReceiveCallback() - -1
|
有用的错误数据是这一句ERRCONNECT_AUTHENTICATION_FAILED [0x00020009]
, 远程的计算机返回了错误代码. 我想问我们的IT部门到底是怎么回事,
肯定不能用Linux下面的错误代码来问, 他们也不用Linux. 因此, 我在自己的虚拟机中用mstsc也测试了一下.
windows下mstsc的错误
问题解决
找我们IT的同学问过之后, 发现是公司的域网络增加了限制, 只允许机器名称是自己的帐号的用户登录, 比如我是fengxxx
, 就必须将自己电脑的名字也改成fengxxx
,
我是万万没想到, 还能有这么一层限制. 因为这个莫名其妙的约定, 增加远程连接不方便, 对安全也没有提供任何实质性的帮助.
在Linux上的解决方案
既然知道了是通过机器名做了限制, 首先我先改了一下系统全局的机器名, 再次连接确实可以连的上.
1
| sudo hostnamectl set-hostname fengxxx
|
考虑再三, 我觉得为了一个rdp
改自己笔记本的机器名完全没必要, 想着有没有什么办法可以hack一下.
首先追踪了一下FreeRdp
中获取机器名的地方:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| BOOL GetComputerNameA(LPSTR lpBuffer, LPDWORD lpnSize) { char* dot; size_t length; char hostname[256];
if (!lpnSize) { SetLastError(ERROR_BAD_ARGUMENTS); return FALSE; }
if (gethostname(hostname, sizeof(hostname)) == -1) return FALSE; }
|
发现它使用的是gethostname
这个函数, 我想起来Linux中可以使用LD_PRELOAD
来hack
系统函数, 我学习了libkeepalive这个库, 可以对gethostname进行替换, 以下是我的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
#ifndef RTLD_NEXT # define _GNU_SOURCE #endif #include <stdlib.h> #include <stdio.h> #include <string.h> #include <dlfcn.h> #include <errno.h> #include <unistd.h>
int gethostname(char *__name, size_t __len);
int min(int x, int y) { return (x < y) ? x : y; }
int gethostname(char *__name, size_t __len) { int (*libc_gethostname)(char *__name, size_t __len);
*(void **)(&libc_gethostname) = dlsym(RTLD_NEXT, "gethostname"); if(dlerror()) { errno = EACCES; return -1; }
const char* s = getenv("FAKEHOST"); if (s==NULL) { printf("Can't get fake hostname, return real host\n"); return (*libc_gethostname)(__name, __len); } int _len = min(strlen(s), __len); strncpy(__name, s, _len); __name[_len] = '\0'; return 0; }
|
针对先前提到的rdp软件, 可以使用如下的方式启动:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| export FAKEHOST="fengxxx" LD_PRELOAD="/home/corvo/GitRepo/libgethostname/libgethostname.so" remmina
export FAKEHOST="fengxxx" export RDP=/home/corvo/GitRepo/FreeRDP/build/client/X11/xfreerdp
LD_PRELOAD="/home/corvo/GitRepo/libgethostname/libgethostname.so" $RDP \ /u:'xxxx' \ /p:xxxx \ /v:xxxxx \ +clipboard \ /gdi:sw +wallpaper -window-drag -menu-anims +fonts +glyph-cache +async-channels \ +home-drive \ /rfx \ +aero \ +wallpaper \ /sound \ /drive:Home,/home/corvo \ /size:1800x950
|
总结
从rdp的安全性来讲, 我认为通过机器名进行限制挺不合理的, 毕竟机器名是可以简单伪造的,
上面的代码就是比较好的例子, 假如某位域管理员看到了这篇博客, 也欢迎提意见.