發帖沒多久,算法就更新了,就算我重新分析,人家依然會更新,所以還是自己學着分析吧。
對於現在 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 的都是高手,不然也不會花錢找我寫這個東西了。
把這方案加到自己項目里,應該也是個不錯的選擇。
今天分享完畢,明天見。