案例-看准网-AES加密解密

网址: https://www.kanzhun.com

 

抓包:

看到第一个请求返回的数据是加密的:

而再来看第二个请求:很明显这些数据是为了选择城市、行业准备的数据。

所以我们来分析第一个请求。我们看看前面第一个请求的截图,数据是密文,所以接下来既需要加密又需要解密。

从启动器开始找:

这里看到 Promise.then 

所以我们应该要到Search搜索 interceptors

还真有:

点第一个JS文件进去看,再在文件里搜:

很明显这个是源码:

继续搜第二个JS文件:

这个JS文件内有我们熟悉的东西了:注意看到 i.interceptors.request.use 和 i.interceptors.response.use 

在这个JS页面上所有interceptors都打断点:

打完断点再运行(搜索一下其他关键字,比如这里搜索杭州):

看到URL是我们需要的URL,但是这里数据params是加密的,

 

再结合我们的 Query String Parameters 请求参数。data 有可能是我们的参数,但是不是最终发送到 Query String Parameters的。

下图的这个断点是第一个断点,之前的操作已经在所有的拦截器已经断点了,第一个断点所在的拦截器就已经是加密的了。

下图说明了数据没有经过拦截器的时候就已经被加密完了。所以拦截器跟加密已经没有关系了。

 

自此,我们通过在Search搜索 interceptors 找加密入口的方式就行不通了。所以要换一个搜索的目标。

经验之谈,其实我们可以在下图中发现一些interceptors 找加密入口的方式就行不通的端倪。如果整个网站用的都是下图的加密方式的话。

那么下图第二个请求中的请求参数也应该是加密的而不是下图所示的数据:

而第二个请求中返回的是不加密的数据。

再看第一个请求中是加密的。

所以interceptors 找加密入口的方式通过这两点就可以判断行不通了。

 

现在尝试其他搜索方式:可以从URL开始搜:

找到一个JS文件有搜索的内容:

点进去后,还要记得需要再到文件内搜索一次,可以看到整个页面只有一个地方有搜索的内容。

但是搜索到的东西并不好用,注意看下图是一个 search 对象

然后下面分别是key 和value,所以我们可以间接的走下面的key,也就是 getSearchSalaryList,所以接下来要找  getSearchSalaryList

注意不要在文件内这样搜:

不要在当前文件搜,要在全局搜,也就是下面:

在所有能搜索到的地方都打上断点:

在下面这个断点中,鼠标划到d,可以看到d的内容

再看看下面的输出:

整体运行完结果,我们可以看到和上面的结果拼接在了一起。

从而可以猜测出,pe 就是类似于python中字典的合并(经验之谈:在前端这种类似字典合并的东西非常常见)。

再来看下函数的输出结果,点击下面的函数

跳转到下面后设置一个断点:

看看这几个参数都是什么

所以可以猜测前面的pe函数合并就是在组装参数。

再继续看看J 函数干了啥

点击后,再打个断点:

看看 t 是什么

看看 e 是什么

n是一个空:

猜测:上面大概率又是在合并,放到console看看:

所以关键点就到 o 了,我们进入 o 看看

进入o 后打一个断点:

看一下参数:

而下面的p函数,我们前面已经见到了,是合并。下图的逻辑是合并url、method等内容,最后和 e 合并,相当于把参数e怼进去。

在这里猜测 r 是发送请求的内容,来看看 r 是啥,从下图可以看到 r 就是把传过来的参数塞进去了。

点下面的按钮,继续一步一步走 

走完一步观察一下变量有没有变化。可以看到没有发生变化

再继续走可以看到有一个运算:

再看看这个数据是啥,得到 r.data 

可以判断不会走下图打叉的代码,走的是下面打勾的代码及后面的代码,直接就走后面去了。(如果看不明白继续下一步就好了)

再看看下面的是啥,他们的结果一样,所以 0, 这个运算都不用管。

然后这个函数自运行了。然后得到一个 o 。

可以看到这个o 是:

可以看到运行的结果每次都不一样:

我们要看看这个函数,点击下面的函数:

可以看下面的函数,其中: Math.random 是随机函数。这个函数随机拿 e 个数(16个数),最后拼接在一起,返回一个字符串。说明这里随机数都可以。

到此:这里随机都可以,我们就可以随便指定一个数:

继续下一步:下图所在行执行逻辑是,判断 r.data 是不是 object (是),如果是就做一个JSON的处理。

再看看 n 是什么(做了一个字符串化的处理)

