Requests 使用js混淆

本次实例的目标是某网站的气象数据,相对来说比较复杂,它涉及到js的混淆,反解以及py摸拟执行js。其实上本次爬取的代码并不多,但是分析过程相当复杂,稍有一点思路不清就会拿不到结果。

本次获取结果的url:https://www.aqistudy.cn/html/city_detail.html

下面是分析过程:

• 点击不同气象指标的选项卡,发现没有相关的请求发送,说明当页面加载出来的时候,所有的气象数据已经加载完毕。
• .筛选条件没有变更时,点击搜索没有发送相关的请求。
        说明网页打开时,数据已经加载完毕。
•   再判断数据是否为动态加载
我们向https://www.aqistudy.cn/html/city_detail.html发起请求,在数据包中发现第一个请求即是:

通过preview看看返回结果:发现里面并没有数据,意味着数据并不在我们发送请求的url对就的数据包中,说明数据是动态加载的。

一般情况下,动态加载的数据是通过ajax请求的,但我们在抓包XHR中发现并没有ajax请求.那我们尝试修改条件,看看是否发起新的请求。
     1.修改时间段,点搜索有请求
     2.修改城市,有两个ajax请求,请求的url相同

分析这两个数据包,发现请求url相同,但请求都有参数d,但值不同,且动态变化。
处理动态数据d:
-- 全局搜索d,不可行
-- 但,在我们在点搜索按钮后有请求发起,因此点击事件发起了请求。
-- 找click事件
借助firefox inspect element

 

 

click绑定了一个函数

分析getData这个函数的js实现:目的就是为了找到ajax请求对应的js代码

通过firefox开发者工具找到js代码,搜索getData函数。

function getData()
{
    state = 0;
    city=$('#city').val();
    $.cookie('dcity', city, {expires : 30});
    //type = $('#type').combobox('getValue');
    type = $('input:radio[name=type]:checked').val();
    getTimeSel();

    if(type=="HOUR")
        {
            var timediff = converTimeFormat($('#dtbEndTime').datetimebox('getValue')).getTime()-converTimeFormat($('#dtbStartTime').datetimebox('getValue')).getTime();
            if(timediff >30*24*3600*1000)
                {
                    showMessage(false,"按小时查询仅支持查询一个月数据,查看长时间变化趋势请选择按日查询!");
                    return ;
                }
        }
    getAQIData();
    getWeatherData();
} 

分析函数的实现:因为它发起的请求一定涉及到请求参数。
这个函数内部并没有发现ajax请求,但有两个函数:getAQIData, getWeatherData

分析这两个函数实现:
实现的区别,(如果函数定义则没有分号,如果是调用则有分号)
两个函数的区别:
不同之处:
    • method变量赋值字符串不一样,分别是:GETDETAIL, GETCITYWEATHER
相同之处:
    • 都没有ajax请求对应的代码,但是有getserverData函数
    • method:GETDETAIL或者GETCITYWEATHER
    • param:字典,有四组键值对
        ○ city:查询城市的名称
        ○ type:HOUR
        ○ startTime:查询开始的时间
        ○ endTIme:查询结束的时间
分析getServerData(method,param,匿名函数,0.5)这个函数的实现,还是为了找打ajax请求对应的代码: 
在本js中只发现了两次getServerData的调用。没有定义。全局搜索在一个叫:jquery-1.8.0.min.js的文件中发现了

但是代码经过混淆,完全看不懂
对代码进行反混淆,

function getServerData(method, object, callback, period)
{
    const key = hex_md5(method + JSON.stringify(object));
    const data = getDataFromLocalStorage(key, period);
    if (!data)
        {
            var param = getParam(method, object);
            $.ajax(
            {
url: '../apinew/aqistudyapi.php',
data: {
d: param
                },
type: "post",
success: function (data)
                {
                    data = decodeData(data);
                    obj = JSON.parse(data);
                    if (obj.success)
                        {
                            if (period > 0)
                                {
                                    obj.result.time = new Date().getTime();
                                    localStorageUtil.save(key, obj.result)
                                }
                            callback(obj.result)
                        }
                    else
                        {
                            console.log(obj.errcode, obj.errmsg)
                        }
                }
            })
        }
    else
        {
            callback(data)
        }
} 

可以看到参数d: param, 而param 又是通过geParam得到
 getParam(method, object)
method:method
object: param字典,四个键值分别是城市名,type, 起始时间,结束时间

请求到的密文数据的解密方式:
decodeData(data):data参数就是响应的密文数据,返回值就是解密后的原文数据

js逆向
• 使用 PyExecJS 库来实现模拟JavaScript代码执行。
• 环境的安装:
• pip install PyExecJS
• 必须还要安装nodeJs的开发环境

将反混淆的所有代码复制到一个js文件中,

这里命名为encoded.js

同时我们封装一个函数,并把它加到encode.js中

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

最终代码:

import execjs
import requests

headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
}

node = execjs.get() 
file = 'encoded.js'
ctx = node.compile(open(file,encoding='utf-8').read()) # 固定用法


#这五个变量会作为getPostParamCode的参数
method = 'GETCITYWEATHER'#GETCITYWEATHER
city = '北京'
type = 'HOUR'
start_time = '2018-01-25 00:00:00'
end_time = '2018-01-25 23:00:00'

#模拟执行getPostParamCode函数, 因为ctx.eval()的参数只能是字符串,所以这里拼接字符串
js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)

params = ctx.eval(js)
# print(params)#请求参数d
url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
data = {
    'd':params
}

#获取了加密的响应数据
response_code = requests.post(url=url,headers=headers,data=data).text
# response_code

#模拟执行decodeData函数对密文数据进行解密
js = 'decodeData("{0}")'.format(response_code)
page_text = ctx.eval(js)
print(page_text.encode('utf-8'))


总结分析步骤:

​   1.判断数据是如何加载的  

    动态加载
    找到动态加载数据的url,必须找到发起的ajax请求,参数d

    找到了:getAQIData(), getWeatherData()函数
        没有 ajax请求

        发现了getServerData() 函数
            定位 getServerData()函数的定义

            定位到jquery-1.8.0.min.js文件

            定位到getServerData()的函数定义,但经过了混淆,看不懂:
                反混淆

                分析getServerData()函数

                发现参数d来自函数 getParam

                    js逆向(PyExecJS)

                        摸拟执行js,发起请求,得到加密的数据,解析数据
 

上一篇:Requests 使用之代理

下一篇:Numpy简单使用