VS平台账号注册机的原理和实现
Kimm King
Kimmking@163.com
2008年12月27日
摘要:通过分析vs平台的网页注册方式,分析验证码,使用c#模拟表单提交,实现账号自动注册。
1 背景介绍
1.1 关于vs平台和账号
vs是一个对战平台。
玩游戏的应该都知道。
我是一个dota“骨灰”级玩家,很久前玩HF,后来玩bn、gg和vs,现在主要是玩vs平台了。从04年开始玩,4年了。在vs上也用了大概有30个号,绝大部分都记不清楚了。
一般情况,如果没做啥坏事,账号没被封,一个账号就够了。但是如果用了优化版、挤房间工具或是作弊器(maphacker、tcpview、必胜不败、进程控制之类),就可能被封号。等级高了,一般也换号,因为赢了没分,输了狂掉。有时候一段时间没玩或重装机器,想玩就不知道上次用的号是啥,当然几个级高的主号,平时和一些朋友和队友cw、ih用的,还记得。
1.2 关于账号注册和本文
言归正传,平时我们需要新的账号的时候,需要到网页上去提交资料、申请一个账号。本文档阐述的就是让程序自动完成这个步骤。直接批量的获取一些账号。当然,如果vs平台管理人员知道了这件事,一定会修改现有的账号申请验证方式,相信我发布解决方案不久就会失效。还有就是,vs的网站服务器是tomcat4,如果很多人同时用工具注册,vs的网站会直接挂掉。
所以,本文重在说明此类工作自动化的设计原理与实现过程,同时也提供一个实现自动注册的小工具。此原理同样使用于目前的论坛、留言板和blog等系统的自动信息提交,根据此思路,大家可以自己动手实现自己的相应工具。
声明:希望大家在某些网站和系统没有表示禁止工具注册和交互的情况下谨慎使用此文提供的工具和技术。
2 技术分析
2.1 网页手工注册方式分析
2.1.1 注册页面
呵呵,首先我们来看看常规的手工注册方式。
注册页面如下:
http://www.vsa.com.cn/user/center/register/user_register.jsp
图1 账号注册页面
从这个表单中可以看到,一共有6个必填的字段,分别是:用户名、两次密码、主职业、验证码和昵称(图上的红圈标记)。
查看此网页的源文件,可以看到各个字段的name分别为:
userName、assword、passwordSure、nickName、profession、iRand。
其中,验证码图片(图上的红方框标记)所在的url为:
http://www.vsa.com.cn/user/center/code/image2.jsp。
源文件中form定义为:
<form name="form1" method="post">。
分析验证和提交的js中,没有定义表单的action,故此表单提交到自己所在的url。
2.1.2 注册账号
手工注册一个账号,提交后,提示如下图:
图2 提示信息
查看页面源文件,最后面如下所示:
<script language="javascript">
alert("注册成功!");
window.location.href="../comm/index.jsp";
</script>
如果注册失败,则可能提示信息为:
l 验证码不正确,注册失败!
l 该用户名已经存在或输入有错!
3 实现原理
3.1 大概步骤
根据以上分析信息,我们可以实现一个网络请求,附带上以上的各个所需信息,服务器即会认为是客户端浏览器提交了一个表单,进而为我们注册一个账号。
大概的步骤如下:
1、获取验证图片和cookie
2、分析验证码
3、模拟提交表单
4、分析是否成功
3.2 获取图片和cookie
一般来说,在同一个浏览器窗口中执行的一系列操作,其各个请求在服务器端是共session的。验证码的原理就是在验证码图片生成前先随即生成一组数字,然后放在此请求的session中,再用此数字生成图片输出到客户端的请求中。
.net基础类库中提供了几个http请求和回应的类:HttpWebRequest和HttpWebResponse。通过请求验证图片,然后得到响应的数据,保存到本地文件。同时获取会话的cookie,存入CookieContainer中,第三步的模拟表单提交时,需要使用此cookie,从而使请求在服务器端可以使用此次会话中session中的数据。
CookieContainer container = new CookieContainer();
string url = "http://www.vsa.com.cn/user/center/code/image2.jsp";
byte[] bs = new byte[4096];
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.CookieContainer = container;
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream resStream = response.GetResponseStream();
int len = resStream.Read(bs, 0, 4096);
string user = textBox3.Text + iii.ToString();
response.Cookies = container.GetCookies(request.RequestUri);
resStream.Close();
string path = @"c:/" + user + ".jpg";
FileStream fs = File.Create(path);
fs.Write(bs, 0, len);
fs.Close();
3.3 分析验证码
这四个步骤里,分析验证码比较麻烦。
为了分析验证码,我们可以先下载几个验证码图片,研究其规律。当然,你可以在注册页面保存图片,也可以在浏览器直接输入其url获取。我的做法是写几句简单的代码,通过url循环获取图片并保存(详见示例程序中的downImg方法)。
一个典型的验证码图片如下:
图3 验证码图片
这样看的不是太清楚,强大的VS.net IDE给我提供了不错的工具。
用VS.net打开:
图4 在vs.net中查看的图片
图上的每一个格子是一个像素。
可以看到背景上的干扰噪声很严重,组成各个数字本身的像素颜色也都不一致。这样好像看不出来什么规律,但是多看几个这样的图片,呵呵,思考下,规律出来了。你看出来了吗?
总结下规律:
1、图的大小是60*20
2、一共有4个数字
3、每个字的起点是(7,3)、(20,3)、(33,3)、(46,3)
4、每个字的宽度是8或着9
5、好像数字比背景的颜色要深
然后我们说说.net中处理图片的简单步骤。
Bitmap bm = new Bitmap(path); // 得到位图对象
Color c = bm.GetPixel(i, j); // 得到位图上坐标为(i,j)的点的颜色
Color类有什么方法呢?ARGB?HSB?
上面的第五条,颜色深?亮度?GetBrightness?
试试,把亮度大于0.5的点都去掉,看看。创建一个新的Bitmap对象bm2,和bm一样大小。循环bm的每一个像素,如果此像素的亮度小于0.5就把bm2的此坐标对应的点设置为黑色。代码如下:
string path = @"E:/test/bmp_bw/51.jpg";
Bitmap bm = new Bitmap(path); // 得到位图对象
int w = bm.Size.Width;
int h = bm.Size.Height;
Bitmap bm2 = new Bitmap(w, h);
Color c = Color.Empty;
for (int i = 0; i < w; i++)
{
for (int j = 0; j < h; j++)
{
c = bm.GetPixel(i, j);
float b = c.GetBrightness();
if (b <= 0.5)
{
bm2.SetPixel(i, j, Color.Blue);
}
}
}
bm2.Save(@"E:/test/bmp_bw/black.jpg");
bm.Dispose();
bm2.Dispose();
输出的图片black.jpg如下:
没有干扰点了!!!说明我们的想法是对的。
(实际上,我开始走了弯路,一直考虑其他的方法,消除干扰的效果都不好。)
此图片拖到vs.net里,黑色和背景是一样的,看不到了。把上面的颜色换成蓝色就可以看到效果了:
图6 处理后的验证码
去掉干扰点后,我们就可以来识别数字了。想点简单可行的办法。最简单可行的办法就是:记录下0到9每一个数字在所在的9*13的格子内的每一个坐标是否有点,然后在每个图片上,先分割出来每个数字占的9*13的区域,取各个点和记录的值做比较,取0到9中重合点最多的那个数字(为什么不取完全一致的呢?因为有些图片去掉干扰后,某些数字缺了一些点)。这个方法其实就是简单的模式识别。
先收集每个数字的矩阵也麻烦。再简化下,不就10个数字吗,越简单越好。每个数字由13行、9列组成,那么,统计每行的点数,就得到一个byte[13],一共10个。以此为基准比较即可。当然,自己数是笨方法,写几句代码,得到一堆。整理下,放在一个byte[10,13]里,作为样本:
private static byte[,] number = new byte[,] { { 3, 7, 4, 4, 4, 4, 4, 4, 4, 4, 4, 7, 3, }, { 3, 5, 5, 2, 2, 2, 2, 2, 2, 2, 2, 8, 8 }, { 5, 7, 3, 2, 2, 2, 2, 2, 2, 2, 2, 8, 8 }, { 5, 8, 3, 2, 2, 4, 6, 3, 2, 2, 4, 7, 5 }, { 2, 3, 3, 4, 4, 4, 4, 4, 9, 9, 2, 2, 2 }, { 8, 8, 2, 2, 2, 5, 7, 3, 2, 2, 4, 7, 5 }, { 4, 6, 3, 2, 2, 6, 8, 6, 4, 4, 5, 7, 4 }, { 8, 8, 2, 1, 2, 2, 1, 2, 1, 2, 2, 2, 2 }, { 5, 7, 4, 4, 4, 5, 5, 5, 4, 4, 6, 7, 5 }, { 4, 7, 5, 4, 4, 6, 8, 6, 2, 2, 3, 6, 4 } };
从每个图片上分割出来的实际数据与此样本比较,从而得到图片中的数字。
由于有些字会模糊,这样不是太准,比如不太准的2和7,3和5。大概带单个字的误差是1%到2%。不过足够了。(宝石哥哥说:人去判断都可能会出错。)
1.1 模拟表单提交
模拟表单提交,这个跟步骤1类似,只不过要传递一些参数。代码如下:
string param = "userName=" + user + "&password=kimmking&passwordSure=kimmking&nickName=kk&profession=3010&iRand=" + rand;
//模拟表单信息。可以自己修改,其中的kimmking即为密码
//user是用户名,rand是验证码,用户名必须由数字 字母和下划线组成
string url2 = "http://www.vsa.com.cn/user/center/register/user_register.jsp";
request = (HttpWebRequest)WebRequest.Create(url2 + "?" + new Random().NextDouble());
request.CookieContainer = container;
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = param.Length;
request.Method = "POST";
Stream myRequestStream = request.GetRequestStream();
StreamWriter myStreamWriter = new StreamWriter(myRequestStream, Encoding.GetEncoding("gb2312"));
myStreamWriter.Write(param);
//把数据写入HttpWebRequest的Request流
myStreamWriter.Close();
myRequestStream.Close();
//关闭打开对象
HttpWebResponse myHttpWebResponse = (HttpWebResponse)request.GetResponse();
//新建一个HttpWebResponse
myHttpWebResponse.Cookies = container.GetCookies(request.RequestUri);
//获取一个包含url的Cookie集合的CookieCollection
Stream myResponseStream = myHttpWebResponse.GetResponseStream();
StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.GetEncoding("gb2312"));
string outdata = myStreamReader.ReadToEnd();
//把数据从HttpWebResponse的Response流中读出
myStreamReader.Close();
myResponseStream.Close();
1.2 分析返回信息
上一步骤中的outdata即是服务器端返回数据。
从中间分析出来最后一段的提示信息即可。方法如下,取outdata(其实是html页面的源文件)的最后100个字符,然后取alert中的那行文字。代码:
string last = outdata.Substring(outdata.Length - 100, 90);
int la = last.IndexOf("alert") + 7;
string tip = last.Substring(la, last.IndexOf(")") - la - 1);
string res = user + "/t" + tip + "/t" + rand + "/r/n";
File.AppendAllText(@"c:/id.txt", res);//相关写入文件
对了,web请求很耗时,呵呵,我的做法是在一个Timer里执行,延时20s。当然,你可以用多线程。至此,一个申请账号的自动化步骤就完成了。
2 所有代码
简单的界面(图7) :
输出的id.txt文件(图 8):
前面是账号,中间是提示信息,后面是验证码。
全部的代码我另外发。
程序中只有createUser主方法和process1分析验证码函数有用,其他的方法是我思考的过程写的一些代码。
J~~~
1 总结
这个想法和工具来源于上周三的晚上我的一个突发的念头。经过两天业余时间的思考,完成和实现了这个注册机。
知识是死的,人是活的。
所以知识需要人来用,才能成为技术。
所以知识要活学活用才能成为技能。
分析现有问题、针对具体情况、把现有的知识综合起来、把现有资源整合到一起,这样才能够解决问题、创造价值。
快到春节了。祝大家春节快乐。
-----The End-----