#!/bin/bash
#
# Code related to ESET's research on Ebury.
# For more information on Ebury see
# https://www.welivesecurity.com/en/eset-research/ebury-alive-unseen-400k-linux-servers-compromised-cryptotheft-financial-gain/
# https://web-assets.esetstatic.com/wls/en/papers/white-papers/ebury-is-alive-but-unseen.pdf
#
# For feedback or questions contact us at: github@eset.com
# https://github.com/eset/malware-research/
#
# This code is provided to the community under the two-clause BSD license as
# follows:
#
# Copyright (C) 2024 ESET
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. 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.
#
# 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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.

declare -A ebury_socket_addr=(
	["/proc/udevd"]="1.5.x"
	["/tmp/dbus-0m9eDQpdXZ"]="1.5.1e"
	["/tmp/dbus-ZP7tFO4xsL"]="1.5.5"
	["/tmp/dbus-Xrga2cOewg"]="1.5.5c"
	["/tmp/dbus-9XZXkmdfpN"]="1.5.6d"
	["/tmp/dbus-n3UUkeqEZG"]="1.5.6dp"
	["/tmp/dbus-n3UUkeqEZG"]="1.5.6ep"
	["/tmp/dbus-luzG4UqDt8"]="1.6.2a"
	["/tmp/dbus-kZ8VEtJDOJ"]="1.6.2d"
	["/tmp/dbus-VdyGBaqZws"]="1.6.2fp"
	["/tmp/dbus-vBWUDhHCHp"]="1.6.2gp"
	["/run/systemd/journal/dlog"]="1.6.3"
	["/run/systemd/log-wuO3nuFBHN"]="1.7.0c"
	["/run/systemd/journal-YAjXO8luqOa"]="1.7.0p"
	["/run/systemd/log-90zMvYX7uL"]="1.7.1c"
	["/run/systemd/log"]="1.7.3"
	["UDEV-4kAmkRW3"]="1.7.3c"
	["/dev/event-E4LgEFWIcy"]="1.7.4c"
	["/dev/stats-MxPAxNpy3x"]="1.7.4d"
	["event-CRbBwZlvXa"]="1.8.x"
	["event-wHPG5cMU8A"]="1.8.2.eX"
	["event-hyAa2VL3WE"]="1.8.2.e3"
)

# Finds the PID of the Ebury process from the inode of the UNIX socket
function find_ebury_pid() {
	find /proc -maxdepth 3 -lname "socket:\[$1\]" 2> /dev/null | cut -d/ -f 3
}

# Finds the inode of an abstract UNIX socket
function find_socket_inode() {
	grep "@$1\$" /proc/net/unix | grep ' 00010000 ' | awk '{ print $7 }'
}

# Tries to connect to an abstract UNIX socket, returns 0 on success
function abstract_unix_socket_exists() {
	if type perl > /dev/null; then
		perl -e 'use Socket; my $s; socket($s, PF_UNIX, SOCK_STREAM, 0) and connect($s, pack_sockaddr_un(chr(0).$ARGV[0])) and close($s) or exit 1' "$1"
	elif type python3 > /dev/null; then
		python3 -c 'import sys; from socket import *; s=socket(AF_UNIX, SOCK_STREAM); s.connect(b"\0" + sys.argv[1].encode()); s.close()' "$1" 2> /dev/null
	else
		echo "Perl or Python are required"
		exit 2
	fi
}

function retry_with_rootkit_evastion {
	if [ -z "$H" -a -f "$BASH_SOURCE" ]; then
		echo "Trying to defuse the userland rootkit"
		H=1 LD_PRELOAD=":" LD_DEBUG=":" bash "$BASH_SOURCE"
	fi
}

function detect() {
	local RED='\033[0;31m'
	local CLEAR='\033[0m'
	for socket in "${!ebury_socket_addr[@]}"; do
		if abstract_unix_socket_exists "$socket"; then
			echo -n -e "${RED}Found abstract UNIX socket \"$socket\" for "
			echo -e "Ebury version ${ebury_socket_addr["$socket"]}${CLEAR}"
			inode="$(find_socket_inode "$socket")"
			if [ -n "$inode" ]; then
				echo "Found socket inode: $inode"
				ebury_pid="$(find_ebury_pid "$inode")"
				if [ -n "$ebury_pid" ]; then
					echo "Process with PID \"$ebury_pid\" should be inspected"
				else
					echo "Could not find the Ebury PID. Is the Ebury userland rootkit active?"
					retry_with_rootkit_evastion
				fi
			else
				echo "Could not find the inode of the socket. Is the Ebury userland rootkit active?"
				retry_with_rootkit_evastion
			fi
			exit 1
		fi
	done
	echo "Ebury UNIX socket was not found. This is not a guarantee that"
	echo "the system is free from Ebury. Please refer to the full list of"
	echo "indicators and inspect the system manually."
}

detect
