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 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
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.
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
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.