VNSee

author photo

by

Samir Ghanem

TL;DR

The VNC protocol is used by many IT administrators to remotely connect and take control of user sessions. The VNC server receiving the connection request has access to the cleartext credentials provided by the administrator.

We created a tool that patches an installation of a RealVNC Connect server to harvest the cleartext credentials used by unsuspecting administrators, as well as open a covert back door. This tool is useful for lateral movement when compromising a low value machine that is administered via RealVNC.

VNSee is available on our GitHub page, enjoy!

The Scenario

Evil Bob’s malicious Office Macro phishing campaign was successful and has deployed malware onto a corporate computer. Unfortunately, the corporate computer is from the marketing department. As you know, half-baked social media posts and blog posts *cough-cough* won’t receive much for ransom and certainly not something evil hackers will write home about. But hackers gotta hack and administrators gotta administer, so the friendly sysadmin Alice has decided to use RealVNC to remotely control workstations in the organisation. Since Alice is security conscious, she opted for the Enterprise License and used Windows Authentication for added protection. As hackers, this didn’t stop us and made us more motivated. If administrators are connecting to the VNC server using Windows credentials, can we capture them?


Evil phisher writing home about their adventures
Evil phisher writing home about their adventures

RealVNC, the real MVP

RealVNC works on a Client-Server model where the server is installed on the target device. In our example this would be the marketing department workstation. A sysadmin would use the RealVNC Client to connect to the various servers and take control of the user’s session. The Remote Framebuffer Protocol (RFP) is used to communicate between the server and the client.

In this blog post we will be focusing on RealVNC version 5.0.5, however the tool we created supports the latest version, which at the time of writing this is 6.7.2.

Finding Credentials

They’ve done studies you know, 60% of the time, a packet capture works every time. Unfortunately, we must have hit the 40% when nothing interesting is in the PCAP. We can see the RFP handshake and then what appears to be encrypted traffic. Cryptography is hard so Wireshark is promptly shut (you win this time enterprise license!). Despite having to work a little harder to find the creds, not seeing a clear text password sent over the network does spark joy.

At this point we couldn’t help ourselves; we went from taking a PCAP to disassembling a binary real quick. We loaded up vncserver.exe into the disassembler and started having a poke around. We go straight to the list of imports and it doesn’t take long before we spot our good friend LogonUserW.
The function prototype looks like this:

BOOL LogonUserW(
  LPCWSTR lpszUsername,
  LPCWSTR lpszDomain,
  LPCWSTR lpszPassword,
  DWORD   dwLogonType,
  DWORD   dwLogonProvider,
  PHANDLE phToken
);

Yes, that’s right, username, domain and password are all strings. Is nice!!!


Totally necessary disassembly screenshot
Totally necessary disassembly screenshot

To verify that the LogonUserW API is being called, we fire up WinDBG, attach it to the VNC server process and add a breakpoint at the LogonUserW function call.


Breakpoint hit on LogonUserW with string arguments
Breakpoint hit on LogonUserW with string arguments

Great , so the credentials are in-fact visible and in cleartext at the call to LogonUserW. This is great news. Now the plan is to load up an evil DLL, hook the LogonUserW API, save off the credentials and then continue as normal. Using Process Monitor we can see what DLLs are loaded by vncserver.exe and can hopefully spot one we can take advantage of.


Process monitor showing missing dnssd.dll
Process monitor showing missing dnssd.dll

Looks like vncserver.exe never found ‘dnssd.dll’. However, being the stable, enterprise solution it is, it kept trucking along making do without it. Sounds like the perfect opportunity to be good samaritans and provide it with the missing DLL that is not at all malicious, promise.

Making an Evil DLL

At this point we simply want to provide a DLL to vncserver.exe by the name dnssd.dll. That DLL will hook the LogonUserW API do some evil and then call the real LogonUserW. To do this we will use Microsoft’s Detours that does exactly that - “re-route Win32 APIs underneath applications”.

Detours makes our job easy. Simply save off the original LogonUserW function pointer (RealLogonUserW) and create another function (EvilLogonUserW) that will replace calls to LogonUserW. EvilLogonUserW looks something like this:

BOOL WINAPI EvilLogonUserW(
    LPCWSTR lpszUsername,
    LPCWSTR lpszDomain,
    LPCWSTR lpszPassword,
    DWORD   dwLogonType,
    DWORD   dwLogonProvider,
    PHANDLE phToken
) {
    /* Do EVIL here! */

    return RealLogonUserW(lpszUsername, lpszDomain, lpszPassword, dwLogonType, dwLogonProvider, phToken);
}

Pretty simple. We build a DLL named dnssd.dll and put it in the same directory as vncserver.exe. It works! But wait, for some reason our DLL loads but then quickly unloads. Annoying, I know, but we’re already hooking functions - one more won’t hurt. We used Detours to hook the FreeLibrary API and just don’t free our own library when called. Lazy, but it works.

// Detour FreeLibrary call that does not free us
static HMODULE OurModule = NULL; // Assign this at the start of DllMain
	
BOOL WINAPI EvilFreeLibrary(HMODULE mod) {
    if (mod == OurModule)
    {
        // kthnxbye
        return TRUE;
    }
    return RealFreeLibrary(mod);
}

Boom! Success! Now we kick back and watch the creds roll in.

Bonus Round

Harvesting credentials is nice, but have you ever installed a backdoor? Since we have hooked VNC server at the point of authentication we can also choose to bypass authentication. We simply add some additional code to allow any user who enters the special password access.

// Check for backdoor password
if (wcscmp(L"Skylight", lpszPassword) == 0) {
	OpenProcessToken(GetCurrentProcess(), GENERIC_READ, phToken);
	ret = TRUE;
}
else {
	ret = RealLogonUserW(lpszUsername, lpszDomain, lpszPassword, dwLogonType, dwLogonProvider, phToken);
}

As you can see, if the password matches the string “Skylight” we get the authentication token of the current process (vncserver.exe) which is running as system and then return TRUE.

Version 6.7.2 Update

The latest version of RealVNC’s VNC Server (6.7.2) doesn’t try to load the missing dnssd.dll and all other DLLs are successfully loaded too. With a little re-engineering we turned our DLL into a “proxy DLL”. At this point we could choose any DLL to proxy. We chose version.dll since vncserver.exe only imports three functions from the library. There are a million blog posts and guides on writing proxy DLLs so we won’t bore you with the details. You can build VNSee for both versions 5.0.5 (dnssd.dll) and 6.7.2 (version.dll) from the source code on GitHub.

Conclusion

There you have it. Going from lame social media content to harvesting admin credentials and installing a stealthy backdoor and all it took was a can-do attitude and Windows Detours.