我主要使用了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
// winpr/libwinpr/sysinfo/sysinfo.c
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
// https://github.com/corvofeng/libgethostname
// 这里具体的策略就是从环境变量`FAKEHOST`中读取字符串, 在程序调用gethostname时返回
//
// gcc -fPIC -c -o libgethostname.o libgethostname.c
// gcc -shared -Wl,-soname,libgethostname.so -o libgethostname.so libgethostname.o -ldl
// 然后你就可以这么用了
// FAKEHOST=fengxxx LD_PRELOAD="./libgethostname.so" hostname
#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的安全性来讲, 我认为通过机器名进行限制挺不合理的, 毕竟机器名是可以简单伪造的, 上面的代码就是比较好的例子, 假如某位域管理员看到了这篇博客, 也欢迎提意见.