Python爬虫之Js逆向案例(1)-知乎搜索
声明:知乎加密逆向分析仅用于研究和学习
这篇文章的学习内容是以知乎关键词搜索接口为案例,对JS 逆向的整个过程进行详细分析;
下面会进行以下几步进行分析(下方演示过程全部使用chrome 浏览器);
- 锁定关键接口;
- 锁定关键字段;
- 破解关键字段;
- python爬虫结果演示;
一.锁定关键接口
进入知乎,在首页上方的搜索输入框中输入任意关键词,点击“查询”,锁定查询接口,如下图:
通过上图中的第一步、第二步、第三步查找,我们肯容易锁定对应的查询接口/search_v3 ;
二. 锁定关键字段
锁定查询接口后,下面我们查看接口请求时需要的数据:
通过查看请求头,其中有几个字段非常可疑(特别是x-zse-96 、x-zst-81 字段,似乎都是加密后的数据):
x-zse-93: 101_3_2.0 x-zse-96: 2.0_aTO8Uvu0o8FxNBOqZqFB2098c0NxbRty1Xxq6i9ynCSY x-zst-81: XXXXXXXXX
这里我们很容易锁定关键字段就是:x-zse-96 ;
有同学可能会问了:“我艹,怎么就很容易锁定是x-zse-96 了?同样x-zst-81也是像是一串密文呀?你是怎么排除x-zst-81 的?”。大家有疑问也很正常,这也是很多社区里其他人分享时这样说的,给他家讲的不明不白,导致学了根没学一样,看了更迷糊了、、、。好了,下面给大家上干货:
高能总结:
- 请求对比:我们搜索不同关键词两次以上,对比请求数据,找不同,
value 不同的字段及是关键字段; - 技巧1:开启
preserve log (上图中的1. 的位置)保留上次的接口请求; (说明:我们每次进行关键词查询时会发现Network 面板的接口都会被重新刷新,上一次的接口数据都没了,没发跟上次接口做对比,开启后上次的请求数据就不会被刷掉了); - 技巧2:接口过滤(上图中的
2. 的位置);(说明:每次请求会有很多我们不需要的接口,过滤后只剩下search_v3 ,更便于对比,更容易的找到不同点,排除干扰)
锁定了关键字段之后,如果我们破解了该字段,那不就意味这个接口就被我们破解了吗?下面对x-zse-96 字段逆向;
三. 破解关键字段
定位x-zse-96 字段出现的位置;方法:调试面板右上角,点击更多 图标>search >输入x-zse-96 >回车,如下图:
高能总结:
在关键字段调试时,最好开启浏览器的无痕模式(chrome 浏览器mac 系统快捷键:?+?+n ),避免浏览器缓存带来的影响。
点击上图中的第五步 格式化代码之后,看到关键字所在位置,如下图,第12509行;
高能总结:
技巧:由于我们此时并不知道这个js文件中该字段出现多少次,以免调试时出现不进断点,所以我们先查询一下,把出现的地方都看一下,无法排除的都打上断点,如下图中,找到两个位置,无法排除哪个没用,所以都打上了断点;
对上图里的标记分析:
可以看到,在js的第12509行出现了x-zse-96 关键字,此行重点是y ,y 的值又来自m.signature ,m.signature 的值又来自u()(f()(s)) ,到此,如果我们解出了s,f,u 的话,那么我们就能得到x-zse-96 的值;
高能总结:
技巧:断点的位置哪里最合适?
说明:打断点时最好是打在函数的首行(上图中第12490行)、return处(上图中第12498行)、结果行(上图中第12509行),其它地方自己按需打点;原因是,如果不在函数首行断点,只打结果行(12509行),上面的变量可能被其它作用域里的数据污染,导致我们数据异常,大家试一试就知道了;
下面开始debug,查找u()(f()(s)) 中的s ,s=[r, a, i, V(c) && c, o].filter(Boolean).join("+") ,s 的值是根据好多字段组成的数组进行拼接而成,通过debug 可找出每个内容的值以及函数的作用:
如上图,很容易得到r,i,o 的值:
r : 每次请求都一样,貌似是个定值;i : 每次请求都一样,查看cookies ,发现这个值跟cookies 里的dc0 的值一摸一样,应该就是来自cookies 了;o : 调试发现是null ;
下面来看a, c 的值是怎么来的,a 是G(e) 函数返回的,如下图:
高能总结:
技巧:有两种方法找到G 函数的具体位置:
1.是鼠标悬浮在G 上,出现悬浮框,点击里面提示的XXXX.js 进入;
2.断点?? 箭头进入
进入G 函数之后发现,里面只是把当前get 请求时的参数进行格式化,然后把pathname 和search 进行字符串拼接而已;c 通过z(t) 函数返回,结果是个""
综上得到了数据:
r : "101_3_2.0" i : cookies.dc0 o : null a :"/api/v4/search_v3?"+ path.search c : '' V(c) : ''
[r, a, i, V(c) && c, o].filter(Boolean).join("+") 这里就是把上面的[r,a,i] 使用+ 拼接成了字符串而已,因此s 的值就是: "101_3_2.0+/api/v4/search_v3?t=general&q=121&correction=1&offset=0&limit=20&filter_fields=&lc_idx=0&show_all_topics=0&search_source=Normal+"ABARdK2yAxWPTq_5HDAIBPahlStL2uhS36M=|1653805418""
得到了s 的值,继续调试u()(f()(s)) ,如下图
高能总结:
技巧:调试到u()(f()(s)) 这行时,u、f、s 前面都有一个?? ,这个箭头表示可以分别对这些函数进行断点
此时,我们把f()(s) 用鼠标选中,发现悬浮框里的值变了,也就是说s 被加密了(根据特征看,貌似是一个md5 加密的值,这里是猜的,没有猜到也无所谓,继续往下看)
单步调试,当断点进入到s 前面时(在12498行需要点击3次单步调试按钮哦),s 前面的肩头会亮起来(如上图),此时点击单步调试按钮?? 进入f 函数里,如下图:
上图里的第二步 的位置就是函数f 的具体位置,如果有js 逆向经验的话,你可能一眼就能看出来,这个地方很像md5 加密函数(大家可以下载一个md5 文件对比一下写法,看看是不是很像);往上翻一下,看到这里确实在运行一个独立的模块,通过模块的规则、关键属性,我们发现它就是md5 加密的代码,所以我们只需要把这个加密块全部扣出来就行了(也可以不扣,可以使用python 里面安装好的md5 工具包,如果不想安装的话,可以直接到这里扣代码,下面演示如何扣代码),扣代码是为了编写python 爬虫时使用;
扣代码:
新建一个文件md5.js ,删除多余的代码,如下图:
我们发现这里是个自执行函数,继续改写,删除函数最后的() ,再把第一行的匿名函数添加一个名字,最后结构就变成了下面这样:
function md5(i) {
XXX
}
高能总结:
扣代码原则:扣代码尽量保证能不改就不改,能少改就少改;
我们看到debug 里的加密的结果是"360a2f13a2029e74cab51c1b720203c2"
用我们扣出来的js 代码验证一下,如下图:
与上面的结果一摸一样,说明我们的结果是对的,继续看u() 函数;
进入u 函数后如下图:
经过观察,我们发现对传进来的值进行了两步操作,第一步是常规的转译encodeURIComponent ,转译之后进行encrypt() 操作,有经验的同学一眼就能看出来,encrypt 函数就是加密函数常用的名字,所以这里我们继续扣代码,这里完全不用管这里的encrypt 函数是怎么实现的,同md5 的扣取方法一样,把u 对应的具体函数b 所在上下文里的代码都扣出来,仔细发现这块代码最终是把b 给导出去了,所以扣出来的代码结构如下:
(function() {
"use strict";
function t(e) {
return (t = "function" == typeof Symbol && "symbol" == typeof Symbol.A ? function(e) {
return typeof e
}
: function(e) {
return e && "function" == typeof Symbol && e.constructor === Symbol && e !== Symbol.prototype ? "symbol" : typeof e
}
)(e)
}
...
...
var b = function(e) {
return __g._encrypt(encodeURIComponent(e))
};
window.sigin_B = b
})()
我们用上面f() 函数得到的结果360a2f13a2029e74cab51c1b720203c2 进行加密,对比一下是不是跟debug 时的一样,如下图:
不出意外,运行报错了,因为js 运行时不在浏览器环境里,所有没有window 对象呀,不慌,没有window ,我们直接安装一个jsdom 不就行了吗,新建文件夹,使用node 包管理工具npm、yarn 在文件夹里使用npm init 初始化项目,然后安装依赖:
yarn add jsdom
npm install jsdom
安装完成之后如下图,在文件最上方引入对应代码,赋值给window ,然后再运行,如下图; 结果又报错了,提示atob不存在,那就使用yarn继续安装atob呗:
yarn add atob
或者
npm install atob
在文件上方引入
const atob = require('atob');
再运行一下试试:
没报错,和debug 的结果对比一下看看:
一抹一样呀,终于出来了,开心!开心!
四.python爬虫结果
有了上面的分析,python 爬虫的编写就非常简单了,由于时间关系,我就不贴代码了,给大家看一下结果吧,如下图:
终结:
- 整体来说知乎搜索接口的逆向不算复杂;
- 这篇文章过程可能写的过于详细了,对有经验的同学可能显的有些啰嗦,主要是想照顾一下刚入门的同学哈;
- 感兴趣的话可以研究一下
x-zst-81 字段看看这个字段是干啥的,尝试解一下,如果需要我这边继续写文章的话,大家可以在文章下面留言,如果大家需要的话,我这边抽时间再写一篇关于x-zst-81 的;
后期会持续分享爬虫案例-100例,有兴趣的同学可加入我的知识星球,有更多技巧、高能总结等你哦!!!;
欢迎加入「python、爬虫、逆向Club」知识星球
|