锐捷 - 认证流程抓包分析

寻找认证服务器

通过抓包发现3个与众不同的包,下面紧接着就是锐捷认证服务器的回复包,协议开始变成EAP协议。

接下来,我们对包的内容进行一波分析,

c8:7f:54:c9:b2:df 不是我的mac地址吗?前面也是6字节,说明前面大概率也是一个mac地址,而且是固定的(多次抓包都一致)。

EPAOL协议

EAPOL (EAP On Lan) 是 EAP 在以太网上面的实现, 由802.1x定义

先来看以太网的帧格式(Ethernet II帧格式)

   6byte     6byte     2byte 46~1500byte  4byte
+---------+----------+------+------------+-----+
| src MAC | dest MAC | type |    Data    | FCS |
+ --------+----------+------+------------+-----+
  • type : Ethernet 帧为802.1x (EAPOL) 分配的type为 0x888e, linux中定义为 ETH_P_PAE

  • data : 存放EAPOL帧

EAPOL帧格式为

  1byte     1byte   2byte
+---------+------+--------+------+
| Version | type | Length | Body |
+---------+------+--------+------+
  • Version : 802.1x版本

    1. 1代表 802.1x-2001

    2. 2代表 802.1x-2004

    3. 3代表 802.1x-2010

  • type : EAPOL除了可以承载EAP消息外, 还可一承载其它的EAPOL消息,该字段用于标识消息的类型

    /// 承载的是EAP消息
    const TYPE_EAP_PACKET: u8 = 0x0;
    /// 发起认证
    const TYPE_EAPOL_START: u8 = 0x1;
    /// 下线
    const TYPE_EAPOL_LOGOFF: u8 = 0x2;
    /// 交换密钥
    const TYPE_EAPOL_KEY: u8 = 0x3;
    /// EAPOL-ASF-ALERT
    const TYPE_EAPOL_ASF_ALERT: u8 = 0x4;
  • Length : Body (数据)的长度, 如果为0, 则代表没有携带数据

  • Body : EAPOL帧的数据部分

当EAPOL帧的类型为 EAPOL-Key时, 其Body的格式为

	1byte
+-----------------+-----------------+
| Descriptor Type | Descriptor Body |
+-----------------+-----------------+

当Descriptor Type为2时, 表示Descriptor Body为EAPOL RSN key, 内容由802.11协议来定义, 见 802.11 spec 11.6.2节

EAPOL帧在二层传送时,必须要有目标MAC地址,当客户端和认证系统彼此之间不知道发送的目标时,其目标MAC地址使用由802.1x协议分配的组播地址01-80-c2-00-00-03 ,二次认证则为01-d0-f9-00-00-03 (二次认证即已经在线了,又发起一次认证)

认证服务器要求验证用户名(非在线状态/认证前不发下线包)

下图是锐捷认证服务器在EAPOL后的一个Request包(针对EAPOL的返回包,但因为他的Code是Request,所以说叫一个请求)

其中我们发现88 8e后面是01 00了,第一个01对应的是802.1x版本号,第二个说明这个是一个EAP-PACKET了,接下来的00 05就是包长度了,其实和上面的EAPOL帧格式没什么区别。

EAP基础包格式

EAP 的基础包格式如下表所示:

Code

Identifier

Length

Data

1 byte

1 byte

2 bytes

variable length

其中 Code 的分配如下表所示:

Code

Assignment

1

Request

2

Response

3

Success

4

Failure

5

Initiate

6

Finish

基础包格式稍微进化一下,那么EAP 的 Request 和 Response 包格式如下表所示:

Code

Identifier

Length

Type

Type-Data

1 byte

1 byte

2 bytes

1 byte

variable length

按照格式分析包

00 05是接下来数据包的长度,我们裁剪出数据包,然后使用EAP基础包格式去尝试理解一下这段包吧!

包的内容是:01 01 00 05 01

  • 第一个01Code代表的是Request的意思。

  • 第二个01Identifier,一般固定。

  • 00 05Type Data的长度

  • 01Type,根据Type表,我们可以知道这个是请求身份验证的意思(

  • 后面的Type Data就一堆00,不用看了......

客户端发送身份认证请求

锐捷扩展数据包通用格式

是一种不携带长度可能会被加密的一种奇怪自定义格式(可能是我见识短浅)接下来简称这种格式为RTV

格式一 不携带长度

Repeated Trait

ID(有时候没有)

Data

4 bytes

1 byte

1byte

0x00 0x00 0x13 0x11

0x01

0x06

格式二 带一个长度

Repeated Trait

ID(有时候没有)

Length

Data

4 bytes

1 byte

1 byte

1byte

0x00 0x00 0x13 0x11

0x01

0x02

0x06

RTV格式加解密源代码

fn reverse_bits(byte: u8) -> u8 {
    let mut result = 0;
    let mut b = byte;
    for _ in 0..8 {
        result = (result << 1) | (b & 1);
        b >>= 1;
    }
    result
}

pub fn decrypt_vendor(encrypted_bytes: &[u8]) -> Vec<u8> {
    encrypted_bytes
        .iter()
        .map(|&b| !b)
        .map(reverse_bits)
        .collect()
}

pub fn encrypt_vendor(decrypted_bytes: &[u8]) -> Vec<u8> {
    decrypted_bytes
        .iter()
        .map(|&b| reverse_bits(b))
        .map(|b| !b)
        .collect()
}

分析数据包(标准EAP认证数据)

为了偷懒,下面是发送用户名认证数据包的相关代码,其中不包括锐捷RTV格式的分析(后面再说)。

let eap_len = (identity.len() + 5) as u16; // 锐捷
let mut packet = BytesMut::with_capacity(16 + 2 + 2 + 2 + 1 + identity.len());
packet.extend_from_slice(&build_eap(dest_mac, local_mac, ETH_P_PAE, VERSION_8021X_2001, TYPE_EAP_PACKET));
packet.put_u16(eap_len);
packet.put_u8(CODE_RESPONSE); // eap code
packet.put_u8(1); // eap id
packet.put_u16(eap_len); // eap len
packet.put_u8(EAP_TYPE_IDENTITY); // eap type
packet.put_slice(identity.as_bytes()); // eap identity
  • 变化不大,就是包类型从EAP_START变成了EAP_PACKET

  • identity是你的用户名(例如12345678

分析数据包(锐捷扩展字段数据)

ff ff 37 77 # 原文是 00 00 13 11,按照RTV加解密可以解密出来下面的数据
7f # 01
af 2c 67 a9 # DHCP分配给客户端的IP(10.203.25.106)
00 00 ff ff # 掩码(255.255.0.0)
af 2c 00 80 # 网关(10.203.255.254)
b1 b1 b1 b1 # DNS(114.114.114.114)
82 a5 # check sum 将位加密的上文数据0x15长度的数据(不携带sum),塞入check_sum计算得到

00 00 13 11 # 8021x.exe的版本信息
38 30 32 31 78 2e 65 78 65 # 8021x.exe
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 # 为8021x.exe字符串保留的垃圾数据
06 29 03 00 # version?
00 # unknown flag

00 00 13 11 
02 # Cmd
04 # Length
1a 28 # unknown flag(固定

00 00 13 11 # 随机字符串X1
17 # Cmd
22 # 长度
33 39 36 39 36 44 36 31 32 36 30 33 33 38 45 39 35 41 38 31 30 33 38 38 34 39 38 36 33 39 66 34 # 可固定(随机字符串)
1a 0c # unknown flags (固定)(IDA goto 0x18ebf0)

00 00 13 11 
18 
06 # len
00 00 00 00 # 
1a 0e # unknown flags 固定

00 00 13 11 
2d 
08 # size
c8 7f 54 c9 b2 df # 本机网卡mac地址
1a 18 # 固定(这里的18逆向的时候是0x08)

00 00 13 11 
2f 
12 # 这里的12在逆向的时候是0x02?可空?
76 04 70 ab 
f8 83 42 4e 
19 02 a7 01 # md5计算获得
b9 71 8b 24 # md5(data + md5_salt) data哪来的?我不知道
1a 09 # 固定
# md5_slat = "!jierui9002pmsus"

00 00 13 11 
35 # ipv4 count? by https://github.com/updateing/minieap/blob/983fd4a5851d4a344fab92666c5cdb83f26b2294/packet_plugin/rjv3/packet_plugin_rjv3_priv.h#L11
03
01 # unknown
1a 18 # 固定

00 00 13 11  
36 12 # 固定
00 00 00 00 00 00 00 00 # ipv6? 网关?
00 00 00 00 00 00 00 00 
1a 18 

00 00 13 11 
38 12 
fe 80 00 00 00 00 00 00 fc f5 9b c0 cf 51 aa 74  # ipv6地址
1a 18 

00 00 13 11 
4e 12 # global ipv6?
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 # 固定
1a 88 

00 00 13 11 
4d # unk V3Hash?
82 
66643161396161313765326o53635353135636536616138323939653463396562353364313864616630343065623036323664613464313832323430353365643432386634366664333430636333663762353164316331626566356634613535343663363337363630373265346337303963636531646562653365393266393032
1a 28

00 00 13 11 
39 22 # 网络服务名称?为什么别人家是纯字母?
bb a5 c1 aa cd f8 
0000000000000000000000000000000000000000000000000000
1a 48

00 00 13 11 
54 42 # 硬盘序列号
53 74 61 74 69 63 3a 41 42 34 35 41 38 36 32 # Static:AB45A862
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 
1a 09

00 00 13 11 
62 # CMD
03 # Len
00 # 锐捷认证系统版本号
1a 09 

00 00 13 11
6b 
03 
00 # dsmscl.exe是否存在
1a 09

00 00 13 11
70 03 # OS BITS
40 # 是64还是32位系统
1a 09

00 00 13 11 
6f 03 # Client Version
00 # 写死
1a 09

00 00 13 11
79 03
02 # 写死
1a 34

00 00 13 11
76 2e # DNS
3231312e3134322e3231312e3132343b35382e32302e3132372e3137303b3232322e3234362e3132392e3830

Check Sum算法

pub fn check_sum(data: &[u8]) -> [u8; 2] {
    const TABLE: [u8; 512] = [
        0x00,0x00,0x21,0x10,0x42,0x20,0x63,0x30,0x84,0x40,0xA5,0x50,0xC6,0x60,0xE7,0x70,
        0x08,0x81,0x29,0x91,0x4A,0xA1,0x6B,0xB1,0x8C,0xC1,0xAD,0xD1,0xCE,0xE1,0xEF,0xF1,
        0x31,0x12,0x10,0x02,0x73,0x32,0x52,0x22,0xB5,0x52,0x94,0x42,0xF7,0x72,0xD6,0x62,
        0x39,0x93,0x18,0x83,0x7B,0xB3,0x5A,0xA3,0xBD,0xD3,0x9C,0xC3,0xFF,0xF3,0xDE,0xE3,
        0x62,0x24,0x43,0x34,0x20,0x04,0x01,0x14,0xE6,0x64,0xC7,0x74,0xA4,0x44,0x85,0x54,
        0x6A,0xA5,0x4B,0xB5,0x28,0x85,0x09,0x95,0xEE,0xE5,0xCF,0xF5,0xAC,0xC5,0x8D,0xD5,
        0x53,0x36,0x72,0x26,0x11,0x16,0x30,0x06,0xD7,0x76,0xF6,0x66,0x95,0x56,0xB4,0x46,
        0x5B,0xB7,0x7A,0xA7,0x19,0x97,0x38,0x87,0xDF,0xF7,0xFE,0xE7,0x9D,0xD7,0xBC,0xC7,
        0xC4,0x48,0xE5,0x58,0x86,0x68,0xA7,0x78,0x40,0x08,0x61,0x18,0x02,0x28,0x23,0x38,
        0xCC,0xC9,0xED,0xD9,0x8E,0xE9,0xAF,0xF9,0x48,0x89,0x69,0x99,0x0A,0xA9,0x2B,0xB9,
        0xF5,0x5A,0xD4,0x4A,0xB7,0x7A,0x96,0x6A,0x71,0x1A,0x50,0x0A,0x33,0x3A,0x12,0x2A,
        0xFD,0xDB,0xDC,0xCB,0xBF,0xFB,0x9E,0xEB,0x79,0x9B,0x58,0x8B,0x3B,0xBB,0x1A,0xAB,
        0xA6,0x6C,0x87,0x7C,0xE4,0x4C,0xC5,0x5C,0x22,0x2C,0x03,0x3C,0x60,0x0C,0x41,0x1C,
        0xAE,0xED,0x8F,0xFD,0xEC,0xCD,0xCD,0xDD,0x2A,0xAD,0x0B,0xBD,0x68,0x8D,0x49,0x9D,
        0x97,0x7E,0xB6,0x6E,0xD5,0x5E,0xF4,0x4E,0x13,0x3E,0x32,0x2E,0x51,0x1E,0x70,0x0E,
        0x9F,0xFF,0xBE,0xEF,0xDD,0xDF,0xFC,0xCF,0x1B,0xBF,0x3A,0xAF,0x59,0x9F,0x78,0x8F,
        0x88,0x91,0xA9,0x81,0xCA,0xB1,0xEB,0xA1,0x0C,0xD1,0x2D,0xC1,0x4E,0xF1,0x6F,0xE1,
        0x80,0x10,0xA1,0x00,0xC2,0x30,0xE3,0x20,0x04,0x50,0x25,0x40,0x46,0x70,0x67,0x60,
        0xB9,0x83,0x98,0x93,0xFB,0xA3,0xDA,0xB3,0x3D,0xC3,0x1C,0xD3,0x7F,0xE3,0x5E,0xF3,
        0xB1,0x02,0x90,0x12,0xF3,0x22,0xD2,0x32,0x35,0x42,0x14,0x52,0x77,0x62,0x56,0x72,
        0xEA,0xB5,0xCB,0xA5,0xA8,0x95,0x89,0x85,0x6E,0xF5,0x4F,0xE5,0x2C,0xD5,0x0D,0xC5,
        0xE2,0x34,0xC3,0x24,0xA0,0x14,0x81,0x04,0x66,0x74,0x47,0x64,0x24,0x54,0x05,0x44,
        0xDB,0xA7,0xFA,0xB7,0x99,0x87,0xB8,0x97,0x5F,0xE7,0x7E,0xF7,0x1D,0xC7,0x3C,0xD7,
        0xD3,0x26,0xF2,0x36,0x91,0x06,0xB0,0x16,0x57,0x66,0x76,0x76,0x15,0x46,0x34,0x56,
        0x4C,0xD9,0x6D,0xC9,0x0E,0xF9,0x2F,0xE9,0xC8,0x99,0xE9,0x89,0x8A,0xB9,0xAB,0xA9,
        0x44,0x58,0x65,0x48,0x06,0x78,0x27,0x68,0xC0,0x18,0xE1,0x08,0x82,0x38,0xA3,0x28,
        0x7D,0xCB,0x5C,0xDB,0x3F,0xEB,0x1E,0xFB,0xF9,0x8B,0xD8,0x9B,0xBB,0xAB,0x9A,0xBB,
        0x75,0x4A,0x54,0x5A,0x37,0x6A,0x16,0x7A,0xF1,0x0A,0xD0,0x1A,0xB3,0x2A,0x92,0x3A,
        0x2E,0xFD,0x0F,0xED,0x6C,0xDD,0x4D,0xCD,0xAA,0xBD,0x8B,0xAD,0xE8,0x9D,0xC9,0x8D,
        0x26,0x7C,0x07,0x6C,0x64,0x5C,0x45,0x4C,0xA2,0x3C,0x83,0x2C,0xE0,0x1C,0xC1,0x0C,
        0x1F,0xEF,0x3E,0xFF,0x5D,0xCF,0x7C,0xDF,0x9B,0xAF,0xBA,0xBF,0xD9,0x8F,0xF8,0x9F,
        0x17,0x6E,0x36,0x7E,0x55,0x4E,0x74,0x5E,0x93,0x2E,0xB2,0x3E,0xD1,0x0E,0xF0,0x1E
    ];
    let mut sum = [0u8; 2];
    let mut index = 0;
    for i in 0..data.len() {
        index = sum[0] ^ data[i];
        sum[0] = sum[1] ^ TABLE[index as usize * 2 + 1];
        sum[1] = TABLE[index as usize * 2];
    }
    sum
}

随机字符串X1分析

v19的类型是QWORD,所以说这里是写了32字节的随机字符串(本质上就是把rand出来16字节,然后toHexString

后续内容

https://github.com/fuqiuluo/mentorust