首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第34期->技术专题
期刊号: 类型: 关键词:
libnet使用举例(14)----从libnet中挖掘BPF的发包机制

作者:NSFocus Security Team
整理:小四 <scz@nsfocus.com>
主页:http://www.nsfocus.com
日期:2002-08-16

这个系列的前面所有文章都可以在那里的Security版找到。本文也是9个月前顺手写
的笔记而已。

Linux用SOCK_PACKET来完成链路层的收发,大家都演练得太熟悉了吧,BPF的收包恐
怕也差不多,俺是有点后续需求,提前做点技术储备,从libnet中挖掘BPF的发包机
制。

先用libnet写一个小程序,在x86/FreeBSD 4.3-RELEASE上发送任意ARP报文。

NetXray抓包后,你会发现,FreeBSD 4.x Kernel在链路层源MAC上做了限制,固定使
用libnet_select_device()得到的网卡设备的Working MAC做源MAC,伪造源MAC失败。
网卡设备的Working MAC是可以用ifconfig fxp0 ether lladdr 00:11:22:33:44:55
这样的命令指定的。关于这个问题后面我们专门讨论一下。

/usr/src/sys/net/if_ethersubr.c中定义了ether_output()函数,这个函数忽略了
指定的源MAC地址,而是直接从接口获取这个源MAC地址。

----------------------------------------------------------------------
/*
* /usr/src/sys/net/if_ethersubr.c
*/
int ether_output ( ifp, m, dst, rt0 )
    register struct ifnet *ifp;
    struct mbuf           *m;
    struct sockaddr       *dst;
    struct rtentry        *rt0;
{
    u_char                        esrc[6], edst[6];
    register struct ether_header *eh;
    int                           hlen;
    struct arpcom                *ac       = IFP2AC( ifp );
    int                           hdrcmplt = 0;

    ... ...
    hlen = ETHER_HDR_LEN;
    switch ( dst->sa_family )
    {
    ... ...
    case pseudo_AF_HDRCMPLT:
        hdrcmplt = 1;
        eh       = ( struct ether_header * )dst->sa_data;
        ( void )memcpy( esrc, eh->ether_shost, sizeof( esrc ) );
        /*
         * 下落式处理
         */
    case AF_UNSPEC:
        loop_copy = -1;  /* if this is for us, don't do it */
        eh        = ( struct ether_header * )dst->sa_data;
        ( void )memcpy( edst, eh->ether_dhost, sizeof( edst ) );
        type      = eh->ether_type;
        break;
    ... ...
    }  /* end of switch */
    /*
     * Add local net header. If no space in first mbuf, allocate another.
     */
    M_PREPEND( m, sizeof( struct ether_header ), M_DONTWAIT );
    if ( m == 0 )
    {
        senderr( ENOBUFS );
    }
    eh = mtod( m, struct ether_header * );
    ( void )memcpy( &eh->ether_type, &type, sizeof( eh->ether_type ) );
    ( void )memcpy( eh->ether_dhost, edst, sizeof( edst ) );
    if ( hdrcmplt )
    {
        ( void )memcpy( eh->ether_shost, esrc, sizeof( eh->ether_shost ) );
    }
    else
    {
        /*
         * 注意这个愚蠢的行为,直接从接口获取源MAC地址
         */
        ( void )memcpy( eh->ether_shost, ac->ac_enaddr,
                        sizeof( eh->ether_shost ) );
    }
    ... ...
    /*
     * Continue with link-layer output
     */
    return( ether_output_frame( ifp, m ) );
}  /* end of ether_output */
----------------------------------------------------------------------

2002-01-21 09:20

hhuu在SMTH的FreeBSD版问起如下问题

> 不太明白为什么那个行为愚蠢,现在5.0还是这样的

回答如下:

实际上源MAC是从主调者传递形参下来的。当然最初的源MAC也是那样获取的,或者是
BPF中指定的。由于一个历史原因,这里一直没有去掉那个判断赋值。当不涉及BPF的
时候就是简单的同义重复了一次,涉及BPF的时候,这个冗余取源MAC的动作导致指定
源MAC失败。换句话说,没有这句,不影响正常的协议栈工作,有这句对协议栈来说
是冗余的。遗憾的是这个冗余碰巧破坏了BPF。这个问题在很早以前的FreeBSD的
Maillist上就有人讨论过了,不是出于安全考虑才多出来的冗余,完全是历史原因。

libnet的作者们建议使用可加载内核模块去修改这个ether_output函数,而不是重新
编译内核。遗憾的是libnet的安装包里提供的是LKM,而不是KLD。FreeBSD 3.1下提
供过可加载内核模块技术(LKM),FreeBSD 4.x开始提供动态内核链接机制(KLD),可
以简单地理解成LKM的升级,参看<<FreeBSD 4.x 动态内核链接机制(KLD)编程指南>>
加强理解。为了动态加载FKLD,确保kern.securelevel小于等于0

# sysctl kern.securelevel
kern.securelevel: -1  <-- 这是缺省值
#

Ok,我们现在要做的就是写这个FKLD,哇,是不是头很大,废话,我头也很大,最后
一次摸FKLD是2000-11-18 23:36,写一个rootkit包,现在要捡回来是比较困难,郁
闷。参看/usr/share/examples/kld/cdev/目录下的例子。

----------------------------------------------------------------------
sys/sys/queue.h

/*
* Tail queue definitions.
*/
#define TAILQ_HEAD(name, type)                                \
struct name                                                   \
{                                                             \
    struct type  *tqh_first;  /* first element             */ \
    struct type **tqh_last;   /* addr of last next element */ \
}

#define TAILQ_ENTRY(type)                                           \
struct                                                              \
{                                                                   \
    struct type  *tqe_next;  /* next element                     */ \
    struct type **tqe_prev;  /* address of previous next element */ \
}

sys/net/if_var.h

TAILQ_HEAD(ifnethead, ifnet);

sys/net/if.c

struct ifnethead ifnet;

sys/net/if_var.h

/*
* Structure defining a network interface.  
*/
struct ifnet
{
    ... ...
    char               *if_name;      /* name, e.g. "en" or "lo"          */
    TAILQ_ENTRY(ifnet)  if_link;      /* all struct ifnets are chained    */
    struct ifaddrhead   if_addrhead;  /* linked list of addresses per if  */
    int                 if_pcount;    /* number of promiscuous listeners  */
    struct bpf_if      *if_bpf;       /* packet filter structure          */
    u_short             if_index;     /* numeric abbreviation for this if */
    short               if_unit;      /* sub-unit for lower level driver  */
    short               if_timer;     /* time 'til if_watchdog called     */
    short               if_flags;     /* up/down, broadcast, etc.         */
    ... ...
    /*
     * procedure handles
     */
    int               (*if_output)    /* output routine (enqueue)         */
        __P((struct ifnet *, struct mbuf *, struct sockaddr *, struct rtentry *));
    ... ...
    int               (*if_ioctl)     /* ioctl routine                    */
        __P((struct ifnet *, u_long, caddr_t));
    ... ...
};
----------------------------------------------------------------------

分析就不多做了,代码如下

----------------------------------------------------------------------
/*
* 从/usr/src/sys/net/if_ethersubr.c中挖这个函数出来,再修改之
*/
static int fake_ether_output ( register struct ifnet *ifp, struct mbuf *m,
                               struct sockaddr *dst, struct rtentry *rt0 )
{

#define IFP2AC(IFP) ((struct arpcom *)IFP)
#define senderr(e)  do { error = (e); goto bad;} while (0)

    short                         type;
    int                           error    = 0;
    int                           hdrcmplt = 0;
    u_char                        esrc[6], edst[6];
    register struct rtentry      *rt;
    register struct ether_header *eh;
    int                           off, loop_copy = 0;
    /*
     * link layer header lenght
     */
    int                           hlen;
    struct arpcom                *ac       = IFP2AC(ifp);

