Fork me on GitHub

百变IP的IP地址反爬

难度: ★☆☆☆☆ 1星

一、目标

目标网站:
https://www.baibianip.com/home/free.html
在这个网站的IP列表中的IP地址,是通过JS解密出来的:
0
查看源代码(view-source:https://www.baibianip.com/home/free.html),页面返回的时候没有IP地址,IP地址是通过这行JS计算并显示的:
1
本次的目标就是破解这个加密。


二、分析

在上面的截图里,解密函数的名字是FFdisharmony,同时它在script下直接调用了,说明FFdisharmony应该是一个全局函数,能够在console上调用的,尝试调用一下:

2

这就有点尴尬,从Elements选中一个IP地址,看下它的解密函数,这里就不贴图了,在上一部分有这个图,可以观察到script里解密的函数名似乎与其它地方不太一样,这个函数的名字似乎是一直是在变的,同时通过在

view-source:https://www.baibianip.com/home/free.html

页面上快速刷新也能够观察到解密函数的名字一直在变,不过这个没关系,从Elements中的script中复制这个函数名字然后粘贴到console执行就可以了,然后双击跟进去:

3

跟进去之后是这样的:

function FFassimilated (s) {
    document . write (ddip(s));
}

同时注意到这个函数没有被匿名函数包围啥的,说明这个ddip函数的作用域很有可能是全局范围的,因此回到console中输入ddip回车:

4

然后双击跟进去,就能拿到这个函数的源代码了:

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代码的地方定义的这个方法,不过这个无所谓了,反正我们已经得到了源码:

5

同时这里我还多刷新跟进去了几次验证了一下,这个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())


仓库:

https://github.com/CC11001100/misc-crawler-public/tree/master/001-anti-crawler-js-re/01-002-www.baibianip.com


请注意爬虫文章具有时效性,本文写于2020-11-25日。

posted @ 2020-11-25 18:57  CC11001100  阅读(679)  评论(0编辑  收藏  举报