记录一次参数破解的过程

写在前面

这篇文章很多是参照大佬的文章来的
感谢Ta提供的教程!
http://ju.outofmemory.cn/entry/341656

目标

中国天气质量在线监测平台
网站:https://www.aqistudy.cn/html/city_detail.html

情景

随意选择一个城市,看看会出现什么
在这里插入图片描述
在这里插入图片描述
emmm,一串莫名其妙的参数
不同于平时的表单参数
这里,我们就需要去分析它的“加密逻辑”

分析

这里,当我们点击搜索的那个图标时,后台出现了ajax请求,说明点击动作被“监听”了
这时,我们想要知道这个点击动作发生时,后台到底做了些什么
借助火狐浏览器,我们可以发现
在这里插入图片描述
可以看到,搜索图标绑定了三个事件
其中 click 事件是在 city_detail.html 的第 139 行处理的,而且是调用了 getData() 函数。
找到这个函数
在这里插入图片描述
经过分析发现它又调用了 getAQIData() 和 getWeatherData() 两个方法,而这两个方法的声明就在下面,再进一步分析发现这两个方法都调用了 getServerData() 这个方法,并传递了 method、param 等参数,然后还有一个回调函数很明显是对返回数据进行处理的,这说明 Ajax 请求就是由这个 getServerData() 方法发起的,如图所示:
在这里插入图片描述

注意这张图片中的那些变量就是我们之后定义的变量内容,具体格式请看后文的 图片集-1

在这里插入图片描述
所以这里我们只需要再找到 getServerData() 方法的定义即可分析它的加密逻辑了。继续搜索,然而在原始 html 文件中没有搜索到该方法,那就继续去搜寻其他的 JavaScript 文件有没有这个定义,终于经过一番寻找,居然在 jquery-1.8.0.min.js 这个文件中找到了:

这里可以这样总结吧,查找一些方法时,现在源代码中寻找,没有就在抓的包中寻找

在这里插入图片描述
有的小伙伴可能会说,jquery.min.js 不是一个库文件吗,怎么会有这种方法声明?嗯,我只想说,最危险的地方就是最安全的地方。

库文件?那是什么?

好了,现在终于找到这个方法了,可为什么看不懂呢?这个方法名后面怎么直接跟了一些奇怪的字符串,而且不符合一般的 JavaScript 写法。其实这里是经过 JavaScript 混淆加密了,混淆加密之后,代码将变为不可读的形式,但是功能是完全一致的,这是一种常见的 JavaScript 加密手段。

新概念get,混淆加密!

下面好多直接复制大佬的分析,自己总结的感觉不太好

JavaScript 混淆之后,其实是有反混淆方法的,最简单的方法便是搜索在线反混淆网站,这里提供一个: http://www.bm8.com.cn/jsConfusion/ ,我们将 jquery-1.8.0.min.js 中第二行 eval 开头的混淆后的 JavaScript 代码复制一下,然后粘贴到这个网站中进行反混淆,就可以看到正常的 JavaScript 代码了,搜索一下就可以找到 getServerData() 方法了,可以看到这个方法确实发出了一个 Ajax 请求,请求了刚才我们分析到的接口:

这个网址访问好像有点慢

在这里插入图片描述

在这里插入图片描述
那么到这里我们又可以发现一个很关键的方法,那就是 getParam(),它接受了 method 和 object 参数,然后返回得到的 param 结果就作为 POST Data 参数请求接口了,所以 param 就是加密后的 POST Data,一些加密逻辑都在 getParam() 方法里面,其方法实现如下:

以下内容位于,混淆的代码里面,稍微找一下就找到了

var getParam = (function () {
        function ObjectSort(obj) {
            var newObject = {};
            Object.keys(obj).sort().map(function (key) {
                newObject[key] = obj[key]
            });
            return newObject
        }
        return function (method, obj) {
            var appId = '1a45f75b824b2dc628d5955356b5ef18';
            var clienttype = 'WEB';
            var timestamp = new Date().getTime();
            var param = {
                appId: appId,
                method: method,
                timestamp: timestamp,
                clienttype: clienttype,
                object: obj,
                secret: hex_md5(appId + method + timestamp + clienttype + JSON.stringify(ObjectSort(obj)))
            };
            # 这里有Base64和AES加密
            # 加密方法有哪些?怎么判断?encrypt?先拿小本本记下
            param = BASE64.encrypt(JSON.stringify(param));
            return AES.encrypt(param, aes_client_key, aes_client_iv)
        }
    })();

可以看到这里使用了 Base64 和 AES 加密。加密之后的字符串便作为 POST Data 传送给服务器了,然后服务器再进行解密处理,然后进行逻辑处理,然后再对处理后的数据进行加密,返回了加密后的数据,那么 JavaScript 再接收到之后再进行一次解密,再渲染才能得到正常的结果。

