Millet Porridge

English version of https://corvo.myseu.cn

0%

Debugging rdp Connection Errors Under a Domain Network

I mainly used the xfreerdp tool, already introduced in an earlier blog.

Coming to the company on Monday, I found my usual freerdp couldn’t connect to company services. I’ll introduce my investigation and solution — not necessarily applicable to all situations, just sharing here.

Errors Encountered in Use

The error on 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

The useful error data is this line: ERRCONNECT_AUTHENTICATION_FAILED [0x00020009] — the remote computer returned an error code. I wanted to ask our IT department what on earth was going on, but I certainly couldn’t ask using Linux error codes — they don’t use Linux either. So I also tested with mstsc in my own virtual machine.

The mstsc error on windows

Problem Solved

After asking our IT colleagues, I found the company’s domain network had added a restriction: only users whose machine name equals their account may log in. For example, I’m fengxxx, so I must also change my computer’s name to fengxxx. I never imagined there could be such a layer of restriction. Because of this inexplicable convention, remote connection becomes less convenient while providing no substantive help to security.

The Solution on Linux

Knowing the restriction was made via machine name, I first changed the system-wide machine name; connecting again indeed worked.

1
sudo hostnamectl set-hostname fengxxx

After much thought, I felt changing my laptop’s machine name just for one rdp was completely unnecessary, and wondered whether there was some way to hack it.

First I traced where FreeRdp gets the machine name:

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;
}

I found it uses the gethostname function. I remembered Linux can use LD_PRELOAD to hack system functions. I studied the libkeepalive library, which can replace gethostname. Here is my code:

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
// The concrete strategy: read a string from the environment variable `FAKEHOST` and return it when the program calls gethostname
//
// gcc -fPIC -c -o libgethostname.o libgethostname.c
// gcc -shared -Wl,-soname,libgethostname.so -o libgethostname.so libgethostname.o -ldl
// then you can use it like this
// 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;
}

For the rdp software mentioned earlier, you can start it like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# remmina startup
export FAKEHOST="fengxxx"
LD_PRELOAD="/home/corvo/GitRepo/libgethostname/libgethostname.so" remmina


# xfreerdp startup
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

Summary

From the perspective of rdp security, I think restricting by machine name is quite unreasonable — after all, the machine name can be easily forged. The code above is a good example. If some domain administrator reads this blog, comments are welcome.