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**: **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 :: F-Secure's C3 Client - a lightweight automated companion with C3 voyages
Mariusz B. / mgeeky, <mb@binary-offensive.com> Mariusz B. / mgeeky, <mb@binary-offensive.com>
@ -22,12 +22,14 @@ Usage: ./c3-client.py [options] <host> <command> [...]
positional arguments: positional arguments:
host C3 Web API host:port host C3 Web API host:port
{alarm,list,get,ping,channel} {alarm,list,get,ping,connector,close,channel}
command help command help
alarm Alarm options alarm Alarm options
list List options list List options
get Get options get Get options
ping Ping Relays ping Ping Relays
connector Connector options
close Close command.
channel Send Channel-specific command channel Send Channel-specific command
optional arguments: optional arguments:
@ -36,6 +38,7 @@ optional arguments:
-d, --debug Display debug output. -d, --debug Display debug output.
-f {json,text}, --format {json,text} -f {json,text}, --format {json,text}
Output format. Can be JSON or text (default). 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 -A user:pass, --httpauth user:pass
HTTP Basic Authentication (user:pass) HTTP Basic Authentication (user:pass)
``` ```
@ -78,16 +81,32 @@ Currently, following commands are supported:
- `alarm` - `alarm`
- `relay` - trigger an alarm whenever a new Relay checks-in on a gateway - `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 - `ping` - ping selected Relays
- `channel` - channel-specific commands - `channel` - channel-specific commands
- `all` - `all`
- `clear` - Clear message queue of every supported channel at once - `clear` - Clear message queue of every supported channel at once
- `mattermost` - `mattermost`
- `create`- Creates a Mattermost Negotiation channel
- `clear` - Clear Mattermost's channel messages to improve bandwidth - `clear` - Clear Mattermost's channel messages to improve bandwidth
- `ldap` - `ldap`
- `create` - Creates a LDAP Negotiation Channel
- `clear` - Clear LDAP attribute to improve bandwidth - `clear` - Clear LDAP attribute to improve bandwidth
- `mssql` - `mssql`
- `create` - Creates a MSSQL Negotiation Channel
- `clear` - Clear DB Table entries to improve bandwidth - `clear` - Clear DB Table entries to improve bandwidth
- `uncsharefile` - `uncsharefile`
- `clear` - Remove all message files to improve bandwidth - `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 os
import sys import sys
import io
import re import re
import time import time
import json import json
@ -10,6 +11,7 @@ import subprocess
import argparse import argparse
import random import random
import string import string
import zipfile
from datetime import datetime from datetime import datetime
@ -17,6 +19,7 @@ config = {
'verbose' : False, 'verbose' : False,
'debug' : False, 'debug' : False,
'host' : '', 'host' : '',
'dry_run' : False,
'command' : '', 'command' : '',
'format' : 'text', 'format' : 'text',
'httpauth' : '', 'httpauth' : '',
@ -88,7 +91,7 @@ class Logger:
def printJson(data): def printJson(data):
print(json.dumps(data, sort_keys=True, indent=4)) print(json.dumps(data, sort_keys=True, indent=4))
def getRequest(url, rawResp = False): def getRequest(url, rawResp = False, stream = False):
auth = None auth = None
if config['httpauth']: if config['httpauth']:
user, _pass = config['httpauth'].split(':') user, _pass = config['httpauth'].split(':')
@ -98,7 +101,13 @@ def getRequest(url, rawResp = False):
fullurl = config["host"] + url fullurl = config["host"] + url
Logger.info(f'GET Request: {fullurl}') 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: if rawResp:
return resp return resp
@ -121,6 +130,18 @@ def postRequest(url, data=None, contentType = 'application/json', rawResp = Fals
resp = None 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'): if contentType.endswith('/json'):
resp = requests.post(fullurl, json=data, headers=headers, auth=auth) resp = requests.post(fullurl, json=data, headers=headers, auth=auth)
else: else:
@ -163,12 +184,11 @@ def printFullGateway(gatewayId):
for d in c['propertiesText']['arguments']: for d in c['propertiesText']['arguments']:
if d['type'] == 'ip': if d['type'] == 'ip':
addr = d['value'] addr = d['value']
break
elif d['type'] == 'uint16': elif d['type'] == 'uint16':
port = d['value'] port = d['value']
break
print(f'{indent} Host: {addr}:{port}\n') print(f'{indent} Connector ID: {c["iid"]}')
print(f'{indent} Host: {addr}:{port}\n')
num = 0 num = 0
print(f'{indent}Channels:') print(f'{indent}Channels:')
@ -465,6 +485,42 @@ def collectRelays(args):
return relays 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): def onPing(args):
if args.keep_pinging > 0: if args.keep_pinging > 0:
while True: while True:
@ -506,20 +562,17 @@ def _onPing(args):
else: else:
print(f'[+] Pinged {pinged} active relays.\n') print(f'[+] Pinged {pinged} active relays.\n')
def getLastGatewayCommandID(gateway, secondOrder = True): def getLastGatewayCommandID():
lastId = 0 lastId = 0
commands = getRequest(f'/api/gateway/{gateway["agentId"]}/command') gateways = getRequest(f'/api/gateway')
for comm in commands:
if secondOrder: for gateway in gateways:
if 'data' in comm.keys(): commands = getRequest(f'/api/gateway/{gateway["agentId"]}/command')
if 'id' in comm['data'].keys(): for comm in commands:
if comm['data']['id'] > lastId:
lastId = comm['data']['id']
else:
if comm['id'] > lastId: if comm['id'] > lastId:
lastId = comm['id'] lastId = comm['id']
return lastId return lastId + random.randint(5, 25)
def onAllChannelsClear(args): def onAllChannelsClear(args):
channels = { channels = {
@ -873,6 +926,8 @@ def onAlarmRelay(args):
try: try:
while True: while True:
time.sleep(2)
currRelays = collectRelays(args) currRelays = collectRelays(args)
currRelayIds = set() currRelayIds = set()
currLastTimestamp = 0 currLastTimestamp = 0
@ -955,6 +1010,174 @@ def getValueOrRandom(val, N = 6):
return val 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): def onMattermostCreate(args):
server_url = args.server_url server_url = args.server_url
if server_url.endswith('/'): server_url = server_url[:-1] if server_url.endswith('/'): server_url = server_url[:-1]
@ -971,8 +1194,8 @@ def onMattermostCreate(args):
else: else:
print(f'[.] Will setup a Mattermost channel on a Gateway named {gateway["name"]} ({gateway["agentId"]})') print(f'[.] Will setup a Mattermost channel on a Gateway named {gateway["name"]} ({gateway["agentId"]})')
secondCommandId = getLastGatewayCommandID(gateway) + 1 secondCommandId = getCommandIdMapping(gateway, 'AddNegotiationChannelMattermost')
commandId = getLastGatewayCommandID(gateway, False) + 1 commandId = getLastGatewayCommandID()
Logger.info(f'Issuing a command with ID = {commandId}') Logger.info(f'Issuing a command with ID = {commandId}')
data = { data = {
@ -1025,8 +1248,281 @@ def onMattermostCreate(args):
if ret.status_code == 201: if ret.status_code == 201:
print('[+] Channel was created.') print('[+] Channel was created.')
else: 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): def parseArgs(argv):
global config global config
@ -1042,6 +1538,7 @@ def parseArgs(argv):
opts.add_argument('-v', '--verbose', action='store_true', help='Display verbose output.') 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('-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('-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)') opts.add_argument('-A', '--httpauth', metavar = 'user:pass', help = 'HTTP Basic Authentication (user:pass)')
subparsers = opts.add_subparsers(help = 'command help', required = True) 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.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) 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 # 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.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) 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 # Channel
# #
@ -1120,16 +1677,16 @@ def parseArgs(argv):
mattermost_parser = mattermost.add_subparsers(help = 'Command to send', required = True) mattermost_parser = mattermost.add_subparsers(help = 'Command to send', required = True)
### Create ### Create
#mattermost_create = mattermost_parser.add_parser('create', help = 'Setup a Mattermost channel.') 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('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('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('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('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('--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('--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', 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.') help = 'User-Agent string to use in HTTP requests.')
#mattermost_create.set_defaults(func = onMattermostCreate) mattermost_create.set_defaults(func = onMattermostCreate)
### Purge ### Purge
mattermost_purge = mattermost_parser.add_parser('clear', help = 'Purge all dangling posts/messages from Mattermost channel.') 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 = ldap_parser.add_parser('clear', help = 'Clear LDAP attribute associated with that channel.')
ldap_clear.set_defaults(func = onLDAPClear) 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
mssql = parser_channel_sub.add_parser('mssql', help = 'MSSQL channel specific commands.') mssql = parser_channel_sub.add_parser('mssql', help = 'MSSQL channel specific commands.')
mssql_parser = mssql.add_subparsers(help = 'Command to send', required = True) 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 = mssql_parser.add_parser('clear', help = 'Clear channel\'s DB Table.')
mssql_clear.set_defaults(func = onMSSQLClearTable) 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 ## UncShareFile
unc = parser_channel_sub.add_parser('uncsharefile', help = 'UncShareFile channel specific commands.') unc = parser_channel_sub.add_parser('uncsharefile', help = 'UncShareFile channel specific commands.')
unc_parser = unc.add_subparsers(help = 'Command to send', required = True) unc_parser = unc.add_subparsers(help = 'Command to send', required = True)