淺析 天涯論壇 回復驗證策略


發帖沒多久,算法就更新了,就算我重新分析,人家依然會更新,所以還是自己學着分析吧。

對於現在 POST 技術滿天飛的時代,防機器人確實是很頭疼的一件事情,類似流量精靈這樣的東西,他可以做到 100% 的真實信息,大批量的訪問。
當然今天不談這些,只是分析下 天涯論壇 回復時的驗證策略。

昨天談到 packer 壓縮,今天我們來看個實例吧。
http://bbs.tianya.cn/m/reply.jsp?item=funinfo&id=4339425
這個是天涯論壇手機端的回復帖子頁面,里面有一個關於回復驗證的js,就是用的 packer壓縮。
http://static.tianyaui.com/global/ty/util/TY.util.userAction.js?v=201404111018

真心不知道他們是怎么想的,1秒還原大法。。。

jQuery(function() {
    function i(B) {
        var D, l, n, C = document.cookie.substr(document.cookie.indexOf("&id=") + "&id=".length);
        C = C.substr(0, C.indexOf("&")), C = "" == C ? 8980291 : C, D = jQuery(B.target), l = (new Date).getTime(), n = "focusout" == B.type ? "blur" : "focusin" == B.type ? "focus" : B.type, n = n.replace("key", "").substring(0, 1), 0 == c && (c = l), d >= f && (d = 2), a["c" == n ? 1 : "f" == n ? 0 : d++] = n + e+++"." + ("c" == n ? B.pageX + ":" + B.pageY : "b" == n ? d : "f" == n ? c : B.which) + "." + (l - ("c" == n || "f" == n ? c : b)), b = l, ("b" == n || 17 == B.which) && (v = a.join(",") + "|" + k(a.join(",") + a[0] + C) + "|" + k(D.val() + a[0]) + "|" + navigator.userAgent + "|v2", 0 == jQuery("#" + h).length ? jQuery('<input type="hidden" id="' + h + '" name="action" value="' + v + '" />').insertAfter(D) : jQuery("#" + h).val(v))
    }

    function k(l) {
        return o(m(p(l)))
    }

    function m(l) {
        return s(t(r(l), 8 * l.length))
    }

    function o(D) {
        var l, n, B, C;
        try {} catch (E) {
            j = 0
        }
        for (l = j ? "0123456789ABCDEF" : "0123456789abcdef", n = "", C = 0; C < D.length; C++) {
            B = D.charCodeAt(C), n += l.charAt(15 & B >>> 4) + l.charAt(15 & B)
        }
        return n
    }

    function p(C) {
        for (var n, B, D = "", l = -1; ++l < C.length;) {
            n = C.charCodeAt(l), B = l + 1 < C.length ? C.charCodeAt(l + 1) : 0, n >= 55296 && 56319 >= n && B >= 56320 && 57343 >= B && (n = 65536 + ((1023 & n) << 10) + (1023 & B), l++), 127 >= n ? D += String.fromCharCode(n) : 2047 >= n ? D += String.fromCharCode(192 | 31 & n >>> 6, 128 | 63 & n) : 65535 >= n ? D += String.fromCharCode(224 | 15 & n >>> 12, 128 | 63 & n >>> 6, 128 | 63 & n) : 2097151 >= n && (D += String.fromCharCode(240 | 7 & n >>> 18, 128 | 63 & n >>> 12, 128 | 63 & n >>> 6, 128 | 63 & n))
        }
        return D
    }

    function r(n) {
        var l, B = Array(n.length >> 2);
        for (l = 0; l < B.length; l++) {
            B[l] = 0
        }
        for (l = 0; l < 8 * n.length; l += 8) {
            B[l >> 5] |= (255 & n.charCodeAt(l / 8)) << l % 32
        }
        return B
    }

    function s(n) {
        var l, B = "";
        for (l = 0; l < 32 * n.length; l += 8) {
            B += String.fromCharCode(255 & n[l >> 5] >>> l % 32)
        }
        return B
    }

    function t(E, F) {
        var G, H, I, J, l, n, B, C, D;
        for (E[F >> 5] |= 128 << F % 32, E[(F + 64 >>> 9 << 4) + 14] = F, G = 1732584193, H = -271733879, I = -1732584194, J = 271733878, l = 0; l < E.length; l += 16) {
            n = G, B = H, C = I, D = J, G = w(G, H, I, J, E[l + 0], 7, -680876936), J = w(J, G, H, I, E[l + 1], 12, -389564586), I = w(I, J, G, H, E[l + 2], 17, 606105819), H = w(H, I, J, G, E[l + 3], 22, -1044525330), G = w(G, H, I, J, E[l + 4], 7, -176418897), J = w(J, G, H, I, E[l + 5], 12, 1200080426), I = w(I, J, G, H, E[l + 6], 17, -1473231341), H = w(H, I, J, G, E[l + 7], 22, -45705983), G = w(G, H, I, J, E[l + 8], 7, 1770035416), J = w(J, G, H, I, E[l + 9], 12, -1958414417), I = w(I, J, G, H, E[l + 10], 17, -42063), H = w(H, I, J, G, E[l + 11], 22, -1990404162), G = w(G, H, I, J, E[l + 12], 7, 1804603682), J = w(J, G, H, I, E[l + 13], 12, -40341101), I = w(I, J, G, H, E[l + 14], 17, -1502002290), H = w(H, I, J, G, E[l + 15], 22, 1236535329), G = x(G, H, I, J, E[l + 1], 5, -165796510), J = x(J, G, H, I, E[l + 6], 9, -1069501632), I = x(I, J, G, H, E[l + 11], 14, 643717713), H = x(H, I, J, G, E[l + 0], 20, -373897302), G = x(G, H, I, J, E[l + 5], 5, -701558691), J = x(J, G, H, I, E[l + 10], 9, 38016083), I = x(I, J, G, H, E[l + 15], 14, -660478335), H = x(H, I, J, G, E[l + 4], 20, -405537848), G = x(G, H, I, J, E[l + 9], 5, 568446438), J = x(J, G, H, I, E[l + 14], 9, -1019803690), I = x(I, J, G, H, E[l + 3], 14, -187363961), H = x(H, I, J, G, E[l + 8], 20, 1163531501), G = x(G, H, I, J, E[l + 13], 5, -1444681467), J = x(J, G, H, I, E[l + 2], 9, -51403784), I = x(I, J, G, H, E[l + 7], 14, 1735328473), H = x(H, I, J, G, E[l + 12], 20, -1926607734), G = y(G, H, I, J, E[l + 5], 4, -378558), J = y(J, G, H, I, E[l + 8], 11, -2022574463), I = y(I, J, G, H, E[l + 11], 16, 1839030562), H = y(H, I, J, G, E[l + 14], 23, -35309556), G = y(G, H, I, J, E[l + 1], 4, -1530992060), J = y(J, G, H, I, E[l + 4], 11, 1272893353), I = y(I, J, G, H, E[l + 7], 16, -155497632), H = y(H, I, J, G, E[l + 10], 23, -1094730640), G = y(G, H, I, J, E[l + 13], 4, 681279174), J = y(J, G, H, I, E[l + 0], 11, -358537222), I = y(I, J, G, H, E[l + 3], 16, -722521979), H = y(H, I, J, G, E[l + 6], 23, 76029189), G = y(G, H, I, J, E[l + 9], 4, -640364487), J = y(J, G, H, I, E[l + 12], 11, -421815835), I = y(I, J, G, H, E[l + 15], 16, 530742520), H = y(H, I, J, G, E[l + 2], 23, -995338651), G = z(G, H, I, J, E[l + 0], 6, -198630844), J = z(J, G, H, I, E[l + 7], 10, 1126891415), I = z(I, J, G, H, E[l + 14], 15, -1416354905), H = z(H, I, J, G, E[l + 5], 21, -57434055), G = z(G, H, I, J, E[l + 12], 6, 1700485571), J = z(J, G, H, I, E[l + 3], 10, -1894986606), I = z(I, J, G, H, E[l + 10], 15, -1051523), H = z(H, I, J, G, E[l + 1], 21, -2054922799), G = z(G, H, I, J, E[l + 8], 6, 1873313359), J = z(J, G, H, I, E[l + 15], 10, -30611744), I = z(I, J, G, H, E[l + 6], 15, -1560198380), H = z(H, I, J, G, E[l + 13], 21, 1309151649), G = z(G, H, I, J, E[l + 4], 6, -145523070), J = z(J, G, H, I, E[l + 11], 10, -1120210379), I = z(I, J, G, H, E[l + 2], 15, 718787259), H = z(H, I, J, G, E[l + 9], 21, -343485551), G = A(G, n), H = A(H, B), I = A(I, C), J = A(J, D)
        }
        return Array(G, H, I, J)
    }

    function u(D, E, l, n, B, C) {
        return A(q(A(A(E, D), A(n, C)), B), l)
    }

    function w(C, D, E, F, l, n, B) {
        return u(D & E | ~D & F, C, D, l, n, B)
    }

    function x(C, D, E, F, l, n, B) {
        return u(D & F | E & ~F, C, D, l, n, B)
    }

    function y(C, D, E, F, l, n, B) {
        return u(D ^ E ^ F, C, D, l, n, B)
    }

    function z(C, D, E, F, l, n, B) {
        return u(E ^ (D | ~F), C, D, l, n, B)
    }

    function A(B, C) {
        var l = (65535 & B) + (65535 & C),
            n = (B >> 16) + (C >> 16) + (l >> 16);
        return n << 16 | 65535 & l
    }

    function q(l, n) {
        return l << n | l >>> 32 - n
    }
    var j, a = [],
        b = 0,
        c = 0,
        d = 2,
        e = 0,
        f = 20,
        g = "#textAreaContainer,#sendMsg_content,#msg_textarea",
        h = "user_action";
    jQuery(document).delegate(g, "blur", i).delegate(g, "keydown", i).delegate(g, "keypress", i).delegate(g, "keyup", i).delegate(g, "focus", i), j = 0
});