    if ( ( ifp->if_flags & ( IFF_UP | IFF_RUNNING ) ) != ( IFF_UP | IFF_RUNNING ) )
    {
        senderr( ENETDOWN );
    }
    rt = rt0;
    if ( rt )
    {
        if ( ( rt->rt_flags & RTF_UP ) == 0 )
        {
            rt0 = rt = rtalloc1( dst, 1, 0UL );
            if ( rt0 )
            {
                rt->rt_refcnt--;
            }
            else
            {
                senderr( EHOSTUNREACH );
            }
        }
        if ( rt->rt_flags & RTF_GATEWAY )
        {
            if ( rt->rt_gwroute == 0 )
            {
                goto lookup;
            }
            if ( ( ( rt = rt->rt_gwroute )->rt_flags & RTF_UP ) == 0 )
            {
                rtfree( rt );
                rt             = rt0;

lookup:

                rt->rt_gwroute = rtalloc1( rt->rt_gateway, 1, 0UL );
                if ( ( rt = rt->rt_gwroute ) == 0 )
                {
                    senderr( EHOSTUNREACH );
                }
            }
        }
        if ( rt->rt_flags & RTF_REJECT )
        {
            if ( rt->rt_rmx.rmx_expire == 0 || time_second < rt->rt_rmx.rmx_expire )
            {
                senderr( rt == rt0 ? EHOSTDOWN : EHOSTUNREACH );
            }
        }
    }
    hlen = ETHER_HDR_LEN;
    switch ( dst->sa_family )
    {
    case AF_INET:
        if ( !arpresolve( ac, rt, m, dst, edst, rt0 ) )
        {
            /*
             * if not yet resolved
             */
            return( 0 );
        }
        off  = m->m_pkthdr.len - m->m_len;
        type = htons( ETHERTYPE_IP );
        break;
    case pseudo_AF_HDRCMPLT:
        hdrcmplt = 1;
        eh       = ( struct ether_header * )dst->sa_data;
        ( void )memcpy( esrc, eh->ether_shost, sizeof( esrc ) );
        /*
         * case语句的下落式处理
         */
    case AF_UNSPEC:
        /*
         * if this is for us, don't do it
         */
        loop_copy = -1;
        eh        = ( struct ether_header * )dst->sa_data;
        ( void )memcpy( edst, eh->ether_dhost, sizeof( edst ) );
        /*
         * FreeBSD Kernel Hacking: 统一允许指定源MAC
         * add by scz@nsfocus.com 2001-12-21 15:57
         */
        ( void )memcpy( esrc, eh->ether_shost, sizeof( esrc ) );
        type      = eh->ether_type;
        break;
    default:
        printf( "%s%d: can't handle af%d\n", ifp->if_name, ifp->if_unit,
                dst->sa_family);
        senderr( EAFNOSUPPORT );
    }
    /*
     * Add local net header. If no space in first mbuf, allocate another.
     */
    M_PREPEND( m, sizeof( struct ether_header ), M_DONTWAIT );
    if ( m == 0 )
    {
        senderr( ENOBUFS );
    }
    eh = mtod( m, struct ether_header * );
    ( void )memcpy( &eh->ether_type, &type, sizeof( eh->ether_type ) );
    ( void )memcpy( eh->ether_dhost, edst, sizeof( edst ) );
    if ( hdrcmplt )
    {
        ( void )memcpy( eh->ether_shost, esrc, sizeof( eh->ether_shost ) );
    }
    else
    {
        /*
         * FreeBSD Kernel Hacking: 统一允许指定源MAC
         * add by scz@nsfocus.com 2001-12-21 15:57
         */
        ( void )memcpy( eh->ether_shost, esrc, sizeof( eh->ether_shost ) );
        /*
         * ( void )memcpy( eh->ether_shost, ac->ac_enaddr, sizeof( eh->ether_shost ) );
         */
    }
    /*
     * If a simplex interface, and the packet is being sent to our
     * Ethernet address or a broadcast address, loopback a copy.
     * XXX To make a simplex device behave exactly like a duplex
     * device, we should copy in the case of sending to our own
     * ethernet address (thus letting the original actually appear
     * on the wire). However, we don't do that here for security
     * reasons and compatibility with the original behavior.
     */
    if ( ( ifp->if_flags & IFF_SIMPLEX ) && ( loop_copy != -1 ) )
    {
        if ( ( m->m_flags & M_BCAST ) || ( loop_copy > 0 ) )
        {
            struct mbuf *n = m_copy( m, 0, ( int )M_COPYALL );

            ( void )if_simloop( ifp, n, dst->sa_family, hlen );
        }
        else if ( bcmp( eh->ether_dhost, eh->ether_shost, ETHER_ADDR_LEN ) == 0 )
        {
            ( void )if_simloop( ifp, m, dst->sa_family, hlen );
            return( 0 );
        }
    }
    /*
     * Handle ng_ether(4) processing, if any
     */
    if ( ng_ether_output_p != NULL )
    {
        if ( ( error = ( *ng_ether_output_p )( ifp, &m ) ) != 0 )
        {

bad:
            if ( m != NULL )
            {
                m_freem( m );
            }
            return( error );
        }
        if ( m == NULL )
        {
            return( 0 );
        }
    }
    /*
     * Continue with link-layer output
     */
    return( ether_output_frame( ifp, m ) );
}  /* end of fake_ether_output */
----------------------------------------------------------------------

