通过chkrootkit学习如何在linux下检测RootKit

Rootkit是一种特殊的恶意软件,它的功能是在安装目标上隐藏自身及指定的文件、进程和网络链接等信息,比较多见到的是Rootkit一般都和木马、后门等其他恶意程序结合使用。Rootkit一词更多地是指被作为驱动程序,加载到操作系统内核中的恶意软件。

chkrootkit简介

chkrootkit是一个linux下检RootKit的脚本,在某些检测中会调用当前目录的检测程序

官网:http://www.chkrootkit.org/

下载源码:ftp://ftp.pangeia.com.br/pub/seg/pac/chkrootkit.tar.gz

解压后执行 make 命令,就可以使用了

一般直接运行,一旦有INFECTED,说明可能被植入了RootKit

1
./chkrootkit | grep INFECTED

总体流程

首先删除别名,确保接下来的一些操作不会用了被RootKit改变了的别名

1
2
3
4
5
6
### workaround for some Bourne shell implementations
unalias login > /dev/null 2>&1
unalias ls > /dev/null 2>&1
unalias netstat > /dev/null 2>&1
unalias ps > /dev/null 2>&1
unalias dirname > /dev/null 2>&1

一开始会检测一些必要的命令是否可用,可执行,因为检测基于这些命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cmdlist="
awk
cut
echo
egrep
find
head
id
ls
netstat
ps
sed
ssh
strings
uname
"

接下来就是检测ps的参数ax好不好使,好使就使用ax,不好使就用-fe

1
2
3
4
5
6
# Check if ps command is ok
if ${ps} ax >/dev/null 2>&1 ; then
ps_cmd="ax"
else
ps_cmd="-fe"
fi

当然还需检测你是否是root,就根据你的id号是否为0

1
2
3
4
if [ `${id} | ${cut} -d= -f2 | ${cut} -d\( -f1` -ne 0 ]; then
echo "$0 need root privileges"
exit 1
fi

接下来就是默认执行所有测试,当然你也可以指定测试的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if [ $# -gt 0 ]
then
### perform only tests supplied as arguments
for arg in $*
do
### check if is a valid test name
if echo "${TROJAN} ${TOOLS}"| \
${egrep} -v "${L_REGEXP}$arg${R_REGEXP}" > /dev/null 2>&1
then
echo >&2 "$0: \`$arg': not a known test"
exit 1
fi
done
LIST=$*
else
### this is the default: perform all tests
LIST="${TROJAN} ${TOOLS}"
fi

接下来只是对是否开启调试模式,用户是否指定了要检测的根目录进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if [ "${DEBUG}" = "t" ]; then
set -x
fi

if [ "${ROOTDIR}" != "/" ]; then

### remove trailing `/'
ROOTDIR=`echo ${ROOTDIR} | ${sed} -e 's/\/*$//g'`

for dir in ${pth}
do
if echo ${dir} | ${egrep} '^/' > /dev/null 2>&1
then
newpth="${newpth} ${ROOTDIR}${dir}"
else
newpth="${newpth} ${ROOTDIR}/${dir}"
fi
done
pth=${newpth}
ROOTDIR="${ROOTDIR}/"
fi

最后便是循环调用各个check函数进行处理了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
for cmd in ${LIST}
do

if echo "${TROJAN}" | \
${egrep} "${L_REGEXP}$cmd${R_REGEXP}" > /dev/null 2>&1
then
if [ "${EXPERT}" != "t" -a "${QUIET}" != "t" ]; then
printn "Checking \`${cmd}'... "
fi
chk_${cmd}
STATUS=$?

### quiet mode
if [ "${QUIET}" = "t" ]; then
### show only INFECTED status
if [ ${STATUS} -eq 0 ]; then
echo "Checking \`${cmd}'... INFECTED"
fi
continue
fi

case $STATUS in
0) echo "INFECTED";;
1) echo "not infected";;
2) echo "not tested";;
3) echo "not found";;
4) echo "infected but disabled";;
5) ;; ### expert mode
esac
else
### external tool
if [ "${EXPERT}" != "t" -a "${QUIET}" != "t" ]; then
printn "Checking \`$cmd'... "
fi
${cmd}

