Updated to TOML v1.0.0.

This commit is contained in:
Jakob Friedl
2025-11-21 15:55:41 +01:00
parent 2f2130927e
commit 6a20c25085
18 changed files with 2533 additions and 115 deletions

View File

@@ -20,7 +20,6 @@ task client, "Build conquest client binary":
requires "nim >= 2.2.4"
requires "parsetoml >= 0.7.2"
requires "nimcrypto >= 0.6.4"
requires "tiny_sqlite >= 0.2.0"
requires "winim >= 3.9.4"

View File

@@ -29,10 +29,10 @@ endpoints = [
# Metadata can be stored in a Header (e.g. JWT Token, Session Cookie), URI parameter or request body
# Encoding is only applied to the payload and not the prepended or appended strings
[http-get.agent.heartbeat]
# placement = { type = "header", name = "Authorization" }
# encoding = { type = "base64", url-safe = true }
# prefix = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
# suffix = ".######################################-####"
placement = { type = "header", name = "Authorization" }
encoding = { type = "base64", url-safe = true }
prefix = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
suffix = ".######################################-####"
# Example: PHP session cookie
# placement = { type = "header", name = "Cookie" }
@@ -45,8 +45,8 @@ endpoints = [
# encoding = { type = "hex" }
# Example: Raw data in GET request body
placement = { type = "body" }
encoding = { type = "rot", key = 2 }
# placement = { type = "body" }
# encoding = { "none" }
# Defines arbitrary URI parameters that are added to the request
[http-get.agent.parameters]

View File

@@ -36,14 +36,14 @@ v = "###########"
# Defines arbitrary headers that are added by the agent when performing a HTTP GET request
[http-get.agent.headers]
Host = "www.youtube.com"
Sec-Ch-Ua = "'Not.A/Brand';v='99', 'Chromium';v='136'"
Sec-Ch-Ua = "\"Not.A/Brand\";v=\"99\", \"Chromium\";v=\"136\""
Sec-Ch-Ua-Mobile = "?0"
Sec-Ch-Ua-Full-Version = "''"
Sec-Ch-Ua-Arch = "''"
Sec-Ch-Ua-Platform = "'Windows'"
Sec-Ch-Ua-Platform-Version = "''"
Sec-Ch-Ua-Model = "''"
Sec-Ch-Ua-Bitness = "''"
Sec-Ch-Ua-Full-Version = "\"\""
Sec-Ch-Ua-Arch = "\"\""
Sec-Ch-Ua-Platform = "\"Windows\""
Sec-Ch-Ua-Platform-Version = "\"\""
Sec-Ch-Ua-Model = "\"\""
Sec-Ch-Ua-Bitness = "\"\""
Sec-Ch-Ua-Wow64 = "?0"
Accept-Language = [
"en-US,en;q=0.9",
@@ -58,7 +58,7 @@ Sec-Fetch-User = "?1"
Sec-Fetch-Dest = "document"
Priority = "u=0, i"
# Defines arbitrary headers that are added to the server's response
# Defines arbitrary headers that are added to the server\"s response
[http-get.server.headers]
Content-Type = "text/html; charset=utf-8"
X-Content-Type-Options = "nosniff"
@@ -67,19 +67,19 @@ Pragma = "no-cache"
Expires = "Mon, 01 Jan 1990 00:00:00 GMT"
Strict-Transport-Security = "max-age=31536000"
X-Frame-Options = "SAMEORIGIN"
Content-Security-Policy = "require-trusted-types-for 'script'"
Content-Security-Policy = "require-trusted-types-for \"script\""
Server = "ESF"
X-Xss-Protection = "0"
P3p = "CP='This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl=de for more info.'"
Alt-Svc = "h3=':443'; ma=2592000,h3-29=':443'; ma=2592000"
P3p = "CP=\"This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl=de for more info.\""
Alt-Svc = "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000"
Set-Cookie = "__Secure-YEC=##############################################################################; Domain=.youtube.com; Expires=Mon, 07-Dec-2026 11:39:54 GMT; Path=/; Secure; HttpOnly; SameSite=lax"
# Defines how the server's response to the task retrieval request is rendered
# Defines how the server"s response to the task retrieval request is rendered
[http-get.server.output]
placement = { type = "body" }
encoding = { type = "base64" }
prefix = "<!DOCTYPE html><html style='font-size: 10px;font-family: Roboto, Arial, sans-serif;' lang='de-DE'><head><script data-id='_gd' nonce='iqZzTrtVB86B0KRGblxg9Q'>window.WIZ_global_data = {'HiPsbb':0,'MUE6Ne':'youtube_web','MuJWjd':false};</script><meta http-equiv='origin-trial' content='"
suffix = "'/><script nonce='iqZzTrtVB86B0KRGblxg9Q'>var ytcfg={d:function(){return window.yt&&yt.config_||ytcfg.data_||(ytcfg.data_={})},get:function(k,o){return k in ytcfg.d()?ytcfg.d()[k]:o},set:function(){var a=arguments;if(a.length>1)ytcfg.d()[a[0]]=a[1];else{var k;for(k in a[0])ytcfg.d()[k]=a[0][k]}}};window.ytcfg.set('EMERGENCY_BASE_URL', '/error_204?"
prefix = "<!DOCTYPE html><html style=\"font-size: 10px;font-family: Roboto, Arial, sans-serif;\" lang=\"de-DE\"><head><script data-id=\"_gd\" nonce=\"iqZzTrtVB86B0KRGblxg9Q\">window.WIZ_global_data = {\"HiPsbb\":0,\"MUE6Ne\":\"youtube_web\",\"MuJWjd\":false};</script><meta http-equiv=\"origin-trial\" content=\""
suffix = "\"/><script nonce=\"iqZzTrtVB86B0KRGblxg9Q\">var ytcfg={d:function(){return window.yt&&yt.config_||ytcfg.data_||(ytcfg.data_={})},get:function(k,o){return k in ytcfg.d()?ytcfg.d()[k]:o},set:function(){var a=arguments;if(a.length>1)ytcfg.d()[a[0]]=a[1];else{var k;for(k in a[0])ytcfg.d()[k]=a[0][k]}}};window.ytcfg.set(\"EMERGENCY_BASE_URL\", \"/error_204?"
# ----------------------------------------------------------
# HTTP POST
@@ -103,14 +103,14 @@ Referer = "https://www.youtube.com/watch?v=###########"
Content-Type = "application/json"
Connection = "Keep-Alive"
Cache-Control = "no-cache"
Sec-Ch-Ua = "'Not.A/Brand';v='99', 'Chromium';v='136'"
Sec-Ch-Ua = "\"Not.A/Brand\";v=\"99\", \"Chromium\";v=\"136\""
Sec-Ch-Ua-Mobile = "?0"
Sec-Ch-Ua-Full-Version = "''"
Sec-Ch-Ua-Arch = "''"
Sec-Ch-Ua-Platform = "'Windows'"
Sec-Ch-Ua-Platform-Version = "''"
Sec-Ch-Ua-Model = "''"
Sec-Ch-Ua-Bitness = "''"
Sec-Ch-Ua-Full-Version = "\"\""
Sec-Ch-Ua-Arch = "\"\""
Sec-Ch-Ua-Platform = "\"Windows\""
Sec-Ch-Ua-Platform-Version = "\"\""
Sec-Ch-Ua-Model = "\"\""
Sec-Ch-Ua-Bitness = "\"\""
Sec-Ch-Ua-Wow64 = "?0"
Cookie = "YSC=###########; SOCS=##############################################; VISITOR_PRIVACY_METADATA=##################################################################; __Secure-1PSIDTS=sidts-#######_##########################################_#########################; __Secure-3PSIDTS=sidts-#######_##########################################_#########################; HSID=####################;"
@@ -123,8 +123,8 @@ pretty-print = [
[http-post.agent.output]
placement = { type = "body" }
encoding = { type = "base64", url-safe = true }
prefix = "{'context':{'client':{'hl':'de','gl':'AT','remoteHost':'$$.1$$.$$.1$$','deviceMake':'','deviceModel':'','visitorData':'Cgt1M016MzRrZmhTUSj12MbIBjInCgJBVBIhEh0SGwsMDg8QERITFBUWFxgZGhscHR4fICEiIyQlJiBe','userAgent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36,gzip(gfe)','clientName':'WEB','clientVersion':'2.20251107.01.00','osName':'Windows','osVersion':'10.0','originalUrl':'https://www.youtube.com/','screenPixelDensity':2,'platform':'DESKTOP','clientFormFactor':'UNKNOWN_FORM_FACTOR','configInfo':{'appInstallData':'"
suffix = "'},'screenDensityFloat':1.5,'userInterfaceTheme':'USER_INTERFACE_THEME_DARK','timeZone':'Europe/Vienna','browserName':'Chrome','browserVersion':'142.0.0.0','acceptHeader':'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7','deviceExperimentId':'ChxOelUzTVRBeU1qQTJPVEV4TkRFNU5qUXhOQT09EPXYxsgGGPXYxsgG','rolloutToken':'CJu4u9qz64jjcxCr8dad-t-QAxjzyIbunueQAw%3D%3D','screenWidthPoints':1920,'screenHeightPoints':1065,'utcOffsetMinutes':60,'connectionType':'CONN_CELLULAR_3G','memoryTotalKbytes':'8000000','mainAppWebInfo':{'graftUrl':'https://www.youtube.com/watch?v=###########&list=RD4WIMyqBG9gs&start_radio=1','pwaInstallabilityStatus':'PWA_INSTALLABILITY_STATUS_UNKNOWN','webDisplayMode':'WEB_DISPLAY_MODE_BROWSER','isWebNativeShareAvailable':true}},'user':{'lockedSafetyMode':false},'request':{'useSsl':true,'internalExperimentFlags':[],'consistencyTokenJars':[]},'clickTracking':{'clickTrackingParams':'CJgFEKVBIhMIucGi957nkAMVneRJBx3cFhscygEErMFOaw=='},'adSignalsInfo':{'params':[{'key':'dt','value':'1762765953510'},{'key':'flash','value':'0'},{'key':'frm','value':'0'},{'key':'u_tz','value':'60'},{'key':'u_his','value':'4'},{'key':'u_h','value':'1200'},{'key':'u_w','value':'1920'},{'key':'u_ah','value':'1152'},{'key':'u_aw','value':'1920'},{'key':'u_cd','value':'24'},{'key':'bc','value':'31'},{'key':'bih','value':'1065'},{'key':'biw','value':'1905'},{'key':'brdim','value':'0,0,0,0,1920,0,1920,1152,1920,1065'},{'key':'vis','value':'1'},{'key':'wgl','value':'true'},{'key':'ca_type','value':'image'}],'bid':'ANyPxKqp2RGW0TLEXMjNbBRm6ZPDYteE8iHnYK0DaJMOiTEHrbqefZtn6qfK_MhA2-ZgnoosEwKaN8pi77jJRptRzz5Rsm-P_w'}},'target':{'videoId':'###########'},'params':'Cg0KCzRXSU15cUJHOWdzIAAyDAiJ2cbIBhCm6ueLAQ%3D%3D'}"
prefix = "{\"context\":{\"client\":{\"hl\":\"de\",\"gl\":\"AT\",\"remoteHost\":\"$$.1$$.$$.1$$\",\"deviceMake\":\"\",\"deviceModel\":\"\",\"visitorData\":\"Cgt1M016MzRrZmhTUSj12MbIBjInCgJBVBIhEh0SGwsMDg8QERITFBUWFxgZGhscHR4fICEiIyQlJiBe\",\"userAgent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36,gzip(gfe)\",\"clientName\":\"WEB\",\"clientVersion\":\"2.20251107.01.00\",\"osName\":\"Windows\",\"osVersion\":\"10.0\",\"originalUrl\":\"https://www.youtube.com/\",\"screenPixelDensity\":2,\"platform\":\"DESKTOP\",\"clientFormFactor\":\"UNKNOWN_FORM_FACTOR\",\"configInfo\":{\"appInstallData\":\""
suffix = "\"},\"screenDensityFloat\":1.5,\"userInterfaceTheme\":\"USER_INTERFACE_THEME_DARK\",\"timeZone\":\"Europe/Vienna\",\"browserName\":\"Chrome\",\"browserVersion\":\"142.0.0.0\",\"acceptHeader\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"deviceExperimentId\":\"ChxOelUzTVRBeU1qQTJPVEV4TkRFNU5qUXhOQT09EPXYxsgGGPXYxsgG\",\"rolloutToken\":\"CJu4u9qz64jjcxCr8dad-t-QAxjzyIbunueQAw%3D%3D\",\"screenWidthPoints\":1920,\"screenHeightPoints\":1065,\"utcOffsetMinutes\":60,\"connectionType\":\"CONN_CELLULAR_3G\",\"memoryTotalKbytes\":\"8000000\",\"mainAppWebInfo\":{\"graftUrl\":\"https://www.youtube.com/watch?v=###########&list=RD4WIMyqBG9gs&start_radio=1\",\"pwaInstallabilityStatus\":\"PWA_INSTALLABILITY_STATUS_UNKNOWN\",\"webDisplayMode\":\"WEB_DISPLAY_MODE_BROWSER\",\"isWebNativeShareAvailable\":true}},\"user\":{\"lockedSafetyMode\":false},\"request\":{\"useSsl\":true,\"internalExperimentFlags\":[],\"consistencyTokenJars\":[]},\"clickTracking\":{\"clickTrackingParams\":\"CJgFEKVBIhMIucGi957nkAMVneRJBx3cFhscygEErMFOaw==\"},\"adSignalsInfo\":{\"params\":[{\"key\":\"dt\",\"value\":\"1762765953510\"},{\"key\":\"flash\",\"value\":\"0\"},{\"key\":\"frm\",\"value\":\"0\"},{\"key\":\"u_tz\",\"value\":\"60\"},{\"key\":\"u_his\",\"value\":\"4\"},{\"key\":\"u_h\",\"value\":\"1200\"},{\"key\":\"u_w\",\"value\":\"1920\"},{\"key\":\"u_ah\",\"value\":\"1152\"},{\"key\":\"u_aw\",\"value\":\"1920\"},{\"key\":\"u_cd\",\"value\":\"24\"},{\"key\":\"bc\",\"value\":\"31\"},{\"key\":\"bih\",\"value\":\"1065\"},{\"key\":\"biw\",\"value\":\"1905\"},{\"key\":\"brdim\",\"value\":\"0,0,0,0,1920,0,1920,1152,1920,1065\"},{\"key\":\"vis\",\"value\":\"1\"},{\"key\":\"wgl\",\"value\":\"true\"},{\"key\":\"ca_type\",\"value\":\"image\"}],\"bid\":\"ANyPxKqp2RGW0TLEXMjNbBRm6ZPDYteE8iHnYK0DaJMOiTEHrbqefZtn6qfK_MhA2-ZgnoosEwKaN8pi77jJRptRzz5Rsm-P_w\"}},\"target\":{\"videoId\":\"###########\"},\"params\":\"Cg0KCzRXSU15cUJHOWdzIAAyDAiJ2cbIBhCm6ueLAQ%3D%3D\"}"
[http-post.server.headers]
Content-Type = "application/json; charset=utf-8"
@@ -135,7 +135,7 @@ Expires = "Mon, 01 Jan 1990 00:00:00 GMT"
Server = "ESF"
X-Xss-Protection = "0"
Strict-Transport-Security = "max-age=31536000"
Alt-Svc = "h3=':443'; ma=2592000,h3-29=':443'; ma=2592000"
Alt-Svc = "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000"
[http-post.server.output]
body = "{'responseContext': {}}"
body = "{\"responseContext\": {}}"

View File

@@ -1,6 +1,5 @@
import parsetoml
import ../utils/io
import ../../common/[types, utils, crypto, serialize]
import ../../common/[types, utils, crypto, profile, serialize]
const CONFIGURATION {.strdefine.}: string = ""

View File

@@ -1,4 +1,4 @@
import httpclient, strformat, strutils, asyncdispatch, base64, tables, parsetoml, random
import httpclient, strformat, strutils, asyncdispatch, base64, tables, random
import ../utils/io
import ../../common/[types, utils, profile]
@@ -11,8 +11,8 @@ proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string =
var body: string = ""
# Define request headers, as defined in profile
for header, value in ctx.profile.getTable(protect("http-get.agent.headers")):
client.headers.add(header, value.getStringValue())
for header in ctx.profile.getTableKeys(protect("http-get.agent.headers")):
client.headers.add(header.key, header.value.getStringValue())
# Select a random endpoint to make the request to
var endpoint = ctx.profile.getString(protect("http-get.endpoints"))
@@ -32,8 +32,8 @@ proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string =
discard
# Define additional request parameters
for param, value in ctx.profile.getTable(protect("http-get.agent.parameters")):
endpoint &= fmt"{param}={value.getStringValue()}&"
for param in ctx.profile.getTableKeys(protect("http-get.agent.parameters")):
endpoint &= fmt"{param.key}={param.value.getStringValue()}&"
try:
# Retrieve binary task data from listener and convert it to seq[bytes] for deserialization
@@ -68,8 +68,8 @@ proc httpPost*(ctx: AgentCtx, data: seq[byte]): bool {.discardable.} =
let client = newAsyncHttpClient(userAgent = ctx.profile.getString(protect("http-post.user-agent")))
# Define request headers, as defined in profile
for header, value in ctx.profile.getTable(protect("http-post.agent.headers")):
client.headers.add(header, value.getStringValue())
for header in ctx.profile.getTableKeys(protect("http-post.agent.headers")):
client.headers.add(header.key, header.value.getStringValue())
# Select a random endpoint to make the request to
var endpoint = ctx.profile.getString(protect("http-post.endpoints"))
@@ -95,8 +95,8 @@ proc httpPost*(ctx: AgentCtx, data: seq[byte]): bool {.discardable.} =
discard
# Define additional request parameters
for param, value in ctx.profile.getTable(protect("http-post.agent.parameters")):
endpoint &= fmt"{param}={value.getStringValue()}&"
for param in ctx.profile.getTableKeys(protect("http-post.agent.parameters")):
endpoint &= fmt"{param.key}={param.value.getStringValue()}&"
try:
# Send post request to team server

File diff suppressed because one or more lines are too long

View File

@@ -1,10 +1,10 @@
import whisky
import tables, times, strutils, strformat, json, parsetoml, base64, native_dialogs
import tables, times, strutils, strformat, json, base64, native_dialogs
import ./utils/[appImGui, globals]
import ./views/[dockspace, sessions, listeners, eventlog, console]
import ./views/loot/[screenshots, downloads]
import ./views/modals/generatePayload
import ../common/[types, utils, crypto]
import ../common/[types, utils, profile, crypto]
import ./core/websocket
proc main(ip: string = "localhost", port: int = 37573) =
@@ -85,7 +85,7 @@ proc main(ip: string = "localhost", port: int = 37573) =
wipeKey(clientKeyPair.privateKey)
of CLIENT_PROFILE:
profile = parsetoml.parseString(event.data["profile"].getStr())
profile = parseString(event.data["profile"].getStr())
of CLIENT_LISTENER_ADD:
let listener = event.data.to(UIListener)

View File

@@ -57,7 +57,7 @@ proc validateDecryption*(key: Key, iv: Iv, encData: seq[byte], sequenceNumber: u
Elliptic curve cryptography ensures that the actual session key is never sent over the network
Private keys and shared secrets are wiped from agent memory as soon as possible
]#
{.compile: "monocypher/monocypher.c".}
{.compile: protect("monocypher/monocypher.c").}
# C function imports from (monocypher/monocypher.c)
proc crypto_x25519*(shared_secret: ptr byte, your_secret_key: ptr byte, their_public_key: ptr byte) {.importc, cdecl.}

View File

@@ -1,25 +1,17 @@
import parsetoml, strutils, sequtils, random, base64
import strutils, sequtils, random, base64
import ./[types, utils]
proc findKey(profile: Profile, path: string): TomlValueRef =
let keys = path.split(".")
let target = keys[keys.high]
var current = profile
for i in 0 ..< keys.high:
let temp = current.getOrDefault(keys[i])
if temp == nil:
return nil
current = temp
return current.getOrDefault(target)
import ./toml/toml
export parseFile, parseString, free, getTableKeys, getRandom
# Takes a specific "."-separated path as input and returns a default value if the key does not exits
# Example: cq.profile.getString("http-get.agent.heartbeat.prefix", "not found") returns the string value of the
# prefix key, or "not found" if the target key or any sub-tables don't exist
# '#' characters represent wildcard characters and are replaced with a random alphanumerical character
# '#' characters represent wildcard characters and are replaced with a random alphanumerical character (a-zA-Z0-9)
# '$' characters are replaced with a random number (0-9)
#[
Helper functions
]#
proc randomChar(): char =
let alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return alphabet[rand(alphabet.len - 1)]
@@ -33,48 +25,53 @@ proc getRandom*(values: seq[TomlValueRef]): TomlValueRef =
return nil
return values[rand(values.len - 1)]
#[
Wrapper functions
]#
proc getStringValue*(key: TomlValueRef, default: string = ""): string =
# In some cases, the profile can define multiple values for a key, e.g. for HTTP headers
# A random entry is selected from these specifications
var value: string = ""
if key.kind == TomlValueKind.String:
value = key.getStr(default)
elif key.kind == TomlValueKind.Array:
value = key.getElems().getRandom().getStr(default)
if key.isNil or key.kind == None:
return default
# Replace '#' with a random alphanumerical character and return the resulting string
var value: string = ""
if key.kind == String:
value = key.strVal
elif key.kind == Array:
let randomElem = getRandom(key.arrayVal)
if randomElem != nil and randomElem.kind == String:
value = randomElem.strVal
# Replace '#' with random alphanumerical character
return value.mapIt(if it == '#': randomChar() elif it == '$': randomNumber() else: it).join("")
proc getString*(profile: Profile, path: string, default: string = ""): string =
proc getString*(profile: Profile, path: string, default: string = ""): string =
let key = profile.findKey(path)
if key == nil:
return default
return key.getStringValue(default)
proc getBool*(profile: Profile, path: string, default: bool = false): bool =
proc getInt*(profile: Profile, path: string, default: int = 0): int =
let key = profile.findKey(path)
if key == nil:
return default
return key.getBool(default)
proc getInt*(profile: Profile, path: string, default = 0): int =
let key = profile.findKey(path)
if key == nil:
return default
return key.getInt(default)
proc getTable*(profile: Profile, path: string): TomlTableRef =
proc getBool*(profile: Profile, path: string, default: bool = false): bool =
let key = profile.findKey(path)
return key.getBool(default)
proc getTable*(profile: Profile, path: string): TomlTableRef =
let key = profile.findKey(path)
if key == nil:
return new TomlTableRef
return key.getTable()
proc getArray*(profile: Profile, path: string): seq[TomlValueRef] =
let key = profile.findKey(path)
if key == nil:
if key.kind != Array:
return @[]
return key.getElems()
return key.getElems()
proc isArray*(profile: Profile, path: string): bool =
let key = profile.findKey(path)
return key.kind == Array
#[
Data transformation
]#
proc applyDataTransformation*(profile: Profile, path: string, data: seq[byte]): string =
# 1. Encoding
var dataString: string

1983
src/common/toml/toml.c Normal file

File diff suppressed because it is too large Load Diff

137
src/common/toml/toml.h Normal file
View File

@@ -0,0 +1,137 @@
#ifndef TOML_H
#define TOML_H
#ifdef _MSC_VER
# pragma warning(disable : 4996)
#endif
#ifdef __cplusplus
# define TOML_EXTERN extern "C"
#else
# define TOML_EXTERN extern
#endif
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
typedef struct toml_table_t toml_table_t;
typedef struct toml_array_t toml_array_t;
typedef struct toml_value_t toml_value_t;
typedef struct toml_timestamp_t toml_timestamp_t;
typedef struct toml_keyval_t toml_keyval_t;
typedef struct toml_arritem_t toml_arritem_t;
typedef struct toml_pos_t toml_pos_t;
// TOML table.
struct toml_table_t {
const char* key; // Key for this table
int keylen; // length of key.
bool implicit; // Table was created implicitly
bool readonly; // No more modification allowed
int nkval; // key-values in the table
toml_keyval_t** kval;
int narr; // arrays in the table
toml_array_t** arr;
int ntbl; // tables in the table
toml_table_t** tbl;
};
// TOML array.
struct toml_array_t {
const char* key; // key to this array
int keylen; // length of key.
int kind; // element kind: 'v'alue, 'a'rray, or 't'able, 'm'ixed
int type; // for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, 'D'ate, 'T'imestamp, 'm'ixed
int nitem; // number of elements
toml_arritem_t* item;
};
struct toml_arritem_t {
int valtype; // for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, 'D'ate, 'T'imestamp
char* val;
toml_array_t* arr;
toml_table_t* tbl;
};
// TOML key/value pair.
struct toml_keyval_t {
const char* key; // key to this value
int keylen; // length of key.
const char* val; // the raw value
};
// Token position.
struct toml_pos_t {
int line;
int col;
};
// Timestamp type; some values may be empty depending on the value of kind.
struct toml_timestamp_t {
// datetime type:
//
// 'd'atetime Full date + time + TZ
// 'l'local-datetime Full date + time but without TZ
// 'D'ate-local Date only, without TZ
// 't'ime-local Time only, without TZ
char kind;
int year, month, day;
int hour, minute, second, millisec;
int tz; // Timezone offset in minutes
};
// Parsed TOML value.
//
// The string value s is a regular NULL-terminated C string, but the string
// length is also given in sl since TOML values may contain NULL bytes. The
// value is guaranteed to be correct UTF-8.
struct toml_value_t {
bool ok; // Was this value present?
union {
struct {
char* s; // string value; must be freed after use.
int sl; // string length, excluding NULL.
};
toml_timestamp_t ts; // datetime
bool b; // bool
int64_t i; // int
double d; // double
} u;
};
// toml_parse() parses a TOML document from a string. Returns 0 on error, with
// the error message stored in errbuf.
//
// toml_parse_file() is identical, but reads from a file descriptor.
//
// Use toml_free() to free the return value; this will invalidate all handles
// for this table.
TOML_EXTERN toml_table_t* toml_parse(char* toml, char* errbuf, int errbufsz);
TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp, char* errbuf, int errbufsz);
TOML_EXTERN void toml_free(toml_table_t* table);
// Table functions.
//
// toml_table_len() gets the number of direct keys for this table;
// toml_table_key() gets the nth direct key in this table.
TOML_EXTERN int toml_table_len(const toml_table_t* table);
TOML_EXTERN const char* toml_table_key(const toml_table_t* table, int keyidx, int* keylen);
TOML_EXTERN toml_value_t toml_table_string(const toml_table_t* table, const char* key);
TOML_EXTERN toml_value_t toml_table_bool(const toml_table_t* table, const char* key);
TOML_EXTERN toml_value_t toml_table_int(const toml_table_t* table, const char* key);
TOML_EXTERN toml_value_t toml_table_double(const toml_table_t* table, const char* key);
TOML_EXTERN toml_value_t toml_table_timestamp(const toml_table_t* table, const char* key);
TOML_EXTERN toml_array_t* toml_table_array(const toml_table_t* table, const char* key);
TOML_EXTERN toml_table_t* toml_table_table(const toml_table_t* table, const char* key);
// Array functions.
TOML_EXTERN int toml_array_len(const toml_array_t* array);
TOML_EXTERN toml_value_t toml_array_string(const toml_array_t* array, int idx);
TOML_EXTERN toml_value_t toml_array_bool(const toml_array_t* array, int idx);
TOML_EXTERN toml_value_t toml_array_int(const toml_array_t* array, int idx);
TOML_EXTERN toml_value_t toml_array_double(const toml_array_t* array, int idx);
TOML_EXTERN toml_value_t toml_array_timestamp(const toml_array_t* array, int idx);
TOML_EXTERN toml_array_t* toml_array_array(const toml_array_t* array, int idx);
TOML_EXTERN toml_table_t* toml_array_table(const toml_array_t* array, int idx);
#endif // TOML_H

301
src/common/toml/toml.nim Normal file
View File

@@ -0,0 +1,301 @@
import random, strutils
# Wrapper for the toml-c library
# Original: github.com/arp242/toml-c/
{.compile: "toml.c".}
type
TomlKeyVal = object
key: cstring
keylen: cint
val: cstring
TomlArrItem = object
valtype: cint
val: cstring
arr: ptr TomlArray
tbl: ptr TomlTable
TomlTable = object
key: cstring
keylen: cint
implicit: bool
readonly: bool
nkval: cint
kval: ptr ptr TomlKeyVal
narr: cint
arr: ptr ptr TomlArray
ntbl: cint
tbl: ptr ptr TomlTable
TomlArray = object
key: cstring
keylen: cint
kind: cint
`type`: cint
nitem: cint
item: ptr TomlArrItem
TomlValue = object
case ok: bool
of false: discard
of true:
s: cstring
sl: cint
TomlTableRef* = ptr TomlTable
TomlValueKind* = enum
String, Int, Bool, Float, Table, Array, None
TomlValueRef* = ref object
case kind*: TomlValueKind
of String:
strVal*: string
of Int:
intVal*: int64
of Bool:
boolVal*: bool
of Float:
floatVal*: float64
of Table:
tableVal*: TomlTableRef
of Array:
arrayVal*: ptr TomlArray
of None:
discard
# C library functions
proc toml_parse(toml: cstring, errbuf: cstring, errbufsz: cint): TomlTableRef {.importc, cdecl.}
proc toml_parse_file(fp: File, errbuf: cstring, errbufsz: cint): TomlTableRef {.importc, cdecl.}
proc toml_free(tab: TomlTableRef) {.importc, cdecl.}
proc toml_table_len(tab: TomlTableRef): cint {.importc, cdecl.}
proc toml_table_key(tab: TomlTableRef, keyidx: cint, keylen: ptr cint): cstring {.importc, cdecl.}
proc toml_table_string(tab: TomlTableRef, key: cstring): TomlValue {.importc, cdecl.}
proc toml_table_int(tab: TomlTableRef, key: cstring): TomlValue {.importc, cdecl.}
proc toml_table_bool(tab: TomlTableRef, key: cstring): TomlValue {.importc, cdecl.}
proc toml_table_double(tab: TomlTableRef, key: cstring): TomlValue {.importc, cdecl.}
proc toml_table_array(tab: TomlTableRef, key: cstring): ptr TomlArray {.importc, cdecl.}
proc toml_table_table(tab: TomlTableRef, key: cstring): TomlTableRef {.importc, cdecl.}
proc toml_array_len(arr: ptr TomlArray): cint {.importc, cdecl.}
proc toml_array_table(arr: ptr TomlArray, idx: cint): TomlTableRef {.importc, cdecl.}
proc toml_array_string(arr: ptr TomlArray, idx: cint): TomlValue {.importc, cdecl.}
proc toml_array_int(arr: ptr TomlArray, idx: cint): TomlValue {.importc, cdecl.}
#[
Retrieve a random element from a TOML array
]#
proc getRandom*(arr: ptr TomlArray): TomlValueRef =
if arr.isNil:
return nil
let n = toml_array_len(arr)
if n == 0:
return nil
let idx = rand(n.int - 1)
# String
let strVal {.volatile.} = toml_array_string(arr, idx.cint)
if strVal.ok:
let strPtr = cast[ptr cstring](cast[int](addr strVal) + 8)[]
if not strPtr.isNil:
return TomlValueRef(kind: String, strVal: $strPtr)
# Table
let table {.volatile.} = toml_array_table(arr, idx.cint)
if not table.isNil:
return TomlValueRef(kind: Table, tableVal: table)
# Int
let intVal {.volatile.} = toml_array_int(arr, idx.cint)
if intVal.ok:
let intPtr = cast[ptr int64](cast[int](addr intVal) + 8)[]
return TomlValueRef(kind: Int, intVal: intPtr)
return nil
#[
Parse TOML string or configuration file
]#
proc parseString*(toml: string): TomlTableRef =
var errbuf: array[200, char]
var tomlCopy = toml
result = toml_parse(tomlCopy.cstring, cast[cstring](addr errbuf[0]), 200)
if result.isNil:
raise newException(ValueError, "TOML parse error: " & $cast[cstring](addr errbuf[0]))
proc parseFile*(path: string): TomlTableRef =
var errbuf: array[200, char]
let fp = open(path, fmRead)
if fp.isNil:
raise newException(IOError, "Cannot open file: " & path)
result = toml_parse_file(fp, cast[cstring](addr errbuf[0]), 200)
fp.close()
if result.isNil:
raise newException(ValueError, "TOML parse error: " & $cast[cstring](addr errbuf[0]))
proc free*(table: TomlTableRef) =
if not table.isNil:
toml_free(table)
#[
Takes a specific "."-separated path as input and returns the TOML Value that it finds
]#
proc findKey*(profile: TomlTableRef, path: string): TomlValueRef =
if profile.isNil:
return TomlValueRef(kind: None)
let keys = path.split(".")
var current = profile
# Navigate through nested tables
for i in 0 ..< keys.len - 1:
let nextTable = toml_table_table(current, keys[i].cstring)
if nextTable.isNil:
return TomlValueRef(kind: None)
current = nextTable
let finalKey = keys[^1].cstring
# Try different types
# {.volatile.} is added to avoid dangling pointers
block findStr:
let val {.volatile.} = toml_table_string(current, finalKey)
if val.ok:
let strPtr = cast[ptr cstring](cast[int](addr val) + 8)[]
if not strPtr.isNil:
return TomlValueRef(kind: String, strVal: $strPtr)
block checkInt:
let val {.volatile.} = toml_table_int(current, finalKey)
if val.ok:
let intPtr = cast[ptr int64](cast[int](addr val) + 8)[]
return TomlValueRef(kind: Int, intVal: intPtr)
block checkBool:
let val {.volatile.} = toml_table_bool(current, finalKey)
if val.ok:
let boolPtr = cast[ptr bool](cast[int](addr val) + 8)[]
return TomlValueRef(kind: Bool, boolVal: boolPtr)
block checkDouble:
let val {.volatile.} = toml_table_double(current, finalKey)
if val.ok:
let dblPtr = cast[ptr float64](cast[int](addr val) + 8)[]
return TomlValueRef(kind: Float, floatVal: dblPtr)
block checkArray:
let arr {.volatile.} = toml_table_array(current, finalKey)
if not arr.isNil:
return TomlValueRef(kind: Array, arrayVal: arr)
block checkTable:
let table {.volatile.} = toml_table_table(current, finalKey)
if not table.isNil:
return TomlValueRef(kind: Table, tableVal: table)
return TomlValueRef(kind: None)
#[
Retrieve the actual value from a TOML value
]#
proc getStr*(value: TomlValueRef, default: string = ""): string =
if value.kind == String:
return value.strVal
return default
proc getInt*(value: TomlValueRef, default: int = 0): int =
if value.kind == Int:
return value.intVal.int
return default
proc getBool*(value: TomlValueRef, default: bool = false): bool =
if value.kind == Bool:
return value.boolVal
return default
proc getTable*(value: TomlValueRef): TomlTableRef =
if value.kind == Table:
return value.tableVal
return nil
proc getElems*(value: TomlValueRef): seq[TomlValueRef] =
if value.kind != Array:
return @[]
let arr = value.arrayVal
let n = toml_array_len(arr)
result = @[]
for i in 0 ..< n:
# Try table first
let table {.volatile.} = toml_array_table(arr, i.cint)
if not table.isNil:
result.add(TomlValueRef(kind: Table, tableVal: table))
continue
# Try string
let strVal = toml_array_string(arr, i.cint)
if strVal.ok:
let strPtr {.volatile.} = cast[ptr cstring](cast[int](addr strVal) + 8)[]
if not strPtr.isNil:
result.add(TomlValueRef(kind: String, strVal: $strPtr))
continue
# Try int
let intVal = toml_array_int(arr, i.cint)
if intVal.ok:
let intPtr {.volatile.} = cast[ptr int64](cast[int](addr intVal) + 8)[]
result.add(TomlValueRef(kind: Int, intVal: intPtr))
proc getTableKeys*(profile: TomlTableRef, path: string): seq[tuple[key: string, value: TomlValueRef]] =
result = @[]
let key = profile.findKey(path)
let table = key.getTable()
if table.isNil:
return
let numKeys = toml_table_len(table)
for i in 0 ..< numKeys:
var keylen: cint
let keyPtr = toml_table_key(table, i.cint, addr keylen)
if keyPtr.isNil:
continue
let key = $keyPtr
let value = profile.findKey(path & "." & key)
if value.kind != None:
result.add((key: key, value: value))
proc getTableValue*(table: TomlTableRef, key: string): TomlValueRef =
if table.isNil:
return TomlValueRef(kind: None)
let ckey = key.cstring
block checkString:
let val {.volatile.} = toml_table_string(table, ckey)
if val.ok:
let strPtr = cast[ptr cstring](cast[int](addr val) + 8)[]
if not strPtr.isNil:
return TomlValueRef(kind: String, strVal: $strPtr)
block checkInt:
let val {.volatile.} = toml_table_int(table, ckey)
if val.ok:
let intPtr = cast[ptr int64](cast[int](addr val) + 8)[]
return TomlValueRef(kind: Int, intVal: intPtr)
block checkBool:
let val {.volatile.} = toml_table_bool(table, ckey)
if val.ok:
let boolPtr = cast[ptr bool](cast[int](addr val) + 8)[]
return TomlValueRef(kind: Bool, boolVal: boolPtr)
return TomlValueRef(kind: None)

