首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第19期->技术专题
期刊号: 类型: 关键词:
tty-watcher核心原理简介

作者:newchess(mailto: newchess@21cn.com)
日期:2001-02-19

ttywatcher是个老牌的终端监控软件,它的结构简介明了,是个做得极精巧的好东东,
它的实现原理很简单,但体现出一种典型的方法,我们不用化多大力气就可以实现内核消息
的截获,虽然它只是针对终端的,但我们很自然地想到能否扩充到网络模块上,实现网络数据
在内核级的截获,过滤......:)


   ttywacher的核心部分是一个流驱动器及一个流模块,两个部分,这两个部分
链到同一个modlinkage上,使用_init(),_info(),_fini()来完成一个模块(一个
流驱动器及一个流模块,实际是关联着的两个部分),具体的分析见月刊中"Solaris
动态内核结构"

   基本原理如下:将流驱动器twtchc及流模块twtch加载,流模块插入到终端流
通路中,将通过的流消息复制一份转发给流驱动器,用户使用getmsg()来取得数据
使用putmsg来发送数据。

user   kernel
     |                         twtch流模块
     |
     |                         |---------|                     |-------|
     |                -------->|---------| ------>             | tty   |  
     |      ......             | |       |    |        ......  |驱动器 |
     |                         | |       |    |                |       |
     |                <--------|-|-------| <--|----            |       |
     |                       | | |   |   |    |                |-------|   
     |                       | |-|---|---|    |
     |                       |   |   |        |                    
     |                       |   |   |        |
     |                       |   |   |        |
     |                       |   |   |        |
     |                       |   |   |        |
     |      twtchc流驱动器   |   |   |        |
     |    |------------------|--------        |
     |    ||--------|        |                |
     |    ||        |        |                |
     |  <--|        |        |                |
     |     |    ----|--------------------------                        
     |     |    |   |                 
     |  -->|-----   |
     |     |--------|
     |         

     由图中可看出流模块的上行及下行队列中的消息将发往流驱动器的读队列的下一个队列
     而流队列的写队列处理例程将消息放入流模块的上行和下行队列的下一个队列,注意图中连接线的位置

下面是几个宏的定义

#define FROM_USER 0   /*上行消息*/
#define FROM_SYS  1   /*下行消息*/

#define TYPE_DATA 0  /* Text */
#define TYPE_SYN  1  /* Start of input. streams module pushed */
#define TYPE_END  2  /* End of input. User hungup */
#define TYPE_TRM  3  /* Stop passing data to/from this user  */
#define TYPE_RSM  4  /* Start/continue passing data to/from this user */



所有经流模块复制的消息都要加上这个结构信息


struct packet {
char from;  /* Is the data FROM_USER or FROM_SYS */
char type;  /* Is this data TYPE_DATA or TYPE_END */
uid_t uid;   /* The uid associated with this message */
dev_t dev;   /* The dev associated with this message */
};


用户状态信息结构,它将在内核中形成一个链表

struct user_state {
uid_t  uid;     /* uid who owns this streams module */
dev_t  dev;     /* dev which owns this streams module */
queue_t *q;      /* This user's q ,注意 是个读队列指针 */
mblk_t *us_mblk;   /* The mblk which holds this record */
char   pass;    /* Pass info to user, or cut them off? */
struct user_state *next; /* The next user_state record */
};


   下面是可加载流模块的函数


    第一个是twtchopen,这个函数将经常性地被调用(每当有一个新的用户终端产生时),它的主要
功能是取得用户信息(用户uid,终端设备号,使用的队列对......),并加入全局的用户状态链表,同时
使用发送消息的方式通知流驱动器.


static int twtchopen(q, dev, flag, sflag, cred)
    queue_t *q;
    dev_t   *dev;
    int     flag;
    int sflag;
