百变IP的IP地址反爬
难度: ★☆☆☆☆ 1星
一、目标
目标网站:
https://www.baibianip.com/home/free.html
在这个网站的IP列表中的IP地址,是通过JS解密出来的:
查看源代码(view-source:https://www.baibianip.com/home/free.html),页面返回的时候没有IP地址,IP地址是通过这行JS计算并显示的:
本次的目标就是破解这个加密。
二、分析
在上面的截图里,解密函数的名字是FFdisharmony,同时它在script下直接调用了,说明FFdisharmony应该是一个全局函数,能够在console上调用的,尝试调用一下:
这就有点尴尬,从Elements选中一个IP地址,看下它的解密函数,这里就不贴图了,在上一部分有这个图,可以观察到script里解密的函数名似乎与其它地方不太一样,这个函数的名字似乎是一直是在变的,同时通过在
view-source:https://www.baibianip.com/home/free.html
页面上快速刷新也能够观察到解密函数的名字一直在变,不过这个没关系,从Elements中的script中复制这个函数名字然后粘贴到console执行就可以了,然后双击跟进去:
跟进去之后是这样的:
function FFassimilated (s) { document . write (ddip(s)); }
同时注意到这个函数没有被匿名函数包围啥的,说明这个ddip函数的作用域很有可能是全局范围的,因此回到console中输入ddip回车:
然后双击跟进去,就能拿到这个函数的源代码了:
function ddip(e0){e1=r13(e0.toString());e2=$.base64.decode(e1);e3=e2.toString().substr(10);l3=e3.length;e4=e3.substr(0,l3-10);return e4}
在这里有个需要注意的点是跟进去的时候展示源码的标签的名字是VMxxx形式的,说明这个代码并不是直接在JS中就有的,应该是在eval或者函数构造器之类的能够执行js代码的地方定义的这个方法,不过这个无所谓了,反正我们已经得到了源码:
同时这里我还多刷新跟进去了几次验证了一下,这个ddip没有多套逻辑,也不会再变了, 这样看来FFassimilated之类的函数名字只是为了把它包一层保护起来,虽然也没护住...
上面的ddip整理一下就是这个样子的:
function ddip(e0){ e1=r13(e0.toString()); e2=$.base64.decode(e1); e3=e2.toString().substr(10); l3=e3.length; e4=e3.substr(0,l3-10); return e4 }
同样的方法在console中输入r13跟进去,拿到r13的代码:
function r13(s) { var b = [], c, i = s.length, a = 'a'.charCodeAt(), z = a + 26, A = 'A'.charCodeAt(), Z = A + 26; while (i--) { c = s.charCodeAt(i); if (c >= a && c < z) { b[i] = rot(c, a, 13) } else if (c >= A && c < Z) { b[i] = rot(c, A, 13) } else { b[i] = s.charAt(i) } } return b.join('') }
这里面还有一个rot,同样的方法即可得到源码:
function rot(t, u, v) { return String.fromCharCode(((t - u + v) % (v * 2)) + u) }
至此,r13的逻辑就比较清楚了,就是把原始的字符串的每个字符向右轮转13位,这个专业的叫法是凯撒加密。
回到ddip继续看,还有一个$.base64.decode,同样的方法在console粘贴跟进去拿到其代码:
Plugin.atob = Plugin.decode = function(coded, utf8decode) { coded = String(coded).split('='); var i = coded.length; do { --i; coded[i] = code(coded[i], true, r64, a256, 6, 8); } while (i > 0);coded = coded.join(''); return Plugin.raw === false || Plugin.utf8decode || utf8decode ? UTF8.decode(coded) : coded; }
看这个名字叫base64,我们先不管其代码到底是啥,只需要验证一下是不是标准的base64就可以了,我们把“CC11001100”进行base64编码得到Q0MxMTAwMTEwMA==,然后在console上运行:
$.base64.decode("Q0MxMTAwMTEwMA==")
得到了原文“CC11001100”,说明这应该是标准的base64,那么其对应的js代码不再重要,调用标准库即可。
前面有几个函数是从VM中拿到的,实际上定义它们是在这个文件中:
https://www.baibianip.com/home/indexjs?_t=1606231394.2582
在下面有两个eval:
eval(function(p, a, c, k, e, d) { e = function(c) { return (c < a ? "" : e(parseInt(c / a))) + ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36)) } ; if (!''.replace(/^/, String)) { while (c--) d[e(c)] = k[c] || e(c); k = [function(e) { return d[e] } ]; e = function() { return '\\w+' } ; c = 1; } ;while (c--) if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b','g'), k[c]); return p; }('f c(3){4=b(3.2());5=$.a.e(4);1=5.2().6(8);9=1.d;7=1.6(0,9-8);g 7}', 17, 17, '|e3|toString|e0|e1|e2|substr|e4|10|l3|base64|r13|ddip|length|decode|function|return'.split('|'), 0, {}));
之前的博客有介绍过eval应该如何解密:
https://www.cnblogs.com/cc11001100/p/8468508.html
使用那篇文章介绍的工具,对这个eval解密,得到:
function ddip(e0) { e1 = r13(e0.toString()); e2 = $.base64.decode(e1); e3 = e2.toString().substr(10); l3 = e3.length; e4 = e3.substr(0, l3 - 10); return e4 }
跟前面从VM中拿到代码一样,另一个r13也是在这个文件中以同样的方式定义的,这里不再赘述。
至此,所有相关的js逻辑已经捋清楚了,接下来就是编码实现。
三、编码实现
#!/usr/bin/env python3 # encoding: utf-8 """ @author: CC11001100 """ import base64 import re import requests from bs4 import BeautifulSoup def crawl(): url = "https://www.baibianip.com/home/free.html" html = requests.get(url).text # 从https://www.baibianip.com/home/indexjs?_t=1606231394.2582最下面取似乎更方便 # 但是因为感觉不是很保险,于是还是老老实实从展示的地方取 doc = BeautifulSoup(html, features="html.parser") ip_list = [] for x in doc.select(".table td script"): # '<script>FFfatefully(\'ZGH5AmL3Zmx5ZwR2AF4lZwHhZmLhAwxkAwR1ZmNkAwRm\'); </script>' ss = re.findall(r"'(\w+)'", str(x)) if ss: s = ss[0] ip_list.append(ddip(s)) return ip_list def ddip(s): t1 = r13_string(s) t2 = base64.b64decode(t1.encode("UTF-8")).decode("UTF-8") t3 = t2[10:] return t3[0:-10] def r13_string(s): r = [] for c in s: r.append(r13_char(c)) return "".join(r) def r13_char(c): if 'a' <= c <= 'z': return chr((ord(c) - ord('a') + 13) % 26 + ord('a')) elif 'A' <= c <= 'Z': return chr((ord(c) - ord('A') + 13) % 26 + ord('A')) else: return c if __name__ == "__main__": # ['165.225.36.69', '43.250.124.98', '221.1.200.242', '1.20.99.61', '178.75.27.131', '177.129.40.78', '45.226.48.101', '96.9.73.80', '51.77.162.148', '170.185.68.14', '195.88.16.155', '178.47.139.50', '31.148.22.110', '81.163.35.120', '176.98.95.247', '114.30.75.206', '85.133.207.14', '106.249.44.10', '185.209.57.55', '186.216.81.21', '195.178.56.35', '151.106.10.52', '221.122.91.65', '81.163.36.210', '165.225.36.80', '170.239.144.2', '14.102.40.169', '212.56.218.90', '41.66.205.112', '195.98.74.141', '74.15.191.160', '71.17.253.132', '43.239.72.117', '103.4.165.122', '89.109.14.179', '51.77.211.175'] print(crawl())
仓库: