g_tk只是QQ空间对日志进行操作的时候,所采取的一套安全机制,如果g_tk字符串的值不对的话,请求是没有办法提交的,因此,很多刚刚涉及HTTP协议技术的人想对QQ空间这尊大佛动手脚的话,只能望而却步。下面我以VB为例,在这里详解一下g_tk的计算方法。
其实g_tk校验是通过skey值来算出来的,弄过QQ登录的人可能都知道,在登录成功之后,cookies里都会返回skey值,通常是以@开头,并且带有一串看似无规则的大小写字母混合,总共10位。下面我们先来抓包看看,g_tk到底用在了哪里,我们以转载日志为例来抓包,
POST /cgi-bin/blognew/blog_quote HTTP/1.1
Accept: */*
Accept-Language: zh-cn
Referer: http://b.qzone.qq.com/proxy.html
If-Modified-Since: 0
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; Tablet PC 2.0)
Host: b.qzone.qq.com
Content-Length: 65
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: pt2gguin=o0138001655; ptcz=0b25a27219dd08bcfe38fc85365593dadb1a2a99cac9f1abfd5fb31a7052f89b; pvid=6724688319; flv=10.0; adid=138001655; adSP=GHTsOtSHTIJdDIr9+GXVoaFY59pet/LONpbU1rA0yPY=_837_326830_1290874683_; adVer=3121; ac=1,030,006; ptui_qstatus=2; uin=o0138001655; skey=@sZmfEEBdt; ptisp=ctc; ssid=s8226120880; login_time=B46BD5B3A93F9EC5226847DB4AE9A71589641475FCCEBBC9; __Q_w_s__appDataSeed=1; randomSeed=220115
uin=138001655&fromuin=715746717&blogid=1286714133&g_tk=1423927145
复制代码
我们可以看到,数据包主体部分最后一个参数就是g_tk值,一般是一串数字。那这个值到底怎么算出来的呢?
因为我们在网页登录QQ的时候,腾讯都会通过cookies里的skey值来计算,用js来算。既然在运算的时候执行了js脚本,那么我们就可以在抓包中获得。那g_tk是通过什么算法算出来的?其实很简单,当我们得到skey后,循环取单字符的二进制并取左值.累加之后就得到后面的g_tk值了,这听上去很复杂,不过算法不用我们自己写,我们只需要执行在腾讯网页登录的时候所执行的那个js脚本就可以了。当然,js不能直接调用,不过既然我写了这篇文章,就已经是有备而来的,js算法我已经整理并写了一个最简单的,代码如下:
function getGTK(str){
var hash = 5381;
for(var i = 0, len = str.length; i < len; ++i){
hash += (hash << 5) + str.charAt(i).charCodeAt();
}
return hash & 0x7fffffff;
} 复制代码
那么我们现在还有两个问题没有解决:
1.如何获取登录后的cookies?
2.如何在VB中执行js代码并得到返回值?
上面两个问题其实到了你们手里,我相信也不会是问题了,下面我再通过代码以及讲解,来剖析并解决这两个所谓的问题。对于HTTP数据包POST/GET,相信看这篇文章的人应该都懂得吧,否则你看了也没用,那么我们可以设计一个登录程序,并在登录之后获取cookies中的skey值,并计算出g_tk。
主界面代码如下:(frmLogin.frm)
'-
' - 腾讯QQ空间g_tk算法
' - 作者:泡面 (QQ138001655 email:admin@mafom.com)
' - 日期:2010/11/28
'-
' - 本源码来自源始时代(http://www.codeages.com),转载时请保留此信息!
' - 本程序仅供学习、交流用,下载后请于24小时内删除,使用所带来的后果概不负责!
'-
Option Explicit
'wininet提供的API函数,用于获取cookies
Private Declare Function InternetGetCookie Lib "wininet.dll" Alias "InternetGetCookieA" (ByVal lpszUrlName As String, ByVal lpszCookieName As String, ByVal lpszCookieData As String, lpdwSize As Long) As Boolean
Private Sub cmdCancel_Click()
End
End Sub
Private Sub cmdLogin_Click()
On Error GoTo hErr
If Len(txtQQNumber.Text) = 0 Then
MsgBox "请输入QQ号码!", vbInformation, "提示"
txtQQNumber.SetFocus
Exit Sub
End If
If Len(txtPassword.Text) = 0 Then
MsgBox "请输入QQ密码!", vbInformation, "提示"
txtPassword.SetFocus
Exit Sub
End If
If Len(txtVlCode.Text) = 0 Then
MsgBox "请输入验证码!", vbInformation, "提示"
txtVlCode.SetFocus
Exit Sub
End If
cmdLogin.Enabled = False
ScriptControl1.Language = "Jscript"
ScriptControl1.Timeout = -1
ScriptControl1.AddCode txtVarHexcase.Text
Dim QQPass As String
Dim retString As String
'对QQ密码进行加密,否则服务器不会通过
QQPass = ScriptControl1.Run("md5", ScriptControl1.Run("md5_3", txtPassword.Text) + UCase(txtVlCode.Text))
'发送登录请求
Inet1.Execute "http://ptlogin2.qq.com/login", "POST", "u=" & txtQQNumber.Text & "&p=" & QQPass & "&verifycode=" & txtVlCode.Text & "&aid=15000101&u1=http%3A%2F%2Fimgcache.qq.com%2Fqzone%2Fv5%2Floginsucc.html%3Fpara%3Dizone&fp=loginerroralert&h=1&ptredirect=1&ptlang=0&from_ui=1&dumy=", "CONTENT-TYPE : application/x-www-form-urlencoded"
Do While Inet1.StillExecuting
DoEvents
Loop
'取得返回数据
Dim BinBuff() As Byte
BinBuff() = Inet1.GetChunk(0, icByteArray)
retString = UTF8_Decode(BinBuff())
'判断登录状态
If InStr(retString, "QQ社区登录") Then
MsgBox "登录成功!", vbInformation, "提示"
Dim nLen As Long
Dim sBuff As String * 1024
nLen = 1024
'获取cookies
InternetGetCookie "http://ptlogin2.qq.com/login", vbNullString, sBuff, nLen
'获取skey值
Dim skey As String
Dim sPos As Long
sPos = InStr(sBuff, "skey=@")
If sPos <> 0 Then
skey = Mid(sBuff, sPos + 5, 10)
MsgBox "从cookies获取到的skey值:" & skey
End If
'执行js脚本,计算g_tk值
Dim js(6) As String
Dim g_tk As String
js(0) = "function getGTK(str){" & vbCrLf
js(1) = "var hash = 5381;" & vbCrLf
js(2) = "for(var i = 0, len = str.length; i < len; ++i){" & vbCrLf
js(3) = " hash += (hash << 5) + str.charAt(i).charCodeAt();" & vbCrLf
js(4) = "}" & vbCrLf
js(5) = " return hash & 0x7fffffff;" & vbCrLf
js(6) = "}"
ScriptControl1.AddCode js(0) & js(1) & js(2) & js(3) & js(4) & js(5) & js(6)
g_tk = ScriptControl1.Run("getGTK", skey)
MsgBox "计算出的g_tk值:" & g_tk
Else
If InStr(retString, "您输入的验证码有误,请重试。") <> 0 Then
MsgBox "您输入的验证码有误,请重试!", vbExclamation, "提示"
ElseIf InStr(retString, "您输入的密码有误,请重试。") <> 0 Then
MsgBox "您输入的密码有误,请重试!", vbExclamation, "提示"
ElseIf InStr(retString, "您的QQ号码存在安全隐患") <> 0 Then
MsgBox "您的QQ号码存在安全隐患!", vbExclamation, "提示"
Else
MsgBox "登录失败,请检查您的密码是否正确!", vbExclamation, "提示"
End If
End If
cmdLogin.Enabled = True
Exit Sub
hErr:
MsgBox "错误:" & Err.Number & vbCrLf & vbCrLf & Err.Description, vbExclamation, "错误"
Exit Sub
End Sub
'获取验证码
Sub GetCode()
On Error Resume Next
Dim Buff() As Byte
'腾讯最新登录接口,验证码已经升级为5位,请求验证码的时候必须要加上QQ号码
Inet1.URL = "http://captcha.qq.com/getimage?aid=46000101&r=0.03652396363445809&uin=" & txtQQNumber.Text & "&vc_type=063620256136860e997ba2ca06c3c10a43c1f346db8e9d98"
Buff() = Inet1.OpenURL(, icByteArray)
With picVlCode
.Picture = PictureFromBits(Buff()) '直接得到Picture对象
.PaintPicture .Picture, 0, 0, .Width, .Height, 0, 0, .ScaleWidth, .ScaleHeight
End With
End Sub
Private Sub Form_Load()
Me.Show
GetCode
txtQQNumber.SetFocus
End Sub 复制代码
模块代码:(mdlALG)
'二进制转UTF8
Declare Function MultiByteToWideChar _
Lib "kernel32" (ByVal CodePage As Long, _
ByVal dwFlags As Long, _
ByVal lpMultiByteStr As Long, _
ByVal cchMultiByte As Long, _
ByVal lpWideCharStr As Long, _
ByVal cchWideChar As Long) As Long
Public Enum CBoolean
CFalse = 0
CTrue = 1
End Enum
Private Const S_OK = 0
Private Declare Function CreateStreamOnHGlobal _
Lib "ole32" (ByVal hGlobal As Long, _
ByVal fDeleteOnRelease As CBoolean, _
ppstm As Any) As Long
Private Declare Function OleLoadPicture _
Lib "olepro32" (pStream As Any, _
ByVal lSize As Long, _
ByVal fRunmode As CBoolean, _
riid As GUID, _
ppvObj As Any) As Long
Public Type GUID
dwData1 As Long
wData2 As Integer
wData3 As Integer
abData4(7) As Byte
End Type
Private Declare Function CLSIDFromString _
Lib "ole32" (ByVal lpsz As Any, _
pclsid As GUID) As Long
Private Const sIID_IPicture = "{7BF80980-BF32-101A-8BBB-00AA00300CAB}"
Private Const GMEM_MOVEABLE = &H2
Private Declare Function GlobalAlloc _
Lib "kernel32" (ByVal uFlags As Long, _
ByVal dwBytes As Long) As Long
Private Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Function GlobalFree Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Sub MoveMemory _
Lib "kernel32" _
Alias "RtlMoveMemory" (pDest As Any, _
pSource As Any, _
ByVal dwLength As Long)
Public Function PictureFromBits(abPic() As Byte) As IPicture
Dim nLow As Long
Dim cbMem As Long
Dim hMem As Long
Dim lpMem As Long
Dim IID_IPicture As GUID
Dim istm As stdole.IUnknown
Dim ipic As IPicture
On Error GoTo Out
nLow = LBound(abPic)
On Error GoTo 0
cbMem = (UBound(abPic) - nLow) + 1
hMem = GlobalAlloc(GMEM_MOVEABLE, cbMem) '分配可移动的内存
If hMem Then
lpMem = GlobalLock(hMem)
If lpMem Then
MoveMemory ByVal lpMem, abPic(nLow), cbMem
Call GlobalUnlock(hMem)
If (CreateStreamOnHGlobal(hMem, CTrue, istm) = S_OK) Then
If (CLSIDFromString(StrPtr(sIID_IPicture), IID_IPicture) = S_OK) Then
Call OleLoadPicture(ByVal ObjPtr(istm), cbMem, CFalse, IID_IPicture, PictureFromBits)
End If
End If
End If
End If
Out:
End Function
Public Function UTF8_Decode(bUTF8() As Byte) As String '二进制解析为UTF8
Dim lRet As Long
Dim lLen As Long
Dim lBufferSize As Long
Dim sBuffer As String
Dim bBuffer() As Byte
lLen = UBound(bUTF8) + 1
If lLen = 0 Then Exit Function
lBufferSize = lLen * 2
sBuffer = String$(lBufferSize, Chr(0))
lRet = MultiByteToWideChar(65001, 0, VarPtr(bUTF8(0)), lLen, StrPtr(sBuffer), lBufferSize)
If lRet <> 0 Then
sBuffer = Mid(sBuffer, 1, lRet)
End If
UTF8_Decode = sBuffer
End Function 复制代码