js逆向:pyhon爬虫空气质量,无线debugger,AES,DES,MD5加密

  • 前言
  • 解决无限debugger
    • 第一次debugger
    • 第二次debugger
  • 加密解密流程分析
    • 请求数据加密
    • 返回数据解密
  • 加解密所需的参数获取
    • 思路分析
    • 流程
  • 完整代码
    • 代码
    • 运行结果

前言

(大家可以结合最后完整代码的注释看文章更佳哦!)
目标网址:空气质量在线
进入目标网址,抓取如下图表格数据。

解决无限debugger

第一次debugger

进入网址,F12与右键均被禁止,采取:设置—更多工具—开发者工具可以打开浏览器调试工具,但会被debugger。

进入如下txsdefwsw调用栈分析,发现左上方定义了3个变量:r,h,e,及一个o函数(看不懂没事),然后下面try里面声明了一个数组a:[“r”, o(“갯”), “g”, o(“갭”), function(t), “b”, “e”, “d”].reverse().join(“”)里面调用了o函数,如下图,通过控制台打印o函数,及function(t),可以发现其实27行上方的代码的作用就是声明了a=‘debugger’。

然后根据如下图可知,生成了一个function(){debugger;}自运行函数,并无限循环,这就是我们卡住的原因。

可在comsol输入下方js代码并运行就能过掉这个断点了;

var A = Function.prototype.constructor;
Function.prototype.constructor = function() {if (arguments[0] = 'debugger') {return function(){};}else{return A;}
}

第二次debugger

但发现我们又进入新的debugger:
阅读代码发现,自运行函数里面有个定时器,定时器里面有个检测屏幕的反调试,和一个function(){debugger;},故setInterval里面并没有正常的逻辑,我们可以在20行下个断点,直接将定时器置空就行,故在console输入如下代码。
注意:打上断点后,需刷新网页,按上述步骤重新过第一次的debugger,然后程序就会停在断点处,再在console执行下方代码就可过第二个debugger。

setInterval = function(){};


到这,发现就可以正常使用浏览器调试工具了。

加密解密流程分析

请求数据加密

选择XHR,改变左边输入框的城市名称,抓包,如下的接口https://www.aqistudy.cn/apinew/aqistudyapi.php为返回所需数据的接口,可以看出请求数据进行了加密。接下来就需要找到h92Fy7f02的生成过程。

通过调用栈分析,进入如下图第三个调用栈

可以找到h92Fy7f02,打上断点调试,发现h92Fy7f02由pDkNw65得到,而pDkNw65由ppz1QG2WgSi(“GETDATA”,{city: “苏州”})得到,故ppz1QG2WgSi为我们需要还原的加密函数。

进入ppz1QG2WgSi加密函数,如下图所示,其中的hex_md5和BASE64均为正常的MD5和BASE64,而AES加密,进行了修改,如下图,传入AES的key与iv进行了md5运算,并截取的不同的部分。


到这里是不是觉得请求所需的加密参数搞定了,并不是,这里还存在两个问题

  1. 这里所需appId,及AES所需的key和iv都是动态的,这里放到加解密所需的参数获取里一起解决。
  2. 上图中最后一步是对param的AES加密,并不一定,还可能是DES,也可能直接经过base64加密就可以了,既没有AES也没有DES,如下图是我抓的两次对比,可以发现不一样,这里的解决方式也放到加解密所需的参数获取里一起解决。
    故此请求所需加密的参数告一段落。

返回数据解密

可以看出返回数据进行了加密,故需解密。

同样,调用栈分析,在第三个调用栈发现返回的加密数据

打下断点,如下图dDzwEjOYrl9Zu即为解密函数

进入dDzwEjOYrl9Zu解密函数:
解密函数简单:分别经过AES解密—DES解密—base解密
同之前请求参数加密一样AES,DES的key与iv经过MD5处理了,且是动态变化的,在加解密所需的参数分析。

加解密所需的参数获取

思路分析

我的第一个思路就是分析主网页:https://www.aqistudy.cn/的网页源代码:没有直接显示我们需要的参数,然后分析里面的script标签引入的js文件:如下图红框的src里面发现可疑

然后分析对应上图src:js/jquery.min.js?v=1.1的包,如下图为返回的内容,里面五个eval,但找了找都没有用,只有最后一个有点用。

最后一个eval执行出的js代码如下:是一个dswejwehxt命名的Base64解密函数,大家可以先记住这个函数名,后面会出现。

function dswejwehxt(tksl) {var b = new Base64();return b.decode(tksl)
}

现在卡住了,主页面居然没有,这里返回我们请求数据的url的包:https://www.aqistudy.cn/apinew/aqistudyapi.php仔细查看一些信息,它的referer并不是主页面https://www.aqistudy.cn/,而是下图中的v=2.3的页面。

