Module 28 - MiniCCC¶
Historical miniclass
This lesson was recovered from the former Sandia minimega site. It is preserved for reference and may describe obsolete software, operating systems, commands, or external resources. Consult the current documentation before applying it.
Introduction¶
The Command and Control (cc) API in minimega provides a mechanism to programmatically execute programs and send and receive files to VMs launched by minimega. In addition, the cc API allows creating TCP tunnels (as well as reverse tunnels) from the host machine to/from any VM.
The cc API supports two modes of communication with VMs:
- The first is a simple TCP connection from the VM to the host via some routable network. In this mode the VMs must be able to communicate directly with the host they are launched on. This can be accomplished with the
tapAPI, or through several other means. - The second connection mode is over either
virtio-serial(for KVM-based VMs), or over a UNIX domain socket (for container-based VMs), which provides a simple I/O layer, similar to a UNIX pipe, between the host and VM. In order to use thevirtio-serialmode, both host and VM must havevirtio-serialsupport. Most modern Linux distributions have this enabled by default. Windows VMs must installvirtio-serialdrivers, available from here. Domain sockets for container-based VMs are created by default when launching containers. Note that to use thevirtioservice, your VM and host both need to havevirtiosupport available.
Both connection modes provide the same features, although the TCP mode can be significantly faster, depending on the underlying network.
Commands issued with the cc API (including file I/O) are always executed in order on the client. This means you can chain commands, such as "send a file, execute that file, then receive the output from that file."
Commands can also contain filters, which allow you to select which VMs execute that command. Filters can be stacked, meaning that you can apply filters such as "execute this command on Windows VMs with an IP in the network 10.0.0.0/24."
miniccc is a virtual machine agent written in Go that can communicate over IP or serial, transfer files, and execute commands. It facilitates communications initiated through calls to the cc API.
Installation¶
This section walks through the process of setting up a VM with a working miniccc client. If you already have an existing miniccc image or just want to review the cc API itself, you can skip to the Commands section in this module.
Windows 7¶
Build a Windows 7 VM
disk create qcow2 /home/ubuntu/w7.qcow 100G
vm config disk /home/ubuntu/w7.qcow
vm config cdrom /home/ubuntu/w7.iso
vm config snapshot false
vm config memory 1024
vm config net 100
vm launch kvm w7
vm start w7
Install virtio for Windows 7¶
wget https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso
vm cdrom change w7 /home/ubuntu/virtio-win.iso
Open Device Manager
- Right click "PCI Simple Communications Controller"
- Update Driver Software
- Browse my computer
D:\vioserial\w7\- Install
- Repeat for the other adapter
Eject the ISO
Building miniccc for x86 windows¶
At the end of /home/ubuntu/minimega/build.bash add
# build windows packages
echo "BUILD PACKAGES (windows)"
echo "protonuke"
GOOS=windows GOARCH=386 go install protonuke
if [[ $? != 0 ]]; then
exit 1
fi
echo "miniccc"
GOOS=windows GOARCH=386 go install miniccc
if [[ $? != 0 ]]; then
exit 1
fi
echo
unset GOOS
And run
Download miniccc¶
Create a tap so the VM can talk to the host via IP.
Start a dnsmasq service so the VM gets an IP automatically
Start a webserver on the host so we can copy over the miniccc binary.
With a browser in the virtual machine navigate to http://1.1.1.1:8000 and download miniccc.exe from either windows_amd64/ or windows_386/ depending on your installation.
Configuring Windows 7 to start miniccc¶
With Notepad create mini.bat file on the desktop with the following contents:
The miniccc client uses several command line switches to control how to connect to minimega, as well as where to store files received for the client, which we'll see throughout this module. Here, we just used the -serial flag to connect using virtio-serial. In Windows, the default virtio-serial cc port is \\.\Global\cc ; note that this is the path we use in this batch file. Move this file to C:\
Open Task Scheduler
- Click 'Create Task'
- Name the task miniccc
- Select Run whether user is logged on or not
- Select Run with highest privileges
- Check Hidden
- Click on the Triggers Tab
- Click New
- Select Begin the Task: At startup
- Click Ok
- Click on the Actions Tab
- Click New
- Click Browse
- Select
C:\mini.bat - Click Ok
- Click Ok
- Click Ok
- Type in a username and password for an Administrative User.
- If the user doesn't have a password you will need to set one first
You can now turn off the virtual machine and delete the tap.
Inject miniccc IP¶
mkdir -p /mnt/kvm
cp /home/ubuntu/win7.qcow /home/ubuntu/win7ip.qcow
qemu-nbd --connect /dev/nbd0 /home/ubuntu/win7ip.qcow
mount /dev/nbd0p2 /mnt/kvm
echo "C:\\miniccc.exe -parent 10.0.0.1" > /mnt/kvm/mini.bat
umount /mnt/kvm
qemu-nbd --disconnect /dev/nbd0
To configure miniccc to connect over TCP instead of serial, we have just used the -parent flag, providing the host/IP of host running minimega.
Ubuntu Server¶
Build an Ubuntu 16.04 image as snapshot false
disk create qcow2 /home/ubuntu/u1604.qcow2 100G
vm config cdrom /home/ubuntu/u1604.iso
vm config disk /home/ubuntu/u1604.qcow2
vm config snapshot false
vm config memory 1024
vm config net 100
vm launch kvm u1604
vm start u1604
Inject miniccc¶
Using a serial connection:
cp /home/ubuntu/u1604.qcow2 /home/ubuntu/u1604ip.qcow2
qemu-nbd --connect /dev/nbd0 /home/ubuntu/u1604.qcow2
mount /dev/nbd0p1 /mnt/kvm
sed -i '$i/miniccc -v=false -serial /dev/virtio-ports/cc -logfile /miniccc.log &' /mnt/kvm/etc/rc.local
cp /home/ubuntu/minimega/bin/miniccc /mnt/kvm/miniccc
umount /mnt/kvm
qemu-nbd --disconnect /dev/nbd0
In Linux, the default virtio-serial cc port is /dev/virtio-ports/cc, which differs from that of Windows (recall that the Windows path was \\.\Global\cc ). Nevertheless, as we previously did with the Windows VMs, we have now set the appropriate path when starting miniccc in our Ubuntu VMs over a serial connection.
Using a connection over IP:
qemu-nbd --connect /dev/nbd0 /home/ubuntu/u1604ip.qcow2
mount /dev/nbd0p1 /mnt/kvm
sed -i '$i/miniccc -v=false -parent 10.0.0.1 -logfile /miniccc.log &' /mnt/kvm/etc/rc.local
umount /mnt/kvm
qemu-nbd --disconnect /dev/nbd0
NOTE: By default, miniccc will create the directory /tmp/miniccc to store state and files in. Files sent to the client will be stored in /tmp/miniccc/files. You can change this directory with the -base flag.
Launch VMs¶
Let's launch the VMs we created and give them IP addresses:
vm kill all
vm flush
clear vm config
vm config disk /home/ubuntu/u1604.qcow2
vm config memory 1024
vm launch kvm u1604
vm config disk /home/ubuntu/win7.qcow
vm launch kvm win7
vm config net 100
vm config disk /home/ubuntu/u1604ip.qcow2
vm launch kvm u1604ip
vm config disk /home/ubuntu/win7ip.qcow
vm config tag foo:bar
vm launch kvm win7ip
vm start all
tap create 100 ip 10.0.0.1/24 mytap
dnsmasq start 10.0.0.1 10.0.0.2 10.0.0.254
We are now ready to exercise the cc API, which we will do in the next section.
Commands¶
This section describes the cc API in greater detail.
Client Information¶
cc lists a count of the number of connected clients
cc <tab> shows all the commands available
background commands exec log process responses send
clients delete filter prefix recv rtunnel tunnel
cc clients lists the connected clients
$ cc clients
host | uuid | hostname | arch | os | ip | mac | namespace
ubuntu | 2220ebfc-d3bd-4caf-8c38-0666309dae5d | WIN-8QHRPBH7HPK | 386 | windows | [fe80::f0d3:56e4:bd6:2a3c 10.0.0.175] | [00:05:5d:96:af:ff] |
ubuntu | 79832887-a373-4bd8-bee7-50ba7e098b81 | WIN-8QHRPBH7HPK | 386 | windows | [] | [] |
ubuntu | bc59214a-86aa-46cb-8810-a62dca455f9d | ubuntu | amd64 | linux | [10.0.0.67 fe80::c2c5:20ff:fe24:be1b] | [c0:c5:20:24:be:1b] |
ubuntu | fa3ce3a5-27f0-4eac-8937-72fe1da13722 | ubuntu | amd64 | linux | [] | [] |
Clients report their UUID, hostname, OS, architecture, IP and MAC addresses to minimega. This information is updated periodically, so if an IP changes, minimega will see the change.
Client information is stored by UUID in minimega. When a client responds to a command, the response is logged by minimega in a subdirectory named after the UUID for that client. We'll discuss responses later.
Executing Commands¶
Executing commands is simple - just issue cc exec with the command you want to execute:
When the client responds to a cc exec command, standard out and standard error are stored in the files stdout and stderr respectively.
In some cases, you may need to wrap a command in quotes or escape special characters. You can inspect current in-flight commands with cc commands, which shows the contents of the command, any applied filters (more on that later), and how many clients have responded:
There are two things to note at this point. First, commands don't go away until you delete them with cc delete command. This means that if you were to reset a VM or start new VMs, they would all see and execute this command. Second, the response from the client isn't printed to screen. Instead, responses are logged in a special directory structure in minimega's base path. You can browse to the responses yourself, or just use the cc responses command to view responses from clients, like this:
$ cc responses all
ubuntu: 1/2220ebfc-d3bd-4caf-8c38-0666309dae5d/stderr:
exec: "echo": executable file not found in %PATH%
1/79832887-a373-4bd8-bee7-50ba7e098b81/stderr:
exec: "echo": executable file not found in %PATH%
1/bc59214a-86aa-46cb-8810-a62dca455f9d/stdout:
hello
1/fa3ce3a5-27f0-4eac-8937-72fe1da13722/stdout:
hello
As you can see Windows isn't too happy. It doesn't know where the echo executable binary is.
In all of the examples so far, each command is run by every connecting client. Sometimes you want to only send commands to specific clients, say, by hostname or IP range. The cc filter command allows you to set a client filter that will be applied to every command issued while the filter is assigned.
Let's set a filter for Windows
# first clear the buffer of responses
clear cc responses
# verify that responses have been cleared
cc responses all
# set a client filter on Windows hosts
cc filter os=windows
# and show the current filter
cc filter
Any commands issued from now on will be executed only by clients that meet all filter fields. You can set multiple filter arguments, such as restricting commands to only Windows machines in a specific IP range.
Now run the the same command again, this time with cmd
cc exec cmd /c 'echo hello'
$ cc responses all
2/2220ebfc-d3bd-4caf-8c38-0666309dae5d/stdout:
hello
2/79832887-a373-4bd8-bee7-50ba7e098b81/stdout:
hello
Some additional information on filters: You can filter on UUID (uuid), hostname (hostname), architecture (arch), OS (os), IP (ip, or ip_range for CIDR notation), MAC address (mac), and tags (tags).
You can also combine filters
# set a client filter on the 1.0.0.0/24 network for Windows clients
cc filter os=windows ip=1.0.0.0/24
To clear the current filter, you can use clear cc filter.
Another example for a basic Linux command would be to set a filter for Linux hosts and run something like cc exec ls / to get a directory listing of the client's root; we'll leave this as an exercise if you want to experiment more here.
Tags are set at VM boot with
The miniccc client itself can also add tags to the info for the VM the client is running on. This enables third-party tools to upstream information about a VM to minimega via miniccc. Tags are key/value pairs, and are added simply by using the -tag switch on a running miniccc instance:
Examining Responses¶
We previously saw that we can use cc responses all to print all responses received from cc. Let's now go into more detail on the cc responses API.
The responses command simply outputs all files of the specified command, indexed by ID as shown in cc commands, with the name of the file. If you don't want the filename shown, you can suffix the response command with raw:
Additionally, note that all is a keyword that otherwise replaces a command ID. We can view responses based on ID with
Keep in mind that if we start a new machine and runs miniccc, it will issue all past commands if it meets the prior filters.
Let's again use cc commands to list our past command ID numbers:
$ cc commands
host | id | prefix | command | responses | background | sent | received | level | filter
ubuntu | 1 | | [echo hello] | 4 | false | [] | [] | |
ubuntu | 2 | | [cmd /c echo hello] | 2 | false | [] | [] | | os=windows
You can delete commands you don't want to run on future miniccc sessions by referencing the ID number
or delete them all with
You can also delete past responses from <base>/miniccc_responses with
or delete them all with
Command Prefixes¶
It's useful to group similar commands into named groups. The cc prefix command allows creating groups of commands by associating all issued commands after a cc prefix command with that prefix:
$ cc prefix holdmybeer
cc filter ip=100.0.0.0
cc exec echo 'this'
cc exec echo 'is'
cc exec echo 'a'
cc exec echo 'test'
clear cc prefix
cc commands
You can then use the prefix name instead of a command id when using cc responses:
or when deleting commands:
Background Commands¶
Sometimes you want to execute a command on the client that doesn't return, such as a daemon or other agent. To tell cc not to wait on a response for a given command, use cc background instead of cc exec:
cc filter os=linux
cc background sleep 30
cc process list all
host | name | uuid | pid | command
ubuntu | 3 | bc59214a-86aa-46cb-8810-a62dca455f9d | 2306 | sleep 30
ubuntu | 3 | fa3ce3a5-27f0-4eac-8937-72fe1da13722 | 2319 | sleep 30
cc process kill 2306
This launched the sleep 30 command in the background. When using background mode, miniccc will execute the command and immediately move on to the next command queued to run.
We also used the cc process API to list all backgrounded processes and then to kill a running background process by supplying a process ID (in this case, 2306). Be careful with the filtering on process killing, so that you don't kill needed processes.
If the virtual machine reboots after connecting to cc, miniccc will prevent a reconnection from that UUID.
File I/O¶
The cc API supports sending and receiving files to and from the client. Files can be specified by name or glob (wildcard). When sending files, they must be rooted in a specific directory provided by minimega.
Sending Files¶
In order to send files to a client, the files must be rooted in the files subdirectory in the minimega base directory. By default minimega uses /tmp/minimega/files. Let's send a simple bash script to the client by placing a file foo.bash in /tmp/minimega/files (or whatever base directory your minimega uses). Have foo.bash do something simple, like echo:
Now we'll send it to the client by using the cc send command.
Clients will fetch the files from minimega before moving on to any other commands. You can inspect file send commands with cc commands, just like we did with cc exec before.
We can also send globs (wildcard) of files, using the * operator. For example:
Files will appear in the files subdirectory in the client's base directory. By default, this is /tmp/miniccc/files.
At this point we can both send and execute a file on the client. Let's execute the script we sent a moment ago:
After the client responds to the cc exec command, we can check the output.
Receiving Files¶
Receiving files is just like sending files, except that you can specify any path on the client to receive files from. Globs (wildcards) work with receiving files too, so you can receive entire directories of files.
Let's modify our example above and send a bash script that creates a file, execute it, and then get that file back. We'll have our bash script create the file /foo/bar.out, and name it bar.bash:
Use cc responses to check the file received after it completes. You'll notice in the output that the file bar.out was received and stored in the response subdirectory /foo. This is because the /foo subdirectory was specified with the recv command.
Full Filesystem Access¶
The cc mount API can be used to mount the guest filesystem on the host. This is accomplished by a 9p server integrated into miniccc that serves the guest's filesystem over the existing connection to the command and control server. cc mount is fully integrated with namespaces - you may mount a VM's filesystem to the head node, regardless of which host is actually running the VM. In order to mount the filesystem, you'll need to use either the name or UUID of the VM. For example, to mount the filesystem for our u1604 VM to /mnt:
To list existing mounts, run it with no arguments:
Let's unmount the VM:
Without an argument, clear cc mount clears all mounts; in general, a UUID or name or path can be supplied as an optional argument. This also occurs when you call clear cc.
TCP Tunnels¶
The cc interface allows creating forward and reverse TCP tunnels over the cc connection, including over virtio-serial connections. This is similar to the forward and reverse tunnel support in ssh. To create a forward tunnel, that is, a listening port on the minimega host that is tunneled to a destination host and port from the perspective of the client, use cc tunnel. When creating a forward tunnel the UUID of the client must be specified. The destination host can be localhost or any other host reachable from the client.
Let's start a process listening on a port and create a tunnel
clear cc responses
cc filter uuid=bc59214a-86aa-46cb-8810-a62dca455f9d
cc background bash python -m SimpleHTTPServer
cc tunnel bc59214a-86aa-46cb-8810-a62dca455f9d 4444 localhost 8000
From the node running the VM navigate to http://127.0.0.1:4444 and it will redirect the traffic over the serial cc channel to the virtual machine.
You can also tunnel in the opposite direction. A reverse tunnel, a listening port on the client tunneled to a host and port reachable from the minimega host, can be created by using cc rtunnel. Reverse tunnels do not require a UUID to be specified, and instead use the current client filter to restrict which clients create the tunnel. That means you can tunnel a port for every client to a resource outside of the experiment.
Start a web server on the host and tunnel out from the virtual machine:
Now on the virtual machine if you connect to localhost:5555 you will see the web server from the host.
You can't delete tunnels without killing miniccc on the VM or the minimega process.
Logging¶
You may adjust the log level of clients at runtime from minimega:
This will apply to clients matching the current filter.
Authors¶
The minimega authors
Created: 30 May 2017
Last updated: 25 May 2022