中間的加密算法可以忽略,可能是md5之類的算法,直接看這部分即可:

var j, a = [],
    b = 0,
    c = 0,
    d = 2,
    e = 0,
    f = 20, // 以上是一些初始化
    g = "#textAreaContainer,#sendMsg_content,#msg_textarea", // 輸入區域
    h = "user_action"; // 最終驗證值放在ID為 user_action 的元素里
jQuery(document).delegate(g, "blur", i).delegate(g, "keydown", i).delegate(g, "keypress", i).delegate(g, "keyup", i).delegate(g, "focus", i), j = 0;
// 監聽 g 變量里的元素,當 聚焦,失去焦點,鍵盤按下,抬起,按鍵 都會觸發 i 函數。那詳細的分析下 i 都做了些什么吧。
function i(B) {
    var D, l, n, C = document.cookie.substr(document.cookie.indexOf("&id=") + "&id=".length);
    C = C.substr(0, C.indexOf("&")), C = "" == C ? 8980291 : C, D = jQuery(B.target), l = (new Date).getTime(), n = "focusout" == B.type ? "blur" : "focusin" == B.type ? "focus" : B.type, n = n.replace("key", "").substring(0, 1), 0 == c && (c = l), d >= f && (d = 2), a["c" == n ? 1 : "f" == n ? 0 : d++] = n + e+++"." + ("c" == n ? B.pageX + ":" + B.pageY : "b" == n ? d : "f" == n ? c : B.which) + "." + (l - ("c" == n || "f" == n ? c : b)), b = l, ("b" == n || 17 == B.which) && (v = a.join(",") + "|" + k(a.join(",") + a[0] + C) + "|" + k(D.val() + a[0]) + "|" + navigator.userAgent + "|v2", 0 == jQuery("#" + h).length ? jQuery('<input type="hidden" id="' + h + '" name="action" value="' + v + '" />').insertAfter(D) : jQuery("#" + h).val(v))
}

