抓取网址:https://www.endata.com.cn/BoxOffice/BO/Year/index.html
抓取目标:影片相关信息
网页分析
打开网页后,按F12
打开开发者工具,然后修改年份。
可以看到生成了一个XHR
的数据包,并且还包含其他影片的封面图片。
点击该数据包,查看数据包内容。可以看到该数据包返回的结果为加密数据。
现在还无法确定该数据是否为我们想要的影片信息,为了搞清楚这个问题,我们点击标头查看其请求的过程。
可以看到该数据包通过发送POST
请求到API
接口 https://www.endata.com.cn/API/GetData.ashx
,去获取数据。然后点击载荷,查看POST
请求发送的表单。
表单结构很简单,只有两个值。
year
:顾名思义即为查询的年份
MethodName
:为固定值BoxOffice_GetYearInfoData
然后查看该数据包的请求头。
从请求头和表单数据可以看出,通过POST
请求去获取该数据包的返回值很简单。难点其实是获取到的返回值是经过加密的数据,那么如何将其还原为正常的结果呢?这个就是我们这个案例需要解决的主要问题。
点击启动器,可以看到该数据包请求调用堆栈,不难看出,前两个是jquery
的JS代码。我们从第三个开始分析。
可以发现该JS代码调用的是一块ajax
代码,去发送数据。
通过其success
部分的JS代码不难分析出其逻辑。
1 | 1 == (e = "{" == e[0] ? JSON.parse(e) : JSON.parse(webInstace.shell(e))).Status || 200 == e.Code ? r(e.Data) : 200 == e.code ? r(e.data) : a(e.Msg) |
该代码的逻辑是,首先判断e
的第一个字符是否是{
,如果是说明e
为JSON
数据,直接调用JSON.parse()
去解析即可;如果不是说明e为加密数据,这个时候调用的是webInstace.shell()
方法对其进行解密。到此我们就直接找到了解密方法为webInstace.shell()
。
在此打上断点,刷新网页,程序就会在断点处截断。
可以看到e
即为我们的加密数据,通过控制台我们也可以直接将其打印出来。
选中webInstace.shell
,进入到该函数内部。
进来之后我们可以看到该方法所在数据包变量名和相关逻辑十分复杂。
这是我们常见的代码混淆模式之一——OB混淆
。
结果解密
扣JS
有一种简单做法是直接将整个数据包复制到JS文件中,使用JS代码对其进行解密。
1 | 整个JS代码,由于文件内容比较大,在此处省略 |
直接运行JS代码,发现会报错。navigator is not defined
。
我们可以把该代码复制到控制台中运行查看结果。
该代码的作用是检测请求是否是浏览器发送的,并且是否有userAgent
字段。如果条件成立直接返回空。这里我们是要对结果进行解密,这个条件肯定是永远不成立的,直接删除该部分代码即可。
重新运行代码。可以发现返回结果已经正常。
接下来编写Python代码调用JS对获取的加密数据进行解析即可。
1 | from functools import partial # 这个函数可以锁定一个函数的参数 |
可以获取到真实数据
分析JS逻辑使用Python改写
上述做法的特点是不用分析直接抓取整个JS文件,比较粗暴,如果JS代码过多就可能出现效率低下的问题。
接下来我们来使用分析JS代码逻辑,使用Python代码按照其逻辑对其进行改写的方式解密数据。
我们把webDES
类复制到一个JS文件中,对其进行逻辑分析,还原代码。
1 | var webDES = function() { |
将其整理一下可以看到该类的结果十分简单,一共就定义了一个变量和两个方法。
先不管上面的变量和方法,分析最后一个方法,将其展开。
现在这个代码无法直接对其进行逻辑分析,因为开发者使用工具对其进行了混淆,变量名和方法名都是一些比较长的字符。现在要做的是将其变量名简化。
第一个要做的是将_0x2246
进行还原改写。
我们通过控制台可以对其进行还原。可以发现其实这些内容就是字符串。
我们一个个对其进行还原即可。过程很简单,但是重复次数多。
1 | var webDES = function () { |
其中还有一些16进制的数值,也对其进行还原。
还原后如下:
1 | var webDES = function () { |
然后分析shell
函数,该函数首先定义了一个变量_0x51eedc
,该变量为对象类型。定义了很多变量与函数的对应关系,便于后续对其进行调用。
这里的函数功能都比较简单,我们可以直接去分析对象下方的代码逻辑,遇到调用_0x51eedc
的内容再回过来看即可。
1 | if (_0x51eedc['pKENi']('tgg', _0x51eedc['wnfPa'])) { |
该代码调用了_0x51eedc
中的wnfPa
和pKENi
经过查询可以知道wnfPa
为固定值ZGz
,pKENi
为函数(功能是比较两个值是否相等,返回true或者false)
所以这里的这个代码实现的功能是判断tgg
和ZGz
的值是否一样。明显这个条件是永远无法成立的。删除即可。
使用类似这样的方法对其他代码进行分析,将代码进行删除和简化。
1 | var webDES = function () { |
然后使用编辑器的替换功能,将复杂变量替换为简单变量。
_0x51eedc--> a
_0x554c90--> b
_0x2cf8ae--> c
_0x9843d3--> func
再把func
内部的局部变量也进行简化。
_0x29d556--> a
_0xcc6df--> b
_0x3d7020--> c
_0x48914b--> result
现在代码就变得简单很多,如下所示
1 | var webDES = function () { |
对其进行解读添加注释如下
1 | var webDES = function () { |
接下来使用Python语言按照其逻辑进行改写。
1 | # endata,ob混淆 |
运行结果如下: