以下是分析实例: 今天来说说WEBQQ登陆过程的分析,抛砖引玉而已 第一步:使用HttpWatch 软件抓取webQQ页面从打开至登陆成功到所有数据包。我们所有到操作均在这里面查找。 等待抓包中..... 这里为了方便 我已经登陆成功一个号码并且抓包成功。 抓包方法很简单 打开IE 打开HttpWatch 点击记录后 开始一系列的登陆操作直到登陆成功后。停止抓 第二步:我们重新打开一个webqq登陆页面。来分析网页源代码。(这里建议大家使用 Chrome 谷歌浏览器。比较方便。它本身带有一个 WEB前端助手 很好)这个前端助手自行安装 1 <input autocomplete="on" name="u" id="u" type="text" style="ime-mode:disabled" class="input01" tabindex="1" value="QQ号码或Email账号"> 2 <input name="p" id="p" maxlength="16" type="password" class="input01" tabindex="2"> 3 <input type="submit" class="signin-btn signin2" value="" tabindex="6" id="login_button"> 4 <form id="loginform" autocomplete="off" name="loginform" action="http://ptlogin2.qq.com/login" method="post" target="_self" style="margin:0px;"> 依次使用 审查元素 查看用户名输入框 密码输入框 和 登陆按钮。这里就要知道HTML的基本概念里 <form></form>这个范围内就是一个表单。提交到是这个范围内到内容。所以 我们要在源代码里找找<form 得到上述结果 第一个用户名框 我们关注一点 name="u" 表示QQ号是从这个框里获取。名字就是 u 第二个是密码输入框 同理 name="p" 表示密码框就是 P 第三个是登陆按钮 type为 submit 表示这是一个提交按钮。点这个就让这个表单提交 第四个就是这个表单的基础信息。我们关注一点 onsubmin 事件表示 该表单被提交。这里就表示 当用户提交表单(点击登陆按钮)后。执行onFormSubmit这个过程。关键1 第三步 我们就需要开始查找这个过程到底进行里什么。 在我们到第一步HttpWatch抓包结果里搜索。 搜索的要求就是 function 这是定义一个函数的意思。表示这个就是此函数的具体过程。 function onFormSubmit(form) { if (form.remember_uin.checked){ '这里判断我们提交到表单内 remember_uin控件的 checked(被选中状态)。我们查看一下这个控件到底是什么。默认是假 return ptui_onLoginEx(form, "qq.com") 不执行 }else{ var myDate=new Date(); myDate.setFullYear(1971,1,1); pt.cookie.set("ptui_loginuin", "", myDate, '/', 'ui.ptlogin2.qq.com'); return ptui_onLogin(form); ’所以这个函数其实是执行到这里。 调用了 ptui_onLogin过程 } } 这里找到里 onFormSubmit <input type="hidden" name="remember_uin" value="1" id="remember_uin" /> ‘一个隐藏的控件 选中状态默认为 假 hidden 表示是隐藏到控件。我们在网页上看不见。其实是可见的。通常会用这种来传递一些不需要用户操作的数据。 第四步 在我们到第一步HttpWatch抓包结果里搜索。ptui_onLogin 在这个文件里 https://ui.ptlogin2.qq.com/js/10047/comm.js 经过分析后 以后的所有操作都在这个文件内。 找到里 好大一堆。我们用代码整理软件来整理下。 这里使用谷歌浏览器到WEB前端助手 太乱了。现在是不是好看多了 我们再来搜索 function ptui_onLogin(A) { 具体过程 try { if (parent.ptlogin2_onLogin) { if (!parent.ptlogin2_onLogin()) { return false } } if (parent.ptlogin2_onLoginEx) { var D = A.u.value; var B = A.verifycode.value; if (str_uintip == D) { D = "" } if (!parent.ptlogin2_onLoginEx(D, B)) { return false } } } catch (C) {} return ptui_checkValidate(A) } 这里简单说下 JS的 try catch try…catch这种结构最常见,它的执行过程是:当没有例外发生执行完毕try块语句后或者发生例外执行完catch块语句后,控制将转移到整个try…catch结构后面的语句。 所以上面那个函数 最终的执行是 return ptui_checkValidate(A) 返回 ptui_checkValidate 过程的结果。 第五步: 在我们到第一步HttpWatch抓包结果里搜索。ptui_checkValidate 依然是这个文件https://ui.ptlogin2.qq.com/js/10047/comm.js 这里我稍稍具体的分析了这个函数过程 function ptui_checkValidate(B) { var A = B.u; ‘把QQ号输入框对象赋值给A var D = B.p; 把QQ密码框对象赋值给D var E = B.verifycode; 把验证码框到值赋值给E if (A.value == "" || str_uintip == A.value) { 判断 如果QQ输入框到内容为空 或者 同 str_uintip一样 则输出错误消息 QQ号码输入框获取焦点 返回失败 pt.show_err(str_no_uin); 在第一步抓包到内容里搜索 str_uintip可以知道内容 var str_uintip = "QQ号码/手机/邮箱"; A.focus(); return false } A.value = A.value.trim(); 如果输入框不为空。则把框里到内容首位空格去掉赋值给 输入框 if (!pt.chkUin(A.value)) { 这里又调用里一个函数判断是否是QQ号码。 如果失败 同样提示输入QQ号,进行输入判断 pt.show_err(str_inv_uin); A.focus(); A.select(); return false } if (D.value == "") { 如果密码内容为空 pt.show_err(str_no_pwd); 输出错误信息 D.focus(); 密码框获取焦点 return false 返回假 } if (E.value == "") { 如果验证码内容为空 if (!isLoadVC) { 判断是否需要验证码 如果没有载入 则载入验证码 loadVC(true); 载入验证码 g_submitting = true; return false } pt.show_err(str_no_vcode); 显示错误 同样可以搜索 var str_no_vcode = "您还没有输入验证码!"; try { E.focus() } catch (C) {} 又是一个 try catch语言 跳到下一句 if (!g_loadcheck) { ptui_reportAttr(78028) } else { ptui_reportAttr(78029) } return false } 这个过程主要是判断验证码 if (E.value.length < 4) { 如果验证码长度小于4 表示验证码错误 pt.show_err(str_inv_vcode); E.focus(); E.select(); return false } if (isLoadVC && !(/^[a-zA-Z0-9]+$/.test(E.value))) { 这里判断 需要载入验证码 且 验证码内容不为 字母和数字组合 pt.show_err(str_correct_vcode); E.focus(); E.select(); return false 显示错误信息 并且返回等待重新输入 } D.setAttribute("maxlength", "32"); 设置密码输入框的 最大输入长度属性为 32 也就是说 密码框可以输入32个字符 ajax_Submit(); 这里调用里一个过程 submit 感觉是提交。我们看看这个过程是不是我们需要的。 ptui_reportNum(g_changeNum); g_changeNum = 0; return true } 第六步 在我们到第一步HttpWatch抓包结果里搜索。ajax_Submit 依然是comm.js function ajax_Submit() { var A = getSubmitUrl("login"); 又调用里一个过程getSubmitUrl 字面意思是 得到(get)提交(submit)链接(url) 我们找出来看看 pt.winName.set("login_param", encodeURIComponent(login_param)); pt.loadScript(A); return } 第七步 在我们到第一步HttpWatch抓包结果里搜索。getSubmitUrl 依然是这个文件 comm.js 这个就不仔细讲解里 其实就是 我们登陆那个 GET链接各个参数的赋值过程。 function getSubmitUrl(K) { var E = true; var C = document.forms[0]; 取表单对象 这里其实就是我们登陆那个表单了。 var A = (pt.isHttps ? "https://ssl." : "http://") + "ptlogin2." + g_domain + "/" + K + "?"; 开始构建提交链接 A var B = document.getElementById("login2qq"); if (pt.regmaster == 2) { A = "http://ptlogin2.function.qq.com/" + K + "?regmaster=2&" } else { if (pt.regmaster == 3) { A = "http://ptlogin2.crm2.qq.com/" + K + "?regmaster=3&" } } for (var J = 0; J < C.length; J++) { if (K == "ptqrlogin" && (C[J].name == "u" || C[J].name == "p" || C[J].name == "verifycode" || C[J].name == "h")) { continue } if (C[J].name == "ipFlag" && !C[J].checked) { A += C[J].name + "=-1&"; continue } if (C[J].name == "fp" || C[J].type == "submit") { continue } if (C[J].name == "ptredirect") { g_ptredirect = C[J].value } if (C[J].name == "low_login_enable" && (!C[J].checked)) { E = false; continue } if (C[J].name == "low_login_hour" && (!E)) { continue } if (C[J].name == "webqq_type" && !B && (!C[J].checked)) { continue } A += C[J].name; A += "="; if (C[J].name == "u" && pt.needAt) { 这里是赋值一个u=qq号 这个不需要知道 反正构成提交路径我们是直接赋值 A += pt.needAt + "&"; continue } if (C[J].name == "p") { 这里其实就是关键的密码加密处理过程了。 前面我们已经知道 p就是密码输入框的名字。 这里判断 如果控件名字=p 就是 如果目前处理的是密码输入框内容。 var M = C.p.value; 把密码框内容赋值给 M var I = hexchar2bin(md5(M)); MD5 加密密码 在进行 hexchar2bin编码 赋值给 I var H = md5(I + pt.uin); 把I的值同 UIN的值链接(这里两个都是文本。文本链接就是 “aa”+"bb"=“aabb” 这样没问题把) 把结果再次MD5 赋值给H var G = md5(H + C.verifycode.value.toUpperCase()); 把H 同 验证码的大写形式进行连接 。 结果再MD5加密。 得到G A += G 好吧 我们的G 就是密码结果 } else { if (C[J].name == "u1" || C[J].name == "ep") { var D = C[J].value; var L = ""; if (g_appid == "1003903" && B) { L = /\?/g.test(D) ? "&" : "?"; var F = document.getElementById("webqq_type").value; L += "login2qq=" + B.value + "&webqq_type=" + F } A += encodeURIComponent(D + L) } else { A += C[J].value } } A += "&" } A += "fp=loginerroralert&action=" + pt.action.join("-") + "-" + (new Date() - g_begTime) + "&mibao_css=" + pt.mibao_css + "&t=" + pt.submitN[pt.uin] + "&g=1"; A += "&js_type=" + pt.js_type + "&js_ver=" + window.g_pt_version + "&login_sig=" + window.g_login_sig; return A } 第七步 1.上一句又几个地方 pt.uin 这个值是怎么来到呢? 我们通过搜索得到 function checkTimeout() { var A = $("u").value.trim(); 'u QQ号码输入框 这里就是把 QQ号码输入框内容删除首尾空格后 赋值给A if (pt.chkAccount.isQQ(A)) { 判断这个QQ号是否正确 如果正确 pt.uin = uin2hex(A); 把我们的QQ号码进行 uin2hex 编码后 赋值给pt.uin $("verifycode").value = ""; loadVC(true) } ptui_reportAttr2(216082) } pt.uin = uin2hex(A); function uin2hex(str) { var maxLength = 16; str = parseInt(str); var hex = str.toString(16); var len = hex.length; for (var i = len; i < maxLength; i++) { hex = "0" + hex } var arr = []; for (var j = 0; j < maxLength; j += 2) { arr.push("\\x" + hex.substr(j, 2)) } var result = arr.join(""); eval('result="' + result + '"'); return result } 一个独立的过程。我们直接用脚本组件运行一下就能得到值 2 密码加密用里很多 MD5 这个好像就是加密关键。我们去提取 调用里 hex_md5,binl2hex,core_md5,str2binl,chrsz......... 我们抽丝剥茧。一个一个到把需要到复制出来 。 这里我们就需要构建一下加密脚本。 增加一个 qqhash函数 传递3个文本参数 依次是 QQ号 密码 验证码 然后返回加密后的密码代码。 至此 完成。 把以下代码加入e语言常量。 然后通过精易模块到 脚本组件模块进行调用。 QQ加密过程查询分析完成 function qqhash(qqnum,password,verifycode){ var I = hexchar2bin(md5(password)); var H = md5(I + uin2hex(qqnum)); var G = md5(H + verifycode.toUpperCase()); return G } var hexcase = 1; var b64pad = ""; var chrsz = 8; var mode = 32; function md5(A) { return hex_md5(A) } function hex_md5(A) { return binl2hex(core_md5(str2binl(A), A.length * chrsz)) } function str_md5(A) { return binl2str(core_md5(str2binl(A), A.length * chrsz)) } function hex_hmac_md5(A, B) { return binl2hex(core_hmac_md5(A, B)) } function b64_hmac_md5(A, B) { return binl2b64(core_hmac_md5(A, B)) } function str_hmac_md5(A, B) { return binl2str(core_hmac_md5(A, B)) } function core_md5(K, F) { K[F >> 5] |= 128 << ((F) % 32); K[(((F + 64) >>> 9) << 4) + 14] = F; var J = 1732584193; var I = -271733879; var H = -1732584194; var G = 271733878; for (var C = 0; C < K.length; C += 16) { var E = J; var D = I; var B = H; var A = G; J = md5_ff(J, I, H, G, K[C + 0], 7, -680876936); G = md5_ff(G, J, I, H, K[C + 1], 12, -389564586); H = md5_ff(H, G, J, I, K[C + 2], 17, 606105819); I = md5_ff(I, H, G, J, K[C + 3], 22, -1044525330); J = md5_ff(J, I, H, G, K[C + 4], 7, -176418897); G = md5_ff(G, J, I, H, K[C + 5], 12, 1200080426); H = md5_ff(H, G, J, I, K[C + 6], 17, -1473231341); I = md5_ff(I, H, G, J, K[C + 7], 22, -45705983); J = md5_ff(J, I, H, G, K[C + 8], 7, 1770035416); G = md5_ff(G, J, I, H, K[C + 9], 12, -1958414417); H = md5_ff(H, G, J, I, K[C + 10], 17, -42063); I = md5_ff(I, H, G, J, K[C + 11], 22, -1990404162); J = md5_ff(J, I, H, G, K[C + 12], 7, 1804603682); G = md5_ff(G, J, I, H, K[C + 13], 12, -40341101); H = md5_ff(H, G, J, I, K[C + 14], 17, -1502002290); I = md5_ff(I, H, G, J, K[C + 15], 22, 1236535329); J = md5_gg(J, I, H, G, K[C + 1], 5, -165796510); G = md5_gg(G, J, I, H, K[C + 6], 9, -1069501632); H = md5_gg(H, G, J, I, K[C + 11], 14, 643717713); I = md5_gg(I, H, G, J, K[C + 0], 20, -373897302); J = md5_gg(J, I, H, G, K[C + 5], 5, -701558691); G = md5_gg(G, J, I, H, K[C + 10], 9, 38016083); H = md5_gg(H, G, J, I, K[C + 15], 14, -660478335); I = md5_gg(I, H, G, J, K[C + 4], 20, -405537848); J = md5_gg(J, I, H, G, K[C + 9], 5, 568446438); G = md5_gg(G, J, I, H, K[C + 14], 9, -1019803690); H = md5_gg(H, G, J, I, K[C + 3], 14, -187363961); I = md5_gg(I, H, G, J, K[C + 8], 20, 1163531501); J = md5_gg(J, I, H, G, K[C + 13], 5, -1444681467); G = md5_gg(G, J, I, H, K[C + 2], 9, -51403784); H = md5_gg(H, G, J, I, K[C + 7], 14, 1735328473); I = md5_gg(I, H, G, J, K[C + 12], 20, -1926607734); J = md5_hh(J, I, H, G, K[C + 5], 4, -378558); G = md5_hh(G, J, I, H, K[C + 8], 11, -2022574463); H = md5_hh(H, G, J, I, K[C + 11], 16, 1839030562); I = md5_hh(I, H, G, J, K[C + 14], 23, -35309556); J = md5_hh(J, I, H, G, K[C + 1], 4, -1530992060); G = md5_hh(G, J, I, H, K[C + 4], 11, 1272893353); H = md5_hh(H, G, J, I, K[C + 7], 16, -155497632); I = md5_hh(I, H, G, J, K[C + 10], 23, -1094730640); J = md5_hh(J, I, H, G, K[C + 13], 4, 681279174); G = md5_hh(G, J, I, H, K[C + 0], 11, -358537222); H = md5_hh(H, G, J, I, K[C + 3], 16, -722521979); I = md5_hh(I, H, G, J, K[C + 6], 23, 76029189); J = md5_hh(J, I, H, G, K[C + 9], 4, -640364487); G = md5_hh(G, J, I, H, K[C + 12], 11, -421815835); H = md5_hh(H, G, J, I, K[C + 15], 16, 530742520); I = md5_hh(I, H, G, J, K[C + 2], 23, -995338651); J = md5_ii(J, I, H, G, K[C + 0], 6, -198630844); G = md5_ii(G, J, I, H, K[C + 7], 10, 1126891415); H = md5_ii(H, G, J, I, K[C + 14], 15, -1416354905); I = md5_ii(I, H, G, J, K[C + 5], 21, -57434055); J = md5_ii(J, I, H, G, K[C + 12], 6, 1700485571); G = md5_ii(G, J, I, H, K[C + 3], 10, -1894986606); H = md5_ii(H, G, J, I, K[C + 10], 15, -1051523); I = md5_ii(I, H, G, J, K[C + 1], 21, -2054922799); J = md5_ii(J, I, H, G, K[C + 8], 6, 1873313359); G = md5_ii(G, J, I, H, K[C + 15], 10, -30611744); H = md5_ii(H, G, J, I, K[C + 6], 15, -1560198380); I = md5_ii(I, H, G, J, K[C + 13], 21, 1309151649); J = md5_ii(J, I, H, G, K[C + 4], 6, -145523070); G = md5_ii(G, J, I, H, K[C + 11], 10, -1120210379); H = md5_ii(H, G, J, I, K[C + 2], 15, 718787259); I = md5_ii(I, H, G, J, K[C + 9], 21, -343485551); J = safe_add(J, E); I = safe_add(I, D); H = safe_add(H, B); G = safe_add(G, A) } if (mode == 16) { return Array(I, H) } else { return Array(J, I, H, G) } } function md5_cmn(F, C, B, A, E, D) { return safe_add(bit_rol(safe_add(safe_add(C, F), safe_add(A, D)), E), B) } function md5_ff(C, B, G, F, A, E, D) { return md5_cmn((B & G) | ((~B) & F), C, B, A, E, D) } function md5_gg(C, B, G, F, A, E, D) { return md5_cmn((B & F) | (G & (~F)), C, B, A, E, D) } function md5_hh(C, B, G, F, A, E, D) { return md5_cmn(B ^ G ^ F, C, B, A, E, D) } function md5_ii(C, B, G, F, A, E, D) { return md5_cmn(G ^ (B | (~F)), C, B, A, E, D) } function core_hmac_md5(C, F) { var E = str2binl(C); if (E.length > 16) { E = core_md5(E, C.length * chrsz) } var A = Array(16), D = Array(16); for (var B = 0; B < 16; B++) { A[B] = E[B] ^ 909522486; D[B] = E[B] ^ 1549556828 } var G = core_md5(A.concat(str2binl(F)), 512 + F.length * chrsz); return core_md5(D.concat(G), 512 + 128) } function safe_add(A, D) { var C = (A & 65535) + (D & 65535); var B = (A >> 16) + (D >> 16) + (C >> 16); return (B << 16) | (C & 65535) } function bit_rol(A, B) { return (A << B) | (A >>> (32 - B)) } function str2binl(D) { var C = Array(); var A = (1 << chrsz) - 1; for (var B = 0; B < D.length * chrsz; B += chrsz) { C[B >> 5] |= (D.charCodeAt(B / chrsz) & A) << (B % 32) } return C } function binl2str(C) { var D = ""; var A = (1 << chrsz) - 1; for (var B = 0; B < C.length * 32; B += chrsz) { D += String.fromCharCode((C[B >> 5] >>> (B % 32)) & A) } return D } function binl2hex(C) { var B = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; var D = ""; for (var A = 0; A < C.length * 4; A++) { D += B.charAt((C[A >> 2] >> ((A % 4) * 8 + 4)) & 15) + B.charAt((C[A >> 2] >> ((A % 4) * 8)) & 15) } return D } function binl2b64(D) { var C = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var F = ""; for (var B = 0; B < D.length * 4; B += 3) { var E = (((D[B >> 2] >> 8 * (B % 4)) & 255) << 16) | (((D[B + 1 >> 2] >> 8 * ((B + 1) % 4)) & 255) << 8) | ((D[ B + 2 >> 2] >> 8 * ((B + 2) % 4)) & 255); for (var A = 0; A < 4; A++) { if (B * 8 + A * 6 > D.length * 32) { F += b64pad } else { F += C.charAt((E >> 6 * (3 - A)) & 63) } } } return F } function hexchar2bin(str) { var arr = []; for (var i = 0; i < str.length; i = i + 2) { arr.push("\\x" + str.substr(i, 2)) } arr = arr.join(""); eval("var temp = '" + arr + "'"); return temp } function uin2hex(str) { var maxLength = 16; str = parseInt(str); var hex = str.toString(16); var len = hex.length; for (var i = len; i < maxLength; i++) { hex = "0" + hex } var arr = []; for (var j = 0; j < maxLength; j += 2) { arr.push("\\x" + hex.substr(j, 2)) } var result = arr.join(""); eval('result="' + result + '"'); return result } |
|