這部分被編譯過了,結構上被優化的比較變態,所以很難直接閱讀,經過整理:

function i(B) {
    var D,
        l,
        n,
        C = document.cookie.substr(document.cookie.indexOf("&id=") + "&id=".length); // 為了取 cookies id 部分。。
    
    C = C.substr(0, C.indexOf("&")), // 取ID值
    C = "" == C ? 8980291 : C, // 如果沒有,就用 8980291
    // 看他取個ID都這么糾結,應該是個新手寫的。。。
    
    D = jQuery(B.target), // 當前元素
    l = (new Date).getTime(), // 當前時間戳,用於計算每步操作時間差
    n = "focusout" == B.type ? "blur" : "focusin" == B.type ? "focus" : B.type, // 修正事件名
    n = n.replace("key", "").substring(0, 1), // 取事件名第一個字符 f, b, d, p, u 分別對應 focus, blur, keydown, keypress, keyup
    0 == c && (c = l), // 上一次操作的時間戳,如果第一次操作,就用變量l的值。
    d >= f && (d = 2), // d 是數組下標,范圍是 2-20。下標 0, 1 固定值特殊處理
    
    a["c" == n ? 1 : "f" == n ? 0 : d++] = n + e+++"." + ("c" == n ? B.pageX + ":" + B.pageY : "b" == n ? d : "f" == n ? c : B.which) + "." + (l - ("c" == n || "f" == n ? c : b)),
    // 這一行代碼就驗證重點,一個長度為 20 的數組,保存着獲得焦點的時間戳和每個按鍵的記錄
    // a[0] 格式固定:"f" + 操作次數 + "." + 當前操作時間戳 + 與上次操作的時間間隔
    // a[0] 的數據類似這樣:f2.1400154199651.2678
    // a[1] 忽略,直接空着即可,我也不知道 c 是什么事件,反正這個用不到。
    // a[2] - a[19] 格式類似,前綴不同。d, p, u 為一組占用3個元素,
    // 例如:[d8.97.167, p8.97.1, u8.97.75] 表示第 8 次操作,按鍵碼是97,d 是 keydown與上次按下間隔167ms,p 是 keypress操作與按下間隔97ms,u 是 keyup與keypress間隔75ms
    // 這樣循環填充數組,比如大於20,會重新從2開始,3個值一組的往后填充
    // 最后一個格式固定:"b" + 操作次數 + "." + 數組循環到幾(2-20之間的值) + "." + 與上次操作間隔。
    // 最后一個的數據類似這樣:b12.14.862
    
    b = l, // 保存本次操作的時間戳
    if ("b" == n || 17 == B.which) { // 如果是失去焦點或者按了 ctrl(電腦上17是ctrl,手機端不知道是不是這個健)
        v = a.join(",") + "|" + k(a.join(",") + a[0] + C) + "|" + k(D.val() + a[0]) + "|" + navigator.userAgent + "|v2",
        // a 數組以 "," 拼接字符串
        // k 應該是 md5 之類的,沒具體測試
        // 相當於 a數組 + "|" + md5(a數組 + 數組第一個元素 + 用戶ID) + "|" + md5(用戶輸入的內容 + 數組第一個元素) + "|" + 瀏覽器信息 + "|v2"
        if (0 == jQuery("#" + h).length) { // 如果不存在 id="user_action" 的元素,就創建並插入到輸入框后面
            jQuery('<input type="hidden" id="' + h + '" name="action" value="' + v + '" />').insertAfter(D)
        } else { // 如果操作就直接更新內容
            jQuery("#" + h).val(v)
        }
    }
}

