声明

本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除!

逆向目标

  • 目标:X米账号登录
  • 主页:aHR0cHM6Ly9hY2NvdW50LnhpYW9taS5jb20v
  • 接口:aHR0cHM6Ly9hY2NvdW50LnhpYW9taS5jb20vcGFzcy9zZXJ2aWNlTG9naW5BdXRoMg==
  • 逆向参数:Form Data:hash: FCEA920F7412B5DA7BE0CF42B8C93759

逆向过程

抓包分析

来到X米的登录页面,随便输入一个账号密码登陆,抓包定位到登录接口为 aHR0cHM6Ly9hY2NvdW50LnhpYW9taS5jb20vcGFzcy9zZXJ2aWNlTG9naW5BdXRoMg==

POST 请求,Form Data 里的参数比较多,分析一下主要参数:

  • serviceParam: {"checkSafePhone":false,"checkSafeAddress":false,"lsrp_score":0.0},从参数的字面意思来看,似乎是在检查手机和地址是否安全,至于具体是什么含义,暂时不得而知,也不知道是在哪个地方设置的。
  • callback: http://order.xxx.com/login/callback?followup=https%3A%2F%2Fwww.xx...... ,回调链接,一般来说是固定的,后面带有 followup 和 sid 参数。
  • qs: %3Fcallback%3Dhttp%253A%252F%252Forder.xxx.com%252Flogin%252Fcallback%2......,把 qs 的值格式化一下可以发现,其实是 callback、sign、sid、_qrsize 四个值按照 URL 编码进行组合得到的。
  • _sign: w1RBM6cG8q2xj5JzBPPa65QKs9w=,这个一串看起来是经过某种加密后得到的,也有可能是网页源码中的值。
  • user: 15555555555,明文用户名。
  • hash: FCEA920F7412B5DA7BE0CF42B8C93759,加密后的密码。

参数逆向

基本参数

先来看一下 serviceParam 等基本参数,一般思路我们是先直接搜索一下看看能不能直接找到这个值,搜索发现 serviceParam 关键字在一个 302 重定向请求里:

我们注意到,当只输入登录的主页 aHR0cHM6Ly9hY2NvdW50LnhpYW9taS5jb20v,它会有两次连续的 302 重定向,来重点分析一下这两次重定向。

第一次重定向,新的网址里有 followupcallbacksignsid 参数,这些我们都是在后面的登录请求中要用到的。

第二次重定向,新的网址里同样有 followupcallbacksignsid 参数,此外还有 serviceParamqs 参数,同样也是后面的登录请求需要用到的。

找到了参数的来源,直接从第二次重定向的链接里提取各项参数,这里用到了 response.history[1].headers['Location'] 来提取页面第二次重定向返回头里的目标地址,urllib.parse.urlparse 来解析重定向链接 URL 的结构,urllib.parse.parse_qs 提取参数,返回字典,代码样例:

import requests
import urllib.parse headers = {
'Host': '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
index_url = '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler'
response = requests.get(url=index_url, headers=headers)
location_url = response.history[1].headers['Location']
urlparse = urllib.parse.urlparse(location_url)
query_dict = urllib.parse.parse_qs(urlparse.query)
print(query_dict) need_theme = query_dict['needTheme'][0]
show_active_x = query_dict['showActiveX'][0]
service_param = query_dict['serviceParam'][0]
callback = query_dict['callback'][0]
qs = query_dict['qs'][0]
sid = query_dict['sid'][0]
_sign = query_dict['_sign'][0] print(need_theme, show_active_x, service_param, callback, qs, sid, _sign)

hash

其他参数都齐全了,现在还差一个加密后的密码 hash,一般来讲这种都是通过 JS 加密的,老方法,全局搜索 hash 或者 hash:,可以在 78.4da22c55.chunk.js 文件里面看到有一句:hash: S()(r.password).toUpperCase(),很明显是将明文的密码经过加密处理后再全部转为大写:

重点是这个 S(),鼠标移上去会发现其实是调用了 78.4da22c55.chunk.js 的一个匿名函数,我们在匿名函数的 return 位置埋下断点进行调试:

e.exports = function(e, n) {
if (void 0 === e || null === e)
throw new Error("Illegal argument " + e);
var r = t.wordsToBytes(u(e, n));
return n && n.asBytes ? r : n && n.asString ? s.bytesToString(r) : t.bytesToHex(r)
}

可以看到传进来的 e 是明文的密码,最后的 return 语句是一个三目运算符,由于 n 是 undefined,所以最后 return 的实际上是 t.bytesToHex(r),其值正是加密后的密码,只不过所有字母都是小写,按照正常思维,我们肯定是开始扣 JS 了,这里传入了参数 r,var r = t.wordsToBytes(u(e, n));,先跟进 u 这个函数看看:

可以看到 u 函数实际上是用到了 567 这个对象方法,在这个对象方法里面,还用到了 129、211、22 等非常多的方法,这要是挨个去扣,那还不得扣到猴年马月,而且还容易出错,代码太多也不好定位错误的地方,所以这里需要转变一下思路,先来看看 t.bytesToHex(r) 是个什么东东,跟进到这个函数:

bytesToHex: function(e) {
for (var t = [], n = 0; n < e.length; n++)
t.push((e[n] >>> 4).toString(16)),
t.push((15 & e[n]).toString(16));
return t.join("")
}

解读一下这段代码,传进来的 e 是一个 16 位的 Array 对象,定义了一个 t 空数组,经过一个循环,依次取 Array 对象里的值,第一次经过无符号右移运算(>>>)后,转为十六进制的字符串,将结果添加到 t 数组的末尾。第二次进行位运算(&)后,同样转为十六进制的字符串,将结果添加到 t 数组的末尾。也就是说,原本传进来的 16 位的 Array 对象,每一个值都经过了两次操作,那么最后结果的 t 数组中就会有 32 个值,最后再将 t 数组转换成字符串返回。

结合一下调用的函数名称,我们来捋一下整个流程,首先调用 wordsToBytes() 方法将明文密码字符串转为 byte 数组,无论密码的长度如何,最后得到的 byte 数组都是 16 位的,然后调用 bytesToHex() 方法,循环遍历生成的 byte 类型数组,让其生成 32 位字符串。

无论密码长度如何,最终得到的密文都是 32 位的,而且都由字母和数字组成,这些特点很容易让人想到 MD5 加密,将明文转换成 byte 数组后进行随机哈希,对 byte 数组进行摘要,得到摘要 byte 数组,循环遍历 byte 数组,生成固定位数的字符串,这不就是 MD5 的加密过程么?

直接把密码拿来进行 MD5 加密,和网站的加密结果进行对比,可以发现确实是一样的,验证了我们的猜想是正确的:

既然如此,直接可以使用 Python 的 hashlib 模块来实现就 OK 了,根本不需要去死扣代码,代码样例:

import hashlib

password = "1234567"
encrypted_password = hashlib.md5(password.encode(encoding='utf-8')).hexdigest().upper()
print(encrypted_password)
# FCEA920F7412B5DA7BE0CF42B8C93759

总结

有的时候需要我们转变思路,不一定每次都要死扣 JS 代码,相对较容易的站点的加密方式无非就是那么几种,有的是稍微进行了改写,有的是把密钥、偏移量等参数隐藏了,有的是把加密解密过程给你混淆了,让你难以理解,如果你对常见的加密方式和原理比较熟悉的话,有时候只需要搞清楚他用的什么加密方式,或者拿到了密钥、偏移量等关键参数,就完全可以自己还原整个加密过程!

完整代码

GitHub 关注 K 哥爬虫,持续分享爬虫相关代码!欢迎 star !

https://github.com/kgepachong/

以下只演示部分关键代码,完整代码仓库地址:

https://github.com/kgepachong/crawler/

Python 登录关键代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*- import json
import hashlib
import urllib.parse import requests index_url = '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler'
login_url = '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler'
headers = {
'Host': '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler',
'Origin': '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler',
'Referer': '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
session = requests.session() def get_encrypted_password(password):
encrypted_password = hashlib.md5(password.encode(encoding='utf-8')).hexdigest().upper()
return encrypted_password def get_parameter():
response = requests.get(url=index_url, headers=headers)
location_url = response.history[1].headers['Location']
urlparse = urllib.parse.urlparse(location_url)
query_dict = urllib.parse.parse_qs(urlparse.query)
# print(query_dict)
return query_dict def login(username, encrypted_password, query_dict):
data = {
'bizDeviceType': '',
'needTheme': query_dict['needTheme'][0],
'theme': '',
'showActiveX': query_dict['showActiveX'][0],
'serviceParam': query_dict['serviceParam'][0],
'callback': query_dict['callback'][0],
'qs': query_dict['qs'][0],
'sid': query_dict['sid'][0],
'_sign': query_dict['_sign'][0],
'user': username,
'cc': '+86',
'hash': encrypted_password,
'_json': True
}
response = session.post(url=login_url, data=data, headers=headers)
response_json = json.loads(response.text.replace('&&&START&&&', ''))
print(response_json)
return response_json def main():
username = input('请输入登录账号: ')
password = input('请输入登录密码: ')
encrypted_password = get_encrypted_password(password)
parameter = get_parameter()
login(username, encrypted_password, parameter) if __name__ == '__main__':
main()

【JS 逆向百例】转变思路,少走弯路,X米加密分析的更多相关文章

  1. python爬虫之JS逆向

    Python爬虫之JS逆向案例 由于在爬取数据时,遇到请求头限制属性为动态生成,现将解决方式整理如下: JS逆向有两种思路: 一种是整理出js文件在Python中直接使用execjs调用js文件(可见 ...

  2. python爬虫之JS逆向某易云音乐

    Python爬虫之JS逆向采集某易云音乐网站 在获取音乐的详情信息时,遇到请求参数全为加密的情况,现解解决方案整理如下: JS逆向有两种思路: 一种是整理出js文件在Python中直接使用execjs ...

  3. 通过JS逆向ProtoBuf 反反爬思路分享

    前言 本文意在记录,在爬虫过程中,我首次遇到Protobuf时的一系列问题和解决问题的思路. 文章编写遵循当时工作的思路,优点:非常详细,缺点:文字冗长,描述不准确 protobuf用在前后端传输,在 ...

  4. 【算法】C语言趣味程序设计编程百例精解

    C语言趣味程序设计编程百例精解 C/C++语言经典.实用.趣味程序设计编程百例精解(1)  https://wenku.baidu.com/view/b9f683c08bd63186bcebbc3c. ...

  5. 网络爬虫之记一次js逆向解密经历

    1 引言 数月前写过某网站(请原谅我的掩耳盗铃)的爬虫,这两天需要重新采集一次,用的是scrapy-redis框架,本以为二次爬取可以轻松完成的,可没想到爬虫启动没几秒,出现了大堆的重试提示,心里顿时 ...

  6. JS逆向之补环境过瑞数详解

    JS逆向之补环境过瑞数详解 "瑞数" 是逆向路上的一座大山,是许多JS逆向者绕不开的一堵围墙,也是跳槽简历上的一个亮点,我们必须得在下次跳槽前攻克它!! 好在现在网上有很多讲解瑞数 ...

  7. JS逆向之浏览器补环境详解

    JS逆向之浏览器补环境详解 "补浏览器环境"是JS逆向者升职加薪的必备技能,也是工作中不可避免的操作. 为了让大家彻底搞懂 "补浏览器环境"的缘由及原理,本文将 ...

  8. Java使用正则表达式取网页中的一段内容(以取Js方法为例)

    关于正则表达式: 表1.常用的元字符 代码 说明 . 匹配除换行符以外的任意字符 \w 匹配字母或数字或下划线或汉字 \s 匹配任意的空白符 \d 匹配数字 \b 匹配单词的开始或结束 ^ 匹配字符串 ...

  9. js—浅谈方法和思路的重要性(首篇求大佬支持)

    js-浅谈方法和思路的重要性 学了这么久的js,我从老师的,同学的代码中发现,老师写的代码比我们的要清楚的很多,基本上没有太多累赘啊,能少的没有少啊等等..... 废话不多说,下面我们来看看这个我的一 ...

  10. PHP程序员从小白到高手,掌握这些技能少走弯路

    PHP程序员从小白到高手,掌握这些技能少走弯路 PHP究竟是不是最好的语言,一直以来是程序员最大的“争议”,但毋庸置疑的是,PHP绝对是最有前途和力量的变成语言,也是你入门最值得学习的语言. 作为老牌 ...

随机推荐

  1. 中断操作:AbortController学习笔记

    前端面试一般喜欢问: 请手写一个带取消功能的延迟函数,axios 取消功能的原理是什么? 如何中断请求fetch的原理分析和应用? 在看来<使用 AbortController 终止 fetch ...

  2. Solon Cloud 2.2.10 架构图发布

    Solon Cloud 是在 Solon 的基础上构建的微服务开发套件.以标准与规范为核心,构建丰富的开放生态.为微服务开发提供了一个通用防腐层(即不用改代码,切换配置即可更换组件). 本次发布,展示 ...

  3. Axure 表格中根据条件设置不同的字体样式--中继器

    中继器+表格,根据条件设置不同的字体样式 思路:根据情形,设置不同的颜色,因为Axure 不能直接对元件的样式进行交互设置,所以借助[动态面板]进行设置 绘制表格详见:https://www.cnbl ...

  4. AudioLDM 2,加速!

    AudioLDM 2 由刘濠赫等人在 AudioLDM 2: Learning Holistic Audio Generation with Self-supervised Pretraining 一 ...

  5. 手把手教你配置JupyterLab 环境

    Python 代码编辑器怎么选?PyCharm.VS Code.Jupyter Notebook 都各有特色. 对于大型代码库,最好还是用传统的 IDE 比较靠谱,但是数据分析等需要可视化操作的场景下 ...

  6. AliPLC 智能丢包补偿算法,提升弱网环境的通话质量

    在线视频/语音通话逐渐成为人们日常生活的一部分,而复杂多变的网络环境会导致部分音频包无法传送到接收端,造成语音信号的短时中断或者卡顿,这会严重影响通话体验,为解决这类问题,阿里云视频云音频技术团队在综 ...

  7. Vue大数据可视化(大屏展示)解决方案

    DataV:组件库基于Vue (React版) ,主要用于构建大屏(全屏)数据展示页面即数据可视化 官网地址: http://datav.jiaminghi.com/guide/#%E7%94%A8% ...

  8. 使用 Sealos 将 ChatGLM3 接入 FastGPT,打造完全私有化 AI 客服

    FastGPT 是一款专为客服问答场景而定制的开箱即用的 AI 知识库问答系统.该系统具备可视化工作流功能,允许用户灵活地设计复杂的问答流程,几乎能满足各种客服需求. 在国内市场环境下,离线部署对于企 ...

  9. C#设计模式13——建造者模式的写法

    1. 什么是建造者模式? 建造者模式是一种创建型设计模式,它通过将一个复杂的对象分解为多个简单的对象并按照一定的顺序进行组装而创建出一个复杂的对象.这样可以使得构造过程更加灵活,同时也可以隐藏创建过程 ...

  10. python之configparser类的使用

    一.定义配置文件格式如下:data.conf [interface] url=http://192.168.37.8:7777/api/mytest2 [switch] switch_car=on [ ...