HOWTO: VMware Player as a remote console (VNC)

Since I am doing a lot of remote systems administration tasks due to the nature of my IT consulting work and since I am also running Linux on all my computers I was looking for a native way how to get a remote console to VMware VMs from linux.

After some searching I found that VMware Player (which has native binaries for Linux) can be used as a VNC client to get to VMs consoles. However, once I have downloaded VMware Player’s bundle and was faced with its requirement to run the installation script as root I became quite unhappy with an idea of running some proprietary software on my machine as root, especially after looking into the bundle and the way the installation script was written. Moreover, there was no need for other parts of VMware Player – I just wanted to have a small tool to be able to hook the remote consoles up under my lovely Linux environment. Therefore, I decided to take a challenge and to tweak the installation so it will be possible to install the whole thing as a non-privileged user. Another sub-goal was to strip the installation further down and prepare a small package with only components needed for remote console sessions.

If you are not concerned about security (and integrity) of your system, e.g. you are fine with the re-installation of the whole system, then it will be cheaper to just install the VMware Player under the root account. In this case you don’t need to read any further since what I am describing below is for those brave hearts who value their systems and who do not want to give a chance to mess their systems up by running low-quality custom installation scripts as root.

Well, if you are still reading, then I hope that my research on this topic and the how-to I have spent considerable time to come up with is worth something and will be of some help to you.

Our starting point is a Linux-based system (it does not matter what distribution you are running, but I did everything on a customised ALT Linux‘s RPM-based distribution) running on an x86 compatible hardware (mine was 32-bit, but I see no issues with 64-bit ones).

The first step is to download VMware Player for your architecture, set proper permissions on the downloaded file, and then extract the payload as follows (you need to ensure that you have at least 40MB of free space on /tmp, BTW):

$ chmod 0700 ./VMware-Player-
$ ./VMware-Player-3.1.3-324285.i386.bundle --console -x $(pwd)/vmplayer
Extracting VMware Installer...done.
No protocol specified
No protocol specified
$ ls -l vmplayer
total 20
drwxr-xr-x 8 vmware vmware 4096 Nov 23 09:01 vmware-installer
drwxr-xr-x 4 vmware vmware 4096 Nov 23 09:01 vmware-ovftool
drwxr-xr-x 5 vmware vmware 4096 Nov 23 09:01 vmware-player
drwxr-xr-x 11 vmware vmware 4096 Nov 23 09:01 vmware-player-app
drwxr-xr-x 3 vmware vmware 4096 Nov 23 09:01 vmware-player-setup

So far so good. We now have the whole bundle unacked into the specified directory and we are interested in just two subdirectories: vmware-player and vmware-player-app, the rest is not related to the functionality we are looking for.

Now, let’s pick up all parts from which we will build our future “VMware remote console” tool. To make it easier create a dedicated subdirectory, e.g. vmrconsole, with the following structure:

$ mkdir -m700 ~/vmrconsole
$ mkdir -m700 ~/vmrconsole/{bin,etc,lib,share}
$ ls -l ~/vmrconsole
total 12
drwx------ 2 vmware vmware 4096 Nov 24 01:21 bin
drwx------ 2 vmware vmware 4096 Nov 24 01:21 etc
drwx------ 2 vmware vmware 4096 Nov 24 01:21 lib
drwx------ 2 vmware vmware 4096 Nov 24 01:21 share

From now on we are going to populate these directories with files from the unpacked bundle.

The first file we are interested in is appLoader – this is the primary executable by the way, we need to copy it to our bin directory and then try to run it:

$ cp ~/vmplayer/vmware-player-app/lib/bin/appLoader ~/vmrconsole/bin/
$ chmod 0700 ~/vmrconsole/bin/appLoader
$ ln -s appLoader ~/vmrconsole/bin/vmplayer
$ ~/vmrconsole/bin/vmplayer
$ echo $?

Huh, this is not very informative, is it? The binary silently exits with error code of 255. Well, you may get other errors at this stage if you don’t have all the required shared libraries installed on you system – however I doubt it since the requirements of this binary are pretty reasonable: glibc and zlib.

OK, let’s take a peek inside and figure out what is going on (originally I used strace with logging to a file, but to keep this article reasonable short I am highlighting important things only):

$ strace -f -eopen ~/vmrconsole/bin/vmplayer 2>&1 | tail -2
open("/etc/localtime", O_RDONLY) = 6
open("/etc/vmware/config", O_RDONLY|O_LARGEFILE) = -1 EACCES (Permission denied)

It looks that vmplayer wants to access a global config file and does not try to look for an alternative, home directory based one. Well, this is understandable since VMware folks did not expect it to be run as a non-privileged process, but we need to deal with this somehow. What are our options here? The simpliest option I could think of at the moment is to substitute the hardcoded absolute path inside the binary with something relative:

$ strings ~/vmrconsole/bin/appLoader | fgrep /etc/vmware | uniq -c
1 /etc/vmware/config
1 /etc/vmware/icu
1 /etc/vmware/ssl/rui.crt
1 /etc/vmware/ssl/rui.key
1 /etc/vmware/ssl/dh512.pem
1 /etc/vmware/ssl/dh1024.pem
$ sed -i 's,/etc/vmware,..//////etc,g' ~/vmrconsole/bin/appLoader

The trick here is to substitute one string with another of the same length (we are modifying a binary so we do not want to mess offsets up) and luckily enough we can use whatever number of slashes we want – they all are considered as a single separator nevertheless. OK, we could have used a hex editor and could have terminated strings with a NULL byte, but the point is that the approach I took is the quickest and is working well. Let’s run the modified binary through strace again, but this time we need to be prepared for the changed behaviour:

$ cd ~/vmrconsole/bin/
$ touch ../etc/config
$ strace -f -emkdir,lstat,open,write ./vmplayer 2>&1 | tail -12
mkdir("/tmp/.private/vmware/vmware-vmplayer", 0700) = -1 EEXIST (File exists)
open("/tmp/.private/vmware/vmware-vmplayer/appLoader-9245.log", O_RDWR|O_CREAT|O_APPEND|O_LARGEFILE, 0644) = 5
open("/etc/localtime", O_RDONLY) = 6
write(5, "Nov 24 01:53:09.824: app-3077760"..., 121) = 121
write(5, "Nov 24 01:53:09.824: app-3077760"..., 60) = 60
write(5, "Nov 24 01:53:09.824: app-3077760"..., 71) = 71
write(5, "Nov 24 01:53:09.824: app-3077760"..., 57) = 57
write(5, "\"\n", 2) = 2
write(5, "Nov 24 01:53:09.825: app-3077760"..., 82) = 82
open("..//////etc/config", O_RDONLY|O_LARGEFILE) = 6
write(5, "Nov 24 01:53:09.825: app-3077760"..., 89) = 89
write(5, "Nov 24 01:53:09.825: app-3077760"..., 73) = 73

Looks better, does not it? Our binary found the config file and was able to open it, however it still produces no output, but it reports something to a log file:

$ tail -7 /tmp/.private/vmware/vmware-vmplayer/appLoader-9245.log
Nov 24 01:53:09.824: app-3077760704| Log for VMware Workstation pid=9245 version=7.1.3 build=build-324285 option=Release
Nov 24 01:53:09.824: app-3077760704| The process is 32-bit.
Nov 24 01:53:09.824: app-3077760704| Host codepage=utf8 encoding=UTF-8
Nov 24 01:53:09.824: app-3077760704| Calling: "./vmplayer"
Nov 24 01:53:09.825: app-3077760704| Using configuration file ..//////etc/config.
Nov 24 01:53:09.825: app-3077760704| libdir entry was not present in ..//////etc/config.
Nov 24 01:53:09.825: app-3077760704| Unable to lookup library directory.

I do not know much about VMware Players config file but according to the log message it wants some variable called libdir and this variable should point to the library directory, so let’s introduce such a variable and try to execute the binary again:

$ echo 'libdir = ..' >> ../etc/config
$ strace -f -emkdir,lstat,open,write ./vmplayer 2>&1 | tail -4
write(5, "LOG NOT INITIALIZED | LoadLibrar"..., 73) = 73
open("../lib/", O_RDONLY) = -1 ENOENT (No such file or directory)
write(5, "LOG NOT INITIALIZED | Error load"..., 153) = 153
write(5, "LOG NOT INITIALIZED | Could not "..., 74) = 74

I hope you have noticed that I have used a relative path for the library directory in the config file and this means that we always should run the binary with its directory being the current working directory. This is a bit inconvenient, but we will solve this with a wrapper script later. Right now, we need to get it working and we see that it tried to dynamically load some library from the library directory. OK, let’s search for this library in the unpacked bundle directory and copy the library file over to our tree:

$ find ~/vmplayer -name
$ cp -a ~/vmplayer/vmware-player-app/lib/lib/ ~/vmrconsole/lib/
$ ldd ~/vmrconsole/lib/ | fgrep 'not found' => not found => not found => not found => not found => not found

The last command showed that depends on some libraries and that their locations are currently unknown to the system. In order to solve this there are two things we need to do:

  1. We need to tell the system where it should search for the libraries;
  2. We need to locate these libraries and put them into a directory where the system will find them.

To accomplish the first thing we need to create a wrapper script around vmplayer and use this script for fine-tuning later. Here is the very basic script for this purpose (created as ~/vmrconsole/bin/

# We must run vmplayer from the directory it resides in since all
# relative paths are solved from there.
ABS_NAME=$(readlink -e $BASH_SOURCE) || exit 1
# check that directories exist, if not notify the user
if [ ! -d "$VMW_BINDIR" ]; then
echo "ERROR: cannot determine the directory where this script resides!" >&2
exit 1
# preserve the current working directory
if ! cd "$VMW_LIBDIR" >/dev/null 2>&1 ; then
echo "ERROR: the '$VMW_LIBDIR' directory does not exist!" >&2
exit 1
# resolve the library directory path (to get rid off ../ inside of it)
VMW_LIBDIR=$(pwd -P 2>/dev/null)
if [ $? -ne 0 -o ! -d "$VMW_LIBDIR" ]; then
echo "ERROR: could not resolve the '$VMW_LIBDIR' directory path!" >&2
exit 1
# return back since it possible that we were called as ./vmrconsole
if ! cd "$OLD_PWD" >/dev/null 2>&1 ; then
echo "ERROR: could not return to the original '$OLD_PWD' directory!" >&2
exit 1
# we don't need the following variable anymore
unset OLD_PWD
# change the current directory to $VMW_BINDIR
if ! cd "$VMW_BINDIR" >/dev/null 2>&1 ; then
echo "ERROR: failed to change directory to '$VMW_BINDIR'!" >&2
exit 1
# set the library search path so the dynamic linker will be able
# to locate locally installed libraries.
# execute the real binary and pass the supplied arguments to it
exec -a "$ORIG_NAME" ./appLoader "$@"

This wrapper script should not be called directly, instead we need to create a symbolic link to this wrapper, e.g. for vmplayer the following should be performed:

$ ln -sf ~/vmrconsole/bin/vmplayer

Since our vmplayer is not a full scale VMware Player I suggest to create another small wrapper script and name it vmrconsole:

$ cat ~/vmrconsole/bin/vmrconsole
ABS_NAME=$(readlink -e $BASH_SOURCE) || exit 1
exec "$BINDIR/vmplayer" -h "$@"
$ chmod 0700 ~/vmrconsole/bin/vmrconsole

Now we need to populate our library directory with the needed libraries. It is a bit tricky to describe since on different systems you will likely end up with different sets of libraries inside our local directory. For example, on my system I have quite a few of the libraries installed from the distribution repositories and these versions of libraries are fresher and with many bug fixes in comparison to the VMWare provided ones.

Anyway, the general approach to install missing libraries is the following – we start with the libraries we determined as missing during our ldd ~/vmrconsole/lib/ | fgrep 'not found' step (we need to locate and copy them over to our library directory):

$ ldd ~/vmrconsole/lib/ | fgrep 'not found' | awk '{ print $1; }' | xargs -i find ~/vmplayer -type f -name '{}' -execdir cp -avL '{}' ~/vmrconsole/lib/ \;
`' -> `/home/vmware/vmrconsole/lib/'
`' -> `/home/vmware/vmrconsole/lib/'
`' -> `/home/vmware/vmrconsole/lib/'
`' -> `/home/vmware/vmrconsole/lib/'
`' -> `/home/vmware/vmrconsole/lib/'

Once this is done we need to follow the following loop until there is no output from the command listed below (in fact, on my system this step was not needed since I had all dependencies in place already, but it is harmless to execute this command anyway):

$ LD_LIBRARY_PATH=~/vmrconsole/lib ldd ~/vmrconsole/lib/* 2>/dev/null | fgrep 'not found' | awk '{ print $1; }' | xargs -i find ~/vmplayer -type f -name '{}' -execdir cp -avL '{}' ~/vmrconsole/lib/ \;

If we try to access any remote VM’s console it will be clear that some parts are still missing:

$ vmrconsole
Failed to open file '/usr/lib/vmware/share/pixmaps/progress.png': No such file or directory
Failed to open file '/usr/lib/vmware/share/pixmaps/eula.png': No such file or directory
Failed to open file '/usr/lib/vmware/share/pixmaps/stream-spinner.png': No such file or directory
Failed to open file '/usr/lib/vmware/share/pixmaps/stream-spinner-stopped.png': No such file or directory
SSLLoadSharedLibrary: Failed to load library cannot open shared object file: No such file or directory

strace is our best friend here, just run the command through strace, examine the log file, and fix stuff properly:

$ strace -f -eopen -o ~/strace.log vmrconsole >/dev/null 2>&1
$ less -n ~/strace.log
$ cd ~/vmrconsole/lib
$ fgrep -lr /vmware *
$ strings | fgrep /vmware
$ sed -i 's,/etc/vmware/,..//////etc/,'
$ sed -i 's,/etc/vmware-installer/,..//////etc/installer/,'
$ strings | fgrep /vmware
$ sed -i 's,/etc/vmware/,..//////etc/,g'
$ strings | fgrep /vmware
$ strings | fgrep /vmware
$ sed -i 's,/etc/vmware/,..//////etc/,g'
$ sed -i 's,/etc/vmware,..//////etc,g'
$ sed -i 's,/usr/lib/vmware/,..//////////etc/,g'
$ sed -i 's,/var/run/vmware,..//////var/run,g'
$ strings | fgrep /usr/lib/vmware
$ strings | fgrep /var/run/vmware
$ sed -i 's,/var/run/vmware,..//////var/run,g'

Ufff, we did a lot of binary patching – luckily, VMware binaries and libraries are not calculating their checksums. Now, we need to put all this stuff we have seen in the strings output and during the execution of the program in place (in accordance to our new relative paths):

$ find ~/vmplayer -name icu
$ cp -aL /home/vmware/vmplayer/vmware-player-app/lib/icu ~/vmrconsole/etc/
$ mkdir -m700 ~/vmrconsole/etc/ssl
$ find ~/vmplayer -name CVP
$ mkdir -m700 ~/vmrconsole/var
$ mkdir -m700 ~/vmrconsole/var/run
$ find ~/vmplayer -name pixmaps
$ mkdir -m700 ~/vmrconsole/share/pixmaps
$ cp -aL /home/vmware/vmplayer/vmware-player-app/lib/share/pixmaps/* ~/vmrconsole/share/pixmaps/
$ cp -aL /home/vmware/vmplayer/vmware-player-app/lib/share/EULA.txt ~/vmrconsole/share/
$ cp -aL /home/vmware/vmplayer/vmware-player-app/lib/share/*.ui ~/vmrconsole/share/
$ cp -aL /home/vmware/vmplayer/vmware-player-app/lib/share/icons ~/vmrconsole/share/

At this stage we should be able to launch the vmplayer program via our wrapper script (I am launching it on a remote machine through SSH, but here I am describing how it should look on the local console):

$ ~/vmrconsole/bin/vmplayer

VMware Player: main window

So far so good, but we need to resolve the issue with the OpenSSL library dependency. To resolve the issue we need to create symbolic links from our local library directory to the system-wide version of the OpenSSL library:

$ ln -s /usr/lib/ ~/vmrconsole/lib/
$ ln -s /usr/lib/ ~/vmrconsole/lib/
$ ls -ld ~/vmrconsole/lib/lib*.so.0.9.8
lrwxrwxrwx 1 vmware vmware 27 Nov 24 05:27 /home/vmware/vmrconsole/lib/ -> /usr/lib/
lrwxrwxrwx 1 vmware vmware 27 Nov 24 05:27 /home/vmware/vmrconsole/lib/ -> /usr/lib/

Let’s try to connect to remote VM’s console again:

$ vmrconsole

VMware Player: credentials window

VMware Player: MKS error message

Well, this error message does not say much except that we are not getting our console :). If we check the log file directory we would see there is a file called player-XXXXX.log (where XXXXX is the PID of the VMware Player that produced this log file). Inside the log file we may see some interesting parts like the following (I have included only those messages which are related to our task):

Nov 24 03:40:42.002: player| CDS error: Cannot locate VMIS, bootstrap file /etc/vmware-installer/bootstrap unavailable!
Nov 24 03:40:42.072: player| Unable to launch vmplayer-daemon: File does not exist.
Nov 24 03:40:42.072: player| Unable to find /home/vmplayer/bin/vmware-unity-helper in attempt to launch daemon.
Nov 24 03:41:04.630: player| Player dispatch: Opening VM while not connected to the daemon.
Nov 24 03:41:05.437: player| ConnectMksClient - calling VMClient_ConnectMksClientEx
Nov 24 03:41:05.437: player| VMClient_ConnectMksClientEx - trying local socket connection
Nov 24 03:41:05.444: player| Cnx_Connect: Returning false because CnxConnectAuthd failed
Nov 24 03:41:05.444: player| Cnx_Connect: Error message: Connection terminated by server
Nov 24 03:41:05.444: player| VMClient_ConnectMksClientEx - trying remote socket connection
Nov 24 03:41:05.611: player| VMClient_ConnectMksClientEx - connecting the MKS client
Nov 24 03:41:05.616: player| vmdbPipe_Streams: Couldn't read
Nov 24 03:41:15.941: player| Gdk: losing last reference to undestroyed window

From the messages quoted above it is quite clear that the program is trying to launch some helper binaries/daemons in the background and to delegate the actual remote console task to them. You can strace/ltrace the whole thing, review the resulting logs and you will find that it tries to execute two helper binaries: vmware-authd and vmware-remotemks, the latter is the remote console engine, while the former is some kind of an authentication daemon and I do not think it is needed for our purposes. I am not going to describe in details how I arrived at the following (it is all clear once you have studied the strace/ltrace log files):

$ find ~/vmplayer -name vmware-authd -o -name vmware-remotemks
$ mkdir -m700 ~/vmrconsole/vmauthd
$ ln -s `which true` ~/vmrconsole/vmauthd/vmware-authd
$ ls -ld ~/vmrconsole/vmauthd/vmware-authd
lrwxrwxrwx 1 vmware vmware 9 Nov 24 04:15 /home/vmware/vmrconsole/vmauthd/vmware-authd -> /bin/true
$ cp -aL ~/vmplayer/vmware-player-app/lib/bin/vmware-remotemks ~/vmrconsole/bin/
$ chmod 0700 ~/vmrconsole/bin/vmware-remotemks
$ strings ~/vmrconsole/bin/vmware-remotemks | fgrep /vmware
$ sed -i 's,/var/run/vmware,..//////var/run,g' ~/vmrconsole/bin/vmware-remotemks
$ sed -i 's,/etc/vmware/,..//////etc/,g' ~/vmrconsole/bin/vmware-remotemks

Another attempt:

$ vmrconsole

VMware Player: hints messages

Ouch, we are presented by a bunch of hints (your mileage may vary since it depends on the environment), but you surely will see the hint/error message presented on the above screenshot.

This is not a showstopper if you are an English-only user - just click on the OK button and you should be able to work with the console, however I think it is just a right thing to do to fix this little bugger:

$ find ~/vmplayer -iname xkeymap
$ cp -aL /home/vmware/vmplayer/vmware-player-app/lib/xkeymap ~/vmrconsole/
$ cp -aL /home/vmware/vmplayer/vmware-player-app/lib/vnckeymap ~/vmrconsole/
$ vmrconsole

VMware Player: remote console window

We are almost done! At least we achieved the goal we set at the beginning of this article.

Now, it is time to ensure that all file/directory permissions are strict enough and to create a tarball, then place it somewhere so we will be able to use our new tool when the right time comes:

$ cd ~/vmrconsole
$ find . -type d -execdir chmod 0700 '{}' \;
$ find . -type f -execdir chmod 0600 '{}' \;
$ chmod 0700 bin/*
$ cd
$ mv vmrconsole vmrconsole-
$ tar cjpf ~/vmrconsole- vmrconsole-
$ ls -lh vmrconsole-
-rw-r--r-- 1 vmware vmware 13M Nov 24 07:06 vmrconsole-

P.S. Oh, boy, it takes 2 hours to investigate and to come up with a solution, then 8 hours to write an article to describe steps to reproduce! I hope that somebody has found this article useful and I would appreciate any comments.