cred_t *cred;
{
register mblk_t *usmp, *nbp;
register struct user_state *us, *tus;
register struct packet *sp;
int s;

     /*转接器twtch_twtch用于流模块与流驱动器之间的消息传送*/
    
       queue_t    *uq = twtch_twtch.twtch_queue;
     
       /*取得调用当前twtchopen函数的用户信息,*/

if ((usmp=allocb(sizeof(struct user_state), BPRI_MED)) == NULL)
  return(ENOMEM);

usmp->b_wptr += sizeof(struct user_state);
us = (struct user_state *)usmp->b_rptr;
us->us_mblk = usmp;
us->uid = cred->cr_uid;
us->dev = *dev;
us->q = q;
us->pass = 1;
us->next = NULL;
      /*
       将用户状态结构加入链表,当然要区分链表中已有或未有两种情况
      */
      
       s=splstr(); /* Enter "mutex" section */

/* Put this user_state struct into the list */

        /*全局链表指针twtch_USP*/

tus=twtch_USP;
if (tus==NULL)
  twtch_USP=us;
else {
  while (tus->next!=NULL && tus->dev!=*dev) tus=tus->next;
  if (tus->dev==*dev) {
   /* Module already pushed for this device */
   freeb(usmp);
   return(0); /* Success anyway */
  }
  tus->next=us;
}
splx(s); /* Exit "mutex" section */

      /*将当前队列对(注:q为读队列指针)与状态结构建立对应关系,意味着以后这个用户的这个终端将使用这个队列对来
        传递消息*/

q->q_ptr = (caddr_t)us;
WR(q)->q_ptr = (caddr_t)us;

      /*构造消息,发往流驱动器*/

if (uq && (nbp=allocb(sizeof(struct packet), BPRI_MED)) !=NULL) {
    /* Send up a SYN packet */
  nbp->b_wptr += sizeof(struct packet);
  sp = (struct packet *)nbp->b_rptr;
  sp->from=FROM_SYS;
  sp->type=TYPE_SYN;
  sp->uid=us->uid;
  sp->dev=us->dev;
        putnext(uq,nbp);
}


    twtchisopen++;
    return(0);  /* return success */
}

/* twtchwput - extremely trivial. If the TAP device is open, then get the
* pointer to the queue. (uq). If the message going through is of DATA type,
* simply copy it, and put it into the uq.
*
* We probably want to do something different like adding the username and
* the direction of the message when we copy it or something.
*/

/*
  twtchwput这个函数是写方put例程,它要做的工作很简单,将经过的m_data类型消息
  复制一份并加个packet结构组成新的消息传给流驱动器的读队列的下一个队列
*/


static int twtchwput(q, mp)
    queue_t    *q;
    mblk_t    *mp;
{
    mblk_t    *bp, *nbp;
    queue_t    *uq = twtch_twtch.twtch_queue;
register struct packet *sp;
register struct user_state *us=((struct user_state *)q->q_ptr);


/*
   将用户的消息复制一份发往 twtchc  device
*/

    if(uq){                                                          /* 假如流驱动器已被打开*/
        if(mp->b_datap->db_type==M_DATA){      /*消息类型又是m_data类型的话*/
            if((bp=dupmsg(mp))!=NULL &&        /*复制一份*/
    (nbp=allocb(sizeof(struct packet), BPRI_MED)) !=NULL) {  /*分配并初始化packet结构的消息*/
    nbp->b_wptr += sizeof(struct packet);
    sp = (struct packet *)nbp->b_rptr;
    sp->from=FROM_SYS;            /*下行消息*/
    sp->type=TYPE_DATA;           /*数据类型消息*/
    sp->uid=us->uid;
    sp->dev=us->dev;
    linkb(nbp, bp);               /*把两个消息连起来 */
                putnext(uq,nbp);                /*发给流驱动器的读队列的上行队列*/
            }
    if (us->pass==0)        
    freemsg(mp);
   else
    putnext(q, mp);
   return;
        }
    }
    putnext(q,mp);
return;
}

twtchrput函数与twtchwput如出一辙(也将消息传给流驱动器的读队列的上行队列),
唯一的不同是在初始化packet结构时sp_from不同,原因是一个是上行,一个是下行


twtchclose函数是open的逆过程,首先要通知流驱动器(当然是发个packet结构消息啦:) ),然后
将用户状态结构从链表中摘除.

static int twtchclose(q, flag)
    queue_t    *q;
    int    flag;
{
queue_t *uq = twtch_twtch.twtch_queue;
register struct packet *sp;
register struct user_state *ssp, *tsp;
register mblk_t *bp;
int s;

    twtchisopen--;
ssp=(struct user_state *)q->q_ptr;

        /*发往流驱动器的读队列的下一个队列*/
if (uq) {
  if ((bp = allocb(sizeof(struct packet), BPRI_MED))==NULL)
   return ENOMEM;
  bp->b_wptr += sizeof(struct packet);
  sp = (struct packet *)bp->b_rptr;
  sp->from=FROM_SYS;
  sp->type=TYPE_END;
  sp->uid=((struct user_state *)q->q_ptr)->uid;
  sp->dev=((struct user_state *)q->q_ptr)->dev;

  putnext(uq, bp);
}

      /*将状态结构从链表中去除*/

       s=splstr();

tsp=twtch_USP; /* Remove this person from this list */
if (tsp==ssp)
  twtch_USP=twtch_USP->next;
else {
  while (tsp->next!=ssp) tsp=tsp->next;
  tsp->next=ssp->next;
}
splx(s);
        /*别忘了结构外面还有个消息外壳,要全释放掉*/
freeb(ssp->us_mblk);

q->q_ptr=NULL;
WR(q)->q_ptr=NULL;
}