看看 L.mA ,点击这个函数

进入后,我们设一个断点:

 

看看e 和 t 两个参数,记得责怪 iv 是随机产生的。

看看 l 函数是什么,然后点进去

可以看到下图是一个加密运算,到此我们就找到了其实就是一个AES加密

我们前面随便找的一个随机的值:  "iQrItMWc5lLubXlP"

既然知道是AES加密,那么:

iv: 随机的值,我们取上面的即可: 'iQrItMWc5lLubXlP'

mode:  因为有iv,所以是CBC模式

现在就差一个key了,我们继续找key。

我们回到下图这个函数,当我们的 u 函数运行完,就会有key

看看:

从上图我们可以看到是16个字节,通过下面的运算,可以得到key

自此,我们加密逻辑全部都有了。

 

来看一个坑,上面的 u 函数,如果 s 有值,就返回s ,如果s没值,就计算后面的逻辑。

在函数内打断点,把其他断点去掉。然后 ctrl + shif + r 刷新

看到s 是空的:

再看 i  也是空的,所以会走后面的代码。但是最后返回的也是 i

所以 e 这个才是关键

注意,下图replace 前面引号的东西

在浏览器console,肉眼看到可能是一个空的,我们就会陷入猜测什么都没有:

但是看看长度:

复制到pycharm中看看:

所以这可能是留给我们的一个坑,让我们跳进去,让我们思维陷进去,以为它是没东西的,实际上它是有东西的,只是我们看不见的一些符号。

上面这个坑这样做的目的:为了隐藏密钥。

只有在下图的地方断点才能看到密钥的值:

自此这个坑的逻辑就理清楚了。继续看其他的,看一下 r 的值,直接是base64了。

回到前面的地方,我们知道加密的东西是一个base64 

然后base64 用正则表达式 replace 替换掉一些东西,把 \ 替换成 _  把 + 替换成 -   把 = 替换成 ~

最后,在最前面抓包关心的两个参数出来了,由于我们前面的请求是get 方法,所以最终:  t 赋值 给b , o 赋值给 kiv

自此,此网站加密逻辑已经全部搞定。当然还有解密逻辑需要继续探索:

其实在我们前面加密函数旁边,就是解密函数,而 iv  是固定死了。所以可以直接搞定了:

 

下面开始写python代码:

# https://www.kanzhun.com/search?cityCode=34&industryCodes=&pageNum=1&query=%E6%93%8D%E4%BD%9C%E5%91%98&type=4

"""
/**
 * 随机的值:  "iQrItMWc5lLubXlP"
 *
 * 加密方式: AES
 * key:  'G$$QawckGfaLB97r'  key
 * iv:  'iQrItMWc5lLubXlP'   随机的.
 * mode:   CBC模式
 *
 * 加密之后的base64 把/处理成_, + 处理成-  =处理成~
 */
"""

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64

import requests
import json

url = "https://www.kanzhun.com/api_to/search/salary.json"

data = {
    "query":"操作员",
    "cityCode":7,
    "industryCodes":"",
    "pageNum":1,
    "limit":15
}
kiv = "iQrItMWc5lLubXlP"

aes = AES.new(key='G$$QawckGfaLB97r'.encode("utf-8"), iv=kiv.encode("utf-8"), mode=AES.MODE_CBC)
ming_bytes = json.dumps(data).encode("utf-8")
ming_bytes = pad(ming_bytes, 16)
mi_bytes = aes.encrypt(ming_bytes)  # 加密
mi = base64.b64encode(mi_bytes).decode()
mi = mi.replace("/", "_").replace("+", "-").replace("=", "~")

params = {
    "b": mi,
    "kiv": kiv
}
headers = {
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36"
}

resp = requests.get(url, params=params, headers=headers)

resp_text = resp.text  # 获取到密文
resp_text_byte = base64.b64decode(resp_text)

# 解密, 不可以用上面加密的那个aes, 需要重新创建aes对象
de_aes = AES.new(key='G$$QawckGfaLB97r'.encode("utf-8"), iv=kiv.encode("utf-8"), mode=AES.MODE_CBC)
resp_ming_bytes = de_aes.decrypt(resp_text_byte)
resp_ming_bytes = unpad(resp_ming_bytes, 16)

resp_ming = json.loads(resp_ming_bytes.decode("utf-8"))
print(resp_ming)

 

posted @ 2023-08-05 16:05  屠魔的少年  阅读(3)  评论(0)    收藏  举报