fi
done

那么接下来每个check方法到底是怎么检测的呢?接下来

检测方法

通过分析脚本,总结出检测方法如下:

  1. 搜索通用的ROOTKIT特征的字符串
  2. 对某种特定的rootkits,或者命令的特殊的感染特征进行检测
  3. 对某种特定的rootkits的生成的特定文件的检测
  4. 对程序的SUID位的设置进行检测
  5. 对ldsopreload的检测
  6. 查找可疑的log文件
  7. 查找可疑的php文件
  8. 检测.history文件
  9. 检测有无程序监听了一些可疑的端口
  10. 检测Linux可加载内核模块
  11. 检测有无隐藏进程
  12. 检测目录的软链接异常
  13. 检测网络接口的异常
  14. 检测用户的登录日志
  15. 检测上一次登录
  16. 检测可疑的没有tty记录的进程

下面对上面这些方法结合脚本代码进行简单说明

搜索通用的ROOTKIT特征的字符串

搜索的是下面的比较通用的ROOTKIT字符串

1
2
# Many trojaned commands have this label
GENERIC_ROOTKIT_LABEL="^/bin/.*sh$|bash|elite$|vejeta|\.ark|iroffer"

可以看到前两个都是shell相关的,相关的示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
chk_chfn () {
STATUS=${NOT_INFECTED}
CMD=`loc chfn chfn $pth`
[ ${?} -ne 0 ] && return ${NOT_FOUND}

if [ "${EXPERT}" = "t" ]; then
expertmode_output "${strings} -a ${CMD}"
return 5
fi

case "${SYSTEM}" in
Linux)
if ${strings} -a ${CMD} | ${egrep} "${GENERIC_ROOTKIT_LABEL}" \
>/dev/null 2>&1
then
STATUS=${INFECTED}
fi;;
FreeBSD)
[ `echo $V | ${awk} '{ if ( $1 >= 5.0) print 1; else print 0 }'` -eq 1 ] && n=1 || n=2
if [ `${strings} -a ${CMD} | \
${egrep} -c "${GENERIC_ROOTKIT_LABEL}"` -ne $n ]
then
STATUS=${INFECTED}
fi;;
esac
return ${STATUS}
}

程序针对Linux和FreeBSD系统分开处理,都是通过strings获取二进制程序中的字符串,再使用egrep命令去正则匹配,匹配成功就将返回值STATUS设置为INFECTED这个常量(这个在文件开头处定义了)

对某种特定的rootkits,或者命令的特殊的感染特征进行检测

比如这个amd命令的某个感染特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
chk_amd () {
STATUS=${NOT_INFECTED}
AMD_INFECTED_LABEL="blah"
CMD=`loc amd amd $pth`
if [ ! -x "${CMD}" ]; then
return ${NOT_FOUND}
fi
if [ "${EXPERT}" = "t" ]; then
expertmode_output "${strings} -a ${CMD}"
return 5
fi
if ${strings} -a ${CMD} | ${egrep} "${AMD_INFECTED_LABEL}" >/dev/null 2>&1
then
STATUS=${INFECTED}
fi
return ${STATUS}
}

下面这个检测crontab的nobody用户,并且定时任务中有数字的, 可能是Lupper.Worm
当然还是有CRONTAB_I_L这个特殊的检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
chk_crontab () {
STATUS=${NOT_INFECTED}
CRONTAB_I_L="crontab.*666"

CMD=`loc crontab crontab $pth`

if [ ! -r ${CMD} ]
then
return ${NOT_FOUND}
fi

if [ "${EXPERT}" = "t" ]; then
expertmode_output "${CMD} -l -u nobody"
return 5
fi
# slackware's crontab have a bug
if ( ${CMD} -l -u nobody | $egrep [0-9] ) >/dev/null 2>&1 ; then
${echo} "Warning: crontab for nobody found, possible Lupper.Worm... "
if ${CMD} -l -u nobody 2>/dev/null | ${egrep} $CRONTAB_I_L >/dev/null 2>&1
then
STATUS=${INFECTED}
fi
fi
return ${STATUS}
}