下面是流驱动器,原码挺长,重要的东西却不多

#define _KERNEL
#include <sys/types.h>
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/uio.h>
#include <sys/buf.h>
#include <sys/user.h>
#include <sys/modctl.h>
#include <sys/open.h>
#include <sys/kmem.h>
#include <sys/poll.h>
#include <sys/conf.h>
#include <sys/cmn_err.h>
#include <sys/stat.h>
#include <sys/stropts.h>
#include <sys/devops.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

#include "twtch.h"

/*
* Solaris (SVR4) Streams Driver declarations
*/



/*下面的东东是流驱动器的*/


static struct module_info rminfo = { 0, "twtchc", 0, INFPSZ, 0, 0 };
static struct module_info wminfo = { 0, "twtchc", 0, INFPSZ, 0, 0 };

static int twtchcopen(), twtchcrput(), twtchcwput(), twtchcclose();

static struct qinit crinit = {
    twtchcrput, NULL, twtchcopen, twtchcclose, NULL, &rminfo, NULL
};

static struct qinit cwinit = {
    twtchcwput, NULL, NULL, NULL, NULL, &wminfo, NULL
};

struct streamtab twtchcvdinfo = { &crinit, &cwinit, NULL, NULL };


/*这个结构很重要(在模块内是全局的),twtch_twtch在驱动器open时被设置,流模块就靠twtch_twtch来给驱动器传消息了*/

struct twtch {
queue_t *twtch_queue; /* set on driver open */
};

static struct twtch  twtch_twtch;

/*用户状态链表头指针*/
static struct user_state *twtch_USP=NULL; /* USER state for both driver and module */


typedef struct {
dev_info_t *dip;
} twtchc_devstate_t;

static void *twtchc_state;

/*
*  Solaris (SVR4) Streams loadable module declarations
*/

/*这个东东用来计数,见twtchopen的++和twtchclose的--*/

static int twtchisopen;   /* keep track of the pushed-module open/close count */


/*这些东东还是驱动器的*/

static struct cb_ops twtchc_cb_ops = {
    nulldev,  /* open */
nulldev,  /* close */
nodev,   /* strategy */
nodev,   /* print */
nodev,   /* dump */
nodev,   /* read */
nodev,   /* write */
nodev,   /* ioctl */
nodev,   /* devmap */
nodev,   /* memmap */
nodev,   /* segmap */
nochpoll,  /* poll */
ddi_prop_op, /* prop_op */
    &twtchcvdinfo, /* stream */
D_NEW
};

static int twtchc_getinfo(), twtchc_identify(), twtchc_attach(), twtchc_detach();

static struct dev_ops twtchc_ops = {
DEVO_REV,
0,
twtchc_getinfo,
twtchc_identify,
nulldev,
twtchc_attach,
twtchc_detach,
nodev,
&twtchc_cb_ops,
(struct bus_ops *)0
};

extern struct mod_ops mod_driverops;

static struct modldrv modldrv = {
&mod_driverops,
"tty watcher driver v1.0",
&twtchc_ops
};




/*这些是流模块的*/

static int twtchopen(), twtchrput(), twtchwput(), twtchclose();

static struct module_info minfo = { 0, "twtch", 0, INFPSZ, 0, 0 };

static struct qinit rinit = {
    twtchrput, NULL, twtchopen, twtchclose, NULL, &minfo, NULL
};

static struct qinit winit = {
    twtchwput, NULL, NULL, NULL, NULL, &minfo, NULL
};

struct streamtab twtchvdinfo = { &rinit, &winit, NULL, NULL };

static struct fmodsw twtch_fmodsw = {
"twtch",
&twtchvdinfo,
0
};

static struct modlstrmod modlstrmod = {
&mod_strmodops,
"tty watcher module v1.0",
&twtch_fmodsw
};



/*二者共用的东西 :)*/