View File

@@ -1,10 +1,12 @@
import tables
import parsetoml, json
import json
import system
import mummy
when defined(client):
import whisky
import ./toml/toml
# Custom Binary Task structure
const
MAGIC* = 0x514E3043'u32 # Magic value: C0NQ
@@ -285,7 +287,7 @@ type
privateKey*: Key
publicKey*: Key
Profile* = TomlValueRef
Profile* = TomlTableRef
WsConnection* = ref object
when defined(server):
@@ -300,6 +302,7 @@ type
threads*: Table[string, Thread[Listener]]
agents*: Table[string, Agent]
keyPair*: KeyPair
profileString*: string
profile*: Profile
client*: WsConnection

View File

@@ -1,5 +1,5 @@
import mummy, terminal, parsetoml, tables
import strutils, strformat, base64
import mummy, terminal
import strutils, strformat
import ./handlers
import ../globals
@@ -81,8 +81,8 @@ proc httpGet*(request: Request) =
# Add headers, as defined in the team server profile
var headers: HttpHeaders
for header, value in cq.profile.getTable("http-get.server.headers"):
headers.add((header, value.getStringValue()))
for header in cq.profile.getTableKeys("http-get.server.headers"):
headers.add((header.key, header.value.getStringValue()))
request.respond(200, headers = headers, body = payload)
@@ -129,8 +129,8 @@ proc httpPost*(request: Request) =
# Add response headers, as defined in team server profile
var headers: HttpHeaders
for header, value in cq.profile.getTable("http-post.server.headers"):
headers.add((header, value.getStringValue()))
for header in cq.profile.getTableKeys("http-post.server.headers"):
headers.add((header.key, header.value.getStringValue()))
# Differentiate between registration and task result packet
var unpacker = Unpacker.init(Bytes.toString(data))