这样在FreeBSD系统上做链路层编程时,与Linux一样可以任意伪造源MAC了,不用重
新编译FreeBSD内核,刚才测试通过。看了看以前的一些讨论,一些人认为这个所谓
的源MAC限制是FreeBSD开发过程中因历史原因而遗留下来的不恰当的代码,并非安全
考虑。

现在回头来看我们今天的主要动机,从libnet中挖掘BPF的发包机制。在文件
freebsd_arpsend.c中,唯一需要关注的是libnet_write_link_layer()函数

----------------------------------------------------------------------
int libnet_write_link_layer ( struct libnet_link_int *l, const char *device,
                              u_char *buf, int len )
{
    int c;

    c = write( l->fd, buf, len );
    return( c );
}

/*
*  Low level packet interface struct
*/
struct libnet_link_int
{
    int     fd;          /* link layer file descriptor                   */
    int     linktype;    /* link type                                    */
    int     linkoffset;  /* link header size (offset till network layer) */
    u_char *device;      /* device name                                  */
};

struct libnet_link_int * libnet_open_link_interface ( char *device, char *ebuf )
{
    struct ifreq            ifr;
    struct libnet_link_int *l;

#if defined(BIOCGHDRCMPLT) && defined(BIOCSHDRCMPLT)
    u_int                   spoof_eth_src = 1;
#endif

    l->fd = libnet_bpf_open( ebuf );
    ... ...
    /*
     * Attach network interface to bpf device.
     */
    strncpy( ifr.ifr_name, device, sizeof( ifr.ifr_name ) );
    if (ioctl(l->fd, BIOCSETIF, (caddr_t)&ifr) == -1)
    {
        ... ...
        goto bad;
    }
    ... ...
    /*
     * NetBSD and FreeBSD BPF have an ioctl for enabling/disabling
     * automatic filling of the link level source address.
     */
#if defined(BIOCGHDRCMPLT) && defined(BIOCSHDRCMPLT)
    if (ioctl(l->fd, BIOCSHDRCMPLT, &spoof_eth_src) == -1)
    {
        ... ...
        goto bad;
    }
#endif
    ... ...
}  /* end of libnet_open_link_interface */
----------------------------------------------------------------------

libnet_bpf_open()所做的就是打开/dev/bpf0这样的设备文件。faint,不会是写打
开/dev/bpf0,然后就可以write()这个句柄了?

测试程序表明,BPF发包机制远比收包机制简单,总结一下

1) open()打开/dev/bpf?设备
2) ioctl( fd , BIOCSETIF, fxp0 )在网络接口与bpf?设备之间建立关联
3) write( fd, ... )发送链路层报文

<待续>
版权所有,未经许可,不得转载