static struct modlinkage modlinkage = {
MODREV_1,
&modldrv,
&modlstrmod,
0
};

int _init(void)
{
int e;

if ((e = ddi_soft_state_init(&twtchc_state, sizeof(twtchc_devstate_t), 1)) != 0)
  return(e);

if ((e = mod_install(&modlinkage)) != 0)
  ddi_soft_state_fini(&twtchc_state);

return(e);
}

int _fini()
{
int e;

if ((e = mod_remove(&modlinkage)) != 0) return(e);

ddi_soft_state_fini(&twtchc_state);

return(e);
}

int _info(modinfop)
struct modinfo *modinfop;
{
return(mod_info(&modlinkage, modinfop));
}

流驱动器的几个函数都没有特别之处,在此就不讨论了

twtchc_identify(dip)

twtchc_attach(dip,cmd)

twtchc_detach(dip, cmd)

twtchc_getinfo(dip,infocmd, arg, result)

只有这几个函数有点意思


static int twtchcopen(q, dev, flag, sflag, cred)
    queue_t *q;
    dev_t   *dev;
    int     flag;
    int sflag;
cred_t *cred;
{
minor_t m_dev;
struct twtch *twtch;
  
  /*对于流驱动器的打开,sflag必须为零,代表一个普通驱动器的打开*/

    if(sflag)   /* check if non-driver open */
        return(OPENFAIL);

    m_dev = getminor(*dev);

  /*检查流驱动器是否已被打开*/

    if(q->q_ptr) /* busy */
  return(OPENFAIL);

  /*设置全局变量twtch_twtch为驱动器的读队列*/
    twtch = &twtch_twtch;
    q->q_ptr = (char *)twtch;
    twtch->twtch_queue = q;

   return(m_dev);
}

函数twtchcwput的作用是完成标准的put例程之外将putmsg()写入的消息传给相应的流模块读队列的下一个队列
可参照原理图理解这个函数

static int twtchcwput(q, mp)
    queue_t    *q;
    mblk_t    *mp;
{
register struct packet *pp;
struct user_state *usp;

      switch(mp->b_datap->db_type){
    
        /*处理M_IOCTL*/
     case M_IOCTL: {     /* NAK all ioctl's */
        struct iocblk *iocp;

        iocp = (struct iocblk *)mp->b_rptr;
        mp->b_datap->db_type=M_IOCNAK;
        qreply(q,mp);
        break;
    }
    
     /*处理M_FLUSH标准做法*/
      case M_FLUSH:
        if(*mp->b_rptr & FLUSHW)
            flushq(q,0);
        if(*mp->b_rptr & FLUSHR){
            flushq(RD(q),0);
            *mp->b_rptr &= ~FLUSHW;
            qreply(q,mp);
       } else
            freemsg(mp);
        break;

      /*关键部分在于对m_data类型消息的处理*/
      case M_DATA:
    default:
  pp = (struct packet *)mp->b_rptr;

                /*在全局链表中查找消息中终端号对应的用户状态信息*/

  for (usp=twtch_USP;usp!=NULL;usp=usp->next)
   if (pp->dev==usp->dev) break;
  /*找不到*/
                if (usp == NULL) /* No such luck finding this connection */
   freemsg(mp);
  else {

                   /*在状态链表中查到了对应终端,脱去packet头分析,如果要求上行,送到流模块的读队列的下
                     一个队列,如果要求下行 送往流模块的写队列的下一个队列    */
   switch (pp->type) {
    case TYPE_DATA:
     if (adjmsg(mp, sizeof(struct packet))==0)
      freemsg(mp);
     else {
      if (pp->from==FROM_USER)
       putnext(usp->q,mp);
      else
       putnext(OTHERQ(usp->q), mp);
     }
     break;
    case TYPE_END:
     putctl(usp->q, M_HANGUP);
     break;
    case TYPE_TRM:
     usp->pass=0;
     break;
    case TYPE_RSM:
     usp->pass=1;
     break;
    default:
     break;
   }
  }
  break;
}
}

/*这个函数没什么作用,但不知去掉行不行:)*/

static int twtchcrput(q, mp)
    queue_t    *q;
    mblk_t    *mp;
{
    putnext(q, mp);
}

static int twtchcclose(q, flag)
    queue_t    *q;
    int    flag;
{
    struct twtch *twtch;

    /*将twtch_twtch置为空*/
    twtch = (struct twtch *) q->q_ptr;
    twtch->twtch_queue = NULL;
}
版权所有,未经许可,不得转载