【JS 逆向百例】转变思路,少走弯路,X米加密分析

声明

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

逆向目标

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

逆向过程

抓包分析

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

01.png

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 重定向请求里:

02.png

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

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

03.png
04.png

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

05.png
06.png

找到了参数的来源,直接从第二次重定向的链接里提取各项参数,这里用到了 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(),很明显是将明文的密码经过加密处理后再全部转为大写:

07.png

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

08.png
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 这个函数看看:

09.png
10.png

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

11.png
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 加密,和网站的加密结果进行对比,可以发现确实是一样的,验证了我们的猜想是正确的:

12.png

既然如此,直接可以使用 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()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容