總體來說,代碼並不是很難,可能光看代碼比較難理解,我是動態調試着看的,不然會很費時間。
他生成的數據大概是這樣:

f8.1400154504548.1565,,d8.97.167,p8.97.1,u8.97.75,d9.117.143,p9.117.4,u9.117.22,b10.8.2283,p4.114.3,u4.114.53,d5.116.122,p5.116.3,u5.116.2,d6.122.136,p6.122.5,u6.122.98,d7.105.49,p7.105.4,u7.105.79|094175120fb2ae4d5977afbe3fc04447|9de7848b9e445ba83c030b90651347fc|Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36|v2

第一個和最后一個格式固定,中間的內容,3個為一組循環填充。
當然 最后一個 不是指數組的最后一個元素,因為他的數據是 2-19 循環填充的,可能最后一個重新輪到 2 或者是 4 所以這個是不固定的。
最重要的數據是:數組第一個值,用戶輸入內容 和 瀏覽器信息
因為他最終生成的驗證值是:a數組 + "|" + md5(a數組 + 數組第一個元素 + 用戶ID) + "|" + md5(用戶輸入的內容 + 數組第一個元素) + "|" + 瀏覽器信息 + "|v2"
這樣的格式,所以其他數據也許可以隨便亂填,我沒具體測試,只是按照他的格式生成的,提交測試通過。

post 成功。

細心的朋友可能會發現怎么是 2014-05-09,
其實這個是之前人家花錢找我寫的一個東西,我不好意思把我寫的代碼直接發出來,畢竟人家花錢的東西,我免費到處亂發也不是回事。
所以只給個思路,有興趣的朋友可以和我深入探討。

天涯的這個驗證思路還是不錯的,記錄每次操作的時間,按鍵,操作間隔,然后把這個按鍵記錄+用戶ID md5一下,又把 用戶輸入的數據+第一個元素 md5 一下。
后台進行數據效驗,這樣能過濾掉一大批機器人 POST 信息,因為不是每個會 POST 的都是高手,不然也不會花錢找我寫這個東西了。

把這方案加到自己項目里,應該也是個不錯的選擇。


今天分享完畢,明天見。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM