首先装了一个Fiddler2,还真不错,事半功倍,webqq从登录到获取相关的信息的http全都抓下来了,然后慢慢的分析。
一、webqq的登录过程
1、判断帐号状态。首先要判断QQ号码的状态,是否正常,是否需要使用验证码登录。
http://check.ptlogin2.qq.com/check?appid=1003903&uin=qq号码&r=随机数 |
该请求返回一个字符串:ptui_checkVC('1','5764292b490a0f82694f3f705ce40b2c67f9ec1bb6f4df98', '\x00\x00\x00\x00\x03\x4f\xf1\x29');
或是:ptui_checkVC('0','!GWD', '\x00\x00\x00\x00\x03\x4f\xf1\x29'); 如果第一个参数是1则需要使用验证码,注意,第三个参数需要截取下来,我将它命名为B1,密码加密时需要。如果参数是0,则不需要验证,取出参数1取出,我将它命名为VC,作为后面加密和登录的验证码。
2、获取验证码
http://check.ptlogin2.qq.com/check?appid=1003903&uin=qq号码&r=随机数 |
该请求返回一个二进制流,需要将二进制流转换为图像,显示在窗体上。
3、第一次登录
http://ptlogin2.qq.com/login?u=QQ号码&p=密码&verifycode=验证码&webqq_type=10&remember_uin=1&login2qq=1&aid=1003903&u1=http%3A%2F%2Fweb.qq.com%2Floginproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&h=1&ptredirect=0&ptlang=2052&from_ui=1&pttype=1&dumy=&fp=loginerroralert&action=4-29-45297&mibao_css=m_webqq&t=1&g=1 |
这里密码的加密方式并不是网上所说的三次加密方式,而是采用以下的方式进行:pwd=Md5(Md5(Md5(pwd) + B1) + VC.ToUpper()) 。注意,如果是使用验证码图片登录,则这里的VC要相应的换成你输入的验证码。
这时,会返回一个字符串:ptuiCB('0','0','http://web.qq.com/loginproxy.html?login2qq=1&webqq_type=10','0','登录成功','旭'); 如果登录不成功,则第一个参数不为0。
4、第二次登录
http://d.web2.qq.com/channel/login2 |
这次需要从cookies里取得ptwebqq ,发送一个 r={"status":"","ptwebqq":"ptwebqq","passwd_sig":"","clientid":"66933334"} 过去,再取回vfwebqq和 psessionid 的值。
至此,webqq的登录完成,你就可以完成你想邪恶的事情了。
二、webqq登录的代码实现
1、请求响应类,请求一个http,并返回一个响应。
internal
class HttpHelper
{
private
static CookieContainer cookieContainer =
new CookieContainer();
private
static String refer =
"
http://ui.ptlogin2.qq.com/cgi-bin/login?target=self&style=5&mibao_css=m_webqq&appid=1003903&enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2F%2Fweb.qq.com%2Floginproxy.html&f_url=loginerroralert&strong_login=1&login_state=10&t=20120504001
";
private
static String userAgent =
"
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 1.1.4322; .NET4.0C; .NET4.0E)
";
private
static String accept =
"
*/*
";
private
static String contentType =
"
application/x-www-form-urlencoded; charset=UTF-8
";
private
static Dictionary<
string,
string> m_cookies =
new Dictionary<
string,
string>();
///
<summary>
///
请求http,获得响应
///
</summary>
///
<param name="url">
http地址
</param>
///
<param name="data">
要发送的数据
</param>
///
<returns></returns>
internal
static HttpWebResponse GetResponse(
string url,
byte[] data =
null)
{
var request = (HttpWebRequest)WebRequest.Create(url);
request.ContentType =
"
application/x-www-form-urlencoded; charset=UTF-8
";
request.UserAgent = userAgent;
request.Accept = accept;
request.ContentType = contentType;
request.Referer = refer;
request.CookieContainer = cookieContainer;
if (data !=
null)
{
request.Method =
"
POST
";
request.ContentLength = data.Length;
using (
var stream = request.GetRequestStream())
{
stream.Write(data,
0, data.Length);
}
}
else
{
request.Method =
"
GET
";
}
return (HttpWebResponse)request.GetResponse();
}
///
<summary>
///
从响应获得字符串
///
</summary>
///
<param name="url"></param>
///
<param name="data"></param>
///
<returns></returns>
internal
static
string GetHtml(
string url,
byte[] data =
null)
{
using (
var response = GetResponse(url, data))
{
ProcessCookies(response.Cookies);
using (
var stream = response.GetResponseStream())
{
using (
var sr =
new StreamReader(stream, Encoding.UTF8))
{
return sr.ReadToEnd();
}
}
}
}
///
<summary>
///
从响应获得图像
///
</summary>
///
<param name="url"></param>
///
<returns></returns>
internal
static Image GetImage(
string url)
{
using (
var response = GetResponse(url))
{
ProcessCookies(response.Cookies);
using (
var stream = response.GetResponseStream())
{
return Image.FromStream(stream);
}
}
}
///
<summary>
///
获取指定键的cookies值
///
</summary>
///
<param name="key"></param>
///
<returns></returns>
internal
static
string GetCookie(
string key)
{
if (!m_cookies.ContainsKey(key))
{
return
string.Empty;
}
return m_cookies[key];
}
///
<summary>
///
处理响应的cookies
///
</summary>
///
<param name="cookies"></param>
private
static
void ProcessCookies(CookieCollection cookies)
{
foreach (Cookie cookie
in cookies)
{
if (m_cookies.ContainsKey(cookie.Name))
{
m_cookies[cookie.Name] = cookie.Value;
}
else
{
m_cookies.Add(cookie.Name, cookie.Value);
}
cookieContainer.Add(cookie);
}
}
}
2、请求解析类,对响应进行解析,生成不同的信息对象
internal
class ResponseHelper
{
///
<summary>
///
解析检验用户状态的响应信息,获得是否需要验证码
///
</summary>
///
<param name="str"></param>
///
<returns></returns>
internal
static CheckResponse ParseCheckResponse(
string str)
{
var s = str.Split(
'
\'
');
return
new CheckResponse
{
NeedVerify = s[
1] ==
"
1
",
VerifyCode = s[
3],
VerifyKey = ToBytes(s[
5])
};
}
///
<summary>
///
解析第一次登录的响应信息,获得是否登录成功
///
</summary>
///
<param name="str"></param>
///
<returns></returns>
internal
static LoginRespose ParseLoginResponse(
string str)
{
var s = str.Split(
'
\'
');
return
new LoginRespose
{
Code =
int.Parse(s[
1]),
Message = s[
9]
};
}
///
<summary>
///
解析第二次登录的响应信息,取得vfwebqq和psessionid
///
</summary>
///
<param name="str"></param>
///
<returns></returns>
internal
static LoginContextResponse ParseContextRespones(
string str)
{
return
new LoginContextResponse
{
VfWebQQ = GetParameterValue(str,
"
vfwebqq
"),
SessionId = GetParameterValue(str,
"
psessionid
")
};
}
///
<summary>
///
转换为字节数组表示
///
</summary>
///
<param name="str"></param>
///
<returns></returns>
private
static
byte[] ToBytes(
string str)
{
var bytes =
new
byte[
8];
for (
var i =
0; i <
8; i++)
{
bytes[i] =
byte.Parse(str.Substring((i *
4) +
2,
2), NumberStyles.HexNumber);
}
return bytes;
}
private
static
string GetParameterValue(
string str,
string key)
{
var l = key.Length;
var i = str.IndexOf(key);
if (i == -
1)
{
return
string.Empty;
}
return str.Substring(i + l +
3, str.IndexOf(
'
,
', i + l +
4) - i - l -
3).Replace(
"
\"
",
"");
}
}
internal
class CheckResponse
{
///
<summary>
///
是否需要验证码
///
</summary>
public
bool NeedVerify {
get;
set; }
///
<summary>
///
验证码
///
</summary>
public
string VerifyCode {
get;
set; }
///
<summary>
///
这个该叫什么key?
///
</summary>
public
byte[] VerifyKey {
get;
set; }
}
internal
class LoginRespose
{
///
<summary>
///
登录返回码
///
</summary>
public
int Code {
get;
set; }
///
<summary>
///
登录信息
///
</summary>
public
string Message {
get;
set; }
}
internal
class LoginContextResponse
{
public
string VfWebQQ {
get;
set; }
public
string SessionId {
get;
set; }
}
}
3、密码加密类,对QQ密码进行加密
internal
class MD5Helper
{
///
<summary>
///
连接两个字节数组
///
</summary>
///
<param name="b1"></param>
///
<param name="b2"></param>
///
<returns></returns>
private
static
byte[] JoinBytes(
byte[] b1,
byte[] b2)
{
var b3 =
new
byte[b1.Length + b2.Length];
Array.Copy(b1, b3, b1.Length);
Array.Copy(b2,
0, b3,
16, b2.Length);
return b3;
}
///
<summary>
///
将字符串加密为字节数组
///
</summary>
///
<param name="input"></param>
///
<returns></returns>
private
static
byte[] Md5ToArray(
string input)
{
return MD5.Create().ComputeHash(Encoding.Default.GetBytes(input));
}
///
<summary>
///
加密字符串,并转换十六进制表示的字符串
///
</summary>
///
<param name="input"></param>
///
<returns></returns>
private
static
string Md5(
string input)
{
var buffer = MD5.Create().ComputeHash(Encoding.Default.GetBytes(input));
var builder =
new StringBuilder();
for (
var i =
0; i < buffer.Length; i++)
{
builder.Append(buffer[i].ToString(
"
X2
"));
}
return builder.ToString();
}
///
<summary>
///
对一个字节数组加密,并转换十六进制表示的字符串
///
</summary>
///
<param name="input"></param>
///
<returns></returns>
private
static
string Md5(
byte[] input)
{
var buffer = MD5.Create().ComputeHash(input);
var builder =
new StringBuilder();
for (
var i =
0; i < buffer.Length; i++)
{
builder.Append(buffer[i].ToString(
"
X2
"));
}
return builder.ToString();
}
///
<summary>
///
对密码进行加密
///
</summary>
///
<param name="password">
QQ密码
</param>
///
<param name="vcode">
第一次检验用户状态时获取的key
</param>
///
<param name="verifyCode">
验证码
</param>
///
<returns></returns>
internal
static
string Md5(
string password,
byte[] vcode,
string verifyCode)
{
var b1 = Md5ToArray(password);
var s1 = Md5(JoinBytes(b1, vcode));
return Md5(s1 + verifyCode);
}
}
4、QQ辅助类,呵呵,客户端类了
internal
class QQHelper
{
private Random m_ran =
new Random();
private
byte[] m_bytes;
private LoginContextResponse m_context;
internal QQHelper(
string number)
{
Number = number;
}
///
<summary>
///
QQ号码
///
</summary>
internal
string Number {
get;
set; }
///
<summary>
///
登录到webqq
///
</summary>
///
<param name="password">
密码
</param>
///
<param name="verifyCode">
验证码
</param>
///
<returns></returns>
internal LoginStatus Login(
string password,
string verifyCode =
null)
{
if (
string.IsNullOrEmpty(verifyCode))
{
var url =
string.Format(
"
http://check.ptlogin2.qq.com/check?appid=1003903&uin={0}&r={1}
", Number, GetRandomNumber());
var vc = ResponseHelper.ParseCheckResponse(HttpHelper.GetHtml(url));
verifyCode = vc.VerifyCode;
m_bytes = vc.VerifyKey;
if (vc.NeedVerify)
{
return LoginStatus.NeedVerify;
}
}
var pwd = MD5Helper.Md5(password, m_bytes, verifyCode);
var loginUrl =
string.Format(
"
http://ptlogin2.qq.com/login?u={0}&p={1}&verifycode={2}&webqq_type=10&remember_uin=1&login2qq=1&aid=1003903&u1=http%3A%2F%2Fweb.qq.com%2Floginproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&h=1&ptredirect=0&ptlang=2052&from_ui=1&pttype=1&dumy=&fp=loginerroralert&action=4-29-45297&mibao_css=m_webqq&t=1&g=1
", Number, pwd, verifyCode);
var login = ResponseHelper.ParseLoginResponse(HttpHelper.GetHtml(loginUrl));
if (login.Code ==
0)
{
Login2();
return LoginStatus.Successd;
}
throw
new Exception(login.Message);
}
///
<summary>
///
第二次登录
///
</summary>
private
void Login2()
{
var url =
"
http://d.web2.qq.com/channel/login2
";
var data =
"
r={\"status\":\"\",\"ptwebqq\":\"
" + HttpHelper.GetCookie(
"
ptwebqq
") +
"
\",\"passwd_sig\":\"\",\"clientid\":\"66933334\"}
";
m_context = ResponseHelper.ParseContextRespones(HttpHelper.GetHtml(url, Encoding.UTF8.GetBytes(data)));
}
///
<summary>
///
获取验证码图片
///
</summary>
///
<returns></returns>
internal Image GetVerifyImage()
{
var url =
"
http://captcha.qq.com/getimage?aid=1003903&uin=
" + Number +
"
&r=
" + GetRandomNumber();
return HttpHelper.GetImage(url);
}
private
string GetRandomNumber()
{
return m_ran.NextDouble().ToString();
}
}
internal
enum LoginStatus
{
NeedVerify,
Successd,
Faild
}