关键词搜索

源码搜索 ×
×

高级实战案例:Python反反爬之JavaScript混淆

发布2021-08-21浏览405次

详情内容

写在前面

很早之前在吾爱破解论坛上看见了【猿人学】Web端爬虫攻防大赛,当时进入他们官网的时候,比赛已经结束了。看着那些题目还挺有意思的,但由于各种原因一直没有机会去做那些题目。最近比较闲,就去把猿人学官网打开看了一下,尝试着完成了第一道题目---JS混淆[源码乱码]

复制代码

"""
当然在学习Python的道路上肯定会困难,没有好的学习资料,怎么去学习呢? 
学习Python中有不明白推荐加入交流群号:928946953 
群里有志同道合的小伙伴,互帮互助, 群里有不错的视频学习教程和PDF!
还有大牛解答!
"""

复制代码

题目


题目网址点我去刷题

抓取所有(5页)机票的价格,并计算所有机票价格的平均值,填入答案。


题目看着挺简单,就是抓取机票的价格,并算出平均值,但是当我们打开开发者调试工具的时候,就会遇到第一个坑

关闭断点

当我们打开调试工具的时候,会弹出这么一个页面


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

查看数据来源

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

接着,我们回到【Network】,点击【XHR】,就会发现多了一条信息
至于,为什么要去点击【XHR】而不是点击其他的地方;其实也很简单,你稍微去检测一下这个网页,就会发现,机票的价格是通过XHR请求获取到的

有些小伙伴可能就要问了,什么是XHR请求,你可以去这篇文章看看XHR 请求


回过头来,我们来看看多出来的那条信息


查看这条信息的Header,发现它的URL很有意思,特别是画蓝色下划线的地方,这里我们先不管,我们接着看一下Preview

这就更有意思了,如我们所预料的,机票的价格都包含在这里面

分析URL

链接和数据都找了,我们去访问一下刚刚的链接(间隔了一段时间),理所当然的报错了

{"error": "token failed"}

JavaScript

复制

我们看一下这条链接

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++里面打开

<script>window.url='/api/match/1';request=function(){var timestamp=Date.parse(new Date()) + 100000000;var m=oo0O0(timestamp.toString())+window.f;var list={"page":window.page,"m":m+'丨'+timestamp/1000};$.ajax({url:window.url,dataType:"json",async:false,data:list,type:"GET",beforeSend:function(request){},success:function(data){data=data.data;let html='';let us_sign=`<div class="b-airfly"><div class="e-airfly"data-reactid=".1.3.3.2.0.$KN5911.0"><div class="col-trip"data-reactid=".1.3.3.2.0.$KN5911.0.0"><div class="s-trip"data-reactid=".1.3.3.2.0.$KN5911.0.0.0"><div class="col-airline"data-reactid=".1.3.3.2.0.$KN5911.0.0.0.0"><div class="d-air"data-reactid=".1.3.3.2.0.$KN5911.0.0.0.0.0:$0"><div class="air"data-reactid=".1.3.3.2.0.$KN5911.0.0.0.0.0:$0.0"><span data-reactid=".1.3.3.2.0.$KN5911.0.0.0.0.0:$0.0.1">中国联合航空</span></div><div class="num"data-reactid=".1.3.3.2.0.$KN5911.0.0.0.0.0:$0.1"><span class="n"data-reactid=".1.3.3.2.0.$KN5911.0.0.0.0.0:$0.1.0">KN5911</span><span class="n"data-reactid=".1.3.3.2.0.$KN5911.0.0.0.0.0:$0.1.1">波音737(中)</span><noscript data-reactid=".1.3.3.2.0.$KN5911.0.0.0.0.0:$0.1.2"></noscript></div></div><noscript data-reactid=".1.3.3.2.0.$KN5911.0.0.0.0.1"></noscript></div><div class="col-time"data-reactid=".1.3.3.2.0.$KN5911.0.0.0.1"><div class="sep-lf"data-reactid=".1.3.3.2.0.$KN5911.0.0.0.1.0"><h2 data-reactid=".1.3.3.2.0.$KN5911.0.0.0.1.0.0">13:50</h2><p class="airport"data-reactid=".1.3.3.2.0.$KN5911.0.0.0.1.0.1"><span data-reactid=".1.3.3.2.0.$KN5911.0.0.0.1.0.1.0">大兴国际机场</span><span data-reactid=".1.3.3.2.0.$KN5911.0.0.0.1.0.1.1"></span></p></div><div class="sep-ct"data-reactid=".1.3.3.2.0.$KN5911.0.0.0.1.1"><div class="range"data-reactid=".1.3.3.2.0.$KN5911.0.0.0.1.1.0">3小时40分钟</div><div class="line"data-reactid=".1.3.3.2.0.$KN5911.0.0.0.1.1.1"></div></div><div class="sep-rt"data-reactid=".1.3.3.2.0.$KN5911.0.0.0.1.2"><noscript data-reactid=".1.3.3.2.0.$KN5911.0.0.0.1.2.0"></noscript><h2 data-reactid=".1.3.3.2.0.$KN5911.0.0.0.1.2.1">17:30</h2><p class="airport"data-reactid=".1.3.3.2.0.$KN5911.0.0.0.1.2.2"><span data-reactid=".1.3.3.2.0.$KN5911.0.0.0.1.2.2.0">宝安机场</span></p></div><noscript data-reactid=".1.3.3.2.0.$KN5911.0.0.0.1.3"></noscript></div></div></div><div class="col-price"data-reactid=".1.3.3.2.0.$KN5911.0.1"><p class="prc"data-reactid=".1.3.3.2.0.$KN5911.0.1.0"><span data-reactid=".1.3.3.2.0.$KN5911.0.1.0.0"><i class="rmb"data-reactid=".1.3.3.2.0.$KN5911.0.1.0.0.0">¥</i><span class="fix_price"data-reactid=".1.3.3.2.0.$KN5911.0.1.0.0.1"><span class="prc_wp"style="width:48px">price_sole</span></span></span></p><div class="vim"data-reactid=".1.3.3.2.0.$KN5911.0.1.1"><span class="v dis"data-reactid=".1.3.3.2.0.$KN5911.0.1.1.$0"></span></div></div><div class="col-fold"data-reactid=".1.3.3.2.0.$KN5911.0.2"><p class="fd"data-reactid=".1.3.3.2.0.$KN5911.0.2.0">收起</p></div></div><noscript data-reactid=".1.3.3.2.0.$KN5911.1"></noscript></div>`;let choice=['中国南方航空','吉祥航空','奥凯航空','九元航空','长龙航空','东方航空','中国国际航空','深圳航空','海南航空','春秋航空','上海航空','西部航空','重庆航空','西藏航空','中国联合航空','云南祥鹏航空','云南英安航空','厦门航空','天津航空','山东航空','四川航空','华夏航空','长城航空','成都航空有','北京首都航空','中华航空','意大利国家航空公司','印度百捷航空','越南航空','远东航空','印度航空公司','印度捷特航空有限公司','以色列航空公司','意大利航空','伊朗航空公司','印度尼西亚鹰航空公司','英国航空公司','西方天空航空','西捷航空','西班牙欧洲航空公司','西班牙航空公司','中国南方航空','吉祥航空','奥凯航空','九元航空','长龙航空','东方航空','中国国际航空','深圳航空','海南航空','春秋航空','上海航空','西部航空','重庆航空','西藏航空','中国联合航空','云南祥鹏航空','云南英安航空','厦门航空','天津航空','山东航空','四川航空','华夏航空','长城航空','成都航空有','北京首都航空','中华航空','意大利国家航空公司','印度百捷航空','越南航空','远东航空','印度航空公司','印度捷特航空有限公司','以色列航空公司','意大利航空','伊朗航空公司','印度尼西亚鹰航空公司','英国航空公司','西方天空航空','西捷航空','西班牙欧洲航空公司','西班牙航空公司'];let op=1; let jic=['北京首都国际机场','上海虹桥国际机场','上海浦东国际机场','天津滨海国际机场','太原武宿机场','呼和浩特白塔机场','沈阳桃仙国际机场','大连周水子国际机场','长春大房身机场','哈尔滨阎家岗国际机场','齐齐哈尔三家子机场','佳木斯东郊机场','厦门高崎国际机场','福州长乐国际机场','杭州萧山国际机场','合肥骆岗机场','宁波栎社机场','南京禄口国际机场','广州白云国际机场','深圳宝安国际机场','长沙黄花机场','海口美亚机场','武汉天河机场','济南遥墙机场','青岛流亭机场','南宁吴墟机场','三亚凤凰国际机场','重庆江北国际机场','成都双流国际机场','昆明巫家坝国际机场','昆明长水国际机场','桂林两江国际机场','西安咸阳国际机场','兰州中川机场','贵阳龙洞堡机场','拉萨贡嘎机场','乌鲁木齐地窝堡机场','南昌向塘机场','郑州新郑机场','北京首都国际机场','上海虹桥国际机场','上海浦东国际机场','天津滨海国际机场','太原武宿机场','呼和浩特白塔机场','沈阳桃仙国际机场','大连周水子国际机场','长春大房身机场','哈尔滨阎家岗国际机场','齐齐哈尔三家子机场','佳木斯东郊机场','厦门高崎国际机场','福州长乐国际机场','杭州萧山国际机场','合肥骆岗机场','宁波栎社机场','南京禄口国际机场','广州白云国际机场','深圳宝安国际机场','长沙黄花机场','海口美亚机场','武汉天河机场','济南遥墙机场','青岛流亭机场','南宁吴墟机场','三亚凤凰国际机场','重庆江北国际机场','成都双流国际机场','昆明巫家坝国际机场','昆明长水国际机场','桂林两江国际机场','西安咸阳国际机场','兰州中川机场','贵阳龙洞堡机场','拉萨贡嘎机场','乌鲁木齐地窝堡机场','南昌向塘机场','郑州新郑机场'];if(window.page){}else{window.page=1}$.each(data,function(index,val){html+=us_sign.replace('price_sole',val.value).replace('中国联合航空',choice[op*window.page]).replace('大兴国际',jic[parseInt(op*window.page/2)+1]).replace('宝安机场',jic[jic.length-parseInt(op*window.page/2)-1]);op+=1});$('.m-airfly-lst').text('').append(html)},complete:function(){},error:function(){alert('数据拉取失败。可能是触发了风控系统,若您是正常访问,请使用谷歌浏览器无痕模式,并且校准电脑的系统时间重新尝试');alert('生而为虫,我很抱歉,请刷新页面,查看问题是否存在');$('.page-message').eq(0).addClass('active');$('.page-message').removeClass('active')}})};request()</script>

JavaScript

复制

粘贴到notepad++,你会发现全部的代码都浓缩成一行了
不要慌,我们可以使用notepad++自带的插件JSTool里面的JSFormat,即可将JS的代码格式化,得到如下结果
(如果没有JSTool插件,可以在notepad++里面安装一下)

ajax里面的内容可以不用管,我们删除多余的代码,可以得到简化后的代码

  1. <script>
  2. request = function () {
  3. var timestamp = Date.parse(new Date()) + 100000000;
  4. var m = oo0O0(timestamp.toString()) + window.f;
  5. var list = {
  6. "page": window.page,
  7. "m": m + '丨' + timestamp / 1000
  8. };
  9. };
  10. request()
  11. </script>

JavaScript

复制

var list 里面的内容,也可以不用管,这里面主要就是包含页码,和m值,于是我们可以再次精简

  1. var timestamp = Date.parse(new Date()) + 100000000;
  2. var m = oo0O0(timestamp.toString()) + window.f;

JavaScript

复制

这里的变量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]
会找到两处代码,一处肯定是定义此函数的代码,而另一处就是调用函数的代码
我们将定义此函数的代码复制下来

oo0O0()函数

先不着急研究里面的代码,先看看函数的返回值,居然是个空字符串


那么,这就意味着 [ var m = window.f ]

  1. var m = oo0O0(timestamp.toString()) + window.f;
  2. var m = window.f

JavaScript

复制

所有,m就只跟window.f有关了,那么window.f是什么呢,去那里找呢?

寻找window.f

怎么找到[window.f],首先我们能想到的,就是去源码中搜索,但是结果并没有那么理想,只出现了一处代码,也就是刚刚那一处代码

在源码中找不到,但是又使用了[window.f],肯定是通过间接的方式定义赋值的,那么它又在在那里定义的呢?

我们再来看一下这行代码

var m = oo0O0(timestamp.toString()) + window.f;

JavaScript

复制

oo0O0函数执行了,但是却返回了空值;而且oo0O0()里面却有很多代码,开发者肯定不会这么闲吧
也不排除,开发者写这些没有的代码误导我们,但是这里面肯定是有内容的

经常做逆向的同学,肯定熟悉eval()函数,而这个函数也出现在了oo0O0()里面
JS中的 eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码
我们再来看一下,oo0O0()函数中的eval()部分

eval(atob(window['b'])[J('0x0', ']dQW')](J('0x1', 'GTu!'), '\x27' + mw + '\x27'));

JavaScript

复制

而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'));

JavaScript

复制

可以看到eval函数里面除了atob(window['b'],还有

  • J('0x0', ']dQW')
  • J('0x1', 'GTu!')
  • 'x27' + mw + 'x27'

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


工具给出了报错提示,[没有找到J]
这是因为oo0O0()函数里面还有一些代码段没有执行
我们先把oo0O0()函数里面eval函数上面的所有代码复制,然后去Console中运行一下

eval函数上面的代码


运行完后,可能会输出一些内容,我们可以不管,接着再次运行J('0x0', ']dQW'),就可以得到结果


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

熟悉的 mwqqppz 是不是出现了呢

但是,工具又报了一个错,[ mw没有找到 ]
这是因为[ mw ]是oo0O0()函数的一个形参
我们回顾一下前面的代码,就知道了

  1. var timestamp = Date.parse(new Date()) + 100000000;
  2. var m = oo0O0(timestamp.toString()) + window.f;
  3. function oo0O0(mw) {
  4. ...
  5. }

JavaScript

复制

这样大家就能理解了,[ mw ] 就是传进来的一个字符串时间戳

那么,[ 'x27' ]是什么呢,我们只需要在Console运行一下就知道了
'x27' = ' (没错就是单引号)

翻译eval()函数

知道eval()函数里面的奇怪符号的意义后,我们就可以翻译一下eval()代码片段

  1. eval(atob(window['b'])[J('0x0', ']dQW')](J('0x1', 'GTu!'), '\x27' + mw + '\x27'));
  2. eval(atob(window['b'])["replace"]("mwqqppz",'mw'));

JavaScript

复制

可能还是有些小伙伴看不懂这行代码,我用Python的语法规则,写一下这行代码,你肯定能懂

eval(atob(window['b']).replace("mwqqppz",'mw'));

JavaScript

复制

其实,很好理解,就是将【atob(window['b'])】里面的【mwqqppz】替换成【mw】
也就是,将【mwqqppz】替换成字符串的时间戳

明白window.f的值从何而来

读到这里的小伙伴,相信你们大致明白了window.f的值从何而来

  1. var timestamp = Date.parse(new Date()) + 100000000;
  2. var m = oo0O0(timestamp.toString()) + window.f;
  3. var m = window.f;
  4. var m = hex_md5(mwqqppz);
  5. var m = hex_md5(timestamp);

JavaScript

复制

通过上面的代码,逻辑已经很清晰了(当面你要从头看到这)
现在我们就通过鬼鬼JS调试工具测试一下,我们的分析对不对

验证答案

首先,我们将【atob(window['b'])】在调试工具Console中运行得到的代码,复制到鬼鬼调试工具中
写一个验证答案的函数

  1. function get_cipher(){
  2. timestamp = '1608971657000';
  3. f = hex_md5(timestamp);
  4. return f;
  5. }

JavaScript

复制

这里面的时间戳,来源于文章开头的链接

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

运行代码,可以看到加密后的时间戳与链接中的密文一模一样


现在,我们就可以愉快的编写Python代码爬取网页数据啦!

最后的爬虫和解python教程出答案

通过上面的一顿操作,我们可以编写一个JS文件,用于生成链接后面的密文
在Python代码中可以通过第三方库execjs,执行这个JS文件,得到密文

代码在里面


话不到多,直接上爬虫代码,很简单,写了一些注释,我就不详细赘述了

  1. # @BY :Java_S
  2. # @Time :2020/12/25 9:10
  3. # @Slogan :够坚定够努力大门自然会有人敲,别怕没人赏识就像三十岁的梵高
  4. import requests
  5. import execjs
  6. import time
  7. def get_md5_value():
  8. # 导入JS,读取需要的js文件
  9. with open(r'JS/jsConfuse.js',encoding='utf-8',mode='r') as f:
  10. JsData = f.read()
  11. # 加载js文件,使用call()函数执行,传入需要执行函数即可获取返回值
  12. psd = execjs.compile(JsData).call('get_cipher')
  13. psd = psd.replace('丨','%E4%B8%A8')
  14. return psd
  15. def get_data(page_num,md5):
  16. url = f'http://match.yuanrenxue.com/api/match/1?page={page_num}&m={md5}'
  17. headers = {
  18. 'Host':'match.yuanrenxue.com',
  19. 'Referer':'http://match.yuanrenxue.com/match/1',
  20. 'User-Agent':'yuanrenxue.project',
  21. }
  22. response = requests.get(url,headers=headers)
  23. return response.json()
  24. if __name__ == '__main__':
  25. sum_num = 0
  26. index_num = 0
  27. for page_num in range(1,6):
  28. info = get_data(page_num,get_md5_value())
  29. price_list = [i['value'] for i in info['data']]
  30. print(f'第{page_num}页的价格列表{price_list}')
  31. sum_num += sum(price_list)
  32. index_num += len(price_list)
  33. time.sleep(1)
  34. average_price = sum_num / index_num
  35. print(f'机票价格的平均值:{average_price}')

相关技术文章

点击QQ咨询
开通会员
返回顶部
×
微信扫码支付
微信扫码支付
确定支付下载
请使用微信描二维码支付
×

提示信息

×

选择支付方式

  • 微信支付
  • 支付宝付款
确定支付下载