Added more commands.

This commit is contained in:
Mariusz B. / mgeeky 2021-03-26 20:05:36 +01:00
parent fc609918df
commit 577f5a0641
6 changed files with 639 additions and 31 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -12,7 +12,7 @@ The script offers subcommands-kind of CLI interface, so after every command one
**General help**:
```
PS D:\> py c3-client.py --help
PS> py .\c3-client.py --help
:: F-Secure's C3 Client - a lightweight automated companion with C3 voyages
Mariusz B. / mgeeky, <mb@binary-offensive.com>
@ -22,12 +22,14 @@ Usage: ./c3-client.py [options] <host> <command> [...]
positional arguments:
host C3 Web API host:port
{alarm,list,get,ping,channel}
{alarm,list,get,ping,connector,close,channel}
command help
alarm Alarm options
list List options
get Get options
ping Ping Relays
connector Connector options
close Close command.
channel Send Channel-specific command
optional arguments:
@ -36,6 +38,7 @@ optional arguments:
-d, --debug Display debug output.
-f {json,text}, --format {json,text}
Output format. Can be JSON or text (default).
-n, --dry-run Do not send any HTTP POST request that could introduce changes in C3 network.
-A user:pass, --httpauth user:pass
HTTP Basic Authentication (user:pass)
```
@ -78,16 +81,32 @@ Currently, following commands are supported:
- `alarm`
- `relay` - trigger an alarm whenever a new Relay checks-in on a gateway
- `connector`
- `turnon`
- `teamserver` - allows to establish connection with a Teamserver
- `turnoff` - closes connection with Connector specified by connector_id
- `close`
- `network` - sends `ClearNetwork` command to specified Gateway
- `channel` - closes selected channel
- `relay` - closes selected Relay(s) and all its bound peripherals, channels and Gateway-Return Channel
- `download`
- `gateway` - downloads gateway executable
- `ping` - ping selected Relays
- `channel` - channel-specific commands
- `all`
- `clear` - Clear message queue of every supported channel at once
- `mattermost`
- `create`- Creates a Mattermost Negotiation channel
- `clear` - Clear Mattermost's channel messages to improve bandwidth
- `ldap`
- `create` - Creates a LDAP Negotiation Channel
- `clear` - Clear LDAP attribute to improve bandwidth
- `mssql`
- `create` - Creates a MSSQL Negotiation Channel
- `clear` - Clear DB Table entries to improve bandwidth
- `uncsharefile`
- `clear` - Remove all message files to improve bandwidth
@ -224,3 +243,10 @@ PS D:\> py c3-client.py http://192.168.0.200:52935 alarm relay -g gate4 --execut
```
### Author
```
Mariusz B. / mgeeky, '21
<mb [at] binary-offensive.com>
```

View File

@ -2,6 +2,7 @@
import os
import sys
import io
import re
import time
import json
@ -10,6 +11,7 @@ import subprocess
import argparse
import random
import string
import zipfile
from datetime import datetime
@ -17,6 +19,7 @@ config = {
'verbose' : False,
'debug' : False,
'host' : '',
'dry_run' : False,
'command' : '',
'format' : 'text',
'httpauth' : '',
@ -88,7 +91,7 @@ class Logger:
def printJson(data):
print(json.dumps(data, sort_keys=True, indent=4))
def getRequest(url, rawResp = False):
def getRequest(url, rawResp = False, stream = False):
auth = None
if config['httpauth']:
user, _pass = config['httpauth'].split(':')
@ -98,7 +101,13 @@ def getRequest(url, rawResp = False):
fullurl = config["host"] + url
Logger.info(f'GET Request: {fullurl}')
resp = requests.get(fullurl, headers=headers, auth=auth)
try:
resp = requests.get(fullurl, headers=headers, auth=auth, stream = stream, timeout = 5)
except requests.exceptions.ConnectTimeout as e:
Logger.fatal(f'Connection with {config["host"]} timed-out.')
except Exception as e:
Logger.fatal(f'GET request failed ({url}): {e}')
if rawResp:
return resp
@ -121,6 +130,18 @@ def postRequest(url, data=None, contentType = 'application/json', rawResp = Fals
resp = None
if config['dry_run']:
print(f'[?] Dry-run mode: Skipping post request ({url})')
if rawResp:
class MockResponse():
def __init__(self, status_code, text):
self.status_code = status_code
self.text = text
return MockResponse(201, '')
else:
return ''
if contentType.endswith('/json'):
resp = requests.post(fullurl, json=data, headers=headers, auth=auth)
else:
@ -163,11 +184,10 @@ def printFullGateway(gatewayId):
for d in c['propertiesText']['arguments']:
if d['type'] == 'ip':
addr = d['value']
break
elif d['type'] == 'uint16':
port = d['value']
break
print(f'{indent} Connector ID: {c["iid"]}')
print(f'{indent} Host: {addr}:{port}\n')
num = 0
@ -465,6 +485,42 @@ def collectRelays(args):
return relays
def processCapability(gateway):
caps = getRequest(f'/api/gateway/{gateway["agentId"]}/capability')
commandIds = {}
channels = {}
peripherals = {}
for gatewayVal in caps['gateway']:
for commandVal in gatewayVal['commands']:
commandIds[commandVal['name'].lower()] = commandVal['id']
Logger.dbg(f'Gateway capability: commands: {commandVal["name"]} = {commandVal["id"]}')
for channel in caps['channels']:
channels[channel['name']] = channel['type']
for peri in caps['peripherals']:
peripherals[peri['name']] = peri['type']
Logger.dbg('Gateway supports following channels: ' + str(', '.join(channels.keys())))
Logger.dbg('Gateway supports following peripherals: ' + str(', '.join(peripherals.keys())))
capability = {
'raw' : caps,
'commandIds' : commandIds,
'channels' : channels,
'peripherals' : peripherals,
}
return capability
def getCommandIdMapping(gateway, command):
capability = processCapability(gateway)
return capability['commandIds'][command.lower()]
def onPing(args):
if args.keep_pinging > 0:
while True:
@ -506,20 +562,17 @@ def _onPing(args):
else:
print(f'[+] Pinged {pinged} active relays.\n')
def getLastGatewayCommandID(gateway, secondOrder = True):
def getLastGatewayCommandID():
lastId = 0
gateways = getRequest(f'/api/gateway')
for gateway in gateways:
commands = getRequest(f'/api/gateway/{gateway["agentId"]}/command')
for comm in commands:
if secondOrder:
if 'data' in comm.keys():
if 'id' in comm['data'].keys():
if comm['data']['id'] > lastId:
lastId = comm['data']['id']
else:
if comm['id'] > lastId:
lastId = comm['id']
return lastId
return lastId + random.randint(5, 25)
def onAllChannelsClear(args):
channels = {
@ -873,6 +926,8 @@ def onAlarmRelay(args):
try:
while True:
time.sleep(2)
currRelays = collectRelays(args)
currRelayIds = set()
currLastTimestamp = 0
@ -955,6 +1010,174 @@ def getValueOrRandom(val, N = 6):
return val
def closeRelay(gateway, relay):
gateway = getRequest(f'/api/gateway/{gateway["agentId"]}')
relayMeta = getRequest(f'/api/gateway/{gateway["agentId"]}/relay/{relay["agentId"]}')
capability = processCapability(gateway)
print('\n[.] step 1: Closing bound Peripherals')
for peri in relayMeta['peripherals']:
name = list(capability['peripherals'].keys())[list(capability['peripherals'].values()).index(peri['type'])]
Logger.info(f'Closing relay\'s peripheral {name} id:{peri["iid"]}')
closePeripheral(gateway, relay, name, peri['iid'])
print('\n[.] step 2: Closing attached channels')
grcChannel = None
for chan in relayMeta['channels']:
if 'isReturnChannel' in chan.keys():
chan['url'] = f'/api/gateway/{gateway["agentId"]}/relay/{relay["agentId"]}/channel/{chan["iid"]}/command'
grcChannel = chan
continue
chanName = list(capability['channels'].keys())[list(capability['channels'].values()).index(chan['type'])]
Logger.info(f'Closing relay\'s channel {chanName} id:{chan["iid"]}')
chan['url'] = f'/api/gateway/{gateway["agentId"]}/relay/{relay["agentId"]}/channel/{chan["iid"]}/command'
closeChannel(chan, chanName)
if not grcChannel:
Logger.fatal(f'Could not determine Gateway-Return Channel of the specified Relay {relay["name"]} / {relay["agentId"]}. \n Probably its unreachable or already closed.')
closeChannel(grcChannel, list(capability['channels'].keys())[list(capability['channels'].values()).index(grcChannel['type'])])
print('\n[.] step 3: closing Relay itself')
closeRelay(gateway, relay)
print('\n[.] step 4: closing a channel being a neighbour for Relay\'s GRC')
closed = False
for relayNode in gateway['relays'] + [gateway,]:
for route in relayNode['routes']:
if route['receivingInterface'] == grcChannel['iid']:
for chan in relayNode['channels']:
if chan['iid'] == route['outgoingInterface']:
if relayNode["agentId"] == gateway['agentId']:
chan['url'] = f'/api/gateway/{gateway["agentId"]}/channel/{chan["iid"]}/command'
else:
chan['url'] = f'/api/gateway/{gateway["agentId"]}/relay/{relayNode["agentId"]}/channel/{chan["iid"]}/command'
closeChannel(chan, list(capability['channels'].keys())[list(capability['channels'].values()).index(chan['type'])])
closed = True
break
if closed: break
if closed: break
if closed: break
if closed:
print('[+] Non-Negotiation channel linked to Relay\'s Gateway-Return Channel was closed.')
def onCloseRelay(args):
relays = collectRelays(args)
if len(relays) == 0:
Logger.fatal('Could not find agent (Gateway or Relay) which should be used to setup a channel.')
for gateway, relay in relays:
print(f'[.] Closing relay {relay["name"]} (in gateway: {gateway["name"]}).')
closeRelay(gateway, relay)
def closePeripheral(gateway, relay, peripheralName, peripheralId):
data = {
"name" : "PeripheralCommandGroup",
"data" : {
"id" : commandsMap['Close'],
"name" : peripheralName,
"command" : "Close",
"arguments" : []
}
}
Logger.info(f'Closing peripheral {peripheralName} (id: {peripheralId}). with following parameters:\n\n' + json.dumps(data, indent = 4))
ret = postRequest(f'/api/gateway/{gateway["agentId"]}/relay/{relay["agentId"]}/peripheral/{peripheralId}/command', data, rawResp = True)
if ret.status_code == 201:
print(f'[+] Peripheral {peripheralName} id:{peripheralId} was closed.')
else:
print(f'[-] Peripheral {peripheralName} id:{peripheralId} was not closed: ({ret.status_code}) {ret.text}')
def closeChannel(channel, channelToClose):
chanId = ''
if 'channelId' in channel.keys(): chanId = channel['channelId']
elif 'channel_id' in channel.keys(): chanId = channel['channel_id']
elif 'iid' in channel.keys(): chanId = channel['iid']
data = {
"name" : "ChannelCommandGroup",
"data" : {
"id" : commandsMap['Close'],
"name" : channelToClose,
"command" : "Close",
"arguments" : []
}
}
Logger.info(f'Closing {channelToClose} channel (id: {chanId}). with following parameters:\n\n' + json.dumps(data, indent = 4))
ret = postRequest(channel["url"], data, rawResp = True)
if ret.status_code == 201:
print(f'[+] Channel {channelToClose} (id: {chanId}) was closed.')
else:
print(f'[-] Channel {channelToClose} (id: {chanId}) was not closed: ({ret.status_code}) {ret.text}')
def closeNetwork(gateway):
data = {
"name":"GatewayCommandGroup",
"data":{
"id":commandsMap['ClearNetwork'],
"name":"Command",
"command":"ClearNetwork",
"arguments": [
{
"type":"boolean",
"name":"Are you sure?",
"value": True
}
]
}
}
Logger.info(f'Closing gateway {gateway["name"]} with following parameters:\n\n' + json.dumps(data, indent = 4))
ret = postRequest(f'/api/gateway/{gateway["agentId"]}/command', data, rawResp = True)
if ret.status_code == 201:
print(f'[+] Gateway {gateway["name"]} (id: {gateway["agentId"]}) was closed.')
else:
print(f'[-] Gateway {gateway["name"]} (id: {gateway["agentId"]}) was not closed: ({ret.status_code}) {ret.text}')
def onCloseNetwork(args):
gateways = getRequest(f'/api/gateway')
for _gateway in gateways:
gateway = getRequest(f'/api/gateway/{_gateway["agentId"]}')
if gateway['name'].lower() == args.gateway_id.lower() or gateway['agentId'] == args.gateway_id.lower():
closeNetwork(gateway)
def onCloseChannel(args):
gateway, relay = findAgent(args.agent_id)
if not relay and not gateway:
Logger.fatal('Could not find agent (Gateway or Relay) which should be used to setup a channel.')
channelToClose = ''
capability = processCapability(gateway)
for chan in capability['channels'].keys():
for a in sys.argv:
if a.lower() == chan.lower():
channelToClose = a
break
if len(channelToClose) > 0: break
if len(channelToClose) == 0:
Logger.fatal('Couldnt identify which channel is to be closed. Specify your channel name in script parameters')
channels = collectChannelsToSendCommand(args, channelToClose)
if len(channels) == 0:
Logger.fatal("Could not find channel to be close. Adjust your agent ID/Name setting and try again.")
for channel in channels:
closeChannel(channel, channelToClose)
def onMattermostCreate(args):
server_url = args.server_url
if server_url.endswith('/'): server_url = server_url[:-1]
@ -971,8 +1194,8 @@ def onMattermostCreate(args):
else:
print(f'[.] Will setup a Mattermost channel on a Gateway named {gateway["name"]} ({gateway["agentId"]})')
secondCommandId = getLastGatewayCommandID(gateway) + 1
commandId = getLastGatewayCommandID(gateway, False) + 1
secondCommandId = getCommandIdMapping(gateway, 'AddNegotiationChannelMattermost')
commandId = getLastGatewayCommandID()
Logger.info(f'Issuing a command with ID = {commandId}')
data = {
@ -1025,8 +1248,281 @@ def onMattermostCreate(args):
if ret.status_code == 201:
print('[+] Channel was created.')
else:
print(f'[-] Channel was not created: {ret.text}')
print(f'[-] Channel was not created: ({ret.status_code}) {ret.text}')
def onLDAPCreate(args):
gateway, relay = findAgent(args.agent_id)
if not relay and not gateway:
logger.fatal('Could not find agent (Gateway or Relay) which should be used to setup a channel.')
url = f'/api/gateway/{gateway["agentId"]}/command'
if relay != None:
url = f'/api/gateway/{gateway["agentId"]}/relay/{relay["agentId"]}/command'
print(f'[.] Will setup a LDAP channel on a Relay named {relay["name"]} ({relay["agentId"]})')
else:
print(f'[.] Will setup a LDAP channel on a Gateway named {gateway["name"]} ({gateway["agentId"]})')
secondCommandId = getCommandIdMapping(gateway, 'AddNegotiationChannelLDAP')
commandId = getLastGatewayCommandID()
Logger.info(f'Issuing a command with ID = {commandId}')
data = {
"name" : "GatewayCommandGroup",
"data" : {
"arguments" : [
{
"type" : "string",
"name" : "Negotiation Identifier",
"value" : getValueOrRandom(args.negotiation_id),
},
{
"type" : "string",
"name" : "Data LDAP Attribute",
"value" : args.data_attribute,
},
{
"type" : "string",
"name" : "Lock LDAP Attribute",
"value" : args.lock_attribute
},
{
"type" : "uint32",
"name" : "Max Packet Size",
"value" : args.max_size,
},
{
"type" : "string",
"name" : "Domain Controller",
"value" : args.domain_controller,
},
{
"type" : "string",
"name" : "Username",
"value" : args.username,
},
{
"type" : "string",
"name" : "Password",
"value" : args.password,
},
{
"type" : "string",
"name" : "User DN",
"value" : args.user_dn,
}
],
"command" : "AddNegotiationChannelLDAP",
"id" : secondCommandId,
"name" : "Command",
},
'id' : commandId,
'name' : 'GatewayCommandGroup'
}
Logger.info('Will create LDAP channel with following parameters:\n\n' + json.dumps(data, indent = 4))
ret = postRequest(url, data, rawResp = True)
if ret.status_code == 201:
print('[+] Channel was created.')
else:
print(f'[-] Channel was not created: ({ret.status_code}) {ret.text}')
def onMSSQLCreate(args):
gateway, relay = findAgent(args.agent_id)
if not relay and not gateway:
logger.fatal('Could not find agent (Gateway or Relay) which should be used to setup a channel.')
url = f'/api/gateway/{gateway["agentId"]}/command'
if relay != None:
url = f'/api/gateway/{gateway["agentId"]}/relay/{relay["agentId"]}/command'
print(f'[.] Will setup a MSSQL channel on a Relay named {relay["name"]} ({relay["agentId"]})')
else:
print(f'[.] Will setup a MSSQL channel on a Gateway named {gateway["name"]} ({gateway["agentId"]})')
secondCommandId = getCommandIdMapping(gateway, 'AddNegotiationChannelMSSQL')
commandId = getLastGatewayCommandID()
Logger.info(f'Issuing a command with ID = {commandId}')
data = {
"name" : "GatewayCommandGroup",
"data" : {
"arguments" : [
{
"type" : "string",
"name" : "Negotiation Identifier",
"value" : getValueOrRandom(args.negotiation_id),
},
{
"type" : "string",
"name" : "Server Name",
"value" : args.server_name,
},
{
"type" : "string",
"name" : "Database Name",
"value" : args.database_name
},
{
"type" : "string",
"name" : "Table Name",
"value" : args.table_name,
},
{
"type" : "string",
"name" : "Username",
"value" : args.username,
},
{
"type" : "string",
"name" : "Password",
"value" : args.password,
},
{
"type" : "boolean",
"name" : "Use Integrated Security (SSPI) - use for domain joined accounts",
"value" : args.sspi,
}
],
"command" : "AddNegotiationChannelMSSQL",
"id" : secondCommandId,
"name" : "Command",
},
'id' : commandId,
'name' : 'GatewayCommandGroup'
}
Logger.info('Will create MSSQL channel with following parameters:\n\n' + json.dumps(data, indent = 4))
ret = postRequest(url, data, rawResp = True)
if ret.status_code == 201:
print('[+] Channel was created.')
else:
print(f'[-] Channel was not created: ({ret.status_code}) {ret.text}')
def onTurnOnTeamserver(args):
gateways = getRequest(f'/api/gateway')
gateway = None
for _gateway in gateways:
g = getRequest(f'/api/gateway/{_gateway["agentId"]}')
if g['name'].lower() == args.gateway_id.lower() or g['agentId'] == args.gateway_id.lower():
gateway = g
break
if not gateway:
Logger.fatal(f'Could not find Gateway with specified gateway_id: {args.gateway_id}')
commandId = getCommandIdMapping(gateway, "TurnOnConnectorTeamServer")
data = {
"name":"GatewayCommandGroup",
"data": {
"id":commandId,
"name":"Command",
"command":"TurnOnConnectorTeamServer",
"arguments": [
{
"type":"ip",
"name":"Address",
"value":args.address
},
{
"type":"uint16",
"name":"Port",
"value":args.port
}
]
}
}
Logger.info(f'Will Turn On connector TeamServer on gateway {gateway["name"]} with following parameters:\n\n' + json.dumps(data, indent = 4))
ret = postRequest(f'/api/gateway/{gateway["agentId"]}/command', data, rawResp = True)
if ret.status_code == 201:
print('[+] Connection with Teamserver established.')
else:
print(f'[-] Could not establish connection with Teamserver: ({ret.status_code}) {ret.text}')
def onTurnOffConnector(args):
gateways = getRequest(f'/api/gateway')
gateway = None
for _gateway in gateways:
g = getRequest(f'/api/gateway/{_gateway["agentId"]}')
if g['name'].lower() == args.gateway_id.lower() or g['agentId'] == args.gateway_id.lower():
gateway = g
break
if not gateway:
Logger.fatal(f'Could not find Gateway with specified gateway_id: {args.gateway_id}')
data = {
"name":"PeripheralCommandGroup",
"data": {
"id":commandsMap['Close'],
"name":"TeamServer",
"command":"TurnOff",
"arguments": []
}
}
Logger.info(f'Will Turn Off connector TeamServer on gateway {gateway["name"]} with following parameters:\n\n' + json.dumps(data, indent = 4))
ret = postRequest(f'/api/gateway/{gateway["agentId"]}/connector/{args.connector_id}/command', data, rawResp = True)
if ret.status_code == 201:
print('[+] Closed connection with Connector.')
else:
print(f'[-] Could not close connection with connector: ({ret.status_code}) {ret.text}')
def onDownloadGateway(args):
gateway_name = getValueOrRandom(args.gateway_name)
_format = 'exe'
arch = 'x64'
if args.format.lower().startswith('dll'): _format = 'dll'
if args.format.lower().endswith('86'): _format = 'x86'
print(f'[.] Downloading gateway executable in format {args.format} with name: {gateway_name}')
url = f'/api/gateway/{_format}/{arch}?name={gateway_name}'
output = getRequest(url, True, stream = True)
data = output.content
if len(args.override_ip) > 0:
data2 = io.BytesIO()
with zipfile.ZipFile(io.BytesIO(data), 'r') as f:
with zipfile.ZipFile(data2, 'w') as g:
for i in f.infolist():
buf = f.read(i.filename)
if i.filename.lower().endswith('.json'):
conf = json.loads(buf)
conf['API Bridge IP'] = args.override_ip
buf = json.dumps(conf, indent=4)
print(f'[.] Overidden stored in JSON configuration IP address to: {args.override_ip}')
g.writestr(i.filename, buf)
data = data2.getvalue()
if args.extract:
with zipfile.ZipFile(io.BytesIO(data), 'r') as f:
for i in f.infolist():
outp = os.path.join(args.outfile, os.path.basename(i.filename))
with open(outp, 'wb') as g:
g.write(f.read(i.filename))
print('[+] Gateway ZIP package downloaded & extracted.')
else:
with open(args.outfile, 'wb') as f:
f.write(data)
print('[+] Gateway ZIP package downloaded.')
def parseArgs(argv):
global config
@ -1042,6 +1538,7 @@ def parseArgs(argv):
opts.add_argument('-v', '--verbose', action='store_true', help='Display verbose output.')
opts.add_argument('-d', '--debug', action='store_true', help='Display debug output.')
opts.add_argument('-f', '--format', choices=['json', 'text'], default='text', help='Output format. Can be JSON or text (default).')
opts.add_argument('-n', '--dry-run', action='store_true', help='Do not send any HTTP POST request that could introduce changes in C3 network.')
opts.add_argument('-A', '--httpauth', metavar = 'user:pass', help = 'HTTP Basic Authentication (user:pass)')
subparsers = opts.add_subparsers(help = 'command help', required = True)
@ -1058,6 +1555,20 @@ def parseArgs(argv):
alarm_relay.add_argument('-g', '--gateway-id', metavar='gateway_id', help = 'ID (or Name) of the Gateway which Relays should be returned. If not given, will result all relays from all gateways.')
alarm_relay.set_defaults(func = onAlarmRelay)
#
# Download
#
download = subparsers.add_parser('download', help = 'Download options')
download_sub = download.add_subparsers(help = 'Download what?', required = True)
download_gateway = download_sub.add_parser('gateway', help = 'Download gateway')
download_gateway.add_argument('-x', '--extract', action='store_true', help = 'Consider outfile as directory path. Then extract downloaded ZIP file with gateway into that directory.')
download_gateway.add_argument('-F', '--format', choices=['exe86', 'exe64', 'dll86', 'dll64'], default='exe64', help = 'Gateway executable format. <format><arch>. Formats: exe, dll. Archs: 86, 64. Default: exe64')
download_gateway.add_argument('-G', '--gateway-name', metavar='GATEWAY_NAME', default='random', help = 'Name of the Gateway. Default: random name')
download_gateway.add_argument('-O', '--override-ip', metavar='IP', help = 'Override gateway configuration IP stored in JSON. By default will use 0.0.0.0')
download_gateway.add_argument('outfile', metavar='outfile', help = 'Where to save output file.')
download_gateway.set_defaults(func = onDownloadGateway)
#
# List
#
@ -1097,6 +1608,52 @@ def parseArgs(argv):
parser_ping.add_argument('-k', '--keep-pinging', metavar='delay', type=int, default=0, help = 'Keep pinging choosen Relays. Will send a ping every "delay" number of seconds. Default: sends ping only once.')
parser_ping.set_defaults(func = onPing)
#
# Connector
#
parser_connector = subparsers.add_parser('connector', help = 'Connector options')
parser_connector.add_argument('gateway_id', metavar = 'gateway_id', help = 'Gateway which should be used to manage its connectors.')
parser_connector_sub = parser_connector.add_subparsers(help = 'What to do about that Connector?', required = True)
## turnon
connector_turnon = parser_connector_sub.add_parser('turnon', help = 'Turn on connector (connects to a Teamserver, Covenant, etc).')
connector_turnon_sub = connector_turnon.add_subparsers(help = 'What kind of connector?', required = True)
### Teamserver
turnon_connector_teamserver = connector_turnon_sub.add_parser('teamserver', help = 'Teamserver connector specific options.')
turnon_connector_teamserver.add_argument('address', metavar = 'address', help = 'Teamserver externalC2 address')
turnon_connector_teamserver.add_argument('port', metavar = 'port', help = 'Teamserver externalC2 port')
turnon_connector_teamserver.set_defaults(func = onTurnOnTeamserver)
## turnoff
connector_turnoff = parser_connector_sub.add_parser('turnoff', help = 'Turn off connector (connects to a Teamserver, Covenant, etc).')
connector_turnoff.add_argument('connector_id', metavar = 'connector_id', help = 'Connector\'s ID that should be closed')
connector_turnoff.set_defaults(func = onTurnOffConnector)
#
# Close
#
parser_close = subparsers.add_parser('close', help = 'Close command.')
parser_close_sub = parser_close.add_subparsers(help = 'Close what?', required = True)
## Network
close_channel = parser_close_sub.add_parser('network', help = 'Close Network / ClearNetwork.')
close_channel.add_argument('gateway_id', metavar = 'gateway_id', help = 'Gateway which network is to be closed. Can be ID or Name.')
close_channel.set_defaults(func = onCloseNetwork)
## Channel
close_channel = parser_close_sub.add_parser('channel', help = 'Close a channel.')
close_channel.add_argument('agent_id', metavar = 'agent_id', help = 'Gateway or Relay that will be used to find a channel to close. Can be ID or Name.')
close_channel.add_argument('-c', '--channel-id', help = 'Specifies ID of the channel to commander. If not given - will issue specified command to all channels in a Gateway/Relay.')
close_channel.set_defaults(func = onCloseChannel)
## Relay
close_channel = parser_close_sub.add_parser('relay', help = 'Close a Relay.')
close_channel.add_argument('relay_id', metavar = 'relay_id', help = 'Relay to be closed. Can be ID or Name.')
close_channel.add_argument('-g', '--gateway-id', metavar='gateway_id', help = 'ID (or Name) of the Gateway runs specified Relay. If not given, will return all relays matching criteria from all gateways.')
close_channel.set_defaults(func = onCloseRelay)
#
# Channel
#
@ -1120,16 +1677,16 @@ def parseArgs(argv):
mattermost_parser = mattermost.add_subparsers(help = 'Command to send', required = True)
### Create
#mattermost_create = mattermost_parser.add_parser('create', help = 'Setup a Mattermost channel.')
#mattermost_create.add_argument('agent_id', metavar = 'agent_id', help = 'Gateway or Relay that will be used to setup a channel. Can be ID or Name.')
#mattermost_create.add_argument('server_url', metavar = 'server_url', help = 'Mattermost Server URL, example: http://192.168.0.100:8888')
#mattermost_create.add_argument('team_name', metavar = 'team_name', help = 'Mattermost Team name where to create channels.')
#mattermost_create.add_argument('access_token', metavar = 'access_token', help = 'Personal Access Token value.')
#mattermost_create.add_argument('--negotiation-id', metavar = 'ID', default='random', help = 'Negotiation Identifier. Will be picked at random if left empty.')
#mattermost_create.add_argument('--channel-name', metavar = 'CHANNEL', default='random', help = 'Channel name to create. Will be picked at random if left empty.')
#mattermost_create.add_argument('--user-agent', metavar = 'USERAGENT', default='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36',
# help = 'User-Agent string to use in HTTP requests.')
#mattermost_create.set_defaults(func = onMattermostCreate)
mattermost_create = mattermost_parser.add_parser('create', help = 'Setup a Mattermost Negotiation channel.')
mattermost_create.add_argument('agent_id', metavar = 'agent_id', help = 'Gateway or Relay that will be used to setup a channel. Can be ID or Name.')
mattermost_create.add_argument('server_url', metavar = 'server_url', help = 'Mattermost Server URL, example: http://192.168.0.100:8888')
mattermost_create.add_argument('team_name', metavar = 'team_name', help = 'Mattermost Team name where to create channels.')
mattermost_create.add_argument('access_token', metavar = 'access_token', help = 'Personal Access Token value.')
mattermost_create.add_argument('--negotiation-id', metavar = 'ID', default='random', help = 'Negotiation Identifier. Will be picked at random if left empty.')
mattermost_create.add_argument('--channel-name', metavar = 'CHANNEL', default='random', help = 'Channel name to create. Will be picked at random if left empty.')
mattermost_create.add_argument('--user-agent', metavar = 'USERAGENT', default='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36',
help = 'User-Agent string to use in HTTP requests.')
mattermost_create.set_defaults(func = onMattermostCreate)
### Purge
mattermost_purge = mattermost_parser.add_parser('clear', help = 'Purge all dangling posts/messages from Mattermost channel.')
@ -1143,6 +1700,19 @@ def parseArgs(argv):
ldap_clear = ldap_parser.add_parser('clear', help = 'Clear LDAP attribute associated with that channel.')
ldap_clear.set_defaults(func = onLDAPClear)
### Create
ldap_create = ldap_parser.add_parser('create', help = 'Setup a LDAP Negotiation channel.')
ldap_create.add_argument('agent_id', metavar = 'agent_id', help = 'Gateway or Relay that will be used to setup a channel. Can be ID or Name.')
ldap_create.add_argument('--data-attribute', metavar = 'data_attribute', default = 'mSMQSignCertificates', help = 'Data LDAP Attribute. Default: mSMQSignCertificates')
ldap_create.add_argument('--lock-attribute', metavar = 'lock_attribute', default = 'primaryInternationalISDNNumber', help = 'Lock LDAP Attribute. Default: primaryInternationalISDNNumber')
ldap_create.add_argument('--max-size', metavar = 'max_size', default = 1047552, type = int, help = 'Max Packet Size. Default: 1047552')
ldap_create.add_argument('domain_controller', metavar = 'domain_controller', help = 'Domain Controller.')
ldap_create.add_argument('username', metavar = 'username', help = 'LDAP username.')
ldap_create.add_argument('password', metavar = 'password', help = 'LDAP password.')
ldap_create.add_argument('user_dn', metavar = 'user_dn', help = 'User Distinguished Name, example: CN=Jeff Smith,CN=users,DC=fabrikam,DC=com')
ldap_create.add_argument('--negotiation-id', metavar = 'ID', default='random', help = 'Negotiation Identifier. Will be picked at random if left empty.')
ldap_create.set_defaults(func = onLDAPCreate)
## MSSQL
mssql = parser_channel_sub.add_parser('mssql', help = 'MSSQL channel specific commands.')
mssql_parser = mssql.add_subparsers(help = 'Command to send', required = True)
@ -1151,6 +1721,18 @@ def parseArgs(argv):
mssql_clear = mssql_parser.add_parser('clear', help = 'Clear channel\'s DB Table.')
mssql_clear.set_defaults(func = onMSSQLClearTable)
### Create
mssql_create = mssql_parser.add_parser('create', help = 'Setup a MSSQL Negotiation channel.')
mssql_create.add_argument('agent_id', metavar = 'agent_id', help = 'Gateway or Relay that will be used to setup a channel. Can be ID or Name.')
mssql_create.add_argument('server_name', metavar = 'server_name', help = 'MSSQL Server name')
mssql_create.add_argument('database_name', metavar = 'database_name', help = 'Database Name.')
mssql_create.add_argument('table_name', metavar = 'table_name', help = 'Table Name.')
mssql_create.add_argument('username', metavar = 'username', help = 'Database username.')
mssql_create.add_argument('password', metavar = 'password', help = 'Database password.')
mssql_create.add_argument('sspi', metavar = 'sspi', type=bool, help = 'Use Integrated Security (SSPI) - use for domain joined accounts. Default: false.')
mssql_create.add_argument('--negotiation-id', metavar = 'ID', default='random', help = 'Negotiation Identifier. Will be picked at random if left empty.')
mssql_create.set_defaults(func = onMSSQLCreate)
## UncShareFile
unc = parser_channel_sub.add_parser('uncsharefile', help = 'UncShareFile channel specific commands.')
unc_parser = unc.add_subparsers(help = 'Command to send', required = True)