Python反反爬之JS混淆---源码乱码 ( 详细教程 )

查看 128|回复 10
作者:Java_S   
写在前面
很早之前在吾爱破解论坛上看见了【猿人学】Web端爬虫攻防大赛,当时进入他们官网的时候,比赛已经结束了。看着那些题目还挺有意思的,但由于各种原因一直没有机会去做那些题目。最近比较闲,就去把猿人学官网打开看了一下,尝试着完成了第一道题目---JS混淆[源码乱码],当然我也去看了两位大佬的讲解,一位是吾爱论坛的漁滒,另一位是B站的暗螟蛉
由于这是一篇迟到的教程,所有我写比较详细,希望对大家有所帮助
文章原链接https://syjun.vip/archives/278.html

题目

抓取所有(5页)机票的价格,并计算所有机票价格的平均值,填入答案。
题目看着挺简单,就是抓取机票的价格,并算出平均值,但是当我们打开开发者调试工具的时候,就会遇到第一个坑
关闭断点
当我们打开调试工具的时候,会弹出这么一个页面

这是一个debug断点,会阻拦我们后面的操作,但是并没有什么关系,很简单就能把它关闭

查看数据来源
接着,我们点击【Network】,按ctrl+f5刷新网页,又会出现阻拦的操作,我们依然点击刚才箭头符号,就可以解决


接着,我们回到【Network】,点击【XHR】,就会发现多了一条信息
至于,为什么要去点击【XHR】而不是点击其他的地方;其实也很简单,你稍微去检测一下这个网页,就会发现,机票的价格是通过XHR请求获取到的
有些小伙伴可能就要问了,什么是XHR请求,你可以去这篇文章看看XHR 请求
回过头来,我们来看看多出来的那条信息

查看这条信息的Header,发现它的URL很有意思,特别是画蓝色下划线的地方,这里我们先不管,我们接着看一下Preview
这就更有意思了,如我们所预料的,机票的价格都包含在这里面

分析URL
链接和数据都找了,我们去访问一下刚刚的链接(间隔了一段时间),理所当然的报错了
{"error": "token failed"}
我们看一下这条链接
> http://match.yuanrenxue.com/api/match/1?m=f289e3140053a9320c137b67e8723ba3%E4%B8%A81608971657
经常写爬虫的同学就会发现,后面的数字串【1608971657】,一定是一个时间戳
也就是说,要想正常的访问这条链接拿到数据,一定要有正常的时间戳
那么,何为正常的时间戳,一定和前面的字符串有关(m=f289e3140053a9320c137b67e8723ba3%E4%B8%A8)
我们继续刷新页面,发现【m=...】里面的字符串随着时间的推移都在进行变化,唯独[%E4%B8%A8]没变
[%E4%B8%A8],这个我想大家只要用过百度搜索就应该很熟悉吧,它是经过UrlEncode处理得到的,我们只需要反解码就能知道[%E4%B8%A8]到达是个什么东东
我们来到,站长工具,将它进行解码

很容易的,我们得到了它的值------>   (没错就是这么一个中文符号)
寻找丨符号
根据题目的提示[js混淆 源码乱码],我们可以想到一个很清晰的思路,就是去源码当中查找
回到页面,点击鼠标右键,点击[查看网页源代码]
按crtl+F ,搜索 符号,会找到唯一的一处代码
仔细地查看代码,发现它是写在script标签里面的
我们将整个script标签复制下来,到notepad++里面打开
粘贴到notepad++,你会发现全部的代码都浓缩成一行了
不要慌,我们可以使用notepad++自带的插件JSTool里面的JSFormat,即可将JS的代码格式化,得到如下结果
(如果没有JSTool插件,可以在notepad++里面安装一下)

