您的位置:首页 > 理论基础 > 计算机网络

玩玩网络课堂的外挂-网站模拟登陆

2017-03-11 18:19 381 查看
转业到地方,脱离了部队,完成从一名部队的军官到地方小公务员的转变,网络生活也开始进行转变。

没想到地方很变态的,虽然没有部队那种强制一个月乌七八糟的各种政治、业务学习,但居然有个网络学习这玩意,也就是让你下班后坐电脑前面看视频,看一个小时就能拿到1学分,一年起码得学够80学分,否则年终考核不及格。公务员年终考核不及格的后果就不用多说了,那关系到钱袋子。

但我为什么要那么老实呆在电脑前面看砖家吹牛呢,不呆电脑前面还不行,那破系统会不定时弹个窗口让你按确定,或出到选择题让你做,不过好处是题目不管你做对还是做错,继续下去就可以了。到这里我就想搞外挂了,准备弄个外挂来搞定这学习,反正要长久使用的,年内完成就可以了。因为刚到地方工作,很多东西没上手,所以这个小外挂可能会更新得断断续续的。

要搞定这个小外挂,原理就是模拟人坐电脑前老实看视频,弹窗就按确定,出题就乱做。

首先,先搞定模拟登录。

先看看这个网站:http://study.huizhou.gov.cn/login.aspx

这是登录的页面,根据度娘各种模拟登录案例、代码,这种使用asp.net的属于最讨厌的网站之一,至于为什么讨厌,后面就知道了。

没写代码前,先分析一下登录过程,正常网页登录,就是填用户名、密码和验证码,点一下登录按钮,就搞定了。但用计算机模拟就头疼了,当然,如果用WebBrowser控件不是什么难事,但太慢了,而且控制弹窗是个很头疼的事,就是想用HttpWebRequest来玩,HttpWebRequest比较底层,控制起来比较灵活,效率也高。

先看看计算机在后面怎么完成登录的,这样我们需要一些工具,拦截计算机与服务器之间的通讯,看看两者如何建立联系并互相传递数据的,这个就是俗称“抓包”的工作,搞清楚后,就模拟这个过程。

抓包的工具不用多说,度娘一下可以给你一大堆,我基本不用,为什么,因为我这个破学习网站不支持其它浏览器,仅能用IE来学习,其它浏览器学不了的,这个也说明了玩它的小外挂就轻松多了,只需考虑IE就可以了。既然只支持IE,抓包就不需要工具了,因为IE11本身就带有了,F12一按,到网络选项卡启动一下,再去浏览器输入网址进行操作,整个过程都看到了。因为简单,这个抓包就不多说了,不懂可以度娘。

抓包的数据很多,其实对模拟登录来说,最关心的是如何发送数据,以及服务器响应后那些数据是有用的。

浏览器发送数据有两种方式,一种是GET,一种是POST,前者简单来说就是通过网址来发送数据,如网址后面那些?xxx=xxxx&yyy=yyy这类的,就把一些需要的数据发给服务器了,这个GET的方式坑爹在于是明文,用户名和密码这类的不适合,所以就用POST来发送了,当然,POST也是明文,通过抓包也是能看到的,但起码不会在浏览器那里看到了,相对安全一点。

好,不多介绍基础知识了,想了解可以去研究http协议,先来搞定验证码先。

看登录页面,有个验证码,虽然很简单,4位纯数字,问题在于它是随机的(伪随机),在winform中模拟登录,就要先获取验证码。
从抓包的数据来看,验证码是一个单独的aspx文件,在登录页面那里查看一下图片属性,就会发现验证码是“http://study.huizhou.gov.cn/CodeImg.aspx?xxxxxx”这样的,再查看一下页面的源文件,验证码图片的src是空的,但有个JS函数用来更改验证码图片的,找到这个JS函数,发现xxxxxx是个时间戳而已,按照它生产的方式,用C#编造一个,好生成验证码的请求地址。