对Ramen Worm进行特征匹配

1
2
3
4
if ${egrep} "^asp" ${ROOTDIR}etc/inetd.conf >/dev/null 2>&1; then
echo "Warning: Possible Ramen Worm installed in inetd.conf"
STATUS=${INFECTED}
fi

对某种特定的rootkits生成的特定文件的检测

如下面的HiDrootkit和t0rn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
### HiDrootkit
if [ "${QUIET}" != "t" ]; then printn \
"Searching for HiDrootkit's default dir... "; fi
if [ -d ${ROOTDIR}var/lib/games/.k ]
then
echo "Possible HiDrootkit installed"
else
if [ "${QUIET}" != "t" ]; then echo "nothing found"; fi
fi

### t0rn
if [ "${QUIET}" != "t" ]; then printn\
"Searching for t0rn's default files and dirs... "; fi
if [ -f ${ROOTDIR}etc/ttyhash -o -f ${ROOTDIR}sbin/xlogin -o \
-d ${ROOTDIR}usr/src/.puta -o -r ${ROOTDIR}lib/ldlib.tk -o \
-d ${ROOTDIR}usr/info/.t0rn ]
then
echo "Possible t0rn rootkit installed"
else
if [ "${QUIET}" != "t" ]; then echo "nothing found"; fi
fi

对程序的SUID位的设置进行检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
chk_basename () {
STATUS=${NOT_INFECTED}
CMD=`loc basename basename $pth`

if [ "${EXPERT}" = "t" ]; then
expertmode_output "${strings} -a ${CMD}"
expertmode_output "${ls} -l ${CMD}"
return 5
fi
if ${strings} -a ${CMD} | ${egrep} "${GENERIC_ROOTKIT_LABEL}" > /dev/null 2>&1
then
STATUS=${INFECTED}
fi

[ "$SYSTEM" != "OSF1" ] &&
{
if ${ls} -l ${CMD} | ${egrep} "^...s" > /dev/null 2>&1
then
STATUS=${INFECTED}
fi
}
return ${STATUS}
}

这个除了检测有无关键字,还检测SUID位有无设置

对ldsopreload的检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
chk_ldsopreload() {
STATUS=${NOT_INFECTED}
CMD="${ROOTDIR}lib/libshow.so ${ROOTDIR}lib/libproc.a"

if [ "${SYSTEM}" = "Linux" ]
then
if [ ! -x ./strings-static ]; then
printn "can't exec ./strings-static, "
return ${NOT_TESTED}
fi

if [ "${EXPERT}" = "t" ]; then
expertmode_output "./strings-static -a ${CMD}"
return 5
fi

### strings must be a statically linked binary.
if ./strings-static -a ${CMD} > /dev/null 2>&1
then
STATUS=${INFECTED}
fi
else
STATUS=${NOT_TESTED}
fi
return ${STATUS}
}

检测是否有libshow.so,libproc.a,有就说明感染了恶意so文件

可以看到为了保险起见,作者使用的是自己目录下静态编译的strings进行检测

查找可疑的log文件

例子如下:

1
2
3
4
5
6
7
8
9
10
files=`${find} ${ROOTDIR}dev ${ROOTDIR}tmp ${ROOTDIR}lib ${ROOTDIR}etc ${ROOTDIR}var \
${findargs} \( -name "tcp.log" -o -name ".linux-sniff" -o -name "sniff-l0g" -o -name "core_" \) \
2>/dev/null`
if [ "${files}" = "" ]
then
if [ "${QUIET}" != "t" ]; then echo "nothing found"; fi
else
echo
echo ${files}
fi

查找可疑的php文件

查找一些可疑的php文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
###
### Suspect PHP files
###
if [ "${QUIET}" != "t" ]; then
printn "Searching for suspect PHP files... "; fi
files="`${find} ${ROOTDIR}tmp ${ROOTDIR}var/tmp ${findargs} -name '*.php' 2> /dev/null`"
if [ `echo abc | head -n 1` = "abc" ]; then
fileshead="`${find} ${ROOTDIR}tmp ${ROOTDIR}var/tmp ${findargs} -type f -exec head -n 1 {} \; | $egrep '#!.*php' 2> /dev/null`"
else
fileshead="`${find} ${ROOTDIR}tmp ${ROOTDIR}var/tmp ${findargs} -type f -exec head -1 {} \; | grep '#!.*php' 2> /dev/null`"
fi
if [ "${files}" = "" -a "${fileshead}" = "" ]; then
if [ "${QUIET}" != "t" ]; then echo "nothing found"; fi
else
echo
echo "${files}"
echo "${fileshead}"
fi

检测.history文件

看看history有没有被清空了,或者软连接到其他地方了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if [ "${QUIET}" != "t" ]; then \
printn "Searching for anomalies in shell history files... "; fi
files=""
if [ ! -z "${SHELL}" -a ! -z "${HOME}" ]; then
files=`${find} ${ROOTDIR}${HOME} ${findargs} -name '.*history' -size 0`
[ ! -z "${files}" ] && \
echo "Warning: \`${files}' file size is zero"
files1=`${find} ${ROOTDIR}${HOME} ${findargs} -name '.*history' \( -links 2 -o -type l \)`
[ ! -z "${files1}" ] && \
echo "Warning: \`${files1}' is linked to another file"
fi
if [ -z "${files}" -a -z "${files1}" ]; then
if [ "${QUIET}" != "t" ]; then echo "nothing found"; fi
fi

检测有无程序监听了一些可疑的端口

检测代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
bindshell () {
PORT="114|145|465|511|600|1008|1524|1999|1978|2881|3049|3133|3879|4000|4369|5190|5665|6667|10008|12321|23132|27374|29364|30999|31336|31337|37998|45454|47017|47889|60001|7222"
OPT="-an"
PI=""
if [ "${ROOTDIR}" != "/" ]; then
echo "not tested"
return ${NOT_TESTED}
fi

if [ "${EXPERT}" = "t" ]; then
expertmode_output "${netstat} ${OPT}"
return 5
fi
for P in `echo $PORT | ${sed} 's/|/ /g'`; do
if ${netstat} "${OPT}" | ${egrep} "^tcp.*LIST|^udp" | ${egrep} \
"[.:]${P}[^0-9.:]" >/dev/null 2>&1
then
PI="${PI} ${P}"
fi
done
if [ "${PI}" != "" ]
then
echo "INFECTED PORTS: ($PI)"
else
if [ "${QUIET}" != "t" ]; then echo "not infected"; fi
fi
}

检测Linux可加载内核模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
lkm ()
{
prog=""
if [ \( "${SYSTEM}" = "Linux" -o \( "${SYSTEM}" = "FreeBSD" -a \
`echo ${V} | ${awk} '{ if ($1 > 4.3 || $1 < 6.0) print 1; else print 0 }'` -eq 1 \) \) -a "${ROOTDIR}" = "/" ]; then
[ -x ./chkproc -a "`find /proc | wc -l`" -gt 1 ] && prog="./chkproc"
[ -x ./chkdirs ] && prog="$prog ./chkdirs"
if [ "$prog" = "" ]; then
echo "not tested: can't exec $prog"
return ${NOT_TESTED}
fi

if [ "${EXPERT}" = "t" ]; then
[ -r /proc/$KALLSYMS ] && ${egrep} -i "adore|sebek" < /proc/$KALLSYMS 2>/dev/null
[ -d /proc/knark ] && ${ls} -la /proc/knark 2> /dev/null
PV=`$ps -V 2>/dev/null| $cut -d " " -f 3 |${awk} -F . '{ print $1 "." $2 $3 }' | ${awk} '{ if ($0 > 3.19) print 3; else if ($0 < 2.015) print 1; else print 2 }'`
[ "$PV" = "" ] && PV=2
[ "${SYSTEM}" = "SunOS" ] && PV=0
expertmode_output "./chkproc -v -v -p $PV"
return 5
fi

### adore LKM
[ -r /proc/$KALLSYMS ] && \
if `${egrep} -i adore < /proc/$KALLSYMS >/dev/null 2>&1`; then
echo "Warning: Adore LKM installed"
fi

### sebek LKM (Adore based)
[ -r /proc/$KALLSYMS ] && \
if `${egrep} -i sebek < /proc/$KALLSYMS >/dev/null 2>&1`; then
echo "Warning: Sebek LKM installed"
fi

### knark LKM
if [ -d /proc/knark ]; then
echo "Warning: Knark LKM installed"
fi

PV=`$ps -V 2>/dev/null| $cut -d " " -f 3 |${awk} -F . '{ print $1 "." $2 $3 }' | ${awk} '{ if ($0 > 3.19) print 3; else if ($0 < 2.11) print 1; else print 2 }'`
[ "$PV" = "" ] && PV=2
[ "${SYSTEM}" = "SunOS" ] && PV=0
if [ "${DEBUG}" = "t" ]; then
${echo} "*** PV=$PV ***"
fi
if ./chkproc -p ${PV}; then
if [ "${QUIET}" != "t" ]; then echo "chkproc: nothing detected"; fi
else
echo "chkproc: Warning: Possible LKM Trojan installed"
fi
dirs="/tmp"
for i in /usr/share /usr/bin /usr/sbin /lib; do
[ -d $i ] && dirs="$dirs $i"
done
if ./chkdirs $dirs; then
if [ "${QUIET}" != "t" ]; then echo "chkdirs: nothing detected"; fi
else
echo "chkdirs: Warning: Possible LKM Trojan installed"
fi
else
if [ "${QUIET}" != "t" ]; then echo "chkproc: not tested"; fi
fi
}

loadable kernel module (LKM),这个是检测内核模块的 ,看看有无adore,sebek这些内核模块

之后调用chkproc,chkdirs进行检测,这两个下面检测有无隐藏进程,会说到

检测有无隐藏进程

这个代码在chkproc.c中,它通过暴力递归,看看有没有/proc目录存在,而ps查不出来的进程,那么就说明有进程隐藏了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 /* Brute force */
strcpy(buf, "/proc/");
retps = retdir = 0;
for (i = FIRST_PROCESS; i <= MAX_PROCESSES; i++)
{
// snprintf(&buf[6], 6, "%d", i);
snprintf(&buf[6], 8, "%d", i);
if (!chdir(buf))
{
if (!dirproc[i] && !psproc[i])
{
#if defined(__linux__)
if (!isathread[i]) {
#endif
retdir++;
if (verbose)
printf ("PID %5d(%s): not in readdir output\n", i, buf);
#if defined(__linux__)
}
#endif
}
if (!psproc[i] ) /* && !kill(i, 0)) */
{
#if defined(__linux__)
if(!isathread[i]) {
#endif
retps++;
if (verbose)
printf ("PID %5d: not in ps output\n", i);
#if defined(__linux__)
}
#endif
}

检测目录的软链接异常

chkdirs比较的是父目录的软链接数和子目录的个数

正常情况下,父目录的软链接数 = 子目录的个数 + 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
if (!linkcount) {
if (lstat(".", &statinfo)) {
fprintf(stderr, "lstat(%s): %s\n", fullpath, strerror(errno));
goto abort;
}
linkcount = statinfo.st_nlink; //获取符号链接数
}

if (!(dirhandle = opendir("."))) {
fprintf(stderr, "opendir(%s): %s\n", fullpath, strerror(errno));
goto abort;
}

numdirs = 0;
dl = (struct dirinfolist *)NULL;
while ((finfo = readdir(dirhandle))) {
if (!strcmp(finfo->d_name, ".") || !strcmp(finfo->d_name, ".."))
continue;

if (lstat(finfo->d_name, &statinfo)) {
fprintf(stderr, "lstat(%s/%s): %s\n",
fullpath, finfo->d_name, strerror(errno));
closedir(dirhandle);
goto abort;
}

if (S_ISDIR(statinfo.st_mode)) { //判断是否是目录
numdirs++;

if (norecurse) continue; /* just count subdirs if "-n" */

/* Otherwise, keep a list of all directories found that have link
count > 2 (indicating directory contains subdirectories). We'll
call check_dir() on each of these subdirectories in a moment...
*/
if (statinfo.st_nlink > 2) {
dptr = dl;
if (!(dl = (struct dirinfolist *)malloc(sizeof(struct dirinfolist)))) {
fprintf(stderr, "malloc() failed: %s\n", strerror(errno));
norecurse = 1;
while (dptr) {
dl = dptr->dil_next;
free((void *)dptr);
dptr = dl;
}
continue;
}

strncpy(dl->dil_name, finfo->d_name, sizeof(dl->dil_name));
dl->dil_lc = statinfo.st_nlink;
dl->dil_next = dptr;
}
}
}
closedir(dirhandle);

/* Parent directory link count had better equal #subdirs+2... */
diff = linkcount - numdirs - 2; //
if (diff) printf("%d\t%s\n", diff, fullpath);

检测网络接口的异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 sniffer () {
if [ "${ROOTDIR}" != "/" ]; then
echo "not tested"
return ${NOT_TESTED}
fi

if [ "$SYSTEM" = "SunOS" ]; then
return ${NOT_TESTED}
fi

if [ "${EXPERT}" = "t" ]; then
expertmode_output "./ifpromisc" -v
return 5
fi
if [ ! -x ./ifpromisc ]; then
echo "not tested: can't exec ./ifpromisc"
return ${NOT_TESTED}
else
[ "${QUIET}" != "t" ] && ./ifpromisc -v || ./ifpromisc -q
fi
}

这个是对网络接口的检测,看看有无开启网卡混杂模式(英语:promiscuous mode)

而PF_PACKET可以操作链路层的数据,可以读取和发送链路层的数据包

1
2
3
4
5
 ./ifpromisc -v
ens3: PF_PACKET(/sbin/dhclient)
virbr0: not promisc and no PF_PACKET sockets
docker0: not promisc and no PF_PACKET sockets
br-47a3d838588a: not promisc and no PF_PACKET sockets

检测用户的登录日志

检测用户的登录相关的log文件

SunOS使用的是check_wtmpx,比较的文件是/var/adm/wtmp,/var/adm/wtmpx,check_wtmpx部分代码,比较这两个文件的一些差异,比如下面的比较uid

1
2
3
4
5
if ( memcmp( utmp_entry.ut_id, utmpx_entry.ut_id, 4 ) != 0 )
{
fprintf( stderr, "[ %u ] utmp_entry.ut_id != utmpx_entry.ut_id\n", wtmp_read_counter );
break;
}

其他linux检测的是var/log/wtmp或者var/adm/wtmp
chkwtmp部分代码,查看有无删除了登录时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
gettimeofday(&mytime, &dummy);
act_time=mytime.tv_sec;
wtmpfile[127]='\0';
memcpy(wtmpfile, WTMP_FILENAME, 127);
if ( argc == 3 && !memcmp("-f", argv[1], 2) && *argv[2])
memcpy(wtmpfile, argv[2], 127);

if ((filehandle=open(wtmpfile,O_RDONLY)) < 0) {
fprintf(stderr, "unable to open wtmp-file %s\n", wtmpfile);
return(2);
}

while (read (filehandle, (char *) &utmp_ent, sizeof (struct utmp)) > 0) {
if (utmp_ent.ut_time == 0)
del_counter++;
else {
if (del_counter) {
printit(del_counter, start_time,
utmp_ent.ut_time);
t_del++;
del_counter=0;
}
start_time=utmp_ent.ut_time;
}
}
close(filehandle);
if (del_counter)
printit(del_counter, start_time, act_time);
exit((int) t_del+del_counter);

检测上一次登录

使用chklastlog程序检测,下面是部分代码,用户的数据通过getpwent函数获取,其实就是通过/etc/passwd获取,检测基于两点

1、通过比较MAX_ID,与当前的遍历的用户的id,看看id是否大于环境变量MAX_ID
2、看看是否有这样的情况:用户名出现在lastlog,wtmp文件中,而在/etc/passwd中没有的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
if ( !nonuser(utmp_ent) && strncmp(utmp_ent.ut_line, "ftp", 3) &&
(uid=localgetpwnam(localpwd,utmp_ent.ut_name)) != NULL )
{
if (*uid > MAX_ID)
{
fprintf(stderr, "MAX_ID is %ld and current uid is %ld, please check\n\r", MAX_ID, *uid );
exit (1);

}
if (!userid[*uid])
{
lseek(fh_lastlog, (long)*uid * sizeof (struct lastlog), 0);
if ((wtmp_bytes_read = read(fh_lastlog, &lastlog_ent, sizeof (struct lastlog))) > 0)
{
if (wtmp_bytes_read < sizeof(struct lastlog))
{
fprintf(stderr, "lastlog entry may be corrupted");
break;
}
if (lastlog_ent.ll_time == 0)
{
if (-1 != (slot = getslot(localpwd, *uid)))
printf("user %s deleted or never logged from lastlog!\n",
NULL != localpwd->uname[slot] ?
(char*)localpwd->uname[slot] : "(null)");
else
printf("deleted user uid(%d) not in passwd\n", *uid);
++status;
}
userid[*uid]=TRUE;
}
}
}
}

检测可疑的没有tty记录的进程

检测的是/var/run/utmp或者/var/adm/utmpx,方法是比较的是ps命令与/var/run/utmp文件之间的差别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
y = fetchps(ps_l);
z = fetchutmp(ut_l);
hdr_prntd = 0;
for (h = 0; h < y; h++) { /* loop through 'ps' data */
mtch_fnd = 0;
for (i = 0; i < z; i++) { /* try and match the tty from 'ps' to one in utmp */
if (ut_l[i].ut_type == LOGIN_PROCESS /* ignore getty processes with matching pid from 'ps' */
&& ut_l[i].ut_pid == ps_l[h].ps_pid)
{
mtch_fnd = 1;
break;
}
else if (strncmp(ps_l[h].ps_tty, ut_l[i].ut_tty, /* compare the tty's */
strlen(ps_l[h].ps_tty)) == 0)
{
mtch_fnd = 1;
break;
}
}
if (!mtch_fnd) {
if (!hdr_prntd) {
printf
(" The tty of the following user process(es) were not found\n");
printf(" in %s !\n", UTMP);
printf("! %-9s %7s %-6s %s\n", "RUID", "PID", "TTY",
"CMD");
hdr_prntd = 1;
}
printf("! %-9s %7d %-6s %s", ps_l[h].ps_user,
ps_l[h].ps_pid, ps_l[h].ps_tty, ps_l[h].ps_args);
}

比如下面的检测结果,而我的/var/run/utmp中是没有tty7这个tty的记录的

1
2
3
4
Checking `chkutmp'...  The tty of the following user process(es) were not found
in /var/run/utmp !
! RUID PID TTY CMD
! root 1076 tty7 /usr/lib/xorg/Xorg -core :0 -seat seat0 -auth /var/run/lightdm/root/:0 -nolisten tcp vt7 -novtswitch
打赏专区