中国医大CT-YOUNG 闪讯和深澜并存的验证机制研究

中国医大CT-YOUNG 闪讯和深澜并存的验证机制研究

更新

2020-03-14:

已经很久未曾了解过学校的网络,本贴中的各种方法可能都已失效。

2017-11-01:

iMakar闪讯认证软件,可以给路由器或者其他设备认证,辽宁省内应该都可以用,其他的地方就不太清楚了

https://github.com/still-night/iMakar-CTYoung-Client/

背景:

2016年的暑假,中国医科大学在全校范围内开启了CMU和CT-Young两个wifi,并且深澜由之前深澜客户端PPPoE拨号的验证方式更改为网页验证,CMU使用深澜,而ct-young使用电信的闪讯客户端来登录.由于之前将深澜移植到路由器上来运行,所以这种改变引起了我很大的好奇,我开始研究如何将新版的验证方式也移植到路由器上来运行.2017年的暑假,学校将学生寝室网口原有的深澜验证改为闪讯,变得更加复杂,导致路由器不能正常在校园网内使用,因此我开始了新的验证方式的研究.
##使用过的工具:

  • CT-Young 闪讯手机客户端
  • SingleNet闪讯电脑客户端
  • Burp Suite Professional 抓包和模拟发包
  • Packet Capture 安卓抓包
  • apktool 安卓app解包
  • dex2jar 安卓app反编译
  • jd-gui 从jar包提取java源码
  • IntelliJ IDEA java编程IDE
  • jdk java编程
  • PyCharm python编程IDE
  • python2.7
  • openwrt路由器
  • xshell 用于连接openwrt路由器

深澜网页验证:

关于深澜的网页验证机制,我是16年的时候破解出来的,现在来总结一下.
深澜是在校内设置一个授权服务器,所有通过无线或者有线连接的设备都会分配到一个10.0.0.0/8的IP地址,但是现在不能连到互联网或者校内的其他服务,只可以连到深澜的登陆服务器 192.168.100.10,通过浏览器访问这个IP会进入登陆的网页(电脑版: http://192.168.100.10:802/srun_portal_pc.php?ac_id=1&),如下图
这里写图片描述

然后填好用户名和密码之后会发送验证信息到到深澜验证服务器

POST /include/auth_action.php HTTP/1.1
Host: 192.168.100.10:802
Connection: keep-alive
Content-Length: 103
Pragma: no-cache
Cache-Control: no-cache
Accept: */*
Origin: http://192.168.100.10:802
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: http://192.168.100.10:802/srun_portal_pc.php?ac_id=1&
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,ja;q=0.6,en;q=0.4
Cookie: login=bQ0pOyR6IXU7PJaQQqRAcBPxGAvxAcrh7N4TPxBk5meltyAaeznoenPbt1%252BLEhhiTV27dJdsVTGeUTu53c6jXcyTU1P106ogFOfl1CbKnr7LK2UTusy8u0IM7bOyZJKAFJT268QAU0%252BjeSyfSJ1o9SFiiR3WX08GCwtGATyne3DhsQag7OroWHMp892coA%253D%253D

action=login&username=20123456&password={B}MTYw&ac_id=1&user_ip=&nas_ip=&user_mac=&save_me=1&ajax=1

其中重要的部分如下

POST /include/auth_action.php HTTP/1.1
Host: 192.168.100.10:802

action=login&username=20123456&password={B}MTYw&ac_id=1&user_ip=&nas_ip=&user_mac=&save_me=1&ajax=1

可以看出是post到服务器来进行验证,第一行第二行是网页和主机地址端口,第三行中分为很多字段,重要的为:

  • action : 操作类型
  • username : 用户名
  • password : 加密后的密码

userip字段在我们学校是无效的,服务器会根据发送请求的源IP来授权上网.
密码的加密方式很简单,通过分析 网页中引用的 srun_portal.js
在第208行附近得到

var   e=encodeURIComponent(base64encode(utf16to8(frm.password.value)));
var d = "action=login&username="+encodeURIComponent(frm.username.value)+
"&password={B}"+e+
"&ac_id="+$("input[name='ac_id']").val()+
"&user_ip="+$("input[name='user_ip']").val()+
"&nas_ip="+$("input[name='nas_ip']").val()+
"&user_mac="+$("input[name='user_mac']").val()+
"&save_me="+save_me+
"&ajax=1";
//可以得到password是如下的表达式
password="{B}"+encodeURIComponent(base64encode(utf16to8(frm.password.value)));

而且通过对手机端的网页(http://192.168.100.10:802/srun_portal_phone.php?ac_id=1)进行分析有个惊喜的发现,发送不加密的密码同样可以被服务器识别

{
 action: 'login',
 username: $("input[name='username']").val(),
 password: frm.password.value,   //未加密的密码直接发送
 ac_id:$("input[name='ac_id']").val(),
 user_mac:$("input[name='user_mac']").val(),
 user_ip:$("input[name='user_ip']").val(),
 nas_ip:$("input[name='nas_ip']").val(),
 save_me: save_me,
 ajax: 1
 }
成功发送数据包之后如果没有返回错误说明已经可以上网了,

网页还会弹出一个弹窗,地址是(http://192.168.100.10/srun_portal_pc_succeed.php)
可以查看账号,时长,ip等信息和注销登陆,如果不小心关掉了这个弹窗,仍旧可以在浏览器输入网址打开它来注销.
注销登陆也是发送一个http数据包来完成,不再赘述.

CT-Young的登陆验证

经过分析,CT-Young采用闪讯客户端验证,有手机和电脑两种验证方式

手机客户端验证

这种验证方式较方便
通过对闪讯手机客户端的抓包,发现手机版的验证是发送http包来验证的.
登陆需要两个步骤:

  1. 获取uuid
  2. 发送账号密码

验证步骤

第一步:获取uuid

POST /showlogin.do HTTP/1.1
User-Agent: China Telecom Client
Host: 219.148.205.34:8090

wlanuserip=100.76.165.196              (要登陆的机器的IP地址)

发送以上的http数据包,可以获取到包含uuid的xml
第二步:发送账号密码

POST /servlets/G3loginServlet HTTP/1.1
User-Agent: China Telecom Client
Host: 219.148.205.34:8090

uuid=62E5BB8D7EC3781A5B69D22A4DD4B75B5767876740F38A52AE539FAE0F2D2550&userip=100.76.165.196&username=edu1894231318&password=795C45999981B7D1796597D43A4F

字段分析

  • uuid: 上一步服务器返回到的uuid可以用于登陆和注销
  • userip: 要验证的机器的ip地址
  • username: 用户名 未加密
  • password: 密码(加密后,加密方法稍后会说)

返回一个xml,其中有 ResponseCode ,200为成功登陆,其他为不成功.

第三步:注销登陆

POST /servlets/G3logoutServlet HTTP/1.1
User-Agent: China Telecom Client
Host: 219.148.205.34:8090

uuid=BC375767876740F38A52AE539FAE0F2D2550&userip=100.76.5.112

字段分析

  • uuid: 上一步服务器返回到的uuid可以用于登陆和注销
  • userip: 要验证的机器的ip地址

同样有一个response code

密码加密方式研究

多次抓包发现手机客户端的密码不随时间动态加密,而是静态加密的,这给破解减小了难度.把上面的数据包在openwrt路由器上发送出去,路由器可以绕过闪讯登陆ctyoung校园网.
使用dex2jar对安卓的闪讯客户端进行反编译,再用jd-gui将代码提取出来,然后对其分析
定位到com.realtech_inc.andproject.chinanet.activity.LogonActivity这个类,定位到方法initWidge下,在代码的第292行有如下语句

//LogonActivity.java
//paramAnonymousView应该是密码,然后又被赋值为加密后的密文
//密钥应该为"LntoLL03"的md5值
paramAnonymousView = AESEncrypt.encrypt(LogonActivity.this.md5("LntoLL03"), paramAnonymousView, true);

于是乎在在线加解密的网站 (http://tool.oschina.net/encrypt),把md5(“LntoLL03”) 得到的 51db252610b6420128de86cc054bc0c8 作为密钥来进行aes的加解密,发现并不能生成想要的结果.

于是又对AESEncrypt这个类进行分析

//AESEncrypt.java
//第29行
public static String encrypt(byte[] paramArrayOfByte, String paramString, boolean paramBoolean)
    throws Exception
  {
    paramArrayOfByte = encrypt(paramArrayOfByte, paramString.getBytes());
    if (paramBoolean) {
      return toHex(paramArrayOfByte);
    }
    return new String(Base64.encode(paramArrayOfByte, 0));
  }

 private static byte[] encrypt(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2)
    throws Exception
  {
    paramArrayOfByte1 = new SecretKeySpec(paramArrayOfByte1, "AES");
    Cipher localCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//  下面的这一条语句用于替换自动生成的再往下的一大串语句
    byte[] arrayOfByte = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} //16个0 aes的拓展密钥?
/*  下面注释掉的内容估计是内联函数,被手动替换为上面的那一条语句
    byte[] arrayOfByte = new byte[16];   
    byte[] tmp23_22 = arrayOfByte;
    tmp23_22[0] = 0;
    byte[] tmp28_23 = tmp23_22;
    tmp28_23[1] = 0;
    byte[] tmp33_28 = tmp28_23;
    tmp33_28[2] = 0;
    byte[] tmp38_33 = tmp33_28;
    tmp38_33[3] = 0;
    byte[] tmp43_38 = tmp38_33;
    tmp43_38[4] = 0;
    byte[] tmp48_43 = tmp43_38;
    tmp48_43[5] = 0;
    byte[] tmp53_48 = tmp48_43;
    tmp53_48[6] = 0;
    byte[] tmp59_53 = tmp53_48;
    tmp59_53[7] = 0;
    byte[] tmp65_59 = tmp59_53;
    tmp65_59[8] = 0;
    byte[] tmp71_65 = tmp65_59;
    tmp71_65[9] = 0;
    byte[] tmp77_71 = tmp71_65;
    tmp77_71[10] = 0;
    byte[] tmp83_77 = tmp77_71;
    tmp83_77[11] = 0;
    byte[] tmp89_83 = tmp83_77;
    tmp89_83[12] = 0;
    byte[] tmp95_89 = tmp89_83;
    tmp95_89[13] = 0;
    byte[] tmp101_95 = tmp95_89;
    tmp101_95[14] = 0;
    byte[] tmp107_101 = tmp101_95;
    tmp107_101[15] = 0;
    tmp107_101;
*/
    System.out.println("bytes to string: " + toHex(arrayOfByte));
    localCipher.init(1, paramArrayOfByte1, new IvParameterSpec(arrayOfByte));//生成密码器
    return localCipher.doFinal(paramArrayOfByte2); //返回最终的密码
  }

原来md5生成的值是16进制的,而我简单的把它当做文本处理的,所以导致的失败,自己写了一段java代码来模拟这个过程

        String paramString="LntoLL03";
        String content="123456";
        MessageDigest localMessageDigest = MessageDigest.getInstance("md5");
        localMessageDigest.update(paramString.getBytes("UTF-8"));
        byte paramArrayOfByteOld[]=localMessageDigest.digest();
        System.out.println(new String(paramArrayOfByteOld));
        
        String result = AESEncrypt.encrypt(paramArrayOfByteOld,content, true);

发现得到的result和抓包得到的加密后密码相同,到这就成功了一大半了

但是由于想最终在路由器openwrt上使用,而java过于庞大,而且在openwrt上没有较好的解决方案,所以准备用python,lua,或者shell脚本改写这个程序.
首先用了我比较熟悉的python

# -*- coding: utf-8 -*- 

#   @Time:  2017/8/28 20:58
#   @Author:[email protected]
#   @File:  AesTest.py
# 使用了pycrypto库
from Crypto.Cipher import AES
content="123456" #要加密的内容
key=b"\x51\xDB\x25\x26\x10\xB6\x42\x01\x28\xDE\x86\xCC\x05\x4B\xC0\xC8" #密钥
exkey=b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" #扩展密钥
obj=AES.new(key,AES.MODE_CBC,exkey)  #创建AES_CBC加密器
#由于AES加密中待加密的文本位数需要为16的整数倍,所以在尾部对不满16整数倍的部分进行填充,分析java代码得到填充的字符为 0x0A
content=content+(16-len(content)%16)*b"\x0A" 
ciphertext=obj.encrypt(content)  #进行加密
#ciphertext为二进制字符串,不能直接打印出来,故进行下一步处理将其以16进制形式显示
hextext="".join(["%0x"%ord(c) for c in ciphertext ])
print(hextext)

运行后与抓包得到的加密后的密码相同,python 改写圆满成功
由于openwrt对lua是原生支持的,所以准备用lua改写,但是中间发生了一点小插曲,而且是很重要的小插曲,所以我放弃了这部分的改写过程

好了,到目前为止,已经成功剖析并且编程实现了ct-young(闪讯)的验证,让我们的设备成功上网.

意外收获:

这个就是上面提到的小插曲.
突发奇想,电信的闪讯登陆的服务器219.148.205.34放置在公网,不只是连接ct-young后可以访问,任何一台连接到公网的机器都可以访问.所以我进行了如下尝试:

  • 设备1和设备2之间没有联系.
  • 设备2连接到ct-young 并获取到一个ip
  • 用一台成功连网的设备1(设备1没有连接ct-young)
  • 设备1按照上面获取uuid,登陆的步骤模拟发包,并把数据包里的ip替换为设备2的ip
  • 设备2成功连接互联网.
  • 如下图.
设备2 设备1 连到ct-young WiFi 不使用客户端登陆 不能连接互联网 在ct-young 获取到的ip地址:<ip02> 请求uuid wlanuserip为设备2的ip:<ip02> 请求到<uuid02> 发送登陆数据包 user ip=<ip02> uu id=<uuid02> username=<用户 名> 密码=<加密后的密 码> 服务器返回成功代 可以正常连 接到互联网 设备2 设备1

这说明一个设备可以使用另一台设备进行登录操作,而本身不需要操作,这使普通路由器也能连ct-young有了可行性.
下一步准备开发手机或电脑的软件来实现普通路由器的上网验证.

电脑客户端验证(比较复杂)

由于寝室墙上网口也改为闪讯验证,所以台式机没有无线网卡只能插网线进行上网.查了一些别人的破解方法,以及自己的剖析得到以下结果.
电脑闪讯客户端其实是一个加了壳的singleNet客户端,在程序根目录的bin里找到singleNet.exe,运行之后有一个类似Windows宽带连接登陆的页面,输入用户名密码同样可以连网
验证机制:

  1. 客户端建立一个pppoe连接
  2. 对密码和当前的时间进行计算,得到一个随时间动态变化的加密后的密码
  3. 将账户名和加密后的密码填入pppoe连接,连接pppoe
  4. 定期发送心跳包给服务器(但是每次都是发送失败,所以估计心跳包验证机制没开启)

此方法比较繁琐,加密方法较为复杂,由于水平有限,对客户端的反编译不是很成功,所以没能自己获得加密方法,网上有对它的研究,大家可以自行搜索.

CT-Young和CMU共存的研究

示意图如下:
连接示意图

最开始很令我好奇的是CMU和CT-Young是怎么在同一个无线ap共存的,而且是截然不同的验证方式,后来通过学习和思考,原来是用VLan虚拟局域网技术,两个网络通过同一个无线AP,同一条线路连到交换机,并被分开成为两个相对独立的局域网.
CMU通过校内的授权服务器192.168.100.10进行用户的授权,未授权时仅可访问192.168.100.10这台服务器.
CT-Young的验证方式我不太确定,但是据我推测是有一台校内的代理网关用于授权,未授权的终端只能访问辽宁电信登录服务器219.148.205.34.授权过程应该是以下几步.
手机登录方式:

  1. 终端连接到CT-Young网络,获取到一个100.76.160.0/20网段内的ip
  2. 用户客户端发送登录请求到辽宁电信登录服务器进行登录
  3. 辽宁电信登录服务器返回给校内网关登录的ip,让校内网关对其放行,同时返回给客户端一个登录的结果
  4. 用户可以连接互联网
  5. 注销时:发送注销请求到辽宁电信登录服务器进行注销
  6. 辽宁电信登录服务器返回给校内网关要注销的ip,让校内网关关掉其对互联网的访问,同时电信登录服务器返回给客户端一个注销成功的结果

电脑登录方式:

  1. 终端建立pppoe连接,ppp用户名为宽带账号,ppp密码为根据时间对密码动态加密后的密码
  2. 校内pppoe服务器获取到请求登录的ppp用户名和密码,并将其发送给辽宁电信登录服务器
  3. 辽宁电信登录服务器对时间戳,用户密码和ppp请求的密码进行计算和比对,然后返回给校内pppoe服务器校验结果
  4. 校内pppoe服务器根据校验结果来决定是不是向该终端提供服务,并与终端建立连接

这是对辽宁沈阳的中国医科大学的校园网的研究,希望对大家能有帮助.


发布了36 篇原创文章 · 获赞 23 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/still_night/article/details/77684059