Requestor is a Command-and-Control (C2) written in Rust that communicates over HTTPS. This tool was made for the RIT Red Team to use during compeitions such as IRSeC, ISTS, UB Lockdown, etc.
To set up this C2, you must have a server that is accessible by a competition host via port 443. This server must have docker installed.
The first step is to clone the requestor repository.
$ git clone https://github.com/drew-young/requestor.git
Following this, navigate to the docker-server directory and use docker compose to start the server.
$ docker compose up --build -d
Ensure the docker containers are running.
$ docker ps
The docker-compose.yaml file contains information regarding the server, database, and reverse proxy. You can edit this file to change variables such as:
- MYSQL_DATABASE: The database created that stores commands, hosts, teams, etc.
- MYSQL_USER: The default user that is created for MySQL.
- MYSQL_PASSWORD: The password for the user created.
- MYSQL_ROOT_PASSWORD: The password for the root account in MySQL.
- DATABASE_URL: The URL of the database (can use Docker hostnames).
- PWNBOARD_URL: The URL of Box Access for PWNBoard.
- INIT_PASSWORD: The password needed to initialize the C2 server with the Database.
- RESET_PASSWORD: The password needed to reset the whole C2 database.
- PWNBOARD_ENABLED: "true" or "false" for if the C2 should post data to PWNBoard.
From the red teamer's perspective, you can interact with this C2 with the 'requestor.py' tool. This script is located in the repository. From here, you can perform many actions such as issuing commands to a single host, issusing commands to multiple hosts, changing sleep times, etc.
To deploy Requestor in a competition environment, the following steps must be followed.
Ansible is used to automate the deployment process.
The first step is to update the inventory.yaml file. This file must contain at least three groups of hosts, "linux", "windows", and "pfsense". Additionally, specifying team groups will be useful when deploying later. Here is an example inventory.yaml file.
---
all:
children:
pfsense:
vars:
ansible_user: admin
ansible_password: changeme
children:
routers:
hosts:
192.168.253.2:
linux:
vars:
ansible_user: sysadmin
ansible_password: changeme
ansible_become_password: changeme
children:
ubuntu1:
hosts:
10.[1:14].1.10:
windows:
vars:
ansible_connection: psrp
ansible_psrp_cert_validation: ignore
ansible_psrp_protocol: https
ansible_psrp_auth: ntlm
ansible_psrp_credssp_auth_mechanism: ntlm
ansible_become_method: runas
children:
ad:
vars:
ansible_user: "Administrator"
ansible_password: "Change.me!"
ansible_become_user: "Administrator"
ansible_become_password: "Change.me!"
hosts:
10.[1:14].1.60:
# team groups
team01:
vars:
team_number: '01'
hosts:
192.168.253.2:
10.1.1.10:
10.1.1.60:
After this file is created, the variables must be updated. The variables are located at ansible/roles/requestor/vars/main.yaml.
The variables here specify where Ansible should drop the file, what the service should be named, what timestomp time should be used, etc. Below is an example vars.yaml file:
pfsense_binary: "/usr/bin/getty" #binary location for pfsense
pfsense_service: "/etc/rc.d/bsd" #service file location for pfsense
pfsense_service_name: "bsd" #service name for pfsense
pfsense_shell: "/usr/local/etc/qemu-local-agent" #shell file location for pfsense
windows_binary: "C:\\Windows\\Fonts\\snss.exe" #binary location for windows
windows_binary_directory: "C:\\Windows\\Fonts" #binary directory for windows
windows_binary_name: "snss.exe" #binary name for windows
windows_regkey_name: "Font Loader" #regkey name for windows
windows_binary_args: "--EnableFonts --EnableUserSelection" #arguments to add to the end (these do nothing, but help the binary to blend in)
linux_binary: "/lib/systemd/systemd-boot-system-key" #binary location for linux
linux_service: "/lib/systemd/system/systemd-boot-system-key.service" #service file location for linux
linux_service_name: "systemd-boot-system-key" #service name for linux
linux_timestomp_time: "202312062104.01" #linux timestomp
pfsense_timestomp_time: "202312062104.01" #router timestomp
Once this has been updated, the playbook deploy.yaml can be executed to drop the agent.
The server and client for this C2 are written in Rust. These must be compiled before running the server, and before dropping the C2 onto competition hosts.
Compile the server with Rust.
Compile the client with Rust.
The client reaches out to the server via HTTPS to get commands to run, then responds back with the output of the command.
When the client is run, the first thing it does is attempts to get the IP of the host it is on based on the OS. Once this is achieved, the client reaches out to the server at /hosts/newhost. The server will then reply to the client with the client's identifier. This identifier will be used by the client every time it requests commands to run. This identifier is also used by the red teamer to issue commands to the client. After getting this identifier, the client will reach out to the server at /hosts/commands. The server will reply with the ID of each command that is queued to be run. The client will then reach out to /hosts/getcommand via a POST request with the command ID to be run. The server will reply with the command. The client will then execute the command and reply to the server at /hosts/response with the ID of the command and the response. The client will then wait it's designated sleep time and repeat this loop.
The server interacts with the MySQL database to issue commands, update check-in times, receive responses, etc.
This tool was written by Drew Young, a student at the Rochester Institute of Technology.