我电脑上相关软件包的版本分别是:
pylibnet:3.0-beta
libnet:1.1.2.1
通过多次对ppp认证的抓包分析后,暂时得出了以下的结论:
要想成功欺骗到服务器,在伪造包的时候需要几个关键域,第一个就是ip头部中的Identification,在链中控制协议进行配置的过程中客户端每发一个包,此域就会加1。第二个就是客户端和服务器端的ip地址。
第三个是gre中标识一个客户端会话的callid值,这个值在整个会话过程中会一直保持,这样服务器在同时有多个客户端的时候就可以区分出来。这里有一点问题,通过抓包可以发现服务器的callid值经常会是0,但是有时候又不是。而目前在攻击程序中恰好是通过看抓到的gre包中callid是不是0来区分客户端和服务器端的,显然这里是需要改进的。
第四个是gre中的Sequence number,在整个连接过程中,客户端每发送一个包这个值就会增加1。
以上就是需要抓包提取出的信息。
接下来就是构造一个LCP NAK的包发送给服务器了。
针对我们要把认证协议从MS-CHAPV2回溯到MS-CHAP,构造出来的ppp包应该如下图所示:
根据多次抓包发现这些值似乎都是固定的,所以构造的时候直接给定值就行了。
我们采用pylibnet中 的函数来构造包(它也是通过libnet来构造包的),pylibnet只提供了构造gre包的函数,并没有提供ppp和lcp包的构造函数。
这里很有必要记录一下在构造gre包的时候遇到的问题。
实际上微软用的gre协议并不是原生的,而是经过它自己改动过的,改动后的协议格式如图所示:
因此不应该调用buile_gre这个函数,经过查阅后发现应该调用的是build_egre这个函数,也即是构造加强版的gre。
通过查看pylibnet的文档可以看到build_egre函数调用方法:
有趣的事情就在这里。通过查看pylibnet的源码可以看到这个函数的实现,里面有一句很关键的代码:
ptag = libnet_build_egre (fv, type, sum, offset, key, seq, len, payload, payload_s, self->l, ptag);
可以看出它是通过调用libnet里面的函数来进行真正的构造,pylibnet只不过做了一些类似包装的工作。
沿着这条踪迹找到libnet_build_egre()函数,以下是它的定义:
libnet_ptag_t
libnet_build_egre(u_int16_t fv, u_int16_t type, u_int16_t sum,
u_int16_t offset, u_int32_t key, u_int32_t seq, u_int16_t len,
u_int8_t *payload, u_int32_t payload_s, libnet_t *l, libnet_ptag_t ptag)
{
return (libnet_build_gre(fv, type, sum, offset, key, seq, len,
payload, payload_s, l, ptag));
}
这个函数似乎并没有做任何的工作,只是把收到的参数又给了 libnet_build_gre,继续看一下 libnet_build_gre的定义。里面有很多类似这样的判断:
注释v0一栏的意思是如果flag中gre的版本是0且sum位被置为1或者routing位被置为1则执行。
注释v1一栏的意思是如果flag中gre的版本是1则执行。
在上面这段代码之前已经构造完成了fv和type这两个域。根据微软给出的GRE包的格式可以知道实际上这里是把sum写进了payload length这个地方,以此类推,offset被当成了call id看待,key被当
成seqnum,seqnum当做ack看待。但pylibnet的文档却没有说明这些问题,带来了很多麻烦。个人认为不仅pylibnet文档有错,libnet也有过错。可以找到libnet对gre的格式是这样定义的:
struct libnet_gre_hdr
{
u_int16_t flags_ver;
#define GRE_CSUM 0x8000
#define GRE_ROUTING 0x4000
#define GRE_KEY 0x2000
#define GRE_SEQ 0x1000
#define GRE_STRICT 0x0800
#define GRE_REC 0x0700
#define GRE_ACK 0x0080
#define GRE_FLAGS_MASK 0x00F8
#define GRE_VERSION_MASK 0x0007
#define GRE_VERSION_0 0x0000
#define GRE_VERSION_1 0x0001
u_int16_t type;
#define GRE_SNA 0x0004
#define GRE_OSI_NETWORK_LAYER 0x00FE
#define GRE_PUP 0x0200
#define GRE_XNS 0x0600
#define GRE_IP 0x0800
#define GRE_CHAOS 0x0804
#define GRE_RFC_826_ARP 0x0806
#define GRE_FRAME_RELAY_ARP 0x0808
#define GRE_VINES 0x0BAD
#define GRE_VINES_ECHO 0x0BAE
#define GRE_VINES_LOOPBACK 0x0BAF
#define GRE_DECNET 0x6003
#define GRE_TRANSPARENT_ETHERNET_BRIDGING 0x6558
#define GRE_RAW_FRAME_RELAY 0x6559
#define GRE_APOLLO_DOMAIN 0x8019
#define GRE_ETHERTALK 0x809B
#define GRE_NOVELL_IPX 0x8137
#define GRE_RFC_1144_TCP_IP_COMPRESSION 0x876B
#define GRE_IP_AUTONOMOUS_SYSTEMS 0x876C
#define GRE_SECURE_DATA 0x876D
#define GRE_PPP 0x880b /* taken from RFC 2637 */
union {
struct {
u_int16_t sum; /* optional */
u_int16_t offset; /* optional */
u_int32_t key; /* optional */
u_int32_t seq; /* optional */
} _gre;
struct {
u_int16_t payload_s; /* optional */
u_int16_t callID; /* optional */
u_int32_t seq; /* optional */
u_int32_t ack; /* optional */
} _egre;
}_data;
#define gre_sum _data._gre.sum
#define gre_offset _data._gre.offset
#define gre_key _data._gre.key
#define gre_seq _data._gre.seq
#define egre_payload_s _data._egre.payload_s
#define egre_callID _data._egre.callID
#define egre_seq _data._egre.seq
#define egre_ack _data._egre.ack
};
可看到针对egre实际上它定义了一个直观的数据结构,但在libnet_build_egre中并没有使用。libnet_build_gre中也只涉及到了对_gre(V0)进行赋值,没有用到_egre(V1)。目前看来调用这个函数就相当于调用build_gre这个函数,build_egre没有任何作用。应该说libnet在libnet_build_egre这个函数中应该做一个改进的。
再看攻击的结果,根据测试,只要伪造的包比真正客户端回应的包早到达服务器,攻击一般都是成功的。这应该也可以算做它的一个漏洞,对于服务器的反悔,客户端并不做检查。