你的密钥被我看见了 !逆向获取密钥
前言
遇到过很多次数据加密的情况。之前都是看到别人如何前端调试,然后逆向获取密钥解密流量,这一次,给大家实战一下。
一、逆向式
我们的目标是某信小程序。
首先还是来看看这个小程序的请求流量。当正常人一眼望去,丝毫理不清传递的是什么,我们基本就可以断定,其对流量进行了加密处理。
然后我们再看看返回请求——依然一头雾水:
经过上面两张图,可以判断这个小程序存在流量加密的情况。至于是全局的流量加密,还是部分流量加密,这个需要经过分析之后才能判断。
如果只是部分流量加密,那么还可以测一测有没有漏网之鱼,如果是全局流量加密的话,就必须对小程序进行逆向获取源代码,再进行分析了。
对某信小程序的逆向采用某神仙模拟器加一个 6.X 版的某信。因为高版本的安卓和某信不再信任系统的证书,这会造成无法抓包的情况,所以使用安卓 5 加 6.X 版本的某信。
再参考网上的文章,从模拟器中脱到了小程序的 wxapkg 文件。这个文件在我的环境中位于:
因为小程序比较多,不知道哪一个才是目标文件。通过 adb 把这几个 wxapkg 文件全部下载下来,然后就是从 wxapkg 文件中提取源代码,这里使用 wxappUnpacker 一键提取:
获取到的目标小程序的源代码文件结构如下,之后便是进入分析阶段了。
二、分析式
【点击查看,相关资料获取】1、网络安全学习路线 2、电子书籍(白帽子)3、安全大厂内部视频 4、100 份 src 文档 5、常见安全面试题 6、ctf 大赛经典题目解析 7、全套工具包 8、应急响应笔记
进入代码审计环节,来分析前端的代码算法。
上面的小程序经过逆向,获取到的源代码结构如下:
因为之前没有分析过某信小程序,所以这里首先学习了一下小程序的一些基本知识。重点有两个,第一个是 app.js,这个文件是整个项目的入口文件,通过 App()方法来注册一个小程序,并确定了整个程序的生命周期。
第二个便是 module.exports,某信小程序的模块化,个人理解就是对外暴露的方法,可以供其他方法调用这个模块,类似于 public 方法。
了解完上面这两个点,再回到项目的源代码本身,通过浏览整个文件结构,发现对于加密的方法定义都位于/utils 文件下。
其中需要关注的是 encrypt.js 文件,里面定义了关于流量的加解密方法。直接上代码。
从上面的代码中看到 encrypt.js 通过 module.exports 对外暴露了 encrypt 和 decrypt 两个方法。这两个应该就是用于对流量进行加密和解密的方法了。先来分析 encrypt 函数:
Encrypt 函数传递两个参数 e 和 n,然后将参数 n 通过 r 函数处理之后赋值给 s,其中 r 函数内容如下。为了安全里面的字符串,截取长度均已经修改,与原程序不同:
r 函数将传递的参数截取一段然后与字符串进行拼接,然后使用 SHA256 函数签名,再截取中间一部分,将字母转换成大写之后返回。
再回到 encrypt 函数,变量 i 存储的 AES 加密解密的密钥,此处使用硬编码的方式写在代码当中。然后变量 u 存储格式化之后的 s 变量,变量 c 存储格式化之后的参数 e。
之后就是加密部分了。
先了解一下 AES 的加密。对于 AES 有不同的模式,本程序中使用了 CBC 的模式,这种模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。
在使用 CBC 模式进行加解密过程中,需要定义一个初始向量。初始向量用于随机数据加密,达到的效果是对相同的明文,传递相同的密钥。不同的初始向量可以产生不同的密文。
再回到代码本身。可以看到 encrypt 函数调用了 AES 模块的 encrypt 方法进行加密,传递的参数,依次为 c 明文,i 密钥,然后变量 u 表示初始向量,采用 CBC 模式进行加密。
Encrypt 函数分析完了,再看 decrypt 函数,上代码:
Decrypt 函数还是传递两个参数 e,s 然后通过 formatString 函数将参数 e 处理一下,其中 formString 函数具体内容如下,对 e 参数进行去除换行空格的处理:
变量 i 还是存储 r 函数处理后的参数 s,变量 u 存储的还是格式化的密钥,变量 c 存储格式化后的变量 i,最后调用 AES 模块的 decrypt 函数进行解密,结果存储在变量 o 当中。
进行解密的参数传递,e 密文,u 密钥,c 初始化向量,采用 CBC 模式解密。这里加密和解密的初始化向量都是由 r 函数进行处理的。
在上面分析中,加密解密传递的都是两个参数,然后其中一个参数是用于生成初始向量的。因为在上面的过程中,这个参数不是固定的,所以需要分析这个参数的来源,这样才能完整的对整个数据进行解密。
再回到项目本身,在 app.js 中引入了一个 request.js 文件,在这个文件当中封装了 http 请求的方法,并对外暴露接口:
在第 108 行中,有一段代码:
这段代码应该就是调用加密函数对请求数据进行了加密。这里传递了两个参数,其中 d 参数就是加密过程中用于生成 iv 向量的参数,而在第 103 行最后可以看到,d 参数的来源是函数 getRandomUuid。接下来看看这个函数的内容:
这个函数的作用只是生成一个随机的字符串然后返回。但是在上一张图的第 105 行代码将这个 d 赋值给了 i.uuid,在我们最开始看请求数据包的时候,在 http 请求的请求体中就存在一个 uuid 的字段:
所以可以确认,这个 uuid 的字段就是用于生成 iv 向量的参数。
然后继续跟进我们的代码,在进行流量加密之后,会调用一个 c 函数,然后再 return。所以我们需要看一下这个 c 函数的具体内容,因为解密的过程应该在这个 c 函数当中:
在第 61 行的位置发送一个 http 请求,如果请求成功进入回调函数,对数据进行解密,这个解密可以看到传递的参数 c.uuid 就是请求包中的 uuid 字段。
三、解密式
整个小程序的分析到这里基本就结束了,接下来只需要根据上面的密钥、iv 向量生成、算法等等写一个解密的脚本就可以了。
为了高(tou)效(lan)作(mo)业(yu),使用之前搞开发写的 PHP 版 AES 解密脚本改了改,最后的效果如下:
输入 uuid 参数和密文,点击解密之后返回明文,将明文修改之后点击加密,返回密文。
返回包的解密:
好了,这个小程序的流量加密分析到这就全部结束了。
评论