QQ农场/校友农场数据格式分析及外挂设计

      前些日子特别爱玩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

QQ农场/校友农场数据格式分析及外挂设计》上有2条评论

  1. 被剑击落

    厉害,这都行,嘿嘿
    你的博客不错,最近在研究PHP方面的一些东西,找到你这里来了,蛮专业的,学习下

评论已关闭。