PS:这是一个系列,坐等我慢慢填坑。

PS:不太会直接能跑的代码,抛砖引玉。

PS:那些我也不太熟练的就不搞了,包括(破滑块、验证码..)

PS: 反编译搞Apk会有很长的几个文章,稍后慢慢更。


最近,和某XX单位的网站gang上了。

他们家的网页只允许在微信客户端打开,抓包就跟蛋疼了。

不过,手上有Root后的Google Nexus5X,也有 whistle 跨平台抓包工具

这个倒没太折腾,抓包工具证书往手机系统根证书一扔,完事。

安卓7.0及以上用户证书导入的问题 - entr0py - 博客园

抓到了包,下面蛋疼的事情开始了。


前言: body 加密

嗯?请求Body是这一串东西?

嗯?时隔三年,神奇海螺又出现了?

  1. // json
  2. {
  3. "encryKey": "14a625eb2ec957f9b53412b01de37044e7e2aa6b4b911111c75091cba2a0315b",
  4. "data": "44bc0dab8db8017603586f40554742d14a0c23dd009e35cae5b5ac87dbf7962a311fae30070763d2b48b564d72191fd07a881ebcccfb7c0fdd33e4067bc5119cee5e2fa5eaac10da995c86c8a092dcc3",
  5. "sign": "cc3f924bbb6d57a15bd3e130230f51e55a04fa9e459d177440fbd10bce4b02d0",
  6. "timestamp": 1627224237000
  7. }

很明显,每个单词我们都知道,每个字母和数值我们也懂。

但是....

除了timestamp我们可以生成,其他的明显是加密后数据和签名。


一点都不高能的预警

先说一下思路:

  1. 捞出核心JS文件
  2. 读懂加密过程,捞出关键参数
  3. 用其他语言实现涉及到的加密函数
  4. 对比加密结果是否一致,尝试去伪造请求

捞JS

首先这货的微信浏览器的,所以没办法使用浏览器开发者工具。

不过,抓包上面不是搞掂了么?直接从抓包结果看HTML就完事。

乖乖一个个请求看,找到第一个返回HTML的响应体。

于是,找到了这个...

哦, 看到了....

<script src="/umi.a029b1fd.js"></script>

看到这货,本宝宝小心脏有点乱跳了。

访问一看。

害,看起来没的那么简单啊,明显这货是被webpack打包后的JS文件。

先下载回来再说...


umi.a029b1fd.js 下载到本地,一看1.5M。

打开一看,毫无疑问没有格式化...

得嘞,大JS文件格式化,先打开我的Ubuntu机器再说。

哦,VS Code format崩;加大内存,继续崩。

搜了一波,找到了神器 Online JavaScript beautifier

文件扔上去,再下载下来...

完事。

毫无疑问,这就是webpack打包后的东西了。

没得事,全局搜一波上面的参数。

完美,看到这个,是不是答案已经出来了。

看看,每个参数怎么算的都告诉我了,还能做撒?还需要做撒?

于是,我午睡去了。

........

其实,最头疼的东西就在这里了。

这时候,很多人会说,上AST 还原JS嘛。

AST抽象语法树--最基础的javascript重点知识,99%的人根本不了解 - SegmentFault 思否

啧啧啧。

道理是这个道理,不过还有其他的思路吗?

直接写个index.html 引入这个JS也成的啊。

  1. <html>
  2. <body>
  3. <h1>test</h1>
  4. </body>
  5. <script src="./app.js"></script>
  6. </html>

开始解JS

  1. var O = Date.parse(new Date),
  2. Y = Object(h["e"])(!1, 16),
  3. j = Object(h["a"])(JSON.stringify({
  4. data: b,
  5. timestamp: O
  6. }), Y),
  7. P = Object(h["f"])(j, Y);
  8. T = {
  9. encryKey: Object(h["a"])(Y, h["b"]),
  10. data: j,
  11. sign: P,
  12. timestamp: O
  13. }

在代码里面看到了一堆这种 h["a"] h["e"],然后跟着参数(j, Y)。

我们明显知道,这是JavaScript的一个函数调用,h看起来是一个map或者是对象,

这里是在调用它的a方法,传入了(j, Y)

在这里,我们最想知道的就是h["a"]的定义是什么样的,

因为知道定义实现,也就能还原完整代码逻辑。

跟一点代码(VS Code跳转定义功能),我们能看到h是什么?

h = n("jNxd"),

看到这里其实是很头疼的,n是个什么玩意我们完全无从得知。

不过这里也能得到点信息,各种各样的函数或者对象都是绑定在”n“上的,

我们只要拿到n,我们需要的h,h[a], h[b] 都知道是什么了。

怎么拿到n呢? 友情提示,善用debugger。


开始寻找n

刚刚我们已经完整把app.js(umi.a029b1fd.js格式化之后的文档)导入我们的index.html