View File

@@ -1,4 +1,4 @@
import terminal, strformat, strutils, sequtils, tables, system, osproc, streams, parsetoml
import terminal, strformat, strutils, sequtils, tables, system, osproc, streams
import ../globals
import ../core/[logger, websocket]
@@ -38,7 +38,7 @@ proc serializeConfiguration(cq: Conquest, listener: Listener, sleepSettings: Sle
packer.addData(cq.keyPair.publicKey)
# C2 profile
packer.addDataWithLengthPrefix(string.toBytes(cq.profile.toTomlString()))
packer.addDataWithLengthPrefix(string.toBytes(cq.profileString))
let data = packer.pack()
packer.reset()

View File

@@ -1,6 +1,5 @@
import strformat, strutils, terminal
import strformat, strutils, terminal, tables
import mummy, mummy/routers
import parsetoml
import ../api/routes
import ../db/database

View File

@@ -1,4 +1,4 @@
import times, json, base64, parsetoml, strformat
import times, json, base64, strformat
import stb_image/write as stbiw
import ./logger
import ../../common/[types, utils, event]
@@ -46,12 +46,12 @@ proc sendPublicKey*(client: WsConnection, publicKey: Key) =
if client != nil:
client.ws.sendEvent(event, client.sessionKey)
proc sendProfile*(client: WsConnection, profile: Profile) =
proc sendProfile*(client: WsConnection, profileString: string) =
let event = Event(
eventType: CLIENT_PROFILE,
timestamp: now().toTime().toUnix(),
data: %*{
"profile": profile.toTomlString()
"profile": profileString
}
)
if client != nil:

View File

@@ -1,5 +1,5 @@
import mummy, mummy/routers
import terminal, parsetoml, json, math, base64, times
import terminal, json, math, base64, times
import strutils, strformat, system, tables
import ./globals
@@ -15,14 +15,15 @@ proc header() =
echo "".repeat(21)
echo ""
proc init*(T: type Conquest, profile: Profile): Conquest =
proc init*(T: type Conquest, profileString: string): Conquest =
var cq = new Conquest
cq.listeners = initTable[string, Listener]()
cq.threads = initTable[string, Thread[Listener]]()
cq.agents = initTable[string, Agent]()
cq.profile = profile
cq.keyPair = loadKeyPair(CONQUEST_ROOT & "/" & profile.getString("private-key-file"))
cq.dbPath = CONQUEST_ROOT & "/" & profile.getString("database-file")
cq.profileString = profileString
cq.profile = parseString(profileString)
cq.keyPair = loadKeyPair(CONQUEST_ROOT & "/" & cq.profile.getString("private-key-file"))
cq.dbPath = CONQUEST_ROOT & "/" & cq.profile.getString("database-file")
cq.client = nil
return cq
@@ -54,7 +55,7 @@ proc websocketHandler(ws: WebSocket, event: WebSocketEvent, message: Message) {.
# Send relevant information to the client
# C2 profile
cq.client.sendProfile(cq.profile)
cq.client.sendProfile(cq.profileString)
# Listeners
for id, listener in cq.listeners:
@@ -140,11 +141,10 @@ proc startServer*(profilePath: string) =
try:
# Initialize framework context
# Load and parse profile
let profile = parsetoml.parseFile(profilePath)
cq = Conquest.init(profile)
let profileString = readFile(profilePath)
cq = Conquest.init(profileString)
cq.info("Using profile \"", profile.getString("name"), "\" (", profilePath ,").")
cq.info("Using profile \"", cq.profile.getString("name"), "\" (", profilePath ,").")
# Initialize database
cq.dbInit()