2015-02-19
Spawning Windows Commands in Wasp Lisp and MOSREF
It's been a while since I last wrote about MOSREF and Wasp Lisp. MOSREF is the secure remote injection framework written in Wasp Lisp. It facilitates penetration testing by enabling a console node to spawn drone nodes on different machines. The console handles communication between nodes and can run lisp programs on any node.
The console can execute programs on other nodes with the input and output redirected to the console. One use for this is to create remote shells. MOSREF uses the Wasp Lisp function spawn-command
. The implementation for this in Linux is fairly small and simple. On Windows drones it's somewhat more difficult. It's not implemented in current Wasp Lisp and attempting to use the sh
command in MOSREF or the spawn-command
function in Lisp fails with an error.
I've been meaning to try implementing this for quite a while and finally got around to it recently. I'm doing the work in the win_spawn branch of my github fork of WaspVM. With that version of Wasp Lisp and MOSREF built with Windows and Linux stubs available you can spawn Windows commands and capture the output:
>> (define a (spawn-command "cmd.exe /c echo hi"))
:: [win32_pipe_connection 5179A0]
>> (wait a)
:: "hi\r\n"
>> (wait a)
:: close
Bidirectional communication works too:
>> (define a (spawn-command "cmd.exe"))
:: [win32_pipe_connection 517770]
>> (wait a)
:: "Microsoft Windows ..."
>> (send "echo hi\n" a)
:: [win32-pipe-output 517748]
>> (wait a)
:: "echo hi\nhi\r\n\r\nE:\l>"
>> (send "exit\n" a)
:: [win32-pipe-output 517748]
>> (wait a)
:: "exit\n"
>> (wait a)
:: close
With that implemented and some minor changes to MOSREF to remove the check for Windows you can interact with remote Windows nodes. I made a quick video demonstrating this. There is no sound but it shows a linux console on the left and a windows shell on the right running in a VM.
I create a Windows drone and copy it to a location the Windows VM can access using the MOSREF cp
command. This actually copies from the console where the drone was create to another Linux drone called tpyo
. The Windows VM is running on the machine running tpyo
and access the drone executable. This is run in the VM to connect to the console.
Once connected I run a few Lisp commands on the Windows node. The lisp is compiled to bytecode on the console, and the bytecode is shipped to the drone where it executes. The result then goes back to the console. This is all normal MOSREF operation and works already, I just do it to ensure things are working correctly.
Next I run a sh
command which executes the command in the windows VM with the result sent back to view on the console. Then I do a typo which breaks the connection because of a bug in my code, oops. I recover
the drone, reconnect, and run a remote shell like I originally intended. This spawning of commands on Windows is the new code I have implemented.
The video is available as mosref.webm, mosref.mp4 or on YouTube.
The implementation on Windows required a bunch of Win32 specific code. I followed an MSDN article on redirecting child process output and another on spawning console processes. This got the basic functionality working pretty quickly but hooking it into the Wasp Lisp event and stream systems took a bit longer.
Wasp uses libevent for asynchronous network and timer functionality. I couldn't find a way for this to be compatible with the Win32 HANDLE's that result from the console spawning code. I ended up writing derived connection
, input
and output
Wasp VM classes for Win32 pipes that used Win32 Asynchronous RPC callbacks to avoid blocking reads. My inspiration for this was the existing Wasp routines to interact with the Win32 console used by the REPL.
A connection
is basically a bidirectional stream where you can obtain an input
and an output
channel. A wait
on an input
channel receives data and a send
on the output
channel transmits data. When wait
is called on the input channel a callback is invoked which should do the read. This can't block otherwise all Wasp VM coroutines will stop running. The callback instead sets a Win32 event which notifies a thread to read data and post the result back to the main thread via an asynchronous procedure call. A send
on the output channel invokes another callback which does the write. Although this can technically block if the pipe buffers are full I currently call Write
directly.
The Wasp VM scheduler has code that checks if there are any active processes running and can do a blocking wait on libevent for notification to prevent spinning a polling loop. This had the side effect of preventing the asynchronous procedure call from running as Windows only executes it at certain control points. I had to insert a check that although our reading process was de-scheduled waiting for the APC, it was in fact still around and needed the event loop to spin so a call to SleepEx
occurs for the APC to run.
I'm still working on testing and debugging the implementation but it works pretty well as is. Before I submit a pull request I want to clean up the code a bit and maybe combine some of the duplicate functionality from the console handling code and the pipe code. I also need to check that I'm cleaning up resources correctly, especially the spawned reading/APC handling threads.
Some minor changes were needed to other parts of Wasp Lisp and those commits are in the github repository. They involve environment variable handling on Windows. First I had to enable the support for it, and then change it so on Windows the environment names were all uppercase. This avoided issues with Wasp looking for commands in PATH
vs the Path
that was set on my machine. On Windows these are case insensitive.
For building a Windows compatible Wasp VM stub and REPL I used a cross compiler on Linux. I used the gcc-mingw-w64
package in Ubuntu for this. Build wasp VM with:
$ ./configure --host=i686-w64-mingw32
$ OS=MINGW32 CC=i686-w64-mingw32-gcc make
This puts the Windows stub in the stubs
directory. I copied this to the stubs
directory of the node running the console so it could generate Windows drones. I had to build libevent for Windows using the same cross compiler and tweak the Wasp Makefile.cf
to find it. Removing the -mno-cygwin
flag was needed as well. I'll do patches to have the makefile work for cross compilation without changes if no one else gets to it.