分享

腾讯的网站如何检测到你的 QQ 已经登录? – 尘埃落定

 root_gao 2013-10-18
2012-07-01 18:04
 
 


在 QQ 已经登录的情况下,手动输入网址打开 QQ 邮箱 或者 QQ 空间 等腾讯网站,可以看到网页已经检测到本地 QQ 客户端已经登录,于是用户可以很方便地一键登录网站而不必再输入用户名密码。这实际上是典型的异构系统单点登录 SSO(single-sign-on)技术。网页怎么会知道我登录的 QQ 号码?腾讯是如何实现的呢?

Qzone_AutoLogin

QQMail_AutoLogin

网上有很多猜测,比如——

  • QQ 登录时在本地某地方存登录 ID 信息(Cookie 或文件),用 js 读,然后去服务器认证。但是现在的浏览器一般有沙箱功能,js 无法读到登录 ID;而且在清空 Cookie 后依然起作用。

  • 以 IP、CPU ID、硬盘 ID 等硬件设备 hash 做唯一标识,QQ 登录时在服务器记录此信息,js 验证。感觉这样依赖环境过多,QQ 不太可能采用此方法。

  • QQ 启动某端口监听,js 连接此端口。但是用 netstat 查看后,QQ 并没有监听端口。

有这么一个神奇的链接,http://login2.qq.com/cgi-bin/qlogin 你一点开,它就检测到你登录了 QQ。通过查看页面源代码,我们可以发现一个关于 ptlogin 的 js 文件,这段代码中,描述了使用 ActivexObject 浏览器插件的过程,于是一切了然。

可是 ActiveX 是 IE 的插件呀,我们使用 Chrome 或者 FireFox 也是可以直接登录的,这是怎么回事呢?

原来,QQ 使用了历史很悠久的 NPAPI(Netscape Plugin Application Programming Interface)接口。NPAPI 几乎支持所有主流浏览器,包括 FireFox、Chrome、Opera(IE 从 5.5 后停止支持 NPAPI,转而使用 ActiveX)。

打开 chrome://plugins/ 我们可以发现自动登录的有关插件,而在路径 C:\Program Files (x86)\Common Files\Tencent\TXSSO 下就可以找到关于 SSO 的相关动态链接库。

Tencent_SSO_plugin

np 插件一般命名都会加np前缀 如 QQ 的这个 npSSOAxCtrlForPTLogin.dll,只要按照标准的写法, 放在浏览器会加载的地方,用的时候写个标签就可以在 js 里面调用了。于是跨浏览器(无视 IE)的插件开发变得相当可行。运行在 NPAPI 插件中的代码拥有当前用户的所有权限,不在沙箱中运行,所以它的扩展程序在被 Chrome 网上应用店接受前要求人工审核。

有点不怀好意的想法开始萌生,我自己的网站能否借用这个插件来检测用户的 QQ 登录呢?写个页面测试一下。

<!DOCTYPE html><html><head><meta charset="utf-8"><title>Tencent SSO Testing</title></head><body><script>var g_vOptData;
var mylocation= "login2.qq.com/cgi-bin1/qlogintest.html";
var pt = {
 ishttps: false,
 low_login: 0,
 keyindex: 9,
 init: function()
 {
  pt.ishttps = /^https/.test(mylocation);
  //if (navigator.mimeTypes["application/nptxsso"]) {
  var B = document.createElement("embed");
  B.type = "application/nptxsso";
  B.style.width = "0px";
  B.style.height = "0px";
  document.body.appendChild(B);
  pt.sso = B
 }
};
pt.init();
 
try {
 if (window.ActiveXObject)
 {
  q_hummerQtrl = new ActiveXObject("SSOAxCtrlForPTLogin.SSOForPTLogin2");
  var A = q_hummerQtrl.CreateTXSSOData();
  q_hummerQtrl.InitSSOFPTCtrl(0, A);
  g_vOptData = q_hummerQtrl.CreateTXSSOData()
 }
 hummer_loaduin();
} catch(B) {
 alert(/create ActiveXObject failed/)
}
 
function hummer_loaduin()
{
 if (window.ActiveXObject)
 {
  var Y = q_hummerQtrl.DoOperation(1, g_vOptData);
  if (null == Y) {
   return
  }
  try
  {
   var T = Y.GetArray("PTALIST");
   var c = T.GetSize();
   var X = "";
   for (var d = 0; d < c; d++)   {    var E = T.GetData(d);    var a = E.GetDWord("dwSSO_Account_dwAccountUin");    var J = "";    var O = E.GetByte("cSSO_Account_cAccountType");    var b = a;    if (O == 1)    {     try     {      J = E.GetArray("SSO_Account_AccountValueList");      b = J.GetStr(0)     } catch(Z) {}    }    var Q = 0;    try {    Q = E.GetWord("wSSO_Account_wFaceIndex")    } catch(Z) {    Q = 0    }    var S = "";    try {    S = E.GetStr("strSSO_Account_strNickName")    } catch(Z) {    S = ""    }    var F = E.GetBuf("bufGTKey_PTLOGIN");    var G = E.GetBuf("bufST_PTLOGIN");    var N = "";    var A = G.GetSize();    for (var W = 0; W < A; W++) {    var B = G.GetAt(W).toString("16");    if (B.length == 1) {    B = "0" + B    }    N += B    }    var M = {     uin: a,     name: b,     type: O,     face: Q,     nick: S,     key: N    };    var str = "QQinfo\r\n"+        "uin:" + M['uin']+"\r\n"+        "name:"+M['name']+"\r\n"+        "type:"+M['type']+"\r\n"+        "face:"+M['face']+"\r\n"+        "nick:"+M['nick']+"\r\n"+        "key:"+M['key']+"\r\n";    alert(str);    q_aUinList[d] = M   }  } catch(Z) {}  } else  {  try {   var M = pt.sso;   var L = M.InitPVA();   if (L != false)   {    var I = M.GetPVACount();    for (var W = 0; W < I; W++)    {     var C = M.GetUin(W);     var D = M.GetAccountName(W);     var K = M.GetFaceIndex(W);     var U = M.GetNickname(W);     var P = M.GetGender(W);     var V = M.GetUinFlag(W);     var f = M.GetGTKey(W);     var R = M.GetST(W);    }    var str = "QQinfo\r\n"+        "uin:" + C +"\r\n"+        "name:"+D+"\r\n"+        "face:"+K +"\r\n"+        "nick:"+U+"\r\n"+        "key:"+f+"\r\n";    alert(str);   }  } catch(Z) {}  } }</script></body></html>

本地打开此页面,create ActiveXObject 失败。腾讯必然在 dll 中就对域名进行了限制,网页是无法篡改的。于是修改本地host文件,加一条:

127.0.0.1 login2.qq.com

再用 login2.qq.com 这个域名去访问本地的这个 html,果然,可以正常拿到 QQ 相关信息。

TencentSsoTesting

好吧,这个截图被我打码打得没啥意义了

PS. Chrome 浏览器自带的开发者工具有一个功能可以格式化被压缩的 js 代码,十分好用。废话不多说,有图说明一切——

pretty_print_js_google_chrome

附xu.js格式化后的代码

function $(A) {
    return document.getElementById(A)
}
$.bom = {query: function(B) {
        var A = window.location.search.match(new RegExp("(/?|&)" + B + "=([^&]*)(&|$)"));
        return !A ? "" : unescape(A[2])
    },getHash: function() {
    }};
var pt = {ishttps: false,low_login: 0,keyindex: 9,init: function() {
        pt.ishttps = /^https/.test(window.location);
        if (navigator.mimeTypes["application/nptxsso"]) {
            var B = document.createElement("embed");
            B.type = "application/nptxsso";
            B.style.width = "0px";
            B.style.height = "0px";
            document.body.appendChild(B);
            pt.sso = B
        }
        try {
            if ($.bom.query("low_login") == "1") {
                pt.low_login = 1;
                $("low_login_box").style.display = "block"
            }
        } catch (A) {
        }
        window.setTimeout(function() {
            ptui_reportAttr(256040, 0.05)
        }, 1000)
    },switchLowLogin: function(A) {
        if (A.checked) {
            $("low_login_hour").disabled = ""
        } else {
            $("low_login_hour").disabled = "disabled"
        }
    }};
pt.init();
STR_QLOGIN = 1;
STR_QLOGIN_OTHER_ERR = 2;
STR_QLOGIN_SELECT_TIP = 3;
STR_QLOGIN_NO_UIN = 4;
STR_QLOGIN_SELECT_OFFLINE = 5;
STR_QLOGINING = 6;
function ptui_mapStr(B) {
    for (i = 0; i < B.length; i++) {
        var A = $(B[i][1]);
        if (A != null) {
            if ("A" == A.nodeName || "U" == A.nodeName || "OPTION" == A.nodeName || "LABEL" == A.nodeName || "P" == A.nodeName) {
                if (A.innerHTML == "") {
                    A.innerHTML = ptui_str(B[i][0])
                }
            } else {
                if ("INPUT" == A.nodeName) {
                    if (A.value == "") {
                        A.value = ptui_str(B[i][0])
                    }
                } else {
                    if ("IMG" == A.nodeName) {
                        A.alt = ptui_str(B[i][0])
                    }
                }
            }
        }
    }
}
function ptui_str(A) {
    A -= 1;
    if (A >= 0 && A < g_strArray.length) {
        return g_strArray[A]
    }
    return ""
}
var g_labelMap = new Array([STR_QLOGIN, "loginbtn"], [STR_QLOGIN_SELECT_TIP, "qlogin_select_tip"]);
ptui_mapStr(g_labelMap);
function getArgs() {
    var B = new Object();
    try {
        var F = location.href.substring(location.href.indexOf("/qlogin?") + 8);
        var E = F.split("&");
        for (var C = 0; C < E.length; C++) {
            var H = E[C].indexOf("=");
            if (H == -1) {
                continue
            }
            var A = E[C].substring(0, H);
            var D = E[C].substring(H + 1);
            D = decodeURIComponent(D);
            B[A] = D
        }
    } catch (G) {
        setTimeout(arguments.callee, 0)
    }
    return B
}
var params = getArgs();
var g_qtarget = params.qtarget;
var g_domain = params.domain;
var g_jumpname = params.jumpname;
var g_param = params.param;
var site = ["qq.com", "paipai.com", "tencent.com", "soso.com", "taotao.com", "tenpay.com", "foxmail.com", "wenwen.com", "3366.com", "imqq.com", "pengyou.com", "qplus.com", "qzone.com", "myapp.com", "kuyoo.cn", "weiyun.com", "wechatapp.com", "51buy.com", "gaopeng.com", "qcloud.com", "qmail.com"];
var flag = false;
for (var i = 0; i < site.length; i++) {
    if (site[i] == g_domain) {
        flag = true
    }
}
if (!flag) {
    g_domain = "qq.com"
}
var q_bInit = false;
var q_hummerQtrl = null;
var g_vOptData = null;
var q_aUinList = new Array();
function ptui_qInit() {
    if (q_bInit) {
        return
    }
    q_bInit = true;
    try {
        if (window.ActiveXObject) {
            q_hummerQtrl = new ActiveXObject("SSOAxCtrlForPTLogin.SSOForPTLogin2");
            var A = q_hummerQtrl.CreateTXSSOData();
            q_hummerQtrl.InitSSOFPTCtrl(0, A);
            g_vOptData = q_hummerQtrl.CreateTXSSOData()
        } else {
        }
        hummer_loaduin();
        if (q_aUinList.length <= 0) {
            msg(ptui_str(STR_QLOGIN_NO_UIN));
            return false
        } else {
            if (ptui_buildUinList) {
                ptui_buildUinList(q_aUinList)
            }
        }
        document.cookie = "ptui_qstatus=2;domain=ptlogin2." + g_domain + ";path=/"
    } catch (B) {
        q_hummerQtrl = null;
        document.cookie = "ptui_qstatus=3;domain=ptlogin2." + g_domain + ";path=/";
        msg(ptui_str(STR_QLOGIN_OTHER_ERR));
        ptui_reportAttr(89217, 0.05)
    }
}
function list() {
    $("qlogin_loading").style.visibility = "hidden";
    if (/^https/g.test(window.location)) {
        $("qlogin_loading").innerHTML = '<img src="https://login2.' + g_domain + '/style.ssl/0/images/load.gif" align="absmiddle" />' + ptui_str(STR_QLOGINING)
    } else {
        $("qlogin_loading").innerHTML = '<img src="http://imgcache.qq.com/ptlogin/v4/style/0/images/load.gif" align="absmiddle" />' + ptui_str(STR_QLOGINING)
    }
    q_bInit = false;
    ptui_qInit();
    if (/^https/g.test(window.location)) {
        return
    }
    if (window.g_time) {
        g_time.time55 = new Date()
    }
    xui_report()
}
function ptui_buildUinList() {
    var G = "";
    var E = $("list_uin");
    if (null == E) {
        return
    }
    var A = q_aUinList.length > 5 ? 5 : q_aUinList.length;
    for (var C = 0; C < A; C++) {
        var F = q_aUinList[C];
        var B = "";
        var D = "";
        if (q_aUinList.length == 1) {
            D = 'style="display:none;"'
        }
        if (C == 0) {
            B = "checked='checked'"
        }
        G += "<li><input type='radio' name='q_uin' id='uin_" + F.uin + "' " + B + D + " /><label for='uin_" + F.uin + "'>" + F.nick.replace(/&/g, "&amp").replace(/</g, "&lt").replace(/>/g, "&gt") + " (" + F.name + ")</label></li>"
    }
    E.innerHTML = G
}
function onQloginSelect() {
    for (var C = 0; C < q_aUinList.length; C++) {
        var D = q_aUinList[C];
        var B = $("uin_" + D.uin);
        if (B != null) {
            if (B.checked) {
                hummer_loaduin();
                var A = hummer_getUinObj(D.uin);
                if (A == null) {
                    msg(ptui_str(STR_QLOGIN_SELECT_OFFLINE), D.uin);
                    return
                }
                $("qlogin_loading").style.visibility = "visible";
                $("loginbtn").className = "btn_gray";
                $("loginbtn").style.color = "gray";
                hummer_login(A, g_domain, g_jumpname, g_param)
            }
        }
    }
}
function hummer_loaduin() {
    q_aUinList.length = 0;
    if (window.ActiveXObject) {
        var Y = q_hummerQtrl.DoOperation(1, g_vOptData);
        if (null == Y) {
            return
        }
        try {
            var T = Y.GetArray("PTALIST");
            var c = T.GetSize();
            var X = "";
            var H = $("list_uin");
            for (var d = 0; d < c; d++) {
                var E = T.GetData(d);
                var a = E.GetDWord("dwSSO_Account_dwAccountUin");
                var J = "";
                var O = E.GetByte("cSSO_Account_cAccountType");
                var b = a;
                if (O == 1) {
                    try {
                        J = E.GetArray("SSO_Account_AccountValueList");
                        b = J.GetStr(0)
                    } catch (Z) {
                    }
                }
                var Q = 0;
                try {
                    Q = E.GetWord("wSSO_Account_wFaceIndex")
                } catch (Z) {
                    Q = 0
                }
                var S = "";
                try {
                    S = E.GetStr("strSSO_Account_strNickName")
                } catch (Z) {
                    S = ""
                }
                var F = E.GetBuf("bufGTKey_PTLOGIN");
                var G = E.GetBuf("bufST_PTLOGIN");
                var N = "";
                var A = G.GetSize();
                for (var W = 0; W < A; W++) {
                    var B = G.GetAt(W).toString("16");
                    if (B.length == 1) {
                        B = "0" + B
                    }
                    N += B
                }
                var M = {uin: a,name: b,type: O,face: Q,nick: S,key: N};
                q_aUinList[d] = M
            }
        } catch (Z) {
        }
    } else {
        try {
            var M = pt.sso;
            var L = M.InitPVA();
            if (L != false) {
                var I = M.GetPVACount();
                for (var W = 0; W < I; W++) {
                    var C = M.GetUin(W);
                    var D = M.GetAccountName(W);
                    var K = M.GetFaceIndex(W);
                    var U = M.GetNickname(W);
                    var P = M.GetGender(W);
                    var V = M.GetUinFlag(W);
                    var f = M.GetGTKey(W);
                    var R = M.GetST(W);
                    q_aUinList[W] = {uin: C,name: D,type: 0,face: K,nick: U,key: R}
                }
                if (typeof (M.GetKeyIndex) == "function") {
                    pt.keyindex = M.GetKeyIndex()
                }
            }
        } catch (Z) {
        }
    }
    switch (q_aUinList.length) {
        case 0:
            ptui_reportAttr(77430, 0.05);
            break;
        case 1:
            ptui_reportAttr(77431, 0.05);
            break;
        default:
            ptui_reportAttr(77432, 0.05)
    }
}
function hummer_getUinObj(B) {
    for (var A = 0; A < q_aUinList.length; A++) {
        var C = q_aUinList[A];
        if (C.uin == B) {
            return C
        }
    }
    return null
}
function unloadpage() {
    document.domain = g_domain;
    try {
        parent.document.body.onbeforeunload = function() {
        };
        parent.document.body.onunload = function() {
        };
        for (var A = 0; A < parent.parent.frames.length; A++) {
            parent.parent.frames[A].onunload = function() {
            };
            parent.parent.frames[A].onbeforeunload = function() {
            }
        }
        if (parent.parent != top) {
            for (var A = 0; A < parent.parent.parent.frames.length; A++) {
                parent.parent.parent.frames[A].onunload = function() {
                };
                parent.parent.parent.frames[A].onbeforeunload = function() {
                }
            }
        }
    } catch (B) {
    }
}
function hummer_login(G, F, A, H) {
    if (A == "") {
        A = "jump"
    }
    var E = (pt.ishttps ? "https://login2." : "http://ptlogin2.") + F + "/" + A + "?";
    var C = $.bom.query("daid");
    var D = $.bom.query("regmaster");
    if (D == 2 && !pt.ishttps) {
        E = "http://ptlogin2.function.qq.com/jump?regmaster=2&"
    } else {
        if (D == 3 && !pt.ishttps) {
            E = "http://ptlogin2.crm2.qq.com/jump?regmaster=3&"
        }
    }
    E += "clientuin=" + G.uin + "&clientkey=" + G.key + "&keyindex=" + pt.keyindex + (C ? "&daid=" + C : "");
    if (pt.low_login == 1 && $("low_login_enable") && $("low_login_enable").checked) {
        E += "&low_login_enable=1&low_login_hour=" + $("low_login_hour").value
    }
    if (H != null && H != "") {
        var B = decodeURIComponent(H);
        if (B.indexOf("#") > -1) {
            B = B.replace(/#/g, "%23")
        }
        E += ("&" + B)
    }
    switch (parseInt(g_qtarget)) {
        case 0:
            unloadpage();
            parent.location.href = E;
            break;
        case 1:
            top.location.href = E;
            break;
        case 2:
            unloadpage();
            parent.parent.location.href = E;
            break;
        default:
            top.location.href = E
    }
}
function msg(A, B) {
    A = '<span style="color:#cc0000;">' + A + '</span><a href="http://support.qq.com/write.shtml?guest=1&fid=713&SSTAG=10011-' + B + '" target="_blank">' + g_strArray[6] + "</a>";
    try {
        var D = $("qlogin_loading");
        if ((D.style.display != "none") && ($("qlogin").style.display != "none")) {
            D.innerHTML = A;
            D.style.display = "";
            D.style.visibility = "visible"
        }
    } catch (C) {
    }
}
function browser_version() {
    var A = navigator.userAgent.toLowerCase();
    return A.match(/msie ([\d.]+)/) ? 2 : A.match(/firefox\/([\d.]+)/) ? 4 : A.match(/chrome\/([\d.]+)/) ? 6 : A.match(/opera.([\d.]+)/) ? 10 : A.match(/version\/([\d.]+).*safari/) ? 13 : 2
}
function xui_speedReport(E) {
    if (pt.isHttps || (window.flag2 && Math.random() > 0.5) || (!window.flag2 && Math.random() > 0.01)) {
        return
    }
    var B = "http://isdspeed.qq.com/cgi-bin/r.cgi?flag1=6000&flag2=1&flag3=" + browser_version();
    var C = 0;
    for (var D in E) {
        if (E[D] < 0 || E[D] > 300000) {
            continue
        }
        B += "&" + D + "=" + E[D];
        C++
    }
    if (C == 0) {
        return
    }
    var A = new Image();
    A.src = B
}
function xui_report() {
    if (Math.random() > 0.5) {
        return
    }
    if (!window.g_time) {
        return
    }
    if (g_time.time50 && g_time.time50 > 0 && g_time.time51 && g_time.time51 > 0 && g_time.time52 && g_time.time52 > 0 && g_time.time53 && g_time.time53 > 0) {
        var A = {};
        A["1"] = g_time.time51 - g_time.time50;
        A["6"] = g_time.time52 - g_time.time50;
        A["2"] = g_time.time54 - g_time.time50;
        A["3"] = g_time.time55 - g_time.time50;
        A["4"] = g_time.time54 - g_time.time53;
        A["5"] = g_time.time55 - g_time.time53
    }
    xui_speedReport(A)
}
function ptui_reportAttr(C, B) {
    if (Math.random() > (B || 1)) {
        return
    }
    url = location.protocol + "//ui.ptlogin2.qq.com/cgi-bin/report?id=" + C;
    var A = new Image();
    A.src = url
}
function pluginBegin() {
}
list();
try {
    $("loginbtn").focus()
} catch (e) {
}
;

参考链接:
http://1.lanz.sinaapp.com/?p=152
http:///articles/2012/02/153.html

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多