用浏览器打开看看页面。

页面没什么问题,我们尝试在app.js上面加点debugger吧。

加在哪呢?(目的只有一个,能获取的到n)

....h附近前面可以吗?

浏览器控制台打开,刷新页面,切换到Console页面。

试试这里能不能有n对象。

咦,看起来有戏。

试试 h = n("jNxd")

很好,很好,看起来这里是OK的,

h["a"]也是一个函数,符合我们上面看到的。

点击一下上面h["a"]输出的内容,可以跳转到函数定义。

于是,我们来到了重头戏。

  1. s = (e, t) => {
  2. var n = i.a.enc.Utf8.parse(t),
  3. r = i.a.enc.Utf8.parse(t),
  4. o = i.a.enc.Utf8.parse(e),
  5. a = i.a.AES.encrypt(o, n, {
  6. iv: r,
  7. mode: i.a.mode.CBC,
  8. padding: i.a.pad.Pkcs7
  9. });
  10. return i.a.enc.Hex.stringify(a.ciphertext)
  11. },
  12. u = (e, t) => {
  13. var n = i.a.enc.Utf8.parse(t),
  14. r = i.a.enc.Utf8.parse(t),
  15. o = i.a.enc.Hex.parse(e),
  16. a = i.a.enc.Base64.stringify(o),
  17. s = i.a.AES.decrypt(a, n, {
  18. iv: r,
  19. mode: i.a.mode.CBC,
  20. padding: i.a.pad.Pkcs7
  21. });
  22. return s.toString(i.a.enc.Utf8).toString()
  23. },
  24. c = (e, t) => i.a.enc.Hex.stringify(i.a.HmacSHA256(e, t)),
  25. l = (e, t, n) => {
  26. var r = "",
  27. i = t,
  28. o = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "-", ".", "~", "!", "@", "#", "$", "%", "^", "*", "(", ")", "_", ":", "<", ">", "?"];
  29. e && (i = Math.round(Math.random() * (n - t)) + t);
  30. for (var a = 0; a < i; a += 1) {
  31. var s = Math.round(Math.random() * (o.length - 1));
  32. r += o[s]
  33. }
  34. return r
  35. }

看看,代码都出来了,还需要撒?

今天的教程结束,早点睡....


微微一笑,好像没那么简单。

宝哥微微一笑,发现事情没那么简单。

已知,上面这一堆东西,

要不是 i.a.AES,要不是 HmacSHA256

没什么花样。

那么最大的问题就是,

这个加密过程是怎么搞的。

加密向量是什么?秘钥在哪?

SHA256用的是什么参数?参与加密的数据是怎么拼接的?

PS:还在写....


重头戏上场

回到上面的代码

  1. if (p["d"] && "get" !== u.toLowerCase() && !g) {
  2. var O = Date.parse(new Date),
  3. Y = Object(h["e"])(!1, 16),
  4. j = Object(h["a"])(JSON.stringify({
  5. data: b,
  6. timestamp: O
  7. }), Y),
  8. P = Object(h["f"])(j, Y);
  9. T = {
  10. encryKey: Object(h["a"])(Y, h["b"]),
  11. data: j,
  12. sign: P,
  13. timestamp: O
  14. }
  15. }

这里可以看出每个变量是怎么来的。

encryKey = Object(h["a"])(Y, h["b"]) // 调用了a方法

O= Date.parse(newDate) // 生成了时间戳

Y=Object(h["e"])(!1,16) // 调用了e方法

P=Object(h["f"])(j,Y) 调用了f方法

于是我们执行一下看看。

Y看起来是个随机字符串,j,p看起来都是字母+数字组合起来的字符串。

分别到定义出看看是撒。

h["e"]

  1. l = (e, t, n) => {
  2. var r = "",
  3. i = t,
  4. o = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "-", ".", "~", "!", "@", "#", "$", "%", "^", "*", "(", ")", "_", ":", "<", ">", "?"];
  5. e && (i = Math.round(Math.random() * (n - t)) + t);
  6. for (var a = 0; a < i; a += 1) {
  7. var s = Math.round(Math.random() * (o.length - 1));
  8. r += o[s]
  9. }
  10. return r
  11. }

哦,生成了随机字符串。

h["a"]

  1. // n("jNxd")["a"] encryKey
  2. s = (e, t) => {
  3. var n = i.a.enc.Utf8.parse(t),
  4. r = i.a.enc.Utf8.parse(t),
  5. o = i.a.enc.Utf8.parse(e),
  6. a = i.a.AES.encrypt(o, n, {
  7. iv: r,
  8. mode: i.a.mode.CBC,
  9. padding: i.a.pad.Pkcs7
  10. });
  11. return i.a.enc.Hex.stringify(a.ciphertext)
  12. },

哦, AES.encrypt 加密,使用的是CBC/Pkcs7对齐

h["f"] HmacSHA256

  1. (e, t) => i.a.enc.Hex.stringify(i.a.HmacSHA256(e, t))

h["b"] 直接返回了一个固定的字符串。(毫无疑问是IV向量和加密Key)

看看,没了啊。

核心的加密代码就这点。

  1. var O = Date.parse(new Date),
  2. Y = Object(h["e"])(!1, 16),
  3. j = Object(h["a"])(JSON.stringify({
  4. data: b,
  5. timestamp: O
  6. }), Y),
  7. P = Object(h["f"])(j, Y);
  8. T = {
  9. encryKey: Object(h["a"])(Y, h["b"]),
  10. data: j,
  11. sign: P,
  12. timestamp: O
  13. }

所以重点代码又回到这里了,看懂这里就是所有的逻辑了。

读一下,也就这样。

  1. 获取当前时间戳 O
  2. 生成随机字符串 Y
  3. 把传入的b(body)和时间戳组合到一起,设定IV向量为Y,使用AES 加密
  4. 把密文 j 和 Y进行SHA256签名
  5. 最用把Y也用AES 加密,这个时候加密IV向量为h["b"]

换个人话

写死了一个iv向量,随机生成一个16位的key,从iv向量对这个Key加密,

用这个Key作为另一个iv变量对请求体Body加密,

然后把上面一堆东西做一个sha256的签名。

哦,说好的前端参数签名加密。


到这里,其实破解过程已经完成了。

这基本也是我睡醒之后,看了台风吃了晚饭回来之后,

开始抄Python 把上面逻辑实现一波的前置思路了。

这个时候,我们也要知道一些东西。

JS加密库 CryptoJS

Python对应的加密库 pycrypto

最后用Python实现这个完整逻辑还是折腾了好一会的,

也抄了不少别的代码,最后贴一下。

  1. from Crypto.Cipher import AES
  2. import base64
  3. import time
  4. import binascii
  5. class AesEncrypt:
  6. def __init__(self, key, iv):
  7. self.key = key.encode('utf-8')
  8. self.iv = iv.encode('utf-8')
  9. # @staticmethod
  10. def pkcs7padding(self, text):
  11. """明文使用PKCS7填充 """
  12. bs = 16
  13. length = len(text)
  14. bytes_length = len(text.encode('utf-8'))
  15. padding_size = length if (bytes_length == length) else bytes_length
  16. padding = bs - padding_size % bs
  17. padding_text = chr(padding) * padding
  18. self.coding = chr(padding)
  19. return text + padding_text
  20. def aes_encrypt(self, content):
  21. """ AES加密 """
  22. cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
  23. # 处理明文
  24. content_padding = self.pkcs7padding(content)
  25. # 加密
  26. encrypt_bytes = cipher.encrypt(content_padding.encode('utf-8'))
  27. # 重新编码
  28. result = str(base64.b64encode(encrypt_bytes), encoding='utf-8')
  29. print("加密hex:", str(binascii.hexlify(encrypt_bytes),encoding='utf-8'))
  30. return result
  31. def aes_encrypt_to_hex(self, content):
  32. """ AES加密 """
  33. cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
  34. # 处理明文
  35. content_padding = self.pkcs7padding(content)
  36. # 加密
  37. encrypt_bytes = cipher.encrypt(content_padding.encode('utf-8'))
  38. # 重新编码
  39. # result = str(base64.b64encode(encrypt_bytes), encoding='utf-8')
  40. return str(binascii.hexlify(encrypt_bytes),encoding='utf-8')
  41. def aes_decrypt(self, content):
  42. """AES解密 """
  43. cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
  44. content = base64.b64decode(content)
  45. text = cipher.decrypt(content).decode('utf-8')
  46. return text.rstrip(self.coding)
  47. if __name__ == '__main__':
  48. key = '123'
  49. iv = '123'
  50. ts = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
  51. p_json = {
  52. "CompanyName": "testmall",
  53. "UserId": "test",
  54. "Password": "grasp@101",
  55. "TimeStamp": "2019-05-05 10:59:26"
  56. }
  57. a = AesEncrypt(key=key, iv=iv)
  58. e = a.aes_encrypt("123")
  59. d = a.aes_decrypt(e)
  60. print("加密:", e)
  61. print("解密:", d)

好了,

真完了,

睡觉睡觉。

编辑于 8 分钟前

【爬虫系列】0. 无内鬼,破解前端JS参数签名的更多相关文章

  1. Web暴力破解--前端JS表单加密进行爆破

    0x01 前言 常见的js实现加密的方式有:md5.base64.shal,写了一个简单的demo作为测试. 0x02 代码 login.html <!DOCTYPE HTML> < ...

  2. 闲聊——浅谈前端js模块化演变

    function时代 前端这几年发展太快了,我学习的速度都跟不上演变的速度了(门派太多了,后台都是大牛公司支撑类似于facebook的react.google的angular,angular的1.0还 ...

  3. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  4. Scrapy入门到放弃01:开启爬虫2.0时代

    前言 Scrapy is coming!! 在写了七篇爬虫基础文章之后,终于写到心心念念的Scrapy了.Scrapy开启了爬虫2.0的时代,让爬虫以一种崭新的形式呈现在开发者面前. 在18年实习的时 ...

  5. 前端构建大法 Gulp 系列 (一):为什么需要前端构建

    系列目录 前端构建大法 Gulp 系列 (一):为什么需要前端构建 前端构建大法 Gulp 系列 (二):为什么选择gulp 前端构建大法 Gulp 系列 (三):gulp的4个API 让你成为gul ...

  6. 【转载】.NET面试题系列[0] - 写在前面

    原文:.NET面试题系列[0] - 写在前面 索引: .NET框架基础知识[1] - .NET框架基础知识(1) http://www.cnblogs.com/haoyifei/p/5643689.h ...

  7. 上传头像,界面无跳转,php+js

    上传头像,界面无跳转的方式很多,我用的是加个iframe那种.下面直接上代码. html: //route 为后端接口//upload/avatar 为上传的头像的保存地址//imgurl=/uplo ...

  8. python 全栈开发,Day134(爬虫系列之第1章-requests模块)

    一.爬虫系列之第1章-requests模块 爬虫简介 概述 近年来,随着网络应用的逐渐扩展和深入,如何高效的获取网上数据成为了无数公司和个人的追求,在大数据时代,谁掌握了更多的数据,谁就可以获得更高的 ...

  9. 爬虫系列1:python简易爬虫分析

    决定写一个小的爬虫系列,本文是第一篇,讲爬虫的基本原理和简易示例. 1.单个网页的简易爬虫 以下爬虫的主要功能是爬取百度贴吧中某一页面的所有图片.代码由主要有两个函数:其中getHtml()通过页面u ...

随机推荐

  1. 多核片上系统(SoC)架构的嵌入式DSP软件设计

    多核片上系统(SoC)架构的嵌入式DSP软件设计 Multicore a System-on-a-Chip (SoC) Architecture SoCs的软件开发涉及到基于最强大的计算模型在各种处理 ...

  2. 操作系统-Linux命令

    一.目录结构 #因为根目录与开机有关,开机过程中仅有根目录会被挂载, 因此根目录下与开机过程有关的目录(以下5个),不能与根目录放到不同的分区去. /etc:配置文件 /dev:所需要的装置文件 /l ...

  3. 源码简析Spring-Integration执行过程

    一,前言 Spring-Integration基于Spring,在应用程序中启用了轻量级消息传递,并支持通过声明式适配器与外部系统集成.这一段官网的介绍,概况了整个Integration的用途.个人感 ...

  4. mybatis之模糊查询

    1.编写接口 List<User> getUserLike(String value); 2.编写映射文件 <select id="getUserLike" re ...

  5. 重新整理 mysql 基础篇————— 事务隔离级别[四]

    前言 简单介绍一下事务隔离的基本 正文 Read Uncommitted(未提交读) 这个就是读未提交.就是说在事务未提交的时候,其他事务也可以读取到未提交的数据. 这里举一个例子,还是前一篇的例子. ...

  6. Git 高级用法,喜欢就拿去用

    如果你觉得 git 很迷惑人,那么这份小抄正是为你准备的! 请注意我有意跳过了 git commit.git pull/push 之类的基本命令,这份小抄的主题是 git 的一些「高级」用法. 导航 ...

  7. 【题解】 hdu2955 Robberies

    有抱负的罗伊·劫匪已经看过很多美国电影,他知道坏人通常会被抓住,经常是因为他们太贪心了.他决定在银行抢劫案中工作一段时间,然后退休后到一所大学从事一份舒适的工作. 题目: 罗伊去几个银行偷盗,他既想多 ...

  8. 从五大结构体,带你掌握鸿蒙轻内核动态内存Dynamic Memory

    摘要:本文带领大家一起剖析了鸿蒙轻内核的动态内存模块的源代码,包含动态内存的结构体.动态内存池初始化.动态内存申请.释放等. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列九 动态内存Dyna ...

  9. [源码解析] 深度学习分布式训练框架 horovod (8) --- on spark

    [源码解析] 深度学习分布式训练框架 horovod (8) --- on spark 目录 [源码解析] 深度学习分布式训练框架 horovod (8) --- on spark 0x00 摘要 0 ...

  10. nginx服务跳转

    1.什么是页面跳转 将URL信息做改变 将URI信息做改变 完成伪静态配置 2.实现页面跳转的方法 http://nginx.org/en/docs/http/ngx_http_rewrite_mod ...