前些日子特别爱玩QQ农场,因为学校这边十点半以后就断网了,所以收菜不及时损失惨重……买狗粮花钱,这不好,想想有什么解决方案没有……手机,手机一直可以上网。听说QQ有手机农场,但是正在内测,很不幸我黄钻0级用不了……考虑到QQ农场也是B/S模型的,也算是Web开发的领域,所以决定自己写一个手机农场的接口吧。
JAVA和C++写手机客户端肯定不行,我都不怎么会,但是PHP还是很不错的。打算做一个类似代理服务器的东西,在服务器上与QQ服务器通讯,转成WAP2.0与手机交互。
第一步就是实现登录。从通常的验证码机制来看,都是登录页写一个验证码的cookie key,然后提交时验证。我使用Firefox + Firebug + FireCookie 用来探测。浏览QQ空间登录页,发现用户名、密码文本框,还有其他一些隐藏域。这些东西都是要提交给QQ验证服务器的验证码Key是在验证图片的HTTP头里面加入的。这样,第一步就是先捕获验证码。这个比较容易,用上次写的PHP的xmlhttp类即可轻松实现。捕获图片,从HTTP头中读取Set-Cookie,将验证码的Key读取出来,写入本地Cookie或者Session中。
[codesyntax lang=”php”]
/*
*验证码代理
*/
require_once ‘./inc/http.class.php’;
$verifycodeURL = ‘http://ptlogin2.qq.com/getimage?aid=’.rand();
$objhttp = new XmlHttpRequest();
$objhttp->open(“GET”, $verifycodeURL);
$objhttp->setRequestHeader(“User-Agent”, ‘Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/4.0; SLCC2; MAXTHON 2.0)’);
$objhttp->setRequestHeader(“Referer”, ‘http://qzone.qq.com/’);
$objhttp->send();
if ($objhttp->readyState == 4) {
$responseCookie = $objhttp->getResponseHeader(“Set-Cookie”);
$arr_CookieItem = explode(“;” ,$responseCookie[0]);
$responseCookie = $arr_CookieItem[0];
$arr_CookieItem = explode(“=” ,$responseCookie);
$responseCookie = trim($arr_CookieItem[1]);
setcookie(“qq_verifycode”, $responseCookie);
header(“Content-Type: image/jpeg”);
echo $objhttp->responseContent;
exit();
}
unset($objhttp);
?>
[/codesyntax]
然后分析都有什么东西以什么格式提交给腾讯登录服务器。用firebug很容易就可以看到了。参数列表如下
(OmniPeek捕获,图片借用下Koma博客的相关文章,特此感谢~)
这时候我们发现密码被加密了,而且每次提交都不一样。从长度和格式来看,应该是MD5加密。去网上查找下资料,说这里的密码加密方式是三次MD5 + 验证码,再MD5。于是我用PHP写了相关函数,但是测试……不一样,和腾讯加密出来的还是不一样。无语了……腾讯的MD5加密函数肯定重写过了…于是我打开相关的JS文件研究……被压缩过了,可读性非常差。本来我算法就不好,这一下更没耐心看了。怎么办?网上出主意是用VC调用JS,但是PHP调用JS也太天方夜谭了……等,ASP好像能同时使用两种语言VBS和JS,太好了,拿来主义直接用。复制http://imgcache.qq.com/ptlogin/js/comm.js,小修改一下,OK,测试通过~终于登录成功了~~
[codesyntax lang=”vb”]
Sub Login()
userName = Trim(Request.Form(“u”))
passWord = Trim(Request.Form(“p”))
verifyCode = Trim(Request.Form(“verifycode”))
loginTo = Trim(Request.Form(“loginto”))
Response.Cookies(“loginto”) = loginTo
passWord = qqmd5(passWord, verifyCode)
postData = “u=” & userName & “&p=” & passWord & “&verifycode=” & verifyCode &_
“&aid=15000102&u1=http%3A%2F%2Fxiaoyou.qq.com%2Findex.php%3Fmod%3Dlogin&fp=&h=1&ptredirect=1&ptlang=0&from_ui=1&dumy=1”
loginURL = “http://ptlogin2.qq.com/login”
Set objhttp = Server.CreateObject(“MSXML2.XMLHTTP”)
objhttp.open “POST”, loginURL, False
objhttp.setRequestHeader “User-Agent”, “Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2b2) Gecko/20091108 Firefox/3.6b2”
objhttp.setRequestHeader “Accept”, “text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8”
objhttp.setRequestHeader “Accept-Language”, “zh-cn,zh;q=0.5”
objhttp.setRequestHeader “Accept-Encoding”, “gzip,deflate”
objhttp.setRequestHeader “Accept-Charset”, “GB2312,utf-8;q=0.7,*;q=0.7”
objhttp.setRequestHeader “Referer”, “http://ui.ptlogin2.qq.com/cgi-bin/login”
objhttp.setRequestHeader “Cookie”, Session(“qqCookies”)
objhttp.send postData
If objhttp.readyState = 4 Then
responseContent = objhttp.responseText
If InStr(responseContent, “验证码”) > 0 Then
Response.Write(“您输入的验证码有误,请返回重新输入
“)
Response.Write(“返回登录页“)
ElseIf InStr(responseContent, “密码”) > 0 Then
Response.Write(“您输入的密码码有误,请返回重新输入
“)
Response.Write(“返回登录页“)
ElseIf InStr(responseContent, “帐号”) > 0 Then
Response.Write(“您输入的帐号有误,请返回重新输入
“)
Response.Write(“返回登录页“)
ElseIf InStr(responseContent, “自动跳转”) > 0 Then
responseHeaders = objhttp.getAllResponseHeaders
Call saveCookie(responseHeaders)
strVisitURL = “http://user.qzone.qq.com/” & userName
Set objhttp2 = Server.CreateObject(“MSXML2.XMLHTTP”)
objhttp2.open “GET”, strVisitURL, False
objhttp2.setRequestHeader “User-Agent”, “Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2b2) Gecko/20091108 Firefox/3.6b2”
objhttp2.setRequestHeader “Accept”, “text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8”
objhttp2.setRequestHeader “Accept-Language”, “zh-cn,zh;q=0.5”
objhttp2.setRequestHeader “Accept-Encoding”, “gzip,deflate”
objhttp2.setRequestHeader “Accept-Charset”, “GB2312,utf-8;q=0.7,*;q=0.7”
objhttp2.setRequestHeader “Referer”, “http://imgcache.qq.com/qzone/v5/loginsucc.html?para=izone”
objhttp2.setRequestHeader “Cookie”, Session(“qqCookies”)
objhttp2.send
If objhttp2.readyState = 4 Then
Call saveCookie(objhttp2.getAllResponseHeaders)
End If
Set objhttps = Nothing
Response.Write(“登录成功!
“)
Response.Write(“进入我的农场“)
Else
Response.Write(“登录失败:未知的错误,您可以尝试重新登录.
“)
Response.Write(“返回登录页“)
End If
End If
Set objhttp = Nothing
End Sub
[/codesyntax]
对于Cookie的处理,我单独写了个过程用于保存服务器发来的Cookie,也贴出来,这个修改下可以复用的~
[codesyntax lang=”vb”]
‘保存Cookies
Sub saveCookie(strHeader)
Set regEx = New RegExp
regEx.Pattern = “Set-Cookie: (.*)”
regEx.IgnoreCase = True
regEx.Global = True
Set Matches = regEx.Execute(strHeader)
For Each Match in Matches
strTemp = strTemp & Match.Value
Next
strTemp = Replace(strTemp, “Set-Cookie: “, “”)
arrTemp = Split(strTemp, “;”)
For i = 0 to Ubound(arrTemp)
arrCookieItem = Split(arrTemp(i), “=”)
strCookieName = Trim(arrCookieItem(0))
If strCookieName <> “PATH” And strCookieName <> “DOMAIN” And strCookieName <> “EXPIRES” Then
strAllCookie = strAllCookie & arrTemp(i) & “; “
End If
Next
strAllCookie = Trim(Replace(strAllCookie, vbcr, “”))
If IsEmpty(Session(“qqCookies”)) Then
Session(“qqCookies”) = strAllCookie
Else
Session(“qqCookies”) = Session(“qqCookies”) & ” ” & strAllCookie
End If
End Sub
[/codesyntax]
登录成功,我们就可以获取数据了。首先用FireBug探测自己的农场,发现接口如下
校友:http://happyfarm.xiaoyou.qq.com/
QZONE: http://happyfarm.qzone.qq.com/
读取我的菜地信息: api.php?mod=user&act=run
读取好友菜地信息:http://nc.xiaoyou.qq.com/cgi-bin/cgi_farm_index?mod=user&act=run&ownerId=好友ID
http://nc.qzone.qq.com/cgi-bin/cgi_farm_index?mod=user&act=run&ownerId=好友ID
读取好友列表: api.php?mod=friend
读取好友菜地状态列表: api.php?mod=friend&act=getListStatus
收菜: api.php?mod=farmlandstatus&act=harvest
POST place=菜地编号,0开始&ownerId=主人ID&farmTime=服务器时间,Unix时间戳&farmKey=农场Key
其他的相关接口大家可以自己用Firebug去探测,我就不贴出来了~都说了就没意思了~~嘿嘿
返回的数据格式是JSON的,这里只说几个关键的地方
1.ASP使用JSON。ASP支持JS作为脚本语言,所以干脆全部都用JS写就行了,JS原生支持JSON,VB使用JSON相当麻烦而且受限,不建议使用
2.返回菜地信息JSON,里面用a、b等等表述的,我说明下
a:种子的编号
b:地的状态,1表示有植物在种
c:曾经是否有草
d:曾经是否有虫子
e:曾经是否干旱
f:大于0有草
g:大于0有虫子
h:等于0干旱
i:优秀程度
j:采摘的次数
l:大于0叶,最小能偷多少?这个不能太确认
m:大于0时,表示还剩下多少个
n:偷过我果实的好友uid列表
0:施肥的次数
p:
q:作物播种时间点
r:更新时间点
3.关于关键的farmkey。这个farmkey在一定程度上增加了分析的难度,加密算法不可见。当然有强人反编译了flash,得出了这个farmkey获取的方式。注意farmTime和farmkey必须保持一致
[codesyntax lang=”vb”]
‘获取Unix时间
Function GetUnixTime()
GetUnixTime = DateDiff(“s”, “1970-1-1 8:00:00”, Now()) – 5
End Function
‘获取FarmKey
Function GetFarmKey(intUnixTime)
intUnixTime = Int(intUnixTime)
GetFarmKey = nmd5(intUnixTime & mid(“密码……自己去百度下” , intUnixTime mod 10 + 1, 20), 32)
End Function
[/codesyntax]
另外,腾讯有防外挂机制,所以HTTP头要尽量模拟像一点,但腾讯并不是依靠HTTP头和Cookie来确定是不是外挂的,而是偷菜次数、频繁性等等,当你有外挂嫌疑时,腾讯就会拉取验证码,如果没有正常输入验证码,腾讯就有可能判断为外挂。
最后嘱咐一点,搞外挂可以,但是不要影响游戏的公平性,本文的目的不是教唆犯罪,嘿嘿,目的是为了研究技术,学习腾讯先进的机制。
本文绝无侵犯腾讯权利的意思,请大家不要将本文提到的技术用于非法用途,本人不承担一切因本文带来的后果、责任或连带责任。
最后附上参考文献
QQ农场数据分析 http://blog.csdn.net/szlanny/archive/2009/10/16/4683968.aspx
QQ农场外挂开发实践 http://wz.csdn.net/url/1549646/
QQ空间、校友农场外挂源代码 http://cjxhd.blog.163.com/blog/static/76989692009818530317/
VC调用JavaScript http://blog.csdn.net/wangningyu/archive/2009/10/19/4699619.aspx
关于qq农场登陆的密码加密问题 http://hi.baidu.com/lihn1987/blog/item/7511f0d04ec109da562c846d.html
WEBQQ网页登陆QQ的HTTP接口研究 http://hi.baidu.com/%B8%D6%B1%CA%BF%F1%B2%DD/blog/item/c5c094093b4a59c73ac7631b.html
怀疑你是不是写软件的?
厉害,这都行,嘿嘿
你的博客不错,最近在研究PHP方面的一些东西,找到你这里来了,蛮专业的,学习下