[Forensics-changes] [SCM] debian-forensics/unhide branch, upstream, updated. upstream/20080519-2-g7b13ec7

Christophe Monniez christophe.monniez at fccu.be
Thu Dec 23 09:14:23 UTC 2010


The following commit has been merged in the upstream branch:
commit 7b13ec7a344a977e869e26706e5cf5012f894d2b
Author: Christophe Monniez <christophe.monniez at fccu.be>
Date:   Thu Dec 23 09:42:59 2010 +0100

    Adding upstream version 20100819.

diff --git a/README.txt b/README.txt
index 6830c67..9acb4d9 100644
--- a/README.txt
+++ b/README.txt
@@ -1,17 +1,25 @@
 **-Unhide-**   yjesus at security-projects.com
 
-Unhide is a forensic tool to find hidden processes and TCP/UDP ports by rootkits / LKMs 
+Unhide is a forensic tool to find hidden processes and TCP/UDP ports by rootkits / LKMs
 or by another hidden technique.
 
 //Unhide (ps)
 
-Detecting hidden processes. Implements three techniques
+Detecting hidden processes. Implements six techniques
 
-* Compare /proc vs /bin/ps output
+1- Compare /proc vs /bin/ps output
 
-* Compare info gathered from /bin/ps with info gathered from syscalls (syscall scanning).
+2- Compare info gathered from /bin/ps with info gathered by walking thru the procfs. ONLY for Linux 2.6 version
 
-* Full PIDs space ocupation (PIDs bruteforcing)
+3- Compare info gathered from /bin/ps with info gathered from syscalls (syscall scanning).
+
+4- Full PIDs space ocupation (PIDs bruteforcing)
+
+5- Compare /bin/ps output vs /proc, procfs walking and syscall. ONLY for Linux 2.6 version
+  Reverse search, verify that all thread seen by ps are also seen by the kernel.
+
+6- Quick compare /proc, procfs walking and syscall vs /bin/ps output. ONLY for Linux 2.6 version
+  It's about 20 times faster than tests 1+2+3 but maybe give more false positives.
 
 // Unhide-TCP
 
@@ -21,7 +29,7 @@ of all TCP/UDP ports availables.
 
 // Files
 
-unhide.c --> Hidden processes, for generic Unix systems (*BSD, Solaris, linux 2.2 / 2.4) 
+unhide.c --> Hidden processes, for generic Unix systems (*BSD, Solaris, linux 2.2 / 2.4)
              It doesn't implement PIDs brute forcing check yet. Needs more testing
 
 unhide-linux26.c --> Hidden processes, Linux 2.6.x
diff --git a/changelog b/changelog
new file mode 100644
index 0000000..b8f3211
--- /dev/null
+++ b/changelog
@@ -0,0 +1,53 @@
+2010-08-19
+- Add GPL v3 Disclaimer in unhide-linux26.c
+- Add new test 'procfs' (via readdir & chdir)
+- Add new test 'reverse'
+- Add new test 'quick'
+- Add option verbose (-v) to allow warning display
+- Add option morecheck (-m), only affect procfs test for now
+- Add option help (-h)
+- Displace usage in usage() function
+- Add Changelog file (this file)
+- Rewamp command line parsing in main()
+- Change checkps() parameter to allow more scalability
+- Minor optimization in brute(), we tried to create 300 more processes than available.
+- Minor optimization : avoid to test our own PID
+- Update the man page and README.txt to reflect changes.
+
+2010-02-01
+- Threads Brute Force added
+- Add needed stuff (includes, defines, ...) to eliminate compilation warning.  (Thanks to J. Walles)
+- Correct a typo in checkps() where fich_tmp is used in place of fich_pgid (Thanks to  P. Gouin)
+- Corrected several FD leaks where files or pipes are read and closed even if they have failed to open. (Thanks to W. Doekes  & P. Gouin)
+- Add warning messages if file or pipe fails to open (compatible with rkhunter use of unhide) (Thanks to W. Doekes & P. Gouin)
+- Add warning messages if a test is skipped (compatible with rkhunter use of unhide). (Thanks to P. Gouin)
+- Correct removing of leading spaces which tests one char too far for end of string in checkps(). (Thanks to P. Gouin)
+- Close fd in get_max_pid().   (Thanks to P. Gouin)
+- Close cmd_file in printbadpid().  (Thanks to P. Gouin)
+- Add display of test name in checkallnoprocps().  (Thanks to P. Gouin)
+- Close fich_processo in checksysinfo() (Thanks to W. Doekes)
+- Avoid potential buffer overflow in checksysinfo()  (Thanks to W. Doekes)
+- Correct allpids[] initialization in brute()  (Thanks to W. Doekes)
+- Modify brute as modifying allpid from within the forked process may have undefined results (Linux vfork() man page) (Thanks to P. Gouin)
+- Add return to main()  (Thanks to W. Doekes)
+- Optimizations (Thanks to P. Gouin)
+
+2009-08-10 (BETA)
+-Improved maxpid routine (Thanks to Jan Iven)
+-Improved false positives detection (Thanks to Jan Iven)
+-Kill() syscall added (Thanks to Jan Iven)
+-Fixed sched_getaffinity() bug (Thanks to Jan Iven)
+-Some minor bug fixes
+
+2008-05-19
+-Fixed a race condition bug that showed false positives (Thanks to Johan Walles)
+-Added manpages (Thanks to Francois Marier)
+
+02-11-2007
+-Minor bugfixes
+-License added
+-sysinfo() syscall added
+
+28-12-2005
+-Initial Release
+
diff --git a/man/unhide.8 b/man/unhide.8
index 6918d9e..19f8c48 100644
--- a/man/unhide.8
+++ b/man/unhide.8
@@ -1,37 +1,72 @@
-.TH "UNHIDE" "8" 
-.SH "NAME" 
-unhide \(em forensic tool to find hidden processes 
-.SH "SYNOPSIS" 
-.PP 
-\fBunhide\fR \fIproc\fR | \fIsys\fR | \fIbrute\fR
-.SH "DESCRIPTION" 
-.PP 
+.TH "UNHIDE" "8" "June 2010" "Administration commands"
+.SH "NAME"
+unhide \(em forensic tool to find hidden processes
+.SH "SYNOPSIS"
+.PP
+\fBunhide\fR [\fIOPTIONS\fR] \fITEST\fR
+.SH "DESCRIPTION"
+.PP
 \fBunhide\fR is a forensic tool to find processes hidden by
 rootkits, Linux kernel modules or by other techniques.  It
 detects hidden processes using three techniques:
-.PP 
-The \fIproc\fR technique consists of comparing /proc with the 
-output of /bin/ps. 
-.PP 
-The \fIsys\fR technique consists of comparing information 
-gathered from /bin/ps with information gathered from system 
-calls. 
-.PP 
-The \fIbrute\fR technique consists of bruteforcing the all 
-process IDs. 
-.SH "SEE ALSO" 
-.PP 
-unhide-tcp (8). 
-.SH "AUTHOR" 
-.PP 
-This manual page was written by Francois Marier francois at debian.org for 
-the \fBDebian\fP system (but may be used by others).  Permission is 
-granted to copy, distribute and/or modify this document under 
-the terms of the GNU General Public License, Version 3 any  
-later version published by the Free Software Foundation. 
- 
-.PP 
-On Debian systems, the complete text of the GNU General Public 
-License can be found in /usr/share/common-licenses/GPL. 
- 
-.\" created by instant / docbook-to-man, Thu 06 Dec 2007, 17:59 
+.PP
+.SH "OPTIONS"
+.TP
+\fB\-h\fR
+display help
+.TP
+\fB\-v\fR
+be verbose, display warning message (default : don't display)
+.TP
+\fB\-m\fR
+do more checks. As of 2010-06-01 version, this option has only
+effect for the dirfunc test. Implies -v
+.PP
+.PP
+.SH "TESTS"
+.PP
+The \fIproc\fR technique consists of comparing /proc with the
+output of /bin/ps.
+.PP
+The \fIprocfs\fR technique consists of comparing information
+gathered from /bin/ps with information gathered by walking in the procfs.
+.PP
+The \fIsys\fR technique consists of comparing information
+gathered from /bin/ps with information gathered from system
+calls.
+.PP
+The \fIbrute\fR technique consists of bruteforcing the all
+process IDs. This technique is only available on Linux 2.6 kernels.
+.PP
+The \fIreverse\fR technique consists of verifying that all threads
+seen by ps are also seen in procfs and by system calls. It is intended to
+verify that a rootkit has not killed a security tool (IDS or other) and
+make the admin believe it's still running.
+This technique is only available on Linux 2.6 kernels.
+.PP
+The \fIquick\fR technique combines the proc, procfs and sys techniques in a
+quick way. It's about 20 times faster but may give false positives.
+This technique is only available on Linux 2.6 kernels.
+.SS "Exit status:"
+.TP
+0
+if OK,
+.TP
+1
+if problems.
+.PP
+Report ls bugs to yjesus at security-projects.com
+.SH "SEE ALSO"
+.PP
+unhide-tcp (8).
+.SH "AUTHOR"
+.PP
+This manual page was written by Francois Marier francois at debian.org and Patrick Gouin.
+Permission is granted to copy, distribute and/or modify this document under
+the terms of the GNU General Public License, Version 3 or any
+later version published by the Free Software Foundation.
+.SH LICENSE
+License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
+.br
+This is free software: you are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law.
diff --git a/unhide-linux26.c b/unhide-linux26.c
index e280714..73bc30e 100644
--- a/unhide-linux26.c
+++ b/unhide-linux26.c
@@ -1,5 +1,20 @@
 /* Unhide yjesus at security-projects.com */
 
+/*
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
 // Needed for unistd.h to declare getpgid() and others
 #define _XOPEN_SOURCE 500
 
@@ -22,11 +37,12 @@
 #include <fcntl.h>
 #include <pthread.h>
 #include <sys/syscall.h>
+#include <ctype.h>
 
 
 // we are looking only for real process not thread and only one by one
 #define COMMAND "ps --no-header -p %i o pid"
-// we ara looking for session ID one by one
+// we are looking for session ID one by one
 #define SESSION "ps --no-header -s %i o sess"
 // We are looking for group ID one by one
 // but ps can't select by pgid
@@ -34,16 +50,43 @@
 // We are looking for all processes even threads
 #define THREADS "ps --no-header -eL o lwp"
 // for sysinfo scanning, fall back to old command, as --no-header seems to create
-// an extra process
+// an extra process/thread
 #define SYS_COMMAND "ps -eL o lwp"
+// an extra process/thread
+#define REVERSE "ps --no-header -eL o lwp,cmd"
+
+// Define mask for the checks to do in checkps
+#define PS_PROC			0x00000001
+#define PS_THREAD			0x00000002
+#define PS_MORE			0x00000004
+
+// Define test number
+#define TST_NONE			0
+#define TST_PROC			1
+#define TST_DIR			2
+#define TST_SYS			3
+#define TST_BRUTE			4
+#define TST_REVERSE		5
+#define TST_QUICK			6
+
+// boolean value
+#define FALSE			0
+#define TRUE			1
 
 
 // sysctl kernel.pid_max
-int maxpid= 32768;
+int maxpid = 32768;
 
 // For Threads sync
 int tid ;
 
+// our own PID
+pid_t mypid ;
+
+// options
+int verbose = FALSE;
+int morecheck = FALSE;
+
 void *funcionThread (void *parametro) {
 
 	tid = (pid_t) syscall (SYS_gettid);
@@ -71,7 +114,7 @@ void get_max_pid(int* newmaxpid) {
 }
 
 
-int checkps(int tmppid, int morechecks) {
+int checkps(int tmppid, int checks) {
 
 	int ok = 0;
 	char pids[30];
@@ -82,64 +125,77 @@ int checkps(int tmppid, int morechecks) {
 
 	FILE *fich_tmp ;
 
+//	printf("in --> checkps\n");   // DEBUG
+
 // The compare string is the same for all test
 	sprintf(compare,"%i\n",tmppid);
 
-	sprintf(command,COMMAND,tmppid) ;
+	if (PS_PROC == (checks & PS_PROC)) {
+		sprintf(command,COMMAND,tmppid) ;
 
-	fich_tmp=popen (command, "r") ;
-	if (fich_tmp == NULL) {
-		printf("Warning : popen failed while ps checking pid %d (memory, or something set errno: %s)\n", tmppid, strerror(errno));
-		return(0);
-	}
+		fich_tmp=popen (command, "r") ;
+		if (fich_tmp == NULL) {
+			if(TRUE == verbose) {
+				printf("Warning : popen failed while ps checking pid %d (memory, or something set errno: %s)\n", tmppid, strerror(errno));
+			}
+			return(0);
+		}
+
+//		while (!feof(fich_tmp) && ok == 0) {
+		{
+			char* tmp_pids = pids;
+
+			fgets(pids, 30, fich_tmp);
+			pids[29] = 0;
 
-	while (!feof(fich_tmp) && ok == 0) {
-		char* tmp_pids = pids;
+//			printf("pids = %s\n", pids);   // DEBUG
+			while( *tmp_pids == ' ' && tmp_pids <= pids+29) {
+				tmp_pids++;
+			}
 
-		fgets(pids, 30, fich_tmp);
-		pids[29] = 0;
+			if (strncmp(tmp_pids, compare, 30) == 0) {ok = 1;}
 
-		while( *tmp_pids == ' ' && tmp_pids <= pids+29) {
-			tmp_pids++;
 		}
 
-		if (strncmp(tmp_pids, compare, 30) == 0) {ok = 1;}
+		if (fich_tmp != NULL)
+			pclose(fich_tmp);
 
+		if (1 == ok) return(ok) ;	 // pid is found, no need to go further
 	}
 
-	if (fich_tmp != NULL)
-		pclose(fich_tmp);
-
-	if (1 == ok) return(ok) ;	 // pid is found, no need to go further
+	if (PS_THREAD == (checks & PS_THREAD)) {
+		FILE *fich_thread ;
 
-	FILE *fich_thread ;
+		fich_thread=popen (THREADS, "r") ;
+		if (fich_thread == NULL) {
+			if(TRUE == verbose) {
+				printf("Warning : popen failed while thread checking pid %d (memory, or something set errno: %s)\n", tmppid, strerror(errno));
+			}
+			return(0);
+		}
 
-	fich_thread=popen (THREADS, "r") ;
-	if (fich_thread == NULL) {
-		printf("Warning : popen failed while thread checking pid %d (memory, or something set errno: %s)\n", tmppid, strerror(errno));
-		return(0);
-	}
+		while (!feof(fich_thread) && ok == 0) {
+			char* tmp_pids = pids;
 
-	while (!feof(fich_thread) && ok == 0) {
-		char* tmp_pids = pids;
+			fgets(pids, 30, fich_thread);
+			pids[29] = 0;
 
-		fgets(pids, 30, fich_thread);
-		pids[29] = 0;
+//			printf("   threads = %s\n", pids);   // DEBUG
+			while( *tmp_pids == ' ' && tmp_pids <= pids+29) {
+				tmp_pids++;
+			}
 
-		while( *tmp_pids == ' ' && tmp_pids <= pids+29) {
-			tmp_pids++;
-		}
+			if (strncmp(tmp_pids, compare, 30) == 0) {ok = 1;}
 
-		if (strncmp(tmp_pids, compare, 30) == 0) {ok = 1;}
 
+		}
+		if (fich_thread != NULL)
+			pclose(fich_thread);
 
+		if (1 == ok) return(ok) ;	 // thread is found, no need to go further
 	}
-	if (fich_thread != NULL)
-		pclose(fich_thread);
 
-	if (1 == ok) return(ok) ;	 // thread is found, no need to go further
-
-	if (morechecks == 1) {
+	if (PS_MORE == (checks & PS_MORE)) {
 
 		FILE *fich_session ;
 
@@ -174,7 +230,9 @@ int checkps(int tmppid, int morechecks) {
 
 		fich_pgid=popen (PGID, "r") ;
 		if (fich_pgid == NULL) {
-			printf("Warning : popen failed while pgid checking pid %d (memory, or something set errno: %s)\n", tmppid, strerror(errno));
+			if(TRUE == verbose) {
+				printf("Warning : popen failed while pgid checking pid %d (memory, or something set errno: %s)\n", tmppid, strerror(errno));
+			}
 			return(0);
 		}
 
@@ -203,46 +261,68 @@ void printbadpid (int tmppid) {
 	int statuscmd ;
 	char cmd[100] ;
 	struct stat buffer;
+	FILE *cmdfile ;
+	char cmdcont[1000];
+	int cmdok = 0;
 
 	printf ("Found HIDDEN PID: %i\n", tmppid) ;
 
 	sprintf(cmd,"/proc/%i/cmdline",tmppid);
 
 	statuscmd = stat(cmd, &buffer);
+//	statuscmd = 0 ;  // DEBUG
 
 	if (statuscmd == 0) {
-
-		FILE *cmdfile ;
-		char cmdcont[1000];
-
 		cmdfile=fopen (cmd, "r") ;
 
 		if (cmdfile != NULL) {
 
 			while (!feof (cmdfile)) {
 
-				fgets (cmdcont, 1000, cmdfile);
-				printf ("Command: %s\n\n", cmdcont);
+				if (NULL != fgets (cmdcont, 1000, cmdfile)) {
+					cmdok = 1;
+					printf ("Command: %s\n\n", cmdcont);
+				}
 			}
 			fclose(cmdfile);
 		}
 	}
+	if (0 == cmdok) {			// fallback : try to readlink the exe
+		ssize_t length ;
+
+		sprintf(cmd,"/proc/%i/exe",tmppid);
+		statuscmd = stat(cmd, &buffer);
+		printf("%s",cmd) ; //DEBUG
+		if (statuscmd == 0) {
+			length = readlink(cmd, cmdcont, 1000) ;
+//			printf("\t%0d\n",(int)length) ; //DEBUG
+			if (-1 != length) {
+				cmdcont[length] = 0;   // terminate the string
+				printf ("Exe: %s\n\n", cmdcont);
+			}
+		}
+		else {
+			printf("  ... maybe a transitory process\n");
+		}
+	}
 }
 
-
 void checkproc() {
 
 	int procpids ;
 	int statusprocbefore, statusprocafter;
 	struct stat buffer;
 
-	printf ("[*]Searching for Hidden processes through /proc scanning\n\n") ;
+	printf ("[*]Searching for Hidden processes through /proc stat scanning\n\n") ;
 
 	for ( procpids = 1; procpids <= maxpid; procpids = procpids +1 ) {
 
 		char directory[100] ;
 
-
+		// avoid ourselves
+		if (procpids == mypid) {
+			continue;
+		}
 		sprintf(directory,"/proc/%d",procpids);
 
 		statusprocbefore = stat(directory, &buffer) ;
@@ -250,7 +330,7 @@ void checkproc() {
 			continue;
 		}
 
-		if(checkps(procpids,0)) {
+		if(checkps(procpids,PS_PROC | PS_THREAD)) {
 			continue;
 		}
 
@@ -263,6 +343,194 @@ void checkproc() {
 	}
 }
 
+void checkchdir() {
+
+	int procpids ;
+	int statusdir;
+	char curdir[PATH_MAX] ;
+//	char scratch[PATH_MAX] ;	// DEBUG
+//	int count = 0;		//DEBUG
+
+	printf ("[*]Searching for Hidden processes through /proc chdir scanning\n\n") ;
+	// get the path where Unhide is ran from.
+	getcwd(curdir, PATH_MAX);
+
+	for ( procpids = 1; procpids <= maxpid; procpids = procpids +1 ) {
+
+		char directory[100] ;
+
+		// avoid ourselves
+		if (procpids == mypid) {
+			continue;
+		}
+
+		sprintf(directory,"/proc/%d",procpids);
+
+		statusdir = chdir(directory) ;
+		// the directory doesn't exist continue with the next one
+		if (statusdir != 0) {
+			continue;
+		}
+		if (morecheck == TRUE) {
+			// find process group ID (the master thread) by reading the status file of the current dir
+			FILE *fich_tmp ;
+			int found_tgid = FALSE;
+			char line[128] ;
+			char* tmp_pids = line;
+			char* end_pid;
+			char new_directory[100] ;
+
+//			printf("directory = '%s'\n", directory);	// DEBUG
+//			getcwd(scratch, PATH_MAX);						// DEBUG
+//			printf("CWD = '%s'\n", scratch);				// DEBUG
+
+			fich_tmp=fopen("status", "r") ;
+			if (fich_tmp == NULL) {
+				if(TRUE == verbose) {
+					printf("Warning : can't open status file for process: %d)\n", procpids);
+				}
+				continue ; // next process
+			}
+			while ((FALSE == found_tgid) && (!feof (fich_tmp))) {
+
+				if (NULL != fgets (line, 128, fich_tmp)) {
+					line[127] = 0;
+					if (0 == strncmp (line, "Tgid:", 5)) {
+						found_tgid = TRUE;
+					}
+				}
+			}
+			fclose(fich_tmp);
+
+			if (TRUE == found_tgid) {
+				tmp_pids = line + 5;
+				while( ((*tmp_pids == ' ') || (*tmp_pids == '\t'))  && (tmp_pids <= line+127)) {
+					tmp_pids++;
+				}
+//				printf("tmp_pids2 = '%s'\n", tmp_pids);   // DEBUG
+				end_pid = tmp_pids;
+				while( isdigit(*end_pid) && end_pid <= line+127) {
+					end_pid++;
+				}
+				*end_pid = 0;  // remove \n
+//				if the number of threads is < to about 40 % of the number of processes,
+//				the next "optimising" test actually produce a slower executable.
+//				if(procpids != atoi(tmp_pids))
+				{   // if the thread isn't the master thread (process)
+//					count++;		// DEBUG
+					sprintf(new_directory,"/proc/%s/task/%d", tmp_pids, procpids) ;
+//					printf("new_dir = %s\n", new_directory);   // DEBUG
+					statusdir = chdir(new_directory) ;
+					if (statusdir != 0) {
+					// the thread is not listed in the master thread task directory
+						if(TRUE == verbose) {
+							printf("Warning : Thread %s said it's in group %d but isn't listed in %s\n", tmp_pids, procpids, new_directory);
+						}
+					}
+				}
+			}
+			else {
+				if(TRUE == verbose) {
+				printf("Warning : Can't find TGID in status file for process': %d)\n", procpids);
+				}
+			}
+		}
+
+		// unlock the proc directory so it can disappear if it's a transitory process
+		chdir(curdir);
+
+		if(checkps(procpids, PS_PROC | PS_THREAD)) {
+			continue;
+		}
+
+		// Avoid false positive on short life process/thread
+		statusdir = chdir(directory) ;
+		if (statusdir != 0) {
+			continue;
+		}
+
+		printbadpid(procpids);
+	}
+	// go back to our path
+	chdir(curdir);
+//	printf("Passages = %d\n", count);	// DEBUG
+}
+
+
+void checkreaddir() {
+
+	int procpids ;
+   DIR *procdir, *taskdir;
+   struct dirent *dir, *dirproc;
+
+	printf ("[*]Searching for Hidden thread through /proc/pid/task readdir scanning\n\n") ;
+
+	procdir = opendir("/proc");
+	if (NULL == procdir) {
+		if(TRUE == verbose) {
+			printf("Warning : Cannot open /proc directory !");
+		}
+		return ;
+	}
+	while ((dirproc = readdir(procdir))) {
+	// As of Linux kernel 2.6 :
+	// readdir directly in /proc only see process, not thread
+	// because procfs voluntary hides threads to readdir
+		char *directory ;
+		char task[100] ;
+
+		directory = dirproc->d_name;
+		if(!isdigit(*directory)) {
+			// not a process directory of /proc
+			continue;
+		}
+//		sprintf(currentproc, "%d", directory);
+
+		sprintf(task, "/proc/%s/task", directory) ;
+//		printf("task : %s", task) ; // DEBUG
+		taskdir = opendir(task);
+		if (NULL == taskdir) {
+			if(TRUE == verbose) {
+				printf("Warning : Cannot open %s directory !", task);
+			}
+			return ;
+		}
+
+		while ((dir = readdir(taskdir)))
+		{
+			char *tmp_d_name ;
+			tmp_d_name = dir->d_name;
+//			printf(" thread : %s\n",tmp_d_name) ;  // DEBUG
+			if (!strcmp(tmp_d_name, ".") || !strcmp(tmp_d_name, "..")) // skip parent and current dir
+				continue;
+			if(!isdigit(*tmp_d_name)) {
+				if(TRUE == verbose) {
+					printf("Warning : Not a a thread ID (%s) in %s\n", tmp_d_name, task) ;
+				}
+				continue;
+			}
+			else if (0 != strcmp(tmp_d_name, directory)) { // thread ID is not the process ID
+//				printf("thread : %s\n",tmp_d_name) ;  // DEBUG
+				procpids = atoi(tmp_d_name) ;
+				if(checkps(procpids,PS_THREAD)) {
+					continue;
+				}
+				printbadpid(atoi(tmp_d_name));
+			}
+			else {
+//				printf("process : %s\n",tmp_d_name) ;  // DEBUG
+				procpids = atoi(tmp_d_name) ;
+				if(checkps(procpids,PS_PROC)) {
+					continue;
+				}
+				printbadpid(atoi(tmp_d_name));
+			}
+		}
+		closedir(taskdir);
+	}
+	closedir(procdir) ;
+}
+
 void checkgetpriority() {
 
 	int syspids ;
@@ -277,12 +545,17 @@ void checkgetpriority() {
 
 		errno= 0 ;
 
+		// avoid ourselves
+		if (syspids == mypid) {
+			continue;
+		}
+
 		ret = getpriority(which, syspids);
 		if ( errno != 0) {
 			continue;
 		}
 
-		if(checkps(syspids,0)) {
+		if(checkps(syspids,PS_PROC | PS_THREAD)) {
 			continue;
 		}
 
@@ -309,12 +582,17 @@ void checkgetpgid() {
 
 		errno= 0 ;
 
+		// avoid ourselves
+		if (syspids == mypid) {
+			continue;
+		}
+
 		ret = getpgid(syspids);
 		if ( errno != 0 ) {
 			continue;
 		}
 
-		if(checkps(syspids,0)) {
+		if(checkps(syspids,PS_PROC | PS_THREAD)) {
 			continue;
 		}
 
@@ -342,11 +620,16 @@ void checkgetsid() {
 
 		errno= 0 ;
 
+		// avoid ourselves
+		if (syspids == mypid) {
+			continue;
+		}
+
 		ret = getsid(syspids);
 		if ( errno != 0) {
 			continue;
 		}
-		if(checkps(syspids,0)) {
+		if(checkps(syspids,PS_PROC | PS_THREAD)) {
 			continue;
 		}
 		errno=0;
@@ -374,11 +657,16 @@ void checksched_getaffinity() {
 
 		errno= 0 ;
 
+		// avoid ourselves
+		if (syspids == mypid) {
+			continue;
+		}
+
 		ret = sched_getaffinity(syspids, sizeof(cpu_set_t), &mask);
 		if (errno != 0) {
 			continue;
 		}
-		if (checkps(syspids,0)) {
+		if (checkps(syspids,PS_PROC | PS_THREAD)) {
 			continue;
 		}
 		errno=0;
@@ -406,12 +694,17 @@ void checksched_getparam() {
 
 		errno= 0 ;
 
+		// avoid ourselves
+		if (syspids == mypid) {
+			continue;
+		}
+
 		ret = sched_getparam(syspids, &param);
 		if ( errno != 0) {
 			continue;
 		}
 
-		if(checkps(syspids,0)) {
+		if(checkps(syspids,PS_PROC | PS_THREAD)) {
 			continue;
 		}
 
@@ -439,12 +732,17 @@ void checksched_getscheduler() {
 
 		errno= 0 ;
 
+		// avoid ourselves
+		if (syspids == mypid) {
+			continue;
+		}
+
 		ret = sched_getscheduler(syspids);
 		if ( errno != 0) {
 			continue;
 		}
 
-		if(checkps(syspids,0)) {
+		if(checkps(syspids,PS_PROC | PS_THREAD)) {
 			continue;
 		}
 
@@ -472,12 +770,17 @@ void checksched_rr_get_interval() {
 
 		errno= 0 ;
 
+		// avoid ourselves
+		if (syspids == mypid) {
+			continue;
+		}
+
 		ret = sched_rr_get_interval(syspids, &tp);
 		if ( errno != 0) {
 			continue;
 		}
 
-		if(checkps(syspids,0)) {
+		if(checkps(syspids,PS_PROC | PS_THREAD)) {
 			continue;
 		}
 
@@ -502,12 +805,18 @@ void checkkill() {
 		int ret;
 
 		errno= 0 ;
+
+		// avoid ourselves
+		if (syspids == mypid) {
+			continue;
+		}
+
 		ret = kill(syspids, 0);
 		if ( errno != 0) {
 			continue;
 		}
 
-		if(checkps(syspids,0)) {
+		if(checkps(syspids,PS_PROC | PS_THREAD)) {
 			continue;
 		}
 
@@ -539,6 +848,11 @@ void checkallnoprocps() {
 
 	for ( syspids = 1; syspids <= maxpid; syspids++ ) {
 
+		// avoid ourselves
+		if (syspids == mypid) {
+			continue;
+		}
+
 		found=0;
 		found_killbefore=0;
 		found_killafter=0;
@@ -588,11 +902,265 @@ void checkallnoprocps() {
 			}
 		} /* else: unreliable */
 		else {
-			printf("Warning : syscall comparison test skipped for PID %d", syspids);
+			if(TRUE == verbose) {
+				printf("Warning : syscall comparison test skipped for PID %d", syspids);
+			}
 		}
 	}
 }
 
+void checkallquick() {
+
+	/* compare the various system calls against each other,
+	 * without invoking 'ps' or looking at /proc */
+
+	int ret;
+	int syspids;
+	struct timespec tp;
+	struct sched_param param;
+	cpu_set_t mask;
+	int found=0;
+	int found_killbefore=0;
+	int found_killafter=0;
+	char directory[100];
+	struct stat buffer;
+	int statusproc, statusdir;
+	char curdir[PATH_MAX] ;
+
+	printf ("[*]Searching for Hidden processes through  comparison of results of system calls, proc, dir and ps\n\n") ;
+
+	// get the path where Unhide is ran from.
+	getcwd(curdir, PATH_MAX);
+
+	for ( syspids = 1; syspids <= maxpid; syspids++ ) {
+
+		// avoid ourselves
+		if (syspids == mypid) {
+			continue;
+		}
+
+		// printf("syspid = %d\n", syspids); //DEBUG
+
+		found=0;
+		found_killbefore=0;
+		found_killafter=0;
+
+		errno=0;
+		ret = kill(syspids, 0);
+		if (errno == 0) found_killbefore=1;
+
+		errno= 0 ;
+		ret = getpriority(PRIO_PROCESS, syspids);
+		if (errno == 0) found++;
+
+		errno= 0 ;
+		ret = getpgid(syspids);
+		if (errno == 0) found++;
+
+		errno= 0 ;
+		ret = getsid(syspids);
+		if (errno == 0) found++;
+
+		errno= 0 ;
+		ret = sched_getaffinity(syspids, sizeof(cpu_set_t), &mask);
+		if (ret == 0) found++;
+
+		errno= 0 ;
+		ret = sched_getparam(syspids, &param);
+		if (errno == 0) found++;
+
+		errno= 0 ;
+		ret = sched_getscheduler(syspids);
+		if (errno == 0) found++;
+
+		errno=0;
+		ret = sched_rr_get_interval(syspids, &tp);
+		if (errno == 0) found++;
+
+		sprintf(directory,"/proc/%d",syspids);
+
+		statusproc = stat(directory, &buffer) ;
+		if (statusproc == 0) {
+			found++;
+		}
+
+		statusdir = chdir(directory) ;
+		if (statusdir == 0) {
+			found++;
+			chdir(curdir);
+		}
+
+		if ((0 != found) || (0 != found_killbefore)) {
+			if(checkps(syspids,PS_PROC | PS_THREAD)) {
+				found++;
+			}
+		}
+
+		errno=0;
+		ret = kill(syspids, 0);
+		if (errno == 0) found_killafter=1;
+
+
+		/* these should all agree, except if a process went or came in the middle */
+		if (found_killbefore == found_killafter) {
+			if ( ! ((found_killbefore == 0 && found == 0) ||
+					  (found_killbefore == 1 && found == 10)) ) {
+				printbadpid(syspids);
+			}
+		} /* else: unreliable */
+		else {
+			if(TRUE == verbose) {
+				printf("Warning : syscall comparison test skipped for PID %d", syspids);
+			}
+		}
+	}
+}
+
+
+void checkallreverse() {
+
+	/* verify if all thread showed by ps are also seen by others */
+
+	int ret;
+	int syspids;
+	struct timespec tp;
+	struct sched_param param;
+	cpu_set_t mask;
+	int found=0;
+	int found_killbefore=0;
+	int found_killafter=0;
+	FILE *fich_tmp;
+	char command[50];
+	char read_line[1024];
+	char lwp[7];
+	int  index;
+	char directory[100];
+	struct stat buffer;
+	int statusproc, statusdir;
+	char curdir[PATH_MAX] ;
+
+	printf ("[*]Searching for Hidden processes by verifying that all thread seen by ps are also seen by others\n\n") ;
+
+	sprintf(command,REVERSE) ;
+
+	// get the path where Unhide is ran from.
+	getcwd(curdir, PATH_MAX);
+
+	fich_tmp=popen (command, "r") ;
+	if (fich_tmp == NULL) {
+		if(TRUE == verbose) {
+			printf("Warning : popen failed while running ps (memory, or something set errno: %s)\n", strerror(errno));
+		}
+		return;
+	}
+
+	while (!feof(fich_tmp)) {
+		char* curline = read_line;
+
+		fgets(read_line, 1024, fich_tmp);
+		read_line[1023] = 0;
+
+//			printf("read_line = %s\n", read_line);   // DEBUG
+		while( *curline == ' ' && curline <= read_line+1023) {
+			curline++;
+		}
+
+		// get LWP
+		index=0;
+		while( isdigit(*curline) && curline <= read_line+1023) {
+			lwp[index++] = *curline;
+			curline++;
+		}
+		lwp[index] = 0; // terminate string
+
+		syspids = -1;
+		syspids = atol(lwp);
+		if (-1 == syspids) continue ; // something went wrong
+
+		// avoid ourselves
+		if (syspids == mypid) {
+			continue;
+		}
+
+		found=0;
+		found_killbefore=0;
+		found_killafter=0;
+
+		errno=0;
+		ret = kill(syspids, 0);
+		if (errno == 0) found_killbefore=1;
+
+		strcpy(directory,"/proc/");
+		strcat(directory,lwp);
+
+		statusproc = stat(directory, &buffer) ;
+		if (statusproc != 0) {
+			found++;
+		}
+
+		statusdir = chdir(directory) ;
+		if (statusdir != 0) {
+			found++;
+		}
+		else {
+			chdir(curdir);
+		}
+
+		errno= 0 ;
+		ret = getpriority(PRIO_PROCESS, syspids);
+		if (errno != 0) found++;
+
+		errno= 0 ;
+		ret = getpgid(syspids);
+		if (errno != 0) found++;
+
+		errno= 0 ;
+		ret = getsid(syspids);
+		if (errno != 0) found++;
+
+		errno= 0 ;
+		ret = sched_getaffinity(syspids, sizeof(cpu_set_t), &mask);
+		if (ret != 0) found++;
+
+		errno= 0 ;
+		ret = sched_getparam(syspids, &param);
+		if (errno != 0) found++;
+
+		errno= 0 ;
+		ret = sched_getscheduler(syspids);
+		if (errno != 0) found++;
+
+		errno=0;
+		ret = sched_rr_get_interval(syspids, &tp);
+		if (errno != 0) found++;
+
+		errno=0;
+		ret = kill(syspids, 0);
+		if (errno == 0) found_killafter=1;
+
+
+		/* these should all agree, except if a process went or came in the middle */
+		if (found_killbefore == found_killafter) {
+			if (0 != found) {
+				printf ("Found HIDDEN PID: %i\n", syspids) ;
+				printf ("\tCommand = %s\n", curline) ;
+					//printf("\tfound = %d\n", found);   // DEBUG
+			}
+		} /* else: unreliable */
+		else {
+			if(TRUE == verbose) {
+				printf("Warning : reverse test skipped for PID %d", syspids);
+			}
+		}
+	}
+
+	if (fich_tmp != NULL)
+		pclose(fich_tmp);
+
+}
+
+
+
 void checksysinfo() {
 
 	struct sysinfo info;
@@ -611,14 +1179,17 @@ void checksysinfo() {
 
 	fich_proceso=popen (SYS_COMMAND, "r") ;
 	if (fich_proceso == NULL) {
-		printf("popen failed while checking sysinfo (memory, or something set errno: %s)\n", strerror(errno));
+		if(TRUE == verbose) {
+			printf("Warning : popen failed while checking sysinfo (memory, or something set errno: %s)\n", strerror(errno));
+		}
 		return;
 	}
 
 	buffer[499] = '\0';
 	while (!feof(fich_proceso)) {
 
-		fscanf( fich_proceso, "%499s", &buffer[0] );
+//		fscanf( fich_proceso, "%499s", &buffer[0] );
+		fgets(buffer, 499, fich_proceso);
 		contador++;
 
 	}
@@ -641,7 +1212,9 @@ void checksysinfo() {
 		}
 	}
 	else {
-		printf("Warning : sysinfo test skipped due to intermittent activity");
+		if(TRUE == verbose) {
+			printf("Warning : sysinfo test skipped due to intermittent activity");
+		}
 	}
 
 }
@@ -670,7 +1243,7 @@ void brute() {
 	}
 
 
-	for (i=0; i < maxpid; i++) {
+	for (i=300; i < maxpid; i++) {
 		int vpid;
 		int status;
 
@@ -695,7 +1268,7 @@ void brute() {
 
 		if (allpids[y] != 0) {
 
-			if(!checkps(allpids[y],1) ) {
+			if(!checkps(allpids[y],PS_PROC | PS_THREAD | PS_MORE) ) {
 
 				printbadpid(allpids[y]);
 
@@ -720,7 +1293,7 @@ void brute() {
 	}
 
 
-	for (i=0; i < maxpid ; i++) {
+	for (i=300; i < maxpid ; i++) {
 		void *status;
 
 		errno= 0 ;
@@ -755,7 +1328,7 @@ void brute() {
 
 		if (allpids[y] != 0) {
 
-			if(!checkps(allpids[y],1) ) {
+			if(!checkps(allpids[y],PS_PROC | PS_THREAD | PS_MORE) ) {
 
 				printbadpid(allpids[y]);
 
@@ -764,44 +1337,83 @@ void brute() {
 
 	}
 
-
-
 }
 
+void usage(char * command) {
 
+	printf("Usage: %s [options] proc | procfs | sys | brute | reverse | quick\n\n", command);
+	printf("Option :\n");
+	printf("   -v          verbose\n");
+	printf("   -h          display this help\n");
+	printf("   -m          more checks (available only with procfs command)\n");
+}
 
 
 int main (int argc, char *argv[]) {
 
-	printf ("Unhide 20100201\n") ;
+int i, test = TST_NONE;
+
+	printf ("Unhide 20100819\n") ;
 	printf ("http://www.security-projects.com/?Unhide\n\n\n") ;
 	get_max_pid(&maxpid);
 
-	if(argc != 2) {
-
-		printf("usage: %s proc | sys | brute\n\n", argv[0]);
+	if(argc < 2) {
+		usage(argv[0]);
 		exit (1);
 	}
 
+	for (i=1 ; i<argc ; i++) {
+		if (strcmp(argv[i], "-v") == 0) {verbose = TRUE;}
+		else if (strcmp(argv[i], "-h") == 0) {usage(argv[0]); exit(0);}
+		else if (strcmp(argv[i], "-m") == 0) {morecheck = TRUE; verbose = TRUE;}
+		else if (strcmp(argv[i], "proc") == 0) {test= TST_PROC;}
+		else if (strcmp(argv[i], "procfs") == 0) {test= TST_DIR;}
+		else if (strcmp(argv[i], "sys") == 0) {test= TST_SYS;}
+		else if (strcmp(argv[i], "brute") == 0) {test= TST_BRUTE;}
+		else if (strcmp(argv[i], "reverse") == 0) {test= TST_REVERSE;}
+		else if (strcmp(argv[i], "quick") == 0) {test= TST_QUICK;}
+		else { printf("Unknown argument\n") ; usage(argv[0]); exit(0);}
+	}
+
 	setpriority(PRIO_PROCESS,0,-20);  /* reduce risk from intermittent processes - may fail, dont care */
 
-	if (strcmp(argv[1], "proc") == 0) {checkproc();}
+	mypid = getpid();
+
+	switch (test) {
+		case TST_PROC :
+			checkproc();
+			break;
+		case TST_DIR :
+			checkreaddir();
+			checkchdir();
+			break;
+		case TST_SYS :
+			checkkill();
+			checkallnoprocps();
+			checkgetpriority();
+			checkgetpgid() ;
+			checkgetsid();
+			checksched_getaffinity();
+			checksched_getparam();
+			checksched_getscheduler();
+			checksched_rr_get_interval();
+			checksysinfo();
+			break;
+		case TST_BRUTE :
+			brute();
+			break;
+		case TST_REVERSE :
+			checkallreverse();
+			break;
+		case TST_QUICK :
+			checkallquick();
+			checksysinfo();
+			break;
+		default :
+			printf("Unknown test\n");
+	}
+
 
-	else if (strcmp(argv[1], "sys") == 0) {
-		checkkill();
-		checkallnoprocps();
-		checkgetpriority();
-		checkgetpgid() ;
-		checkgetsid();
-		checksched_getaffinity();
-		checksched_getparam();
-		checksched_getscheduler();
-		checksched_rr_get_interval();
-		checksysinfo();
 
-	}
-	else if(strcmp(argv[1], "brute") == 0) {
-		brute();
-	}
 	return 0;
 }

-- 
debian-forensics/unhide



More information about the forensics-changes mailing list