目錄
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\n", lastlogfile); return(1); } //開啟"/var/log/wtmp"檔案 if ((fh_wtmp = open(wtmpfile, O_RDONLY)) < 0) { fprintf(stderr, "unable to open wtmp-file %s\n", 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\n"); printf("\n%s:%d:%d:%s:%s:%s\n",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\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))) { //1. 如果本次登入的使用者在lastlog中的沒有對應的登入記錄(即這是一個突然新增的新使用者登入),則表明是一個可疑使用者登入行為 printf("user %s deleted or never logged from lastlog!\n", NULL != localpwd->uname[slot] ? (char*)localpwd->uname[slot] : "(null)"); } else { //2. 檢測本次登入的使用者是否有在/etc/passwd中出現 printf("deleted user uid(%d) not in passwd\n", *uid); } ++status; } userid[*uid]=TRUE; } } } } #if 0 printf("\n"); #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\n", 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]='\0'; printf("%s and %s", buffer, ctime( (time_t *) &end)); } int main(int argc, char*argv[]) { int filehandle; struct utmp utmp_ent; struct timeval mytime; struct timezone dummy; long start_time, act_time; int del_counter, t_del; char wtmpfile[128]; del_counter=t_del=0; start_time=0; 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); } #endif
5. ifpromisc.c
#include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #ifdef __linux__ #include <linux/if.h> #include <linux/if_arp.h> #include <linux/if_ether.h> #include <dirent.h> #include <sys/stat.h> #else #include <net/if.h> #ifndef __OpenBSD__ #include <net/if_arp.h> #endif #endif #ifdef SOLARIS2 #include <sys/sockio.h> #endif #include <stdio.h> #include <errno.h> #include <fcntl.h> #include <ctype.h> #include <stdlib.h> #include <string.h> #include <unistd.h> struct interface { char name[IFNAMSIZ]; // interface name short type; // if type short flags; // various flags #ifdef __linux__ int index; //interface index #endif }; char *Release = "chkrootkit package", *Version = "@(#) ifpromisc)"; int skfd = -1; /* AF_INET or AF_PACKET raw socket desc. */ int q = 0; /* Quiet mode on or off */ struct packet_info { int index; int type; int proto; int inode; char *cmd; struct packet_info *next; }; #ifdef __linux__ /* * the contents of /proc/net/packet */ static struct packet_info *proc_net_packet = 0; /* * read the entries from /proc/net/packet */ static void read_proc_net_packet() { FILE *proc; char buf[80]; proc = fopen("/proc/net/packet", "r"); if (!proc) { if (errno != ENOENT) { perror("opening /proc/net/packet"); } return; } /* skip the header */ fgets(buf, 80, proc); while (fgets(buf, 80, proc)) { int type = 0; unsigned int proto = 0; int index = 0; unsigned int inode = 0; if (sscanf(buf, "%*p %*d %d %x %d %*d %*u %*u %u", &type, &proto, &index, &inode) == 4) { struct packet_info *pi; pi = (struct packet_info *)malloc(sizeof(struct packet_info)); pi->type = type; pi->proto = proto; pi->index = index; pi->inode = inode; pi->cmd = 0; pi->next = proc_net_packet; proc_net_packet = pi; } else { fprintf(stderr, "cannot grok /proc/net/packet: %s", buf); } } fclose(proc); } /* look up an entry from /proc/net/packet by inode */ static struct packet_info *find_packet_info(int inode) { struct packet_info *p; for (p = proc_net_packet; p; p = p->next) { if (p->inode == inode) { return p; } } return NULL; } /* walk a processes fd dir looking for sockets with inodes that match the inodes from /proc/net/packet, when a match is found, the processes exe is stored 獲取正在進行處理"raw network packets"的程式(疑似sniffer程式)的程式列表 */ static void walk_process(char *process) { DIR *dir; struct dirent *ent; char path[1024]; if (snprintf(path, sizeof(path), "/proc/%s/fd", process) == -1) { fprintf(stderr, "giant process name! %s\n", process); return; } if ((dir = opendir(path)) == NULL) { if (errno != ENOENT) { perror(path); } return; } while ((ent = readdir(dir))) { struct stat statbuf; struct packet_info *info; if (snprintf(path, sizeof(path), "/proc/%s/fd/%s", process, ent->d_name) == -1) { fprintf(stderr, "giant fd name /proc/%s/fd/%s\n", process, ent->d_name); continue; } if (stat(path, &statbuf) == -1) { perror(path); continue; } if (S_ISSOCK(statbuf.st_mode) && (info = find_packet_info(statbuf.st_ino))) { char link[1024]; memset(link, 0, sizeof(link)); /* no need to check rv since it has to be long enough, * otherwise, one of the ones above will have failed */ snprintf(path, sizeof(path), "/proc/%s/exe", process); readlink(path, link, sizeof(link) - 1); info->cmd = strdup(link); } } closedir(dir); } /* walk the proc file system looking for processes, call walk_proc on each * process */ static void walk_processes() { DIR *dir; struct dirent *ent; if ((dir = opendir("/proc")) == NULL) { perror("/proc"); return; } while ((ent = readdir(dir))) { /* we only care about dirs that look like processes */ if (strspn(ent->d_name, "0123456789") == strlen(ent->d_name)) { walk_process(ent->d_name); } } closedir(dir); } /* return 1 if index is a member of pcap_session_list, 0 otherwise. */ static int has_packet_socket(int index) { struct packet_info *p; for (p = proc_net_packet; p; p = p->next) { if (p->index == index) { return 1; } } return 0; } #endif /* __linux__ */ static void ife_print(struct interface *ptr) { #ifdef __linux__ //檢測當前網路卡介面是否處於"PROMISC(混雜模式)" int promisc = ptr->flags & IFF_PROMISC; //檢測當前網路卡介面是否正在進行"raw packet"的處理(同樣也是sniffer的特徵) int has_packet = has_packet_socket(ptr->index); if (promisc || has_packet) { printf("%s:", ptr->name); if (promisc) printf(" PROMISC"); if (has_packet) { struct packet_info *p; printf(" PF_PACKET("); p = proc_net_packet; if (p) { printf("%s", p->cmd); for (p = p->next; p; p = p->next) { if (p->index == ptr->index) { printf(", %s", p->cmd); } } } printf(")"); } printf("\n"); } else { if (!q) printf("%s: not promisc and no PF_PACKET sockets\n", ptr->name); } #else if (ptr->flags & IFF_PROMISC) printf("%s is %s", ptr->name, "PROMISC"); else { if (!q) printf("%s is %s", ptr->name, "not promisc"); } putchar('\n'); #endif } /* Fetch the inteface configuration from the kernel. */ static int if_fetch(char *ifname, struct interface *ife) { struct ifreq ifr; memset((char *) ife, 0, sizeof(struct interface)); strncpy(ife->name, ifname, sizeof(ife->name)); strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); //獲得介面標誌 if (ioctl(skfd, SIOCGIFFLAGS, &ifr) < 0) return(-1); ife->flags = ifr.ifr_flags; #ifdef __linux__ /* store the device index */ if (ioctl(skfd, SIOCGIFINDEX, &ifr) < 0) return(-1); ife->index = ifr.ifr_ifindex; #endif return(0); } static void if_print() { char buff[1024]; /* struct interface { char name[IFNAMSIZ]; // interface name short type; // if type short flags; // various flags #ifdef __linux__ int index; //interface index #endif }; */ struct interface ife; struct ifconf ifc; struct ifreq *ifr; int i; ifc.ifc_len = sizeof(buff); ifc.ifc_buf = buff; //獲取所有網路卡介面的資訊 if (ioctl(skfd, SIOCGIFCONF, &ifc) < 0) { fprintf(stderr, "SIOCGIFCONF: %s\n", strerror(errno)); return; } ifr = ifc.ifc_req; for (i = ifc.ifc_len / sizeof(struct ifreq); --i >= 0; ifr++) { if (if_fetch(ifr->ifr_name, &ife) < 0) { #ifdef __linux__ fprintf(stderr, "%s: unknown interface.\n", ifr->ifr_name); #endif continue; } if (!memcmp(ifr->ifr_name, "lo", 2)) continue; ife_print(&ife); } } int main(int argc, char **argv) { if (argc == 2 && !memcmp(argv[1], "-q", 2)) { q++; } /* Create a channel to the NET kernel. */ if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("socket"); exit(-1); } #ifdef __linux__ read_proc_net_packet(); walk_processes(); #endif if_print(); (void) close(skfd); exit(0); }
Relevant Link:
http://blog.cloudpassage.com/2012/09/05/warn-packet-sniffer-running/
6. chkproc.c
#if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__sun) int main (){ return 0; } #else #include <stdio.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <dirent.h> #include <ctype.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <sys/stat.h> #if defined(__sun) #include <procfs.h> #include <fcntl.h> #endif #include <sys/resource.h> #define PS_SUN 0 #define PS_LOL 1 #define PS_COM 2 #define PS_LNX 3 #define PS_MAX 3 #define ENYELKM "/proc/12345" // #define ENYELKM "/tmp/12345" #if defined(__sun) #define FIRST_PROCESS 0 #else #define FIRST_PROCESS 1 #endif #define MAX_PROCESSES 99999 #define MAX_BUF 1024 #if !defined (SIGXFSZ) #define SIGXFSZ 25 #endif static char *ps_cmds[] = { "ps -edf", "ps auxw", "ps mauxw 2>&1 ", "ps auxw -T | tr -s ' '| cut -d' ' -f2-", }; int psproc [MAX_PROCESSES+1]; int dirproc[MAX_PROCESSES+1]; #if defined(__linux__) int isathread[MAX_PROCESSES+1]; #endif /* * read at most the first (size-1) chars into s and terminate with a '\0'. * stops reading after a newline or EOF. if a newline is read, it will be * the last char in the string. if no newline is found in the first * (size-1) chars, then keep reading and discarding chars until a newline * is found or EOF. */ char *readline(char *s, int size, FILE *stream) { char *rv = fgets(s, size, stream); if (strlen(s) == (size-1) && s[size-1] != '\n') { char buf[MAX_BUF]; fgets(buf, MAX_BUF, stream); while (strlen(buf) == (MAX_BUF-1) && buf[MAX_BUF-1] != '\n') { fgets(buf, MAX_BUF, stream); } } return rv; } int main(int argc, char **argv) { char buf[MAX_BUF], *p, path[MAX_BUF]; char *pscmd = (char *)0; FILE *ps; //開啟/proc虛擬目錄 DIR *proc = opendir("/proc"); struct dirent *dir; struct stat sb; int i, j, retps, retdir, pv, verbose; long ret = 0L; char * tmp_d_name; #if defined(__linux__) int maybeathread; #endif #if defined(__sun) psinfo_t psbuf; #endif pv = verbose = 0; if (!proc) { perror("proc"); exit (1); } for (i = 1; i < argc; i++) { if (!memcmp(argv[i], "-v", 2)) { verbose++; } else if (!memcmp(argv[i], "-?", 2)) { printf("Usage: %s [-v] [-v] -p <num>\n", argv[0]); return 0; } #if defined(__linux__) else if (!memcmp(argv[i], "-p", 2)) { if (i+1 < argc) { pv = atoi(argv[++i]); } else { printf("Usage: %s [-v] [-v] [-p procps version]\n", argv[0]); return 0; } } #endif } #if defined(__sun) pscmd = ps_cmds[PS_SUN]; #elif !defined (__linux__) pscmd = ps_cmds[PS_COM]; #endif #if defined(__linux__) if (pv < 1 || pv > PS_MAX) pv = 1; pscmd = ps_cmds[pv]; /* printf("pv = %d\n\r", pv); /* -- DEBUG */ #endif /* printf("pscmd = %s\n\r", pscmd); /* -- DEBUG */ if (!(ps = popen(pscmd, "r"))) { perror("ps"); exit(errno); } *buf = 0; readline(buf, MAX_BUF, ps); /* Skip header */ #if defined(__sun) if (!isspace(*buf)) #else if (!isalpha(*buf)) #endif { readline(buf, MAX_BUF, ps); /* Skip header */ if (!isalpha(*buf) && pv != PS_LNX) { if (pv != PS_LOL) execlp(argv[0], argv[0], "-p 1", NULL); fprintf(stderr, "OooPS!\n"); exit(2); } } if (!memcmp(buf, "ps:", 3) && (pv != PS_LOL)) execlp(argv[0], argv[0], "-p 1", NULL); for (i = FIRST_PROCESS; i <= MAX_PROCESSES; i++) { /* Init matrix */ psproc[i] = dirproc[i] = 0; #if defined(__linux__) isathread[i] = 0; #endif } while (readline(buf, MAX_BUF, ps)) { p = buf; #if defined(__sun) while (isspace(*p)) /* Skip spaces */ p++; #endif while (!isspace(*p)) /* Skip User */ p++; while (isspace(*p)) /* Skip spaces */ p++; /* printf(">>%s<<\n", p); /* -- DEBUG */ ret = atol(p); if ( ret < 0 || ret > MAX_PROCESSES ) { fprintf (stderr, " OooPS, not expected %ld value\n", ret); exit (2); } psproc[ret] = 1; } pclose(ps); while ((dir = readdir(proc))) { #if defined(__linux__) maybeathread = 0; #endif tmp_d_name = dir->d_name; if (!strcmp(tmp_d_name, ".") || !strcmp(tmp_d_name, "..")) continue; #if defined(__linux__) if (*tmp_d_name == '.') { /* here we catch the new NTPL threads in linux. They are listed in /proc as PIDs with a period prepended */ tmp_d_name++; maybeathread = 1; } #endif if(!isdigit(*tmp_d_name)) continue; #if defined(__linux__) else if (maybeathread) { isathread[atol(tmp_d_name)] = 1; /* mark it as a linux NTPL thread if it's in the form of "\.[0-9]*" */ if (verbose) printf("%ld is a Linux Thread, marking as such...\n", atol(tmp_d_name)); } #endif /* printf("%s\n", tmp_d_name); /* -- DEBUG */ dirproc[atol(tmp_d_name)] = 1; } closedir(proc); /* Brute force */ strcpy(buf, "/proc/"); retps = retdir = 0; for (i = FIRST_PROCESS; i <= MAX_PROCESSES; i++) { snprintf(&buf[6], 6, "%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 } #if defined(__linux__) if(!isathread[i]) { #endif /* if ((!dirproc[i] || !psproc[i]) && !kill(i, 0) && (verbose > 1)) */ if ((!dirproc[i] || !psproc[i]) && (verbose > 1)) { #if defined(__linux__) j = readlink ("./cwd", path, sizeof(path)); path[(j < sizeof(path)) ? j : sizeof(path) - 1] = 0; printf ("CWD %5d: %s\n", i, path); j = readlink ("./exe", path, sizeof(path)); path[(j < sizeof(path)) ? j : sizeof(path) - 1] = 0; printf ("EXE %5d: %s\n", i, path); #elif defined(__FreeBSD__) j = readlink ("./file", path, sizeof(path)); path[(j < sizeof(path)) ? j : sizeof(path) - 1] = 0; printf ("FILE %5d: %s\n", i, path); #elif defined(__sun) if ((j = open("./psinfo", O_RDONLY)) != -1) { if (read(j, &psbuf, sizeof(psbuf)) == sizeof(psbuf)) printf ("PSINFO %5d: %s\n", i, psbuf.pr_psargs); else printf ("PSINFO %5d: unknown\n", i); close(j); } else printf ("PSINFO %5d: unknown\n", i); #endif } #if defined(__linux__) } #endif } #ifndef __FreeBSD__ else { errno = 0; getpriority(PRIO_PROCESS, i); if (!errno) { retdir++; if (verbose) printf ("PID %5d(%s): not in getpriority readdir output\n", i, buf); } } #endif } if (retdir) printf("You have % 5d process hidden for readdir command\n", retdir); if (retps) printf("You have % 5d process hidden for ps command\n", retps); #if defined(__linux__) kill(1, 100); /* Check for SIGINVISIBLE Adore signal */ if (kill (1, SIGXFSZ) < 0 && errno == 3) { printf("SIGINVISIBLE Adore found\n"); retdir+= errno; } /* Check for Enye LKM */ if (stat(ENYELKM, &sb) && kill (12345, 58) >= 0) { printf("Enye LKM found\n"); retdir+= errno; } #endif return (retdir+retps); } #endif
7. chkdirs.c
#if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__sun) || defined (hpux) || defined (__bsdi__) || defined (bsdi) || defined (__APPLE__) #include <limits.h> #elif defined(__APPLE__) && defined(__MACH__) #include <sys/syslimits.h> #endif #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <dirent.h> #include <string.h> #include <errno.h> #ifndef NAME_MAX #define NAME_MAX PATH_MAX #endif struct dirinfolist { char dil_name[NAME_MAX+1]; int dil_lc; struct dirinfolist *dil_next; }; void usage () { fprintf(stderr, "chkdirs [-n] dir ...\n"); exit(255); } char *make_pathname (char *path, char *dir, char **buffer) { int plen, pathname_len, bufsize, offs; bufsize = 0; plen = strlen(path); pathname_len = plen + strlen(dir) + 2; if (!(*buffer) || (sizeof(*buffer) < pathname_len)) { if (buffer) free((void *)*buffer); bufsize = (pathname_len > PATH_MAX) ? pathname_len : PATH_MAX; if (!(*buffer = (char *)malloc(bufsize))) { return((char *)NULL); } } if (dir[0] == '/') { /* "dir" is absolute pathname, don't prepend "path" */ offs = 0; } else { strncpy(*buffer, path, bufsize); if ((*buffer)[plen-1] == '/') { /* "path" ends in "/", don't add extra */ offs = plen; } else { (*buffer)[plen] = '/'; offs = plen + 1; } } strncpy((*buffer)+offs, dir, bufsize - offs); return((*buffer)); } int check_dir (char *dir, char *path, int linkcount, int norecurse) { int diff = -1; int plen, buflen, numdirs; char *curpath, *fullpath; DIR *dirhandle; struct dirent *finfo; struct dirinfolist *dl, *dptr; struct stat statinfo; /* When called recursively, "path" will be the full path of the cwd, but when called from main() "path" is empty. We need the cwd path so we can chdir() back at the end of this routine, as well as when printing errors and other output. */ if (!path || !(plen = strlen(path))) { buflen = PATH_MAX; retry: if (!(curpath = (char *)malloc(buflen))) { fprintf(stderr, "malloc() failed: %s\n", strerror(errno)); return(-1); } if (!getcwd(curpath, buflen)) { if (errno == ERANGE) { free((void *)curpath); buflen = buflen * 2; goto retry; } else { fprintf(stderr, "getcwd() failed: %s\n", strerror(errno)); return(-1); } } } else { /* "path" is set, so just copy it into "curpath" */ if (!(curpath = (char *)malloc(plen+1))) { fprintf(stderr, "malloc() failed: %s\n", strerror(errno)); return(-1); } strncpy(curpath, path, plen+1); } /* Now set "fullpath" to be the absolute path name of the directory we will be checking (prepend "curpath" if "dir" is not already an absolute pathname). */ fullpath = (char *)NULL; if (!make_pathname(curpath, dir, &fullpath)) { fprintf(stderr, "make_pathname() failed: %s\n", strerror(errno)); free((void *)curpath); return(-1); } if (chdir(dir)) { fprintf(stderr, "chdir(%s): %s\n", fullpath, strerror(errno)); free((void *)curpath); free((void *)fullpath); return(-1); } /* Again, "linkcount" (the link count of the current directory) is set only if check_dir() is called recursively. Otherwise, we need to stat the directory ourselves. */ 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); /* Now check all subdirectories in turn... */ while (dl) { check_dir(dl->dil_name, fullpath, dl->dil_lc, norecurse); dptr = dl->dil_next; free((void *)dl); dl = dptr; } abort: if (chdir(curpath)) { fprintf(stderr, "Final chdir(%s) failed (%s) -- EXIT!\n", curpath, strerror(errno)); exit(255); } free((void *)fullpath); free((void *)curpath); return(diff); } int main (int argc, char **argv) { int norecurse = 0; int i, retval; int c; opterr = 0; while ((c = getopt(argc, argv, "n")) > 0) { switch (c) { case 'n': norecurse = 1; break; default: usage(); } } if (argc <= optind) usage(); { for (i = optind; i < argc; i++) { retval = check_dir(argv[i], (char *)NULL, 0, norecurse); } } exit(retval); }
8. check_wtmpx.c
#if !defined(__SunOS__) && !defined(SOLARIS2) int main () { return 0; } #else #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/file.h> #include <sys/time.h> #include <sys/types.h> #include <sys/stat.h> #include <pwd.h> #include <time.h> #include <utmp.h> #include <utmpx.h> #include <lastlog.h> #include <fcntl.h> #include <unistd.h> #define WTMP_FILENAME "/var/adm/wtmp" #define WTMPX_FILENAME "/var/adm/wtmpx" struct file_utmp_entry { char ut_user[8]; /* User login name */ char ut_id[4]; /* /etc/inittab id */ char ut_line[12]; /* device name (console, lnxx) */ int16_t ut_pid; /* process id */ int16_t ut_type; /* type of entry */ struct { int16_t e_termination; /* Process termination status */ int16_t e_exit; /* Process exit status */ } ut_exit; /* The exit status of a process */ uint32_t ut_time; /* time entry was made */ }; struct timeval_32 { uint32_t tv_sec; /* seconds */ int32_t tv_usec; /* and microseconds */ }; /* * This data structure describes the utmp *file* contents using * fixed-width data types. It should only be used by the implementation. * * Applications should use the getutxent(3c) family of routines to interact * with this database. */ struct file_utmpx_entry { char ut_user[32]; /* user login name */ char ut_id[4]; /* inittab id */ char ut_line[32]; /* device name (console, lnxx) */ uint32_t ut_pid; /* process id */ int16_t ut_type; /* type of entry */ struct { int16_t e_termination; /* process termination status */ int16_t e_exit; /* process exit status */ } ut_exit; /* exit status of a process */ struct timeval_32 ut_tv; /* time entry was made */ int32_t ut_session; /* session ID, user for windowing */ int32_t pad[5]; /* reserved for future use */ int16_t ut_syslen; /* significant length of ut_host */ char ut_host[257]; /* remote host name */ }; static void usage ( char * arg ) { fprintf( stderr, " Usage: %s [-h] [-w wtmp] [-x wtmpx]\n", arg ); exit( EXIT_FAILURE ); } /* end of usage */ int main ( int argc, char * argv[] ) { int fd_wtmp, fd_wtmpx; char filename_wtmp[128] = WTMP_FILENAME; char filename_wtmpx[128] = WTMPX_FILENAME; ssize_t wtmp_bytes_read; ssize_t wtmpx_bytes_read; uint32_t wtmp_read_counter = 0; uint32_t wtmpx_read_counter = 0; int c; struct file_utmp_entry utmp_entry; struct file_utmpx_entry utmpx_entry; opterr = 0; /* Don't want getopt() writing to stderr */ while ( ( c = getopt( argc, argv, "hw:x:" ) ) != EOF ) { switch ( c ) { case 'w': strncpy( filename_wtmp, optarg, 128 ); filename_wtmp[127] = '\0'; break; case 'x': strncpy( filename_wtmpx, optarg, 128 ); filename_wtmpx[127] = '\0'; break; case 'h': case '?': usage( argv[0] ); break; } /* end of switch */ } /* end of while */ fd_wtmp = open( filename_wtmp, O_RDONLY ); if ( fd_wtmp < 0 ) { fprintf( stderr, "Unable to open %s\n", filename_wtmp ); return( EXIT_FAILURE ); } fd_wtmpx = open( filename_wtmpx, O_RDONLY ); if ( fd_wtmpx < 0 ) { fprintf( stderr, "Unable to open %s\n", filename_wtmpx ); close( fd_wtmp ); return( EXIT_FAILURE ); } while ( 1 ) { wtmpx_bytes_read = read( fd_wtmpx, &utmpx_entry, sizeof( struct file_utmpx_entry ) ); if ( wtmpx_bytes_read > 0 ) { if ( wtmpx_bytes_read < sizeof( struct file_utmpx_entry ) ) { fprintf( stderr, "wtmpx entry may be corrupted\n" ); break; } wtmpx_read_counter++; } wtmp_bytes_read = read( fd_wtmp, &utmp_entry, sizeof( struct file_utmp_entry ) ); if ( wtmp_bytes_read > 0 ) { if ( wtmp_bytes_read < sizeof( struct file_utmp_entry ) ) { fprintf( stderr, "wtmp entry may be corrupted\n" ); break; } wtmp_read_counter++; } if ( ( wtmpx_bytes_read <= 0 ) || ( wtmp_bytes_read <= 0 ) ) { break; } if ( strncmp( utmp_entry.ut_user, utmpx_entry.ut_user, 8 ) != 0 ) { fprintf( stderr, "[ %u ] ut_user %s <-> %s\n", wtmp_read_counter, utmp_entry.ut_user, utmpx_entry.ut_user ); break; } 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; } if ( strcmp( utmp_entry.ut_line, utmpx_entry.ut_line ) != 0 ) { fprintf( stderr, "[ %u ] ut_line %s <-> %s\n", wtmp_read_counter, utmp_entry.ut_line, utmpx_entry.ut_line ); break; } if ( utmp_entry.ut_pid != utmpx_entry.ut_pid ) { fprintf( stderr, "[ %u ] ut_pid %d <-> %d\n", wtmp_read_counter, utmp_entry.ut_pid, utmpx_entry.ut_pid ); break; } if ( utmp_entry.ut_type != utmpx_entry.ut_type ) { fprintf( stderr, "[ %u ] ut_type %d <-> %d\n", wtmp_read_counter, utmp_entry.ut_type, utmpx_entry.ut_type ); break; } if ( utmp_entry.ut_time != utmpx_entry.ut_tv.tv_sec ) { fprintf( stderr, "[ %u ] ut_time %08X <-> %08X\n", wtmp_read_counter, utmp_entry.ut_time, utmpx_entry.ut_tv.tv_sec ); break; } } /* end of while */ if ( wtmpx_read_counter != wtmp_read_counter ) { fprintf( stderr, "wtmpx or wtmp entry may be deleted\n" ); } close( fd_wtmpx ); close( fd_wtmp ); return( EXIT_SUCCESS ); } /* end of main */ #endif
9. strings.c
#include <stdio.h> #include <strings.h> #include <sys/types.h> #include <sys/stat.h> #include <ctype.h> #include <stdlib.h> #ifdef __FreeBSD__ #include <string.h> #endif #define MAXFILESIZE (4 * 1024 * 1024) /* * Many options here. The current choice produces a little more output * than gnu strings */ /* * Try to get the filesize via stat, and get a buffer to match * Naievely allocate a 4MB buffer if we can't * Fails badly if it allocate a buffer big enough to load a file */ unsigned char *filebuf(FILE *pf, int *sz) { struct stat buf; unsigned char *cdata; if (fstat(fileno(pf), &buf) < 0) { perror("fstat"); exit (1); } *sz = buf.st_size; if(*sz == 0) *sz = MAXFILESIZE; if ((cdata = malloc(*sz+1)) == NULL) { perror("malloc"); exit (1); } return cdata; } /* * Find printable strings of 4 or more characters * Always scans entire file (-a option of gnu strings) */ void strings(FILE *pf) { static char printme[1024]; int sz; unsigned char *cdata; int nread; int printmeindex; cdata = filebuf(pf,&sz); nread = fread(cdata, 1, sz, pf); printmeindex = 0; if (nread > 0) { int i; unsigned char c; int isprintable; int iseol; for (i = 0; i < nread; i++) { c = cdata[i]; isprintable = isprint(c); iseol = 0; if (c == 0 || c == '\n' || printmeindex >= sizeof(printme)-1) iseol = 1; if (iseol || !isprintable) { if (printmeindex > 3 && iseol) { printme[printmeindex++] = 0; printf("%s\n", printme); printmeindex = 0; } } else if (isprintable) { printme[printmeindex++] = c; } } } if (printmeindex > 3) { printme[printmeindex++] = 0; printf("%s\n", printme); printmeindex = 0; } free(cdata); } /* * Silently accepts the -a option */ int main(int argc, char **argv) { if (argc > 1) { int i; for (i = 1; i < argc; i++) { FILE *pf; if (strcmp(argv[i], "-a") == 0) continue; if ((pf = fopen(argv[i],"rb")) == NULL) { perror("fopen"); return(1); } strings(pf); } } else { strings(stdin); } return(0); }
10. chkrootkit的使用場景及其侷限
Relevant Link:
http://xteam.baidu.com/?p=237&qq-pf-to=pcqq.group
Copyright (c) 2014 LittleHann All rights reserved