libnet使用举例(14)----从libnet中挖掘BPF的发包机制

来源:互联网 发布:java工程师是什么 编辑:程序博客网 时间:2024/04/28 14:06
libnet使用举例(14)----从libnet中挖掘BPF的发包机制

作者:NSFocus Security Team
整理:小四
主页: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的升级,参看<>
加强理解。为了动态加载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, ... )发送链路层报文
原创粉丝点击