以上内容,翻译一下,我们先把数据加密交给服务器–>服务器解密处理、再加密–>JavaScript接收解密并渲染
Emmmm,好像和Ta说的差不多

所以这里还需要分析服务器传回的数据是怎样解密的。顺腾摸瓜,很容易就找到一个 decodeData() 方法,其定义如下:

以下代码位于getParam()方法的上面
这里存在一个问题,怎么知道所找的函数使我们需要的解密函数
上面小本本记着的,Emmmm,长得挺像的,上面是加密,这个是解密吧?

function decodeData(data) {
        data = AES.decrypt(data, aes_server_key, aes_server_iv);
        data = DES.decrypt(data, des_key, des_iv);
        data = BASE64.decrypt(data);
        return data
    }

发现了,上面是encrypt,这里是decrypt,一个是加密,一个是解密!
嗯,这里又经过了三层解密,才把正常的明文数据解析出来。

大致操作

所以一切都清晰了,我们需要实现两个过程才能正常使用这个接口,即实现 POST Data 的加密过程和 Response Data 的解密过程。其中 POST Data 的加密过程是 Base64 + AES 加密,Response Data 的解密是 AES + DES + Base64 解密。加密解密的 Key 也都在 JavaScript 文件里能找到,我们用 Python 实现这些加密解密过程就可以了。

所以接下来怎么办?接着刚啊!

接着刚才怪!

何必去费那些事去用 Python 重写一遍 JavaScript,万一二者里面有数据格式不统一或者二者由于语言不兼容问题导致计算结果偏差,上哪里去 Debug?

那怎么办?这里我们借助于 PyExecJS 库来实现 JavaScript 模拟就好了。

这里涉及到PyExecJS的安装过程,博主自己之前捣鼓好了,就不写了
有需要,请看大佬的过程,或者自行百度

接下来我们将刚才反混淆的 JavaScript 保存成一个文件,叫做 encryption.js,然后用 PyExecJS 模拟运行相关的方法即可。

将之前进行反混淆操作的那串代码复制粘贴,保存为后缀为 .js 文件,博主自己讲它命名为1.js,后来想想大佬的命名更科学一点

首先我们来实现加密过程,这里 getServerData() 方法其实已经帮我们实现好了,并实现了 Ajax 请求,但这个方法里面有获取 Storage 的方法,Node.js 不适用,所以这里我们直接改写下,实现一个 getEncryptedData() 方法实现加密,在 encryption.js 里面实现如下方法:

为什么?不适用?可能是没有这个方法吧
答案:加粗内容

function getEncryptedData(method, city, type, startTime, endTime) {
    var param = {};
    param.city = city;
    param.type = type;
    param.startTime = startTime;
    param.endTime = endTime;
    return getParam(method, param);
}

这个是什么意思。。。得理解一下
自定义的一些变量,参考之前的 getAQIData() 方法的那张图片中的内容

接着我们模拟执行这些方法即可:

import execjs

# Init environment
node = execjs.get()

# Params
# 以下内容具体格式参照后文的 图片集-1
method = 'GETCITYWEATHER'
city = '北京'
type = 'HOUR'
start_time = '2018-01-25 00:00:00'
end_time = '2018-01-25 23:00:00'

# Compile javascript
file = 'encryption.js'
ctx = node.compile(open(file).read())

# Get params
js = 'getEncryptedData("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
params = ctx.eval(js)

这里我们首先定义一些参数,如 method、city、start_time 等,这些都可以通过分析 JavaScript 很容易得出其规则。

然后这里首先通过 execjs(即 PyExecJS)的 get() 方法声明一个运行环境,然后调用 compile() 方法来执行刚才保存下来的加密库 encryption.js,因为这里面包含了一些加密方法和自定义方法,所以只有执行一遍才能调用。

接着我们再构造一个 js 字符串,传递这些参数,然后通过 eval() 方法来模拟执行,得到的结果赋值为 params,这个就是 POST Data 的加密数据。

接着我们直接用 requests 库来模拟 POST 请求就好了,也没必要用 jQuery 自带的 Ajax 了,当然后者也是可行的,只不过需要加载一下 jQuery 库。

接着我们用 requests 库来模拟 POST 请求:

这里博主自己是用scrapy里面的方法实现的,方法大同小异

# Get encrypted response text
api = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
response = requests.post(api, data={'d': params})

这样 response 的内容就是服务器返回的加密的内容了。

接下来我们再调用一下 JavaScript 中的 decodeData() 方法即可实现解密:

# Decode data
js = 'decodeData("{0}")'.format(response.text)
decrypted_data = ctx.eval(js)

这样 decrypted_data 就是解密后的字符串了,解密之后,实际上是一个 JSON 字符串:

以下数据直接复制的大佬文章里面的数据

{'success': True, 'errcode': 0, 'errmsg': 'success', 'result': {'success': True, 'data': {'total': 22, 'rows': [{'time': '2018-01-25 00:00:00', 'temp': '-7', 'humi': '35', 'wse': '1', 'wd': '东北风', 'tq': '晴'}, {'time': '2018-01-25 01:00:00', 'temp': '-9', 'humi': '38', 'wse': '1', 'wd': '西风', 'tq': '晴'}, {'time': '2018-01-25 02:00:00', 'temp': '-10', 'humi': '40', 'wse': '1', 'wd': '东北风', 'tq': '晴'}, {'time': '2018-01-25 03:00:00', 'temp': '-8', 'humi': '27', 'wse': '2', 'wd': '东北风', 'tq': '晴'}, {'time': '2018-01-25 04:00:00', 'temp': '-8', 'humi': '26', 'wse': '2', 'wd': '东风', 'tq': '晴'}, {'time': '2018-01-25 05:00:00', 'temp': '-8', 'humi': '23', 'wse': '2', 'wd': '东北风', 'tq': '晴'}, {'time': '2018-01-25 06:00:00', 'temp': '-9', 'humi': '27', 'wse': '2', 'wd': '东北风', 'tq': '多云'}, {'time': '2018-01-25 07:00:00', 'temp': '-9', 'humi': '24', 'wse': '2', 'wd': '东北风', 'tq': '多云'}, {'time': '2018-01-25 08:00:00', 'temp': '-9', 'humi': '25', 'wse': '2', 'wd': '东风', 'tq': '晴转多云转多云间晴'}, {'time': '2018-01-25 09:00:00', 'temp': '-8', 'humi': '21', 'wse': '3', 'wd': '东北风', 'tq': '晴转多云转多云间晴'}, {'time': '2018-01-25 10:00:00', 'temp': '-7', 'humi': '19', 'wse': '3', 'wd': '东北风', 'tq': '晴转多云转多云间晴'}, {'time': '2018-01-25 11:00:00', 'temp': '-6', 'humi': '18', 'wse': '3', 'wd': '东北风', 'tq': '多云'}, {'time': '2018-01-25 12:00:00', 'temp': '-6', 'humi': '17', 'wse': '3', 'wd': '东北风', 'tq': '多云'}, {'time': '2018-01-25 13:00:00', 'temp': '-5', 'humi': '17', 'wse': '2', 'wd': '东北风', 'tq': '多云'}, {'time': '2018-01-25 14:00:00', 'temp': '-5', 'humi': '16', 'wse': '2', 'wd': '东风', 'tq': '多云'}, {'time': '2018-01-25 15:00:00', 'temp': '-5', 'humi': '15', 'wse': '2', 'wd': '北风', 'tq': '多云'}, {'time': '2018-01-25 16:00:00', 'temp': '-5', 'humi': '16', 'wse': '2', 'wd': '东北风', 'tq': '多云'}, {'time': '2018-01-25 17:00:00', 'temp': '-5', 'humi': '16', 'wse': '2', 'wd': '东风', 'tq': '多云'}, {'time': '2018-01-25 18:00:00', 'temp': '-6', 'humi': '18', 'wse': '2', 'wd': '东风', 'tq': '晴间多云'}, {'time': '2018-01-25 19:00:00', 'temp': '-7', 'humi': '19', 'wse': '2', 'wd': '东风', 'tq': '晴间多云'}, {'time': '2018-01-25 20:00:00', 'temp': '-7', 'humi': '19', 'wse': '1', 'wd': '东风', 'tq': '晴间多云'}, {'time': '2018-01-25 21:00:00', 'temp': '-7', 'humi': '19', 'wse': '0', 'wd': '南风', 'tq': '晴间多云'}]}}}

后记

具体代码,将在后面上传
2019-05-10 补充
图片集-1
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
具体代码:
链接:https://pan.baidu.com/s/1DDD6S10R6ggwI5tlWQZtQA
提取码:zik3

总结

因为边做边写的原因,可能思路有的地方比较乱,这里来整理一下

我们发现想要获得的数据是一串加密的字符

我们需要知道这串字符是怎样加密的,于是借助火狐浏览器,我们知道了点击动作执行时所发生的事件

根据这个事件,我们找到了很多的方法,包括了加密的方法,这里我们遇到了js混淆

反混淆后,我们得到了所需要的可阅读的方法

我们将这些加密的方法保存在本地,利用Execjs本地执行js

寻找我们需要的参数,这里需要会打断点,得到参数的格式

最后,定义参数,本地运行js文件,得到加密的字符串,发送请求

success!

END!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值