目录
1. Chkrootkit Introduce 2. Source Code Frame 3. chklastlog.c 4. chkwtmp.c 5. ifpromisc.c 6. chkproc.c 7. chkdirs.c 8. check_wtmpx.c 9. strings.c 10. chkrootkit的使用场景及其局限
1. Chkrootkit Introduce
chkrootkit是一个Linux系统下的查找检测rootkit后门的工具,需要明白的是,chkrootkit是一款ring3级别的rootkit检测工具,所以从某种程序上来说,chkrootkit能做的事也很有限,但是我们也必须明白,攻防对抗中并不是一味的追求底层kernel的hacking技术,往往多种技术结合(ring3、ring0)能够获得更好的效果
关于ring3、ring0下的rootkit攻防的平衡取舍,请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/3910696.html
Relevant Link:
http://www.centospub.com/make/chkrootkit.html http://www.bootf.com/556.html http://www.chkrootkit.org/
2. Source Code Frame
chkrootkit的总体代码功能框架如下
1. 系统日志审计检查 1.1 chklastlog.c 1) 根据"/var/log/wtmp"和"/var/log/lastlog"进行交叉比较,检查当前系统是否存在新增帐号异常登录 2) 检测本次登录的用户是否有在/etc/passwd中出现 3) 检查/etc/passwd中是否有白名单之外的"超级用户(uid)",这是一种异常现象 1.2 chkwtmp.c 1) 对登录日志进行清除,造成日志文件中存在一段的"登录日志空档期",chkwtmp.c的目的就是发现这个"空档期",从而发现可疑的入侵现象 2. 网络状态审计检测 2.1 ifpromisc.c 1) 检测当前系统的网卡接口是否正在进行"raw packet"的处理(sniffer的特征) 检测当前系统中是否有sniffer程序在进行嗅探操作,"/proc/net/packet"这个虚拟目录保存了那些需要处理"raw network packets"的进程、及相关网络信息,正常情况下,一般的进程是不需要收发、处理"raw network packets"的,如果发现这类进程,则说明这是一个可疑sniffer进程(有可能是rootkit在进行嗅探操作) 2) 检测当前系统的网卡是否处于"PROMISC(混杂模式)" 3. 进程隐藏状态检测 3.1 chkproc.c 1) 针对readdir句柄劫持的检测 很多rootkit常常会对"/proc/的readdir句柄"句柄(而内部还是调用的Sys_getdents64)进行劫持从而进行进程隐藏,针对这个现象,chkrootkit采取了2种策略 1.1) 采用read原生系统调用来对/proc进行读写(绕过rootkit对Sys_getdents64系统调用的劫持) 1.2) 将/proc/number/(进程列表的枚举结果和"ps -edf、ps auxw、ps mauxw 2>&1、ps auxw -T | tr -s ' '| cut -d' ' -f2-"的结果进行对比,因为ps这类进程枚举命令内部调用的系统调用是/proc/的readdir句柄(),可以发现进程隐藏的迹象 2) 针对进程枚举指令劫持的检测 进程隐藏是LKM Rootkit常用的功能,rootkit常常会通过替换ps等指令程序为恶意程序(会自动过滤掉对rootkit自身的枚举)
3. chklastlog.c
1) 根据"/var/log/wtmp"和"/var/log/lastlog"进行交叉比较,检查当前系统是否存在新增帐号异常登录 2) 检测本次登录的用户是否有在/etc/passwd中出现 3) 检查/etc/passwd中是否有白名单之外的"超级用户(uid)",这是一种异常现象
code
#if defined(SOLARIS2) || defined(__linux__) #define HAVE_LASTLOG_H 1 #else #undef HAVE_LASTLOG_H #endif #if __FreeBSD__ > 9 int main () { return 0; } #else #include <stdio.h> #ifdef __linux__ #include <stdlib.h> #endif #include <sys/stat.h> #include <unistd.h> #include <string.h> #include <signal.h> #include <pwd.h> #include <sys/types.h> #include <utmp.h> #if (HAVE_LASTLOG_H) #include <lastlog.h> #endif #include <sys/file.h> #ifdef SOLARIS2 #include <fcntl.h> #endif #ifdef __FreeBSD__ #define WTMP_FILENAME "/var/log/wtmp" #define LASTLOG_FILENAME "/var/log/lastlog" #endif #ifdef __OpenBSD__ #include <stdlib.h> #define WTMP_FILENAME "/var/log/wtmp" #define LASTLOG_FILENAME "/var/log/lastlog" #endif #ifndef WTMP_FILENAME #define WTMP_FILENAME "/var/adm/wtmp" #endif #ifndef LASTLOG_FILENAME #define LASTLOG_FILENAME "/var/adm/lastlog" #endif #define TRUE 1L #define FALSE 0L long total_wtmp_bytes_read = 0; size_t wtmp_file_size; uid_t *uid; void read_status(); struct s_localpwd { int numentries; uid_t *uid; char **uname; }; #ifndef SOLARIS2 int nonuser(struct utmp utmp_ent); #endif struct s_localpwd *read_pwd(); void free_results(struct s_localpwd *); uid_t *localgetpwnam(struct s_localpwd *, char *); int getslot(struct s_localpwd *, uid_t); #define MAX_ID 99999 int main(int argc, char*argv[]) { int fh_wtmp; int fh_lastlog; /* struct lastlog { int32_t ll_time; // When user logged in char ll_line[UT_LINESIZE]; // Terminal line name char ll_host[UT_HOSTSIZE]; // Host user came from }; 用于查看那所用账号的最后登录时间 */ struct lastlog lastlog_ent; /* struct utmp { char ut_line[UT_LINESIZE]; // Terminal line name char ut_name[UT_NAMESIZE]; // User’s login name char ut_host[UT_HOSTSIZE]; // Host user came from int32_t ut_time; // When user logged in }; /var/log/wtmp 记录当前和历史上登录到系统的用户的登录tty、登录用户名、来源和时间等信息。和/var/log/lastlog一样,这个文件是一个二进制文件,需要用last命令查看 last -f /var/log/wtmp */ struct utmp utmp_ent; long userid[MAX_ID]; long i, slot; int status = 0; long wtmp_bytes_read; /* struct stat { dev_t st_dev; // ID of device containing file -文件所在设备的ID ino_t st_ino; // inode number -inode节点号 mode_t st_mode; // protection -保护模式 nlink_t st_nlink; // number of hard links -链向此文件的连接数(硬连接) uid_t st_uid; // user ID of owner -user id gid_t st_gid; // group ID of owner - group id dev_t st_rdev; // device ID (if special file) -设备号,针对设备文件 off_t st_size; // total size, in bytes -文件大小,字节为单位 blksize_t st_blksize; // blocksize for filesystem I/O -系统块的大小 blkcnt_t st_blocks; // number of blocks allocated -文件所占块数 time_t st_atime; // time of last access - 最近存取时间 time_t st_mtime; // time of last modification - 最近修改时间 time_t st_ctime; // time of last status change - 最近创建时间 }; */ struct stat wtmp_stat; struct s_localpwd *localpwd; struct passwd *user; uid_t *uid; char wtmpfile[128], lastlogfile[128]; memcpy(wtmpfile, WTMP_FILENAME, 127); memcpy(lastlogfile, LASTLOG_FILENAME, 127); while (--argc && ++argv) /* poor man getopt */ { if (!memcmp("-f", *argv, 2)) { if (!--argc) { break; } ++argv; memcpy(wtmpfile, *argv, 127); } else if (!memcmp("-l", *argv, 2)) { if (!--argc) { break; } ++argv; memcpy(lastlogfile, *argv, 127); } } //信号的安装(确定要接收和处理的信号),指定read_status()作为信号处理函数 signal(SIGALRM, read_status); //信号的发送,专门为SIGALRM信号而设,在指定的时间seconds秒后,将向进程本身发送SIGALRM信号,又称为闹钟时间 alarm(5); for (i=0; i < MAX_ID; i++) { userid[i]=FALSE; } //打开"/var/log/lastlog"文件 if ((fh_lastlog = open(lastlogfile, O_RDONLY)) < 0) { fprintf(stderr, "unable to open lastlog-file %s ", lastlogfile); return(1); } //打开"/var/log/wtmp"文件 if ((fh_wtmp = open(wtmpfile, O_RDONLY)) < 0) { fprintf(stderr, "unable to open wtmp-file %s ", wtmpfile); close(fh_lastlog); return(2); } //获取/var/log/wtmp的文件属性: struct stat,保存在wtmp_stat结构体中 if (fstat(fh_wtmp, &wtmp_stat)) { perror("chklastlog::main: "); close(fh_lastlog); close(fh_wtmp); return(3); } wtmp_file_size = wtmp_stat.st_size; //获取"/etc/passwd"文件结构化内容 localpwd = read_pwd(); /* 3. 检查/etc/passwd中是否有白名单之外的"超级用户(uid)",这是一种异常现象 */ while((user = getpwent())!=0) { //默认白名单只有root用户 if ((user->pw_uid == 0 || user->pw_gid == 0) && user->pw_name != "root") { printf("dedect Suspicious Account "); printf(" %s:%d:%d:%s:%s:%s ",user->pw_name, user->pw_uid, user->pw_gid, user->pw_gecos,user->pw_dir,user->pw_shell); } } endpwent(); /* "/var/log/wtmp"是一个记录用户登录信息的列表,每一行都记录了一次用户的登录信息,接下来的代码对其进行逐行遍历 */ while ((wtmp_bytes_read = read(fh_wtmp, &utmp_ent, sizeof (struct utmp))) >0) { if (wtmp_bytes_read < sizeof(struct utmp)) { fprintf(stderr, "wtmp entry may be corrupted"); break; } total_wtmp_bytes_read += wtmp_bytes_read; /* 对当前遍历中的"struct utmp"进行过滤检查 1. 是否是"shutdonw"用户 2. 当前登录用户账户命是否在"/etc/passwd"中存在 */ 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 ", 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))) { //1. 如果本次登录的用户在lastlog中的没有对应的登录记录(即这是一个突然新增的新用户登录),则表明是一个可疑用户登录行为 printf("user %s deleted or never logged from lastlog! ", NULL != localpwd->uname[slot] ? (char*)localpwd->uname[slot] : "(null)"); } else { //2. 检测本次登录的用户是否有在/etc/passwd中出现 printf("deleted user uid(%d) not in passwd ", *uid); } ++status; } userid[*uid]=TRUE; } } } } #if 0 printf(" "); #endif free_results(localpwd); close(fh_wtmp); close(fh_lastlog); return(status); } #ifndef SOLARIS2 /* minimal funcionality of nonuser() */ int nonuser(struct utmp utmp_ent) { return (!memcmp(utmp_ent.ut_name, "shutdown", sizeof ("shutdown"))); } #endif void read_status() { double remaining_time; static long last_total_bytes_read=0; int diff; diff = total_wtmp_bytes_read-last_total_bytes_read; if (diff == 0) diff = 1; remaining_time=(wtmp_file_size-total_wtmp_bytes_read)*5/(diff); last_total_bytes_read=total_wtmp_bytes_read; printf("Remaining time: %6.2f seconds ", remaining_time); /* signal(SIGALRM,read_status); alarm(5); */ } struct s_localpwd *read_pwd() { /* struct passwd { char * pw_name; // Username, POSIX.1 char * pw_passwd; //Password __uid_t pw_uid; // User ID, POSIX.1 __gid_t pw_gid; // Group ID, POSIX.1 char * pw_gecos; // Real Name or Comment field char * pw_dir; // Home directory, POSIX.1 char * pw_shell; // Shell Program, POSIX.1 }; */ struct passwd *pwdent; int numentries=0,i=0; struct s_localpwd *localpwd; //setpwent()用来将getpwent()的读写地址指回密码文件开头 setpwent(); /* 获取"/etc/passw"文件的信息,getpwent()用来从密码文件(/etc/passwd)中读取一项用户数据,该用户的数据以passwd 结构返回。第一次调用时会取得第一位 用户数据,之后每调用一次就会返回下一项数据,直到已无任何数据时返回NULL */ while ((pwdent = getpwent())) { numentries++; } endpwent(); localpwd = (struct s_localpwd *)malloc((size_t)sizeof(struct s_localpwd)); localpwd->numentries=numentries; localpwd->uid = (uid_t *)malloc((size_t)numentries*sizeof(uid_t)); localpwd->uname = (char **)malloc((size_t)numentries*sizeof(char *)); for (i=0;i<numentries;i++) { localpwd->uname[i] = (char *)malloc((size_t)30*sizeof(char)); } i=0; setpwent(); while ((pwdent = getpwent()) && (i<numentries)) { localpwd->uid[i] = pwdent->pw_uid; memcpy(localpwd->uname[i], pwdent->pw_name, (strlen(pwdent->pw_name)>29)?29:strlen(pwdent->pw_name)+1); i++; } endpwent(); return(localpwd); } void free_results(struct s_localpwd *localpwd) { int i; free(localpwd->uid); for (i=0;i<(localpwd->numentries);i++) { free(localpwd->uname[i]); } free(localpwd->uname); free(localpwd); } uid_t *localgetpwnam(struct s_localpwd *localpwd, char *username) { int i; size_t len; for (i=0; i<(localpwd->numentries);i++) { len = (strlen(username) > 9) ? 30 : strlen(username) + 1; if (!memcmp(username, localpwd->uname[i],len)) { return &(localpwd->uid[i]); } } return NULL; } int getslot(struct s_localpwd *localpwd, uid_t uid) { int i; for (i=0; i<(localpwd->numentries);i++) { if (localpwd->uid[i] == uid) { return i; } } return -1; } #endif
Relevant Link:
https://www.mirbsd.org/htman/i386/man5/lastlog.htm
4. chkwtmp.c
"/var/log/wtmp"记录了当前和历史上登录到系统的用户的登录tty、登录用户名、来源和时间等信息,黑客在入侵了主机后,会对登录日志进行清除,造成日志文件中存在一段的"登录日志空档期",chkwtmp.c的目的就是发现这个"空档期",从而发现可疑的入侵现象
#if __FreeBSD__ > 9 int main () { return 0; } #else #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <utmp.h> #include <time.h> #include <sys/time.h> #include <sys/file.h> #ifdef SOLARIS2 #include <fcntl.h> #endif #ifdef __FreeBSD__ #define WTMP_FILENAME "/var/log/wtmp" #else #ifndef WTMP_FILENAME #define WTMP_FILENAME "/var/adm/wtmp" #endif #endif void printit(counter, start, end) int counter; long start,end; { char buffer[30]; printf("%d deletion(s) between ", counter); strncpy(buffer, ctime( (time_t *) &start), 30); buffer[24]='