string code = Math.Ceiling(((Convert.ToInt64((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds)* 9301 + 49297) % 233280)/ 233280.0* 10000000).ToString();
这个code就是地址栏后面的xxxxxx伪随机数啦。

winform的界面自己看着办,拖一个PictureBox控件来用于显示验证码。

但在显示验证码前,先搞清一个原理,服务器是怎么知道判断浏览器提交过了的验证码是正确的呢?简单来说,就是服务器先生成一个4位纯数字的伪随机数,用session保存起来,然后把这4位伪随机数装扮成有点难识别的图片给浏览器显示。这个服务器与浏览器之间的通讯是通过cookie来传递的,抓包时可以看到用get方式去访问“http://study.huizhou.gov.cn/CodeImg.aspx?xxxxxx”返回数据有个set-cookie,所以模拟登录玩验证码不但要取得验证码,还得取得这个cookie,因为提交验证码时必须把这个cookie一起提交,否则服务器无法识别出会认为验证码错误的。

因为cookie要在提交数据时使用,所以先定义一个全局的cookie,考虑到登录后服务器肯定还要再发一个cookie过来,干脆定义个容器,把cookie全装起来。

CookieContainer cookies = new CookieContainer();
考虑到加载窗体时就要加载验证码图片,单击验证码图片时也要换图片,编个函数玩吧

private void setRegCode()
{
System.GC.Collect();
string url = @"http://study.huizhou.gov.cn/CodeImg.aspx?";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Referer = "http://study.huizhou.gov.cn/login.aspx";
request.Accept = "image/png, image/svg+xml, image/*;q=0.8,*/*;q=0.5";
request.Method = "GET";
request.UserAgent = "Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko";
request.CookieContainer = cookies;
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
if (response.Cookies.Count > 0)
cookies.Add(response.Cookies);
Stream resStream = response.GetResponseStream();
checkCode_Pic.Image = new Bitmap(resStream);
resStream.Close();
resStream = null;
response.Close();
response = null;
}
因为我觉得比较简单,就没写注释,简单讲需要,这里有几个函数需要引用System.Net;和System.IO;的,第一条语句我忘记删了,这个是清理内存的,因为我当时调试太频繁,我的计算机内存只有1G,给拖死了,只好清理一下。第二到第八句就创建reques的Header的,其实Header很多的,但有这几个就够了,至于头的数据是什么,自己看着办吧,我是懒的,直接把IE的那里抓包的头给弄进来,安全第一。

注意这个request.CookieContainer = cookies;这句是关键中的关键,指定request的cookie容器,没有这句,cookies.Add(response.Cookies);这句是无法执行的,这个函数代码其余就很容易了解了,先造一个request的访问头,用这个访问头去访问服务器request.Method = "GET";这句指定的传数据是用GET的方式。HttpWebResponse response = (HttpWebResponse)request.GetResponse();这句创建一个对象获得服务器的响应数据,判断服务器是否返回cookie,如果有,就用cookies.Add(response.Cookies);把cookie添加到容器里面。其实这里很不严谨的,万一请求失败,服务器没返回数据呢,我这里没处理,因为这个仅提供个获取验证码的思路给大家看看而已,真正使用的时候要对没返回数据的情况进行处理的。因为Header那里说明了请求的返回的数据是图片形式,所以把数据流直接赋值给PictureBox控件。

好,拿到验证码和cookie了,接下来就模拟登录啦。

上面说过,有用户名和密码的,基本都是用POST的方法的,先看看一个不怎么严谨的函数。

public static CookieContainer GetLoginCookie(string UserName,string PassWord,string CheckCode,CookieContainer CheckCookie)
{
Dictionary<string, string> Po
4000
stDic = new Dictionary<string, string>();
try
{
PostDic.Add(@"__VIEWSTATE", HelperIDAL.Config.GetConfig(@"AppConfig/VIEWSTATE"));
PostDic.Add(@"__EVENTVALIDATION", HelperIDAL.Config.GetConfig(@"AppConfig/EVENTVALIDATION"));
PostDic.Add(@"ctl05$hdIsDefault", "0");
PostDic.Add(@"ctl05$UserName", UserName);
PostDic.Add(@"ctl05$Password", PassWord);
PostDic.Add(@"ctl05$code_op", CheckCode);
PostDic.Add(@"ctl05$LoginButton.x", "25");
PostDic.Add(@"ctl05$LoginButton.y", "14");
string result = null;
foreach (string key in PostDic.Keys)
{
result += @HttpUtility.UrlEncode(key, Encoding.GetEncoding("utf-8")) + "=" + @HttpUtility.UrlEncode(PostDic[key], Encoding.GetEncoding("utf-8")) + "&";
}
result = result.Substring(0, result.Length - 1);
byte[] PostData= Encoding.UTF8.GetBytes(result);
HttpWebRequest request = null;
string url = @"http://study.huizhou.gov.cn/login.aspx";   //登录页面
request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.Referer = "http://study.huizhou.gov.cn/login.aspx";
request.Accept = @"text/html, application/xhtml+xml, */*";
request.UserAgent = @"Mozilla/5.0(Windows NT 6.1;Trident/7.0;rv: 11.0)like Gecko";
request.ContentType = @"application/x-www-form-urlencoded";
request.AllowAutoRedirect = false;
request.CookieContainer = CheckCookie;
request.ContentLength = PostData.Length;
Stream requestStream = request.GetRequestStream();
requestStream.Write(PostData, 0, PostData.Length);
requestStream.Close();
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
if (response.Cookies.Count > 0)
{
CheckCookie.Add(response.Cookies);
return CheckCookie;
}
else
return null;
}
catch
{
return null;
}
}
因为模拟登录的目的是获取登录后服务器返回的cookie,所以定义这个函数就是返回一个cookie的容器,由于有验证码,所以这个函数必须传递一个装有验证码页面返回的cookie的容器过去。

简单讲讲这个函数,前面部分是凑POST数据的字典,看抓包的数据就知道了,POST的都是“字段名=值”这种形式的,用&把这些连接起来,但抓包时你会发现其实很多数据是不需要的,用字典Dictionary这种方式其实是方便调试,看看那些字段是不需要的。asp.net的网站坑爹的就在这里了,很多控件的ID都是什么ct1xx$yyyy这种形式的,我度娘其它案例的代码时没注意到这个,只对值进行了url编码,没对字段名进行编码,所以失败了N次才发现这个问题。函数中那个foreach就是对字典的字段名和值进行url编码的。

重点中的重点,asp.net网站模拟登录中最坑爹的知识点来了。注意字典中__VIEWSTATE和__EVENTVALIDATION这个两个字段了吗?不要管值,这个函数抄是没用的HelperIDAL.Config.GetConfig(@"AppConfig/VIEWSTATE")这个是我另外一个类中的函数,用来读取一个xml文件节点的值的。__VIEWSTATE和__EVENTVALIDATION这两个字段坑爹在于不同的计算机访问会有不同的值,变态的asp.net网站甚至同一台计算机每次访问都是不同的值的,这两货是asp.net网站防post数据用的,后面再详细讲。先简单讲讲其他代码。

byte[] PostData= Encoding.UTF8.GetBytes(result);就是把POST数据进行编码,变成数据流。接下来的代码就是造request的Header,和获取验证码差不多,就是Method 改成了POST,Accept改成接收所有数据,不懂如何设置就直接全抄抓包数据。request.AllowAutoRedirect = false;这句比较重要,很多网站验证完后会跳转的,这句是不让跳转,为什么?因为我们要的是cookie跳走了,怎么获得cookie,跳走都是发送cookie了。request.ContentLength
= PostData.Length; Stream requestStream = request.GetRequestStream(); requestStream.Write(PostData, 0, PostData.Length);这三句就是把POST数据送到request里面去发送,接下来就是等服务器响应,获取cookie。所以说不严谨的函数和获取 验证码的函数一样,不考虑服务器不返回数据的情况。

接回来重点讲讲坑爹的__VIEWSTATE和__EVENTVALIDATION这两货,我这里用xml文件保存起来是因为我发现在同一台计算机是不会变的,而且这两货可以通过查看源代码的方式找到。万一碰到变态网站每次访问都会变的怎么办,还能怎么办,只好每次Post数据前先获取一次了。还好,一般验证码那个图片是不会有这个的,否则就头疼了。

既然这个是"http://study.huizhou.gov.cn/login.aspx"页面产生的,那就好办了,大不了在Post数据前,先抓到这两货。

下面先看看这个函数,这个函数是我用来给计算机第一次访问是抓__VIEWSTATE和__EVENTVALIDATION这两货写入xml文件中的,供参考。

public static bool UpdataAspConfig()
{
System.GC.Collect();
string url = @"http://study.huizhou.gov.cn/login.aspx?";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Referer = "http://study.huizhou.gov.cn/login.aspx";
request.Accept = "text/html, application/xhtml+xml, */*";
request.Method = "GET";
request.UserAgent = "Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko";
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream resStream = response.GetResponseStream();
StreamReader resReader = new StreamReader(resStream, Encoding.UTF8);
string HtmlStr = resReader.ReadToEnd();
resStream.Close();
resStream = null;
resReader.Close();
resReader = null;
response.Close();
response = null;
HtmlStr = HelperIDAL.Html.ClearHtmlTag(HtmlStr);
string VIEWSTATE = HelperIDAL.Html.GetOnlyInfo(@"id=""__VIEWSTATE"" value=""(.*?)""", HtmlStr);
string EVENTVALIDATION = HelperIDAL.Html.GetOnlyInfo(@"id=""__EVENTVALIDATION"" value=""(.*?)""", HtmlStr);
if (VIEWSTATE != "" && EVENTVALIDATION != "")
{
HelperIDAL.Config.SetConfig(@"AppConfig/VIEWSTATE", VIEWSTATE);
HelperIDAL.Config.SetConfig(@"AppConfig/EVENTVALIDATION", EVENTVALIDATION);
return true;
}
else
return false;
}
别想着抄代码,没用的,这里用了不少我另外一个类里面的函数。

string VIEWSTATE = HelperIDAL.Html.GetOnlyInfo(@"id=""__VIEWSTATE"" value=""(.*?)""", HtmlStr);
这个是关键,哈哈,这个函数有点看不懂,就这样就可以抓了到这两货了?

public static string GetOnlyInfo(string RegexStr,string HtmlStr)
{
MatchCollection Match = Regex.Matches(HtmlStr, @RegexStr, RegexOptions.IgnoreCase | RegexOptions.Multiline);
string Info = null;
foreach (Match NextMatch in Match)
{
Info +=  NextMatch.Groups[1].Value;
}
return Info;
}
如果稍微动一点正则表达式的应该就看明白了。

HelperIDAL.Html.GetOnlyInfo(@"id=""__VIEWSTATE"" value=""(.*?)""", HtmlStr);其实就是把一个正则表达式和request访问http://study.huizhou.gov.cn/login.aspx返回的数据送过去,利用正则表达式把需要的值给抽出来。简单讲一下这个正则表达式,因为这个很讨厌,正则表达式很灵活,有很多种方式可以完成。

先看看__VIEWSTATE这个的源代码

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwULLTE2ODU1MjI0NjFkGAEFHl9fQ29udHJvbHNSZXF1aXJlUG9zdEJhY2tLZXlfXxYCBRBjdGwwNSRjYlJlbWVtYmVyBRFjdGwwNSRMb2dpbkJ1dHRvbmEQfPUVhApmGPdyXkXNdlq8KhFi" />

因为这个除了value的值会变,其余的是不会变的,所以就简单了,id就是__VIEWSTATE,这个绝逼是唯一的,所以正则表达式中,一定要把id给弄进去,这样可以保证取得唯一的结果,正则表达式中涉及到转义,所以双引号必须用两个,这样转义后就会只有一个了。(.*?)这个是核心,括号表示需要获取这段字符串,.表示匹配一个字符*表示重复匹配到出现后面的字符为止,?表示广泛匹配,不需要太严格,连符号都不放过。如果要深入了解,只好去研究正则表达式了。

反正这个函数把id弄进去,就可以抽取出__VIEWSTATE和__EVENTVALIDATION的值了。

正则表达式很强大的,玩网页游戏外挂的必须要研究的这玩意的。

再给大家看一个函数,也是玩正则表达式的,同样可以获取出__VIEWSTATE和__EVENTVALIDATION的值,不过更灵活而已。

//获取指定ID的Html节点的value的值,使用前请确保该ID是唯一的,否则会把值连接在一起。
public static string GetNodeValueByID(string TagName,string NodeID,string HtmlStr)
{
MatchCollection Match = Regex.Matches(HtmlStr, @"<" + TagName + @".*? id=""" + NodeID + @""".*? value=""(.*?)""", RegexOptions.IgnoreCase);
string Result = null;
foreach(Match NextMatch in Match)
{
Result += NextMatch.Groups[1].Value;
}
return Result;
}

这个函数如果要获取__VIEWSTATE的值,就调用 GetNodeValueByID("input","__VIEWSTATE",HtmlStr);HtmlStr就是服务器返回的网页html代码。
坑爹的asp.net网站模拟登录,就这样可以完成了,成功获取了登录的cookie,再访问其它网页抓数据,把容器附带过去就OK了。

简单步骤如下

定义一个全局的cookie容器:CookieContainer cookies = new CookieContainer(); 

利用setRegCode()这个函数把验证码页面返回的cookie添加到全局cookies这个容器里面,注意这个函数不能写到类里面,只能在form页面代码里面写。

利用cookies= GetLoginCookie(UserName,PassWord,CheckCode,cookies);就可以把上面的cookie带去Post数据并返回登录的cookie了。

再访问其它需登录才能访问的页面,在造Header时request.CookieContainer=cookies;就可以把cookie带过去进行授权访问了。

至于分析其它页面返回的数据,在完成小外挂的制作,那就是玩正则表达式了,这个我还在学习中,有空更新点内容。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: