• 主页
  • 搞机
  • 留言
  • 关于
  • 笔记
  • 时光
所有文章 友链 关于本站

  • 主页
  • 搞机
  • 留言
  • 关于
  • 笔记
  • 时光

写一个Drcom第三方客户端

2018-03-01

前一阵子弄了一个Drcom登陆客户端,用于登陆校园网,具体可以看中南大学联通校园网络第三方客户端,这次简单说一下这个东西是怎么做出来的。

这也是我第二次写WPF程序,很多地方都是一边学习一边瞎搞的。

项目地址:https://github.com/MonoLogueChi/Drcom

原理分析➴

首先是自己抓吧,还有参考GitHub上已有的项目,得出了登陆方式,然后写了一个简单的网页版,测试了一下。

就是利用POST提交表单登陆,用Get方式注销,表单为:

NameValue
DDDDD账号
upass密码
0MKKey
Submit%E7%99%BB+%E5%BD%95 (转义过来也就是登 陆)

前面都是很常规的东西,我个人觉得最有意思的就是后面获取登陆IP和登陆提示那一部分了。

界面和交互➴

设计界面➴

界面设计部分xaml没啥意思,就不放了,最后大概就是下面这张图这样

交互逻辑➴

其中包括功能:

  • IP输入和自动获取IP;
  • 账号输入和记住账号;
  • 密码输入和记住密码;
  • 登陆和注销按钮;
  • 检查更新和关于软件;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
using System.Diagnostics;
using System.Windows;

using Drcom.net;

namespace Drcom
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Settings();
}

//初始化配置
private void Settings()
{
if (Setting.GetSetting("nip") == "" || Setting.GetSetting("nip") == null)
{
GetIp.IsChecked = true;
}
else
{
TextIp.Text = Setting.GetSetting("nip");
}
if (Setting.GetSetting("uid") != "")
{
SaveUid.IsChecked = true;
TextUid.Text = Setting.GetSetting("uid");
}
if (Setting.GetSetting("pwd") != "")
{
SavePwd.IsChecked = true;
TextPwd.Password = Setting.GetSetting("pwd");
}
}

//登陆按钮
private void LoginNet(object sender, RoutedEventArgs e)
{
//获取账号密码ip
string nip = TextIp.Text;
string uid = TextUid.Text;
string pwd = TextPwd.Password;

//获取登陆IP
if (GetIp.IsChecked == true)
{
TextIp.Text = CsuNet.LoginIP();
nip = TextIp.Text;
}
//保存IP,账号和密码
if (TextIp.Text != null & TextIp.Text != "")
{
Setting.UpdateSetting("nip", nip);
}
if (SaveUid.IsChecked == true)
{
Setting.UpdateSetting("uid", uid);
}
if (SavePwd.IsChecked == true)
{
Setting.UpdateSetting("pwd", pwd);
}

var relust = CsuNet.LoginCsuNet(nip, uid, pwd);

MessageBox.Show(relust);
}

//注销按钮
private void LogoutNet(object sender, RoutedEventArgs e)
{
string nip = TextIp.Text;

var relust = CsuNet.LogoutCsuNet(nip);

MessageBox.Show(relust);
}

//关于软件
private void About(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
Process.Start("http://url.xxwhite.com?id=5a884de19f5454543ef4201e");
}
//检查更新
private void FindNew(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
string[] version = NewApp.IsNew();
MessageBox.Show("当前版本为:" + version[0] + "\r\n" +
"最新版本为:" + version[1] + "\r\n" +
"最后更新时间为:" + version[2]);
if (version[0] != version[1])
{
Process.Start("https://gitee.com/monologuechi/Drcom/releases");
}
}
}
}

接下来就是各项功能的实现了

核心功能➴

我们的核心功能便是登陆和注销功能,对于这两部分,其实很好理解,就是POST和GET。这两部分的代码我都写在了Drcom.net.CsuNet.cs中了

登陆➴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public static string LoginCsuNet(string nip, string uid, string pwd)
{
if (nip == "" || uid == "" || pwd == "")
{
return "请检查登陆ip,账号和密码是否输入正确";
}
else
{
try
{
string content = "DDDDD=" + uid + "@zndx&upass=" + pwd + "&0MKKey= +Login";
string posthost = "http://" + nip + "/";
string result = null;

HttpWebRequest req = (HttpWebRequest)WebRequest.Create(posthost);
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
byte[] data = Encoding.UTF8.GetBytes(content);
req.ContentLength = data.Length;
using (Stream reqStream = req.GetRequestStream())
{
reqStream.Write(data, 0, data.Length);
reqStream.Close();
}
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
Stream stream = resp.GetResponseStream();
//获取响应内容
using (StreamReader reader = new StreamReader(stream, Encoding.ASCII))
{
result = reader.ReadToEnd();
return LoginCaes(result);
}
}
catch (Exception)
{
return "发生未知错误\r\n请检查登陆IP是否填写正确";
}
}
}

注销➴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static string LogoutCsuNet(string nip)
{
try
{
string gethost = "http://" + nip + "/F.htm";
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(gethost);
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
Stream stream = resp.GetResponseStream();
//获取响应内容
using (StreamReader reader = new StreamReader(stream, Encoding.ASCII))
{
string result = reader.ReadToEnd();
return LoginCaes(result);
}
}
catch (Exception)
{
return "发生未知错误";
}

}

至此,软件的基本功能就都已经实现了。

获取错误提示➴

这一部分纯粹靠抓包获得,我们抓 GET http://119.39.119.2/F.htm,在返回响应中获得了错误代码,并且得到了登陆时长和流量使用的数据,有用 的信息包括:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
Msg=14;time='5         ';flow='200       ';fsele=1;fee='0         ';xsele=0;xip='000.000.000.000.';mac='00-00-00-00-00-00';va=00;vb=00;vc=00;vd=0000;ve=0000;vf=0000;
ipm="77277702";ss1="000d48462d8d";ss2="3905";ss3="0a000104";ss4="28d244d94383";msga='';/* can not modify !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
pp="<br>";
flow0=flow%1024;flow1=flow-flow0;flow0=flow0*1000;flow0=flow0-flow0%1024;fee1=fee-fee%100;
flow3='.';
if(flow0/1024<10)flow3='.00';
else{if(flow0/1024<100)flow3='.0';}
UT="已使用时间 : "+time +" Min"+pp;
UF="已使用流量 : "+flow1/1024+flow3+flow0/1024+" MByte"+pp;
if(fsele==1)UM="本账号余额 : "+"RMB"+fee1/10000;
else UM="";
function DispTFM(){
switch(Msg){
case 0:
case 1:
if((Msg==1)&&(msga!="")){
switch(msga){
case 'error0':
document.write("本IP不允许Web方式登录");
break;
case 'error1':
document.write("本账号不允许Web方式登录");
break;
case 'error2':
document.write("本账号不允许修改密码");
break;
default:
document.write(msga);
break;}
}
else document.write("账号或密码不对,请重新输入");
break;
case 2:
document.write("该账号正在使用中,请您与网管联系"+pp+xip+pp+mac);
break;
case 3:
document.write("本账号只能在指定地址使用<br>This account can be used on the appointed address only."+pp+xip);
break;
case 4:
document.write("本账号费用超支或时长流量超过限制");
break;
case 5:
document.write("本账号暂停使用");
break;
case 6:
document.write("System buffer full");
break;
case 8:
document.write("本账号正在使用,不能修改");
break;
case 9:
document.write("新密码与确认新密码不匹配,不能修改");
break;
case 10:
document.write("密码修改成功");
break;
case 11:
document.write("本账号只能在指定地址使用 :"+pp+mac);
break;
case 7:
document.write(UT+UF+UM);
break;
case 14:
document.write("注销成功"+pp+UT+UF+UM);
break;
case 15:
document.write("登录成功"+pp+UT+UF+UM);
break;
}}

内容有点长,我们使用字符串拆分,获取有用的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public static string LoginCaes(string result)
{
if (result.Contains("Msg="))
{
string[] FMsg = Regex.Split(result, "Msg=", RegexOptions.IgnoreCase);
int Msg = Convert.ToInt32(FMsg[1].Substring(0, 2));
switch (Msg)
{
case 0: return "未知错误";
case 1:
{
string msga = Regex.Split(FMsg[1], "msga=", RegexOptions.IgnoreCase)[1].Substring(1, 1);
if (msga != "\'") { return "错误代码:" + msga; }
else { return "账号或密码错误"; }
}
case 2: return "该账号正在使用中,请您与网管联系";
case 3: return "本账号只能在指定地址使用";
case 4: return "本账号费用超支或时长流量超过限制";
case 5: return "本账号暂停使用";
case 6: return "System buffer full";
case 8: return "本账号正在使用,不能修改";
case 7: return "未知错误";
case 9: return "新密码与确认新密码不匹配,不能修改";
case 10: return "密码修改成功";
case 11: return "本账号只能在指定地址使用";
case 12: return "未知错误";
case 13: return "未知错误";
//注销成功,还要获取一些信息
case 14:
{
try
{
int time = Convert.ToInt32(Regex.Split(FMsg[1], "time=", RegexOptions.IgnoreCase)[1].Substring(1, 9));
float flow = Convert.ToSingle(Regex.Split(FMsg[1], "flow=", RegexOptions.IgnoreCase)[1].Substring(1, 9));
return "注销成功 \r\n" +
"本次使用时长:" + time + " Min \r\n" +
"本次使用流量:" + (flow / 1024f).ToString("F2") + " MByte";
}
catch (Exception) { return "注销成功"; }
}
case 15: return "登录成功";
}
return "未知错误";
}
else { return "您应该大概也许可能已经成功登陆了"; }
}

获取登陆IP➴

这一项功能的发现,纯属偶然,抓包中无意抓到的,在未登录状态下,随便发送一个GET请求,返回响应里就会有登陆IP的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static string LoginIP()
{
try
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://pingtcss.qq.com/");
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
Stream stream = resp.GetResponseStream();
//获取响应内容
using (StreamReader reader = new StreamReader(stream, Encoding.ASCII))
{
string result = reader.ReadToEnd();

if (result.Contains("v4serip"))
{
string[] Fnip = Regex.Split(result, "v4serip='", RegexOptions.IgnoreCase);
string[] nip = Regex.Split(Fnip[1], "'", RegexOptions.IgnoreCase);
return nip[0];
}
else { return null; }
}
}
catch (Exception) { return null; }
}

保存设置和检查更新➴

读取和保存配置➴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using System;
using System.Configuration;

namespace Drcom.net
{
public class Setting
{
//获取设置
public static string GetSetting(string key)
{
try
{
string value = ConfigurationManager.AppSettings[key].ToString();
return value;
}
catch (Exception) { return null; }
}
//写入设置
public static void UpdateSetting(string key, string value)
{
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

try
{
if (config.AppSettings.Settings[key] != null)
{
config.AppSettings.Settings.Remove(key);
}
config.AppSettings.Settings.Add(key, value);
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");
}
catch (Exception) { }
}
}
}

软件检查更新➴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using System;
using System.Reflection;
using System.Xml.Linq;

namespace Drcom.net
{
public class NewApp
{
public static string[] IsNew()
{
string version = Assembly.GetExecutingAssembly().GetName().Version.ToString();

try
{
string versionxml = "http://v.xxwhite.com/version/Drcom.xml?t=" + DateTime.Now.ToFileTimeUtc().ToString();
XDocument oXDoc = XDocument.Load(versionxml);
XElement root = oXDoc.Root;
XElement lastversion = root.Element("version");
XElement data = root.Element("data");

string[] versiondata = new string[3] { version, lastversion.Value, data.Value };

return versiondata;
}
catch (Exception)
{
return (new string[3] { version, "未检测到最新版本", "未检测到更新时间" });
}
}
}
}

写在后面的➴

至此,所有功能均已介绍完毕,有兴趣的同学可以接盘继续开发,项目地址在中南大学联通校园网络第三方客户端中。

友情提示,修改密码功能其实实现也很简单,GitHub上已经找到有事项该功能的项目了,只要把IP改成自己的,剩下基本照抄就能实现了。

赏

请作者吃辣条

支付宝
微信
  • 本文作者: MonoLogueChi
  • 本文链接: https://blog.xxwhite.com/2018/drcom-develop.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
  • C#
  • 软件
推荐几个不错的小工具
中南大学联通校园网络第三方客户端

获取文章MarkDown文件 本文阅读量 8848
  1. 1. 原理分析
  2. 2. 界面和交互
    1. 2.1. 设计界面
    2. 2.2. 交互逻辑
  3. 3. 核心功能
    1. 3.1. 登陆
    2. 3.2. 注销
    3. 3.3. 获取错误提示
    4. 3.4. 获取登陆IP
  4. 4. 保存设置和检查更新
    1. 4.1. 读取和保存配置
    2. 4.2. 软件检查更新
  5. 5. 写在后面的
© 2015-2021 MonoLogueChi
蒙ICP备17004911号-1
Hexo Theme Yilia by Litten
本站由 提供CDN加速/云存储服务
  • 所有文章
  • 友链
  • 关于本站

tag:

  • 搞机
  • 建站笔记
  • Unity
  • 测试
  • 随便水水
  • C#
  • 涨姿势
  • 软件
  • 炎黄幼儿园
  • 小工具
  • 香橙派
  • 服务器
  • 个人电脑
  • dotnet
  • Linux
  • 硬件
  • 软路由
  • NAS
  • OMV
  • timeline
  • 叉叉白
  • 大姐姐的博客Minemine
  • 酷安基佬
  • Se7en
  • 姬长信
  • staunchkai
  • 随遇而安
  • 2401的晚秋咖啡
  • CareyQ
  • Junzhou Liu
  • Yi-Yun
  • LmCjl在线工具
  • Freetao’s Blog
叉叉白 一个小白搞机的记事本