故接下来分析https://www.aqistudy.cn/html/city_realtime.php?v=2.3这个请求包,同样发现返回的页面信息并没有直接的我们需要的key啊,iv啊,appID啊,继续分析script标签引入的js,如下框的src=/js/encrypt_的链接发现可疑。(注意,这个src后面的v=的数字是变化的,故需从此处页面源代码正则匹配获得)

如下图是一个src=/js/encrypt_的链接的返回的内容,又是eval
在浏览器执行eval后,如下图(代码太长,仅仅截取的部分),发现还有eval,但是eval(dswejwehxt(…))的js代码,是不是眼熟,dswejwehxt是之前分析的一个Base64解密函数。

故在本地将上述代码Base64解密,并eval后的部分js代码如下:

终于找到我们所需的KEY,iv,appid了

流程

故此,所需的均分析完毕,这里再梳理一遍流程:

  1. 通过这个https://www.aqistudy.cn/html/city_realtime.php?v=2.3链接,正则匹配得到js/encrypt_的动态url。
  2. 请求上面得到的动态url,获得响应数据,响应数据类似于这样eval(dswejwehxt(…))的字符串。
  3. 解密eval(dswejwehxt(…))字符串,得到明文js代码,并从中解析得到加解密所需的参数,包括aes的key,iv;des的key,iv;appid
  4. 加密请求数据,并封装成需要的post请求格式
  5. 带着第4步的加密数据,对返回所需接口url:https://www.aqistudy.cn/apinew/aqistudyapi.php发送请求,得到返回的密文数据
  6. 解密返回的密文数据得到明文。

完整代码

代码

感谢看到这里的小伙伴。

