Random Software Exploration and Exploitation - Input Director
by Keramas
Come along for a journey exploring software that set off my spidey senses for finding juicy ways to take advantage of it in malicious ways. Input Director
is a software-based KVM, and while it has some built in security, misconfigurations could lead to severe consequences in corporate network environments. Get ready to reverse a network protocol and whip up a script for remote system compromise.
TL;DR
If using Input Director on any hosts, be absolutely sure to set a solid, lengthy password for encryption. Failure to do so can result in system compromise due to the ability to send packets to open and execute remote files without any kind of checks. Selecting the security option to limit to specific hosts or subnets is not enough, as this can be bypassed with source IP spoofing.
As a user, do not just install and use random software all nilly-willy on corporate machines, and system administrators should be aware of what is being installed on employee machines to prevent risk.
This blog is not detailing innate vulnerabilities or bugs in the software–this is purely exploiting intentional functionality due to misconfigurations that are possible.
Scanner + remote file execution proof-of-concept for misconfigured Input Director instances:
Prologue
With many people at home due to Covid-19, the desire to create an efficient environment between work machines and personal machines is understandable.
My buddy approached me asking if I had heard of “Input Director”, which he learned of by another friend. Unfamiliar with it, I used good ol’ Google, and when I saw that it was a software-based KVM solution, my interest was piqued to see what kind of things I could do to abuse it.
The reason he asked was that a coworker had recommended it to him for easily switching between a corporate-issued laptop and a personal laptop, and he had wanted to see if I had an opinion on it. Whether he knew it would trigger my curiosity and set me off on a weekend hacking frenzy, I am not too sure (he did), but it made for a fun weekend research project nonetheless!
Exploring the program
This software acts as a virtual KVM switch, which allows keyboard/mouse input (and other features) to be transferred to other hosts over a network connection so it can be controlled. The same software needs to be run on both systems, while one will be set to “master” mode and another to “slave” mode. Essentially a client and server where it is (mainly) one way commands issues from the client to the server.
As this is essentially a remote access tool when you boil it down to its core, there is a lot to play with and look at. It uses a virtual-device driver, several executables/helper executables, uses an RPC protocol over UDP, and other fun-filled rabbit holes. While I took a cursory look at everything, I will only really be focusing on the network-side of things for the moment.
Protocol Exploration
I installed Input Director on two Windows 10 virtual machine hosts, and had Wireshark running on one of them to record traffic while performing actions.
The following is an example of the traffic seen.
Analyzing this a bit, there were only a couple of aspects that were changing from packet to packet. Luckily, 8 years ago someone did a large amount of research on this protocol, and the general construction was outlined on GitHub, which saved me quite a bit of time!
I decided to start building out a Python script to construct packets, and then make a session request while spinning up my own listening port on 31234 to listen for callbacks from the host. Sending a session setup
packet (“\x1A” flag) with a 16-byte GUID, which can be arbitrary (I used “0xdeadbeefdeadbeef” x2), if the target host is open, it will respond with an ACK-like packet over port 31234.
from pwn import *
sessionPacket = dict ({
"magicHeader": b"\x17\x22\x01\x00\x03\x8F\x3B\xD4\x17\x22\x01\x00",
"headerLength": b"\xB4\x00\x00\x00",
"protocolVersion": b"\x13\x00\x00\x00",
"packetType": b"\x1A\x00\x00\x00", # -> This is the RPC command for session start
"packetLength": b"\xA4\x00\x00\x00",
"sequenceNumber": b"\x01\x00\x00\x00",
"sessionKey": b"\xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef",
"sourceAddress": b"\xC0\xA8\x19\x01",
"sourcePort": b"\x02\x7A\x00\x00",
"headerLengthRepeat": b"\xB4\x00\x00\x00",
"encType": b"\x00",
"encPwTestAndUnknown": b"\x00\x00\x00",
"data": b"\x90" * 100,
})
def genPacket(packettype):
buffer = b""
for key in packettype.keys():
buffer += packettype[key]
return buffer
r = remote("172.16.185.132", 31234, typ="udp")
packet = genPacket(sessionPacket)
r.send(packet)
r.close()
Executing the above and observing the listener, the remote host sends an ACK message which is the “1B” byte in the hex received.
This simple process can be used to determine if a host is utilizing encryption or not (more on this later), but if a host is employing encryption, the same ACK will not be received, and it should come back as an error due to differing configuration settings.
Now that communication with the host is successful, the next step was to ensure that other commands could be sent to the host. To test this, an improved script was written out, and packets were constructed to send an RPC command for a “ctrl+alt+delete”.
As seen above, it works–so how to abuse a password-less host?
Before diving into more malicious things that can be done, Binary Ninja was used to disassemble and decompile the code for the main InputDirector.exe binary. There are A LOT of subroutines to parse through when it comes to looking for interesting things, so where to start? Well, thanks to the research done long ago by uix5
, and also as observed in the packets, we have a magic value, and the packet construction needs to take place somewhere in the code. Because of this it’s possible to search for that hex string, and we find sub_42c7b0
which shows the packet being constructed.
The hardcoded magic value for the packet is shown, and each byte of the packet is being placed into sequential offsets of the memory address at EAX. Ultimately what I was interested in finding was a switch case area in the code where each RPC command byte is parsed. To do this, we can work backwards from this routine. Checking the stack trace in WinDBG we can see the functions called prior to this.
Tracing these functions back within Binary Ninja, this nice subroutine of code can be found where different code paths are indeed swapped depending on the RPC command issued.
If we want to analyze the packet construction in realtime, we can set a breakpoint on the original subroutine, and monitor the values. As an example, once the breakpoint is hit and the instructions are stepped through, analyzing the spot in memory that is being written to looks just like our packet start should.
I won’t be going too deep into the assembly or code right now, but it’s possible I look at it again if anything interesting pops up due to fuzzing endeavors.
RTFM for RCE
After I was able to get a grasp on the protocol, I built out a Python script to communicate with hosts running the Input Director service. Based on what I knew about the program, my initial goal was to essentially build out a “Rubber Ducky” type deal where you can just send keystroke payloads to do things.
However, I should have read the usage documentation beforehand, because there is a juicy feature and flag (not present in the research from 8 years ago) that allows for a host to open and execute a file from a designated share.
I captured the traffic to analyze and noticed it was using “\x21” as a command flag for this (located at offset 0x3D), and the end of the packet contains the UNC path + file string with a null terminator.
With this in mind, I spun up an SMB share on an Ubuntu VM with Impacket, constructed the packet in my script, and instructed it to retrieve a test file to open.
This also has the added bonus of grabbing an NTLMv2 hash from the target user which can be subjected to cracking. A text file is boring though. How about an executable? I used a separate tool to create an AV-evasive Cobalt Strike launcher and hosted that on my SMB share and instructed the host to grab it.
Doh! It downloads and tries to open it, but there is a confirmation pop-up that warns about safety–yadda, yadda. Luckily, there is no need to worry about this since we can send arbitrary keystrokes. All that needs to be done is to send a “tab+tab+enter” combination to accept the pop-up, and the payload should be executed.
I had a bit of trouble sending single key commands as my packets always seemed to be a bit off when I built them, so instead, I decided to use the “mirroring feature” and just copy out the raw bytes from Wireshark. The mirroring function is called with the “\x0B” flag, and is basically the same as a regular input command “\x02” but it does not switch the focus of the master (not that it matters for this anyway). From that byte stream, we just want the actual keystroke portion.
\x09\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\xe9\x0f\x01
Input Director uses the Windows KEYBDINPUT
structure to send along input data, and the structure is the following:
typedef struct tagKEYBDINPUT {
WORD wVk;
WORD wScan;
DWORD dwFlags;
DWORD time;
ULONG_PTR dwExtraInfo;
} KEYBDINPUT, *PKEYBDINPUT, *LPKEYBDINPUT;
This means we can edit the bytes accordingly to simulate keyboard presses. The “\x09” is the virtual-key code and the “\x0f” is the scan code. What is important is the dwFlag
parameter which will dictate what is happening with the key. In this case, we want to hit and release the keys each time which is accounted for by repeating the same bytes, but with a “\x02” for this flag:
\x09\x00\x0f\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\xe9\x0f\x01
So, we will use this twice (two tabs and two tab key releases), and then follow it with a similar byte stream but replacing the “\x09” with a “\x0d” for ENTER and its scan code “\x1c”. No release key byte stream is needed, so in total, five different packets are sent for this.
\x0d\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\xe9\x0f\x01
This can be confirmed by just opening a text file with note pad an observing the cursor.
With this working, and putting it all together, the following is a fully working example of the proof-of-concept to force a listening host to download a launcher from an SMB, open it, accept the warning message, and execute it for a reverse connection.
What about firewall rules?
Input Director indeed comes with built-in security options for limiting communication to certain subnets or specific hosts. However, without any kind of spoofing mitigation present on a target network, this is easily bypassable by constructing spoofed source IP packets with Scapy.
send(IP(src=spoofedIP,dst=targetIP)/UDP(sport=50505,dport=31234)/packetdatahere)
Naturally, this requires some luck or recon-based knowledge for what subnets and/or hosts are permitted. While it won’t be possible to detect password-less hosts with a session start command alone since the “ACKs” cannot be received (The host will try to reply to the spoofed IP, not yours.), it is still possible to determine this by using a session start command followed by a file open command that leads to a non-existant file on a Burp Collaborator server or another host that is listening for SMB connections.
With that said, the following is an example where the host is only permitting connections from the 192.168.25.0
subnet. The modified script includes Scapy logic to send all the commands previously, but from a spoofed IP within the permitted subnet.
This is additionally possible with the specific host setting as well; however, this will take much more guesswork. The other caveat with this option is that the spoofed host needs to be on and active or else the connection will fail. This does not apply for the subnet setting.
Encryption
Now that I have laid out most of the malicious things that are possible, it’s important to note that all of the above is possible only when the listening host is not using encryption. Hosts that have encryption enabled will reply with a “\x19”-type packet that complains about unmatched encryption/non-matching password.
When setting a password on hosts with Input Director, the value is stored as a base64’d SHA256 hash in the registry:
reg query "HKLM\SOFTWARE\WOW6432Node\Input Director\Sec"
This is only retrievable by an administrator user; however, in the event that a host that has been designated as a master becomes compromised, it is possible to dump the hashed keys for each of the listening hosts, which can then be subjected to hash cracking.
In the event that a plaintext is retrieved, it is very possible to use that to AES encrypt fabricated packets so that the hosts accept commands, but that is for another day perhaps.
Scanner and Remote File Execution Tool for Password-less Hosts
As this runs over UDP and tools such as Nmap do not really have any kind of fingerprinting capabilities for this service, I wrote a multi-function Python-based proof-of-concept to detect and abuse hosts with open instances of Input Director running on them.
Super niche perhaps, and the scanner isn’t perfect, but if you happen to land on a network during a penetration test where you see this installed, or happen to see the default UDP port 31234 open on a host, maybe it comes in handy!
Future Fuzzing Fun
For funsies, I also set up a quick fuzzing harness for Input Director using Boofuzz
. This is just a starting point for initial fuzzing attempts, and I was only quickly cycling through various RPC command flags to look for crashes, but perhaps I will develop this a bit more for better fuzzing and see what turns up. Several RPC commands with malformed input elicited crashes, but looking at the debugger it wasn’t necessarily too interesting when looking at where/how it crashed. It needs a lot more work to be efficient, such as hooking it up to Process Monitor, so that Boofuzz knows when crashes happen. But again, for another day perhaps! My weekend project is complete for the moment.
Conclusion
Get client rekt indeed! But on a serious note, while setting a strong password for encryption when using this goes a long way to mitigate against malicious activity, it’s probably not a good idea to install this on your corporate machines without admin approval and configuring since it could be dangerous.
If using Input Director, be absolutely sure to set a solid, lengthy password. Failure to do so can result in system compromise due to the ability to send packets to open and execute remote files without any kind of checks. Selecting the security option to limit to specific hosts or subnets is not enough, as this can be bypassed with source IP spoofing.
As a user, do not just install and use random software all nilly-willy on corporate machines, and on the otherside of the equation, system administrators should be aware of what is being installed on employee machines to prevent risk.
On a sidenote, randomly exploring things that grab your curiosity does a lot for both learning and developing/nuturing your current skills, whether that be protocol analysis, reversing, or just scripting out something for fun. Run with wild tangeants and see where they go! (All while abiding by the law of course!)
References:
- https://www.inputdirector.com/
- https://github.com/uix5/info-inpdir
- https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-keybdinput
- https://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
- https://gist.github.com/Keramas/3ad44cefc47e5a8e0872092c15e5ca41