ajax里面的内容可以不用管,我们删除多余的代码,可以得到简化后的代码
var list  里面的内容,也可以不用管,这里面主要就是包含页码,和m值,于是我们可以再次精简
var timestamp = Date.parse(new Date()) + 100000000;
var m = oo0O0(timestamp.toString()) + window.f;
这里的变量m,是不是很熟悉,我们再来看一下之前的链接
> http://match.yuanrenxue.com/api/match/1?m=f289e3140053a9320c137b67e8723ba3%E4%B8%A81608971657
大胆的猜测一下,m后面的值肯定是通过这里的代码实现的
我们先来看一下var m 后面的内容
  • oo0O0(),肯定是一个函数
  • timestamp.toString(),有一点编程基础的同学应该能猜到,这是将时间戳的一串数字转化成字符串类型
  • window.f,窗口这个对象调用了f,至于f是什么东东,我们先不管

    很显然,我们又有了一个新的突破口,oo0O0()函数
    分析oo0O0()函数
    我们回到包含网页源码的页面,搜索[oo0O0]
    会找到两处代码,一处肯定是定义此函数的代码,而另一处就是调用函数的代码
    我们将定义此函数的代码复制下来
    function oo0O0(mw) {
        window.b = '';
        for (var i = 0, len = window.a.length; i > (-0x2 * C & 0x6)) : 0x0) {
                        W = m['indexOf'](W)
                    }
                    return A
                };
                var t = function (w, m) {
                    var T = [],
                    A = 0x0,
                    C,
                    b = '',
                    W = '';
                    w = Y(w);
                    for (var R = 0x0, v = w['length']; R
    先不着急研究里面的代码,先看看函数的返回值,居然是个空字符串
    那么,这就意味着 [ var m = window.f ]
    var m = oo0O0(timestamp.toString()) + window.f;
    var m = window.f
    所有,m就只跟window.f有关了,那么window.f是什么呢,去那里找呢?
    寻找window.f
    怎么找到[window.f],首先我们能想到的,就是去源码中搜索,但是结果并没有那么理想,只出现了一处代码,也就是刚刚那一处代码
    在源码中找不到,但是又使用了[window.f],肯定是通过间接的方式定义赋值的,那么它又在在那里定义的呢?
    我们再来看一下这行代码
    var m = oo0O0(timestamp.toString()) + window.f;
    oo0O0函数执行了,但是却返回的空值;而且oo0O0()里面却有很多代码,开发者肯定不会这么闲吧
    也不排除,开发者写这些没有的代码误导我们,但是这里面肯定是有内容的
    经常做逆向的同学,肯定熟悉eval()函数,而这个函数也出现在了oo0O0()里面
    JS中的 eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码
    我们再来看一下,oo0O0()函数中的eval()部分
    eval(atob(window['b'])[J('0x0', ']dQW')](J('0x1', 'GTu!'), '\x27' + mw + '\x27'));
    而eval()里面还调用了一个函数atob()
    atob() 方法用于解码使用 base-64 编码的字符串
    我们将 【atob(window['b'])】复制下来,在调试工具的Console运行一下,可以得到了下列代码

    我们将这些代码复制下来,在notepad++里面格式化一下,就可以得到md5加密的算法,我这里展示一下部分代码

    可以很清楚的看到,【window.f】出现了,通过调用hex_md5()来实现赋值;但是又出现了一个新的问题,【mwqqppz】是什么呢?
    寻找mwqqppz
    我们再来看一下刚刚提到的eval代码片段
    eval(atob(window['b'])[J('0x0', ']dQW')](J('0x1', 'GTu!'), '\x27' + mw + '\x27'));
    可以看到eval函数里面除了atob(window['b'],还有
  • J('0x0', ']dQW')
  • J('0x1', 'GTu!')
  • '\x27' + mw + '\x27'

    我们再到调试工具的Console运行一下上面列出来的代码

    工具给出了报错提示,[没有找到J]
    这是因为oo0O0()函数里面还有一些代码段没有执行
    我们先把oo0O0()函数里面eval函数上面的所有代码复制,然后去Console中运行一下
    运行完后,可能会输出一些内容,我们可以不管,接着再次运行J('0x0', ']dQW'),就可以得到结果

    接着,我们依次运行J('0x1', 'GTu!')  ,  '\x27' + mw + '\x27',可以得到下列结果

    熟悉的 mwqqppz 是不是出现了呢
    但是,工具又报了一个错,[ mw没有找到 ]
    这是因为[ mw ]是oo0O0()函数的一个形参
    我们回顾一起前面的代码,就知道了
    var timestamp = Date.parse(new Date()) + 100000000;
    var m = oo0O0(timestamp.toString()) + window.f;
    function oo0O0(mw) {
    ...
    }
    这样大家就能理解了,[ mw ] 就是传进来的一个字符串时间戳
    那么,[ '\x27'  ]是什么呢,我们只需要在Console运行一下就知道了
    '\x27' = '  (没错就是单引号)
    翻译eval()函数
    知道eval()函数里面的奇怪符号的意义后,我们就可以翻译一下eval()代码片段
    eval(atob(window['b'])[J('0x0', ']dQW')](J('0x1', 'GTu!'), '\x27' + mw + '\x27'));
    eval(atob(window['b'])["replace"]("mwqqppz",'mw'));
    可能还是有些小伙伴看不懂这行代码,我用Python的语法规则,写一下这行代码,你肯定能懂
    eval(atob(window['b']).replace("mwqqppz",'mw'));
    其实,很好理解,就是将【atob(window['b'])】里面的【mwqqppz】替换成【mw】
    也就是,将【mwqqppz】替换成字符串的时间戳
    明白window.f的值从何而来
    读到这里的小伙伴,相信你们大致明白了window.f的值从何而来
    var timestamp = Date.parse(new Date()) + 100000000;
    var m = oo0O0(timestamp.toString()) + window.f;
    var m = window.f;
    var m = hex_md5(mwqqppz);
    var m = hex_md5(timestamp);
    通过上面的代码,逻辑已经很清晰了(当面你要从头看到这)
    现在我们就通过鬼鬼JS调试工具测试一下,我们的分析对不对
    验证答案
    首先,我们将【atob(window['b'])】在调试工具Console中运行得到的代码,复制到鬼鬼调试工具中
    写一个验证答案的函数
    function get_cipher(){
        timestamp = '1608971657000';
        f = hex_md5(timestamp);
        return f;
    }
    这里面的时间戳,来源于文章开头的链接

    http://match.yuanrenxue.com/api/match/1?m=f289e3140053a9320c137b67e8723ba3%E4%B8%A81608971657

    运行代码,可以看到加密后的时间戳与链接中的密文一模一样
    现在,我们就可以愉快的编写Python代码爬取网页数据啦!

    最后的爬虫和解出答案
    通过上面的一顿操作,我们可以编写一个JS文件,用于生成链接后面的密文
    在Python代码中可以通过第三方库execjs,执行这个JS文件,得到密文
    (由于代码有点多,我就不展示了,下面以文件的方式给大家)
    话不到多,直接上爬虫代码,很简单,写了一些注释,我就不详细赘述了
    # @BY     :Java_S
    # home.php?mod=space&uid=238618   :2020/12/25 9:10
    # @Slogan :够坚定够努力大门自然会有人敲,别怕没人赏识就像三十岁的梵高
    import requests
    import execjs
    import time
    def get_md5_value():
        # 导入JS,读取需要的js文件
        with open(r'JS/jsConfuse.js',encoding='utf-8',mode='r') as f:
            JsData = f.read()
        # 加载js文件,使用call()函数执行,传入需要执行函数即可获取返回值
        psd = execjs.compile(JsData).call('get_cipher')
        psd = psd.replace('丨','%E4%B8%A8')
        return psd
    def get_data(page_num,md5):
        url = f'http://match.yuanrenxue.com/api/match/1?page={page_num}&m={md5}'
        headers = {
            'Host':'match.yuanrenxue.com',
            'Referer':'http://match.yuanrenxue.com/match/1',
            'User-Agent':'yuanrenxue.project',
        }
        response = requests.get(url,headers=headers)
        return response.json()
    if __name__ == '__main__':
        sum_num = 0
        index_num = 0
        for page_num in range(1,6):
            info = get_data(page_num,get_md5_value())
            price_list = [i['value'] for i in info['data']]
            print(f'第{page_num}页的价格列表{price_list}')
            sum_num += sum(price_list)
            index_num += len(price_list)
            time.sleep(1)
        average_price = sum_num / index_num
        print(f'机票价格的平均值:{average_price}')

    航空, 国际机场

  • Java_S
    OP
      


    lbbas 发表于 2020-12-25 20:12
    技术贴呀  感谢楼主的分享。
    不过这已经解析出了方法。为何不直接在python里面实现get_cipher()这个函数呢 ...

    md5的加密算法太复杂了,直接运行js代码方便一点
    chhzll   

    大佬牛呀.....最近在学python 刚开始大部分基础搞定之后...迷茫过几天 看到了抖音可视化分析那篇帖子,就自己仿照学习着写了一遍受益良多,巧的是自己最近也在学习js哈哈哈,加油
    Java_S
    OP
      

    鬼鬼JS调试工具挺好用的,有需要的同学吗?有的话,我整理一下发出来
    https://wwx.lanzoux.com/iKHjqjpccoh
    叶凯   

    牛批,我遇到反爬的是直接放弃,换其他家爬,佩服这样的大神
    麦子1995   


    麦子1995 发表于 2020-12-25 18:02
    大佬厉害呀,谢谢分享,还会继续吗

    会的,可能更新时间会有点慢,毕竟要期末考试了
    Java_S
    OP
      


    Java_S 发表于 2020-12-25 17:57
    鬼鬼JS调试工具挺好用的,有需要的同学吗?有的话,我整理一下发出来

    想要!谢谢楼主
    980041382   


    980041382 发表于 2020-12-25 18:14
    想要!谢谢楼主

    https://wwx.lanzoux.com/iKHjqjpccoh  
    不谢哈
    Java_S
    OP
      

    好厉害呀!@
    andyfky   

    技术贴呀  感谢楼主的分享。
    不过这已经解析出了方法。为何不直接在python里面实现get_cipher()这个函数呢?
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部