import time
import json
import requests
import re
import execjs
import base64
from Crypto.Cipher import AES, DES
from hashlib import md5def get_miwen_md5(miwen):obj = md5()obj.update(miwen.encode("utf-8"))miwen = obj.hexdigest()return miwendef AES_encrypt(key, iv, miwen):'''AES加密:对key,iv进行了MD5加密:param key AES加密用到的key::param iv AES加密用到的iv::param miwen: 待加密的数据:return: 加密后的数据'''key = get_miwen_md5(key)[16:32].encode('utf-8')iv = get_miwen_md5(iv)[:16].encode('utf-8')aes = AES.new(key, mode=AES.MODE_CBC, IV=iv)bs = miwen.encode("utf-8")que = 16 - len(bs) % 16  # 缺少字节的个数bs += (que * chr(que)).encode("utf-8")result = aes.encrypt(bs)  # 要求加密的内容必须是字节b64 = base64.b64encode(result).decode()return b64def DES_encrypt(key, iv, miwen):'''DES加密:对key,iv进行了MD5加密:param key DES加密用到的key::param iv DES加密用到的iv::param miwen: 待加密的数据:return: 加密后的数据'''key = get_miwen_md5(key)[:8].encode('utf-8')iv = get_miwen_md5(iv)[24:].encode('utf-8')des = DES.new(key=key, mode=DES.MODE_CBC, IV=iv)bs = miwen.encode("utf-8")que = 8 - len(bs) % 8  # 缺少字节的个数bs += (que * chr(que)).encode("utf-8")result = des.encrypt(bs)b64 = base64.b64encode(result).decode()return b64def get_data(data):'''解析得到一个包含key,iv,appId等信息的字典,方便后续使用:param data:包含key,iv,appId的js明文代码::return: 封装好的key,iv,appId的一个字典'''dic_data = {}key_iv_list = re.findall('const\s*(.*?)\s*=\s*"(.*?)";', data)appId = re.findall("var appId\s*=\s*'(.*?)';", data)[0]dic_data['appId'] = appIdfor key, iv in key_iv_list:dic_data[key] = ivprint(data.count('AES.encrypt(param'))if data.count('AES.encrypt(param'):dic_data['Params_AES_key'], dic_data['Params_AES_iv'] = re.findall('AES.encrypt\(param,\s*(.*?),\s*(.*?)\);', data)[0]dic_data['Params_AES_key'], dic_data['Params_AES_iv'] = dic_data[dic_data['Params_AES_key']], dic_data[dic_data['Params_AES_iv']]if data.count('DES.encrypt(param'):dic_data['Params_DES_key'], dic_data['Params_DES_iv'] = re.findall('DES.encrypt\(param,\s*(.*?),\s*(.*?)\);', data)[0]dic_data['Params_DES_key'], dic_data['Params_DES_iv'] = dic_data[dic_data['Params_DES_key']], dic_data[dic_data['Params_DES_iv']]return dic_datadef get_res(url):headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36'}resp = requests.get(url, headers=headers)resp.encoding = 'utf-8'return resp.textdef de_eval(code):'''用来处理eval()的js代码:param code: 开头eval()的代码:return: 执行eval后的js代码'''js_eval = '''function decode(code) {code2 = code.replace(/^eval/, '');return eval(code2);}'''jsde = execjs.compile(js_eval)de_eval_code = jsde.call("decode", code)return de_eval_codedef get_requests_data(city):''':param city:需请求的城市:return :返回加密后的请求数据'''appId = data_dic['appId']obj = {'city': city}clienttype = 'WEB'method = "GETDATA"timestamp = int(time.time()*1000)param = {'appId': appId,'method': "GETDATA",'timestamp': timestamp,'clienttype': clienttype,'object': obj,'secret': get_miwen_md5(appId + method + str(timestamp) + clienttype + json.dumps(obj, ensure_ascii=False).replace(" ", ""))}# print(param['secret'])param = base64.b64encode(str.encode(json.dumps(param, ensure_ascii=False).replace(" ", ""), 'utf-8')).decode("utf-8")# print(param=='eyJhcHBJZCI6IjliZjg4NDFlNWE5MDEwYmY5ZjMyYjNmODhmMDJiYjUyIiwibWV0aG9kIjoiR0VUREFUQSIsInRpbWVzdGFtcCI6MTY1OTYwODI2ODU1NSwiY2xpZW50dHlwZSI6IldFQiIsIm9iamVjdCI6eyJjaXR5Ijoi5YyX5LqsIn0sInNlY3JldCI6ImFiM2E2MDNhZWZkOWFlNTcxZGViYjRjZTAzYWEyMzM3In0=')# print()if 'Params_AES_key' in data_dic.keys():param = AES_encrypt(data_dic['Params_AES_key'], data_dic['Params_AES_iv'], param)elif 'Params_DES_key' in data_dic.keys():param = DES_encrypt(data_dic['Params_DES_key'], data_dic['Params_DES_iv'], param)# print(param)return paramdef decrypt_data(params):'''返回数据解密 1. AES解密 2. DES解密 3. B64解密:param params: 加密数据:return: 明文数据'''# AES解密AES_key_id, AES_iv_id = re.findall('AES.decrypt\(data,\s*(.*?),\s*(.*?)\);', data)[0]AES_key, AES_iv = data_dic[AES_key_id], data_dic[AES_iv_id]AES_key, AES_iv = get_miwen_md5(AES_key)[16:32], get_miwen_md5(AES_iv)[:16]aes = AES.new(AES_key.encode(), mode=AES.MODE_CBC, IV=AES_iv.encode())bs = base64.b64decode(params.encode())params = aes.decrypt(bs).decode().strip("")# DES解密DES_key_id, DES_iv_id = re.findall('DES.decrypt\(data,\s*(.*?),\s*(.*?)\);', data)[0]DES_key, DES_iv = data_dic[DES_key_id], data_dic[DES_iv_id]DES_key, DES_iv = get_miwen_md5(DES_key)[:8], get_miwen_md5(DES_iv)[24:]aes = DES.new(DES_key.encode(), mode=DES.MODE_CBC, IV=DES_iv.encode())bs = base64.b64decode(params.encode())params = aes.decrypt(bs).decode().strip("")# B64解密params = base64.b64decode(params)params = params.decode("utf-8")return json.loads(params)if __name__ == '__main__':'''找到返回信息包含加解密所需参数的url:密钥,iv,appId 的url(即为如下的data_url)'''url = 'https://www.aqistudy.cn/html/city_realtime.php?v=2.3'page_source = get_res(url)data_url = 'https://www.aqistudy.cn/js/encrypt_' + re.findall('<script.*?/js/encrypt_(.*?)"></script>', page_source)[0]# print(data_url)'''请求data_url,返回文本的是eval(dswejwehxt(...)),其中dswejwehxt的次数不确定,dswejwehxt为base64解密如下代码即解决eval(dswejwehxt(...))文本,得到明文数据data'''page_source = get_res(data_url).strip()data = de_eval(page_source)while True:for i in range(data.count("dswejwehxt")):if i == 0:data = re.findall("dswejwehxt\('(.*?)'\)", data)[0]data = base64.b64decode(data.encode()).decode("utf-8")if data.startswith('eval'):data = de_eval(data)if data.count("dswejwehxt") == 0 and not data.startswith('eval'):break'''从data解析得到加解密需要的key,iv,appId等,封装进了data_dic字典这里注意返回的data,可能是压缩的,也可能是格式化的,故写对两种情况下通用的正则表达式'''with open('temp2.js', 'w') as fp:fp.write(data)data_dic = get_data(data)# print(data_dic)'''加密请求数据,并封装成需要的字典格式'''post_data = get_requests_data('北京')postdatakey = re.findall('data:\s*\{\s*(.*?):', data)[0]post_data = { postdatakey: post_data }# 对返回所需数据的接口发送请求url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36'}params = requests.post(url=url, headers=headers, data=post_data).text# 将返回的密文数据解密decrypt_data = decrypt_data(params)print(decrypt_data)

运行结果

【python爬虫】js逆向:空气质量在线平台,解决反调试,加密相关推荐

  1. js逆向 空气质量检测平台

    js逆向 空气质量检测平台 郑重声明 郑重声明:本项目的所有代码和相关文章, 仅用于经验技术交流分享,禁止将相关技术应用到不正当途径,因为滥用技术产生的风险与本人无关. 反调试绕过 url:https ...

  2. 【Python爬虫:唯美girl,charles解决反调试】

    Python爬虫:唯美girl,charles解决反调试 Python爬虫:唯美girl,不让F12,我就要! 前言 1.目标简要说明 2.解决无法打开浏览器开发者工具 2.1解决思路 2.2思路具体 ...

  3. python爬虫JS逆向:X咕视频密码与指纹加密分析

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:煌金的咸鱼 PS:如有需要Python学习资料的小伙伴可以加点击下方 ...

  4. **超防 ja3+加速乐(三种加密(md5,sha1,sha256)) 实战(python爬虫js逆向)

    **超防 加速乐+ja3 实战(python爬虫js逆向) 地址 aHR0cHM6Ly93d3cuaGVmZWkuZ292LmNuL2NvbnRlbnQvY29sdW1uLzY3OTQ4MTE/cGF ...

  5. python爬虫js逆向加密,Web爬虫处理参数js加密、js混淆、js逆向

    中国空气质量在线监测平台(https://www.aqistudy.cn/html/city_detail.html)在众多的练习中,关闭了前台数据信息的展示,也就是说现在网页是这样的: 但我们主要学 ...

  6. Python爬虫——天气信息空气质量数据

    import requests from bs4 import BeautifulSoup from pandas import read_csv from IPython.display impor ...

  7. 腾讯爬虫python_【Python爬虫+js逆向】Python爬取腾讯漫画!

    前一段假期期间,博主已经自学完了Python反爬虫的相关内容,面对各大网站的反爬机制也都有了一战之力.可惜因实战经验不足,所以总体来说还是一个字--菜.前两天,在学习并实战爬取了博主最爱看的腾讯动漫后 ...

  8. python爬虫JS逆向加密破解之百度翻译

    最近在从基础学习JS逆向,来分享一下百度翻译JS逆向的整个过程,也有助于自己加深记忆. JS逆向可以说是爬虫工程师必备的知识点了,但是如果对前端知识不够了解还是学起来很有难度的. 想学习的话可以在B站 ...

  9. gta python解指纹_python爬虫JS逆向:X咕视频密码与指纹加密分析

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:煌金的咸鱼 正文 先来看看今天的受害者: aHR0cDovL3d3d ...

最新文章

  1. 作为一个程序员。数学重要吗,下面python大牛告诉你
  2. 本地window cmd 远程连接外网redis
  3. Android 导致OOM的常见原因
  4. Scrapy-xpath用法以及实例
  5. 神策数据 × 水滴汽车:着眼车主忠诚度,实现转型期逆势增长!
  6. 利用mvc 模型绑定验证方法验证普通类对象数据是否合法
  7. Entity Framework 普通操作(复习用)——感觉有点不对,需要撸代码验证
  8. C++设计模式-开放-封闭原则基本概念与实例
  9. mysql使用占位费付_美国大学硕士占位费怎么支付?三种支付方式任选!
  10. linux xorg 文件 位置,Linux系统中xorg.conf文件详细介绍
  11. 正则表达+验证 [记录]
  12. linux下的图形界面扫雷游戏(Gtk+2.0)
  13. python和java的区别-python和java的区别,看了这个就会区分了!
  14. 超实用的JavaScript技巧及最佳实践(下)
  15. Word多级标题设置和自动生成目录
  16. scratch编程滑雪者游戏教程
  17. Bugku CTF web22(Web)
  18. 山重水复疑无路 柳暗花明又一村
  19. 水杯测试用例(500ml的塑料水杯)
  20. 磁盘最优存储问题---Python

热门文章

  1. 数组去重newset
  2. marquee功能:marquee的速度怎么设置
  3. 关于QXDM的安装,解决Win7下QIK报错的问题
  4. hanLP的分词的使用
  5. SpEL之#和$的区别
  6. JavaScript 代码格式化及编写工具
  7. 简单工厂SimpleFactory
  8. 【Integrated Electronics系列——模拟电子技术基础】
  9. 仿微信、QQ评论点击事件
  10. 考研英语阅读技巧总结(唐迟)