【JavaScript 逆向】某升学助考网登录参数逆向,Hook + 跟栈

本文介绍了如何破解采用MD5+AES加密的登录接口。首先,通过网络抓包找到登录接口及加密参数,然后通过JavaScript逆向分析,寻找加密入口,调试分析加密逻辑,包括MD5摘要加密sign参数和AES加密password参数。最后,模拟执行加密过程,实现了在Python中复现加密算法,成功登录网站。
摘要由CSDN通过智能技术生成

前言

        现在一些网站对 JavaScript 代码采取了一定的保护措施,比如变量名混淆、执行逻辑混淆、反调试、核心逻辑加密等,有的还对数据接口进行了加密,这次的案例是对 MD5 + AES 加密方式的破解。

        MD5 对数据进行有损压缩,结果不可逆无法还原,不能算为加密算法,是摘要算法,不论数据有多长,都会生成固定的 128 位的散列值,但是可以检测数据是否被篡改,因为只要改动了任何一个 bit 的数据,摘要结果都会不一样。

        AES 是对称加密,对称加密是指加密和解密时使用同一个密钥,这种加密方式加密速度非常快,适合经常发送数据的场合,缺点是密钥的传输比较麻烦。

声明

        本文章中所有内容仅供学习交流,相关链接做了脱敏处理,若有侵权,请联系我立即删除!

案例目标

网址:aHR0cHM6Ly93ZWIuZXd0MzYwLmNvbS9yZWdpc3Rlci8jL2xvZ2lu

登录接口:aHR0cHM6Ly9nYXRld2F5LmV3dDM2MC5jb20vYXBpL2F1dGhjZW50ZXIvdjIvb2F1dGgvbG9naW4vYWNjb3VudA==

以上均做了脱敏处理,Base64 编码及解码方式:

import base64
# 编码
# result = base64.b64encode('待编码字符串'.encode('utf-8'))
# 解码
result = base64.b64decode('待解码字符串'.encode('utf-8'))
print(result)

常规 JavaScript 逆向思路

一般情况下,JavaScript 逆向分为三步:

  1. 寻找入口:逆向在大部分情况下就是找一些加密参数到底是怎么来的,关键逻辑可能写在某个关键的方法或者隐藏在某个关键的变量里,一个网站可能加载了很多 JavaScript 文件,如何从这么多的 JavaScript 文件的代码行中找到关键的位置,很重要
  2. 调试分析:找到入口后,我们定位到某个参数可能是在某个方法中执行的了,那么里面的逻辑是怎么样的,调用了多少加密算法,经过了多少赋值变换,需要把整体思路整理清楚,以便于断点或反混淆工具等进行调试分析
  3. 模拟执行:经过调试分析后,差不多弄清了逻辑,就需要对加密过程进行逻辑复现,以拿到最后我们想要的数据

接下来开始正式进行案例分析:

寻找入口 

        进入到某升学助考网的登录页面,F12 打开开发者人员工具,切换到 network 准备查看网络抓包请求,随便输入一个账号、密码,查看网络抓包请求情况,如下图,可以看到抓包到的这条数据,返回了登录响应的内容,即此处为登录的接口:

通过观察请求头中响应返回的数据,可以发现 Request Headers 中的 sign 和 Request Payload 中的 password 是经过加密了的,用户名则为明文传输:

调试分析

sign 参数 

        CTRL + SHIFT + F 全局搜索 sign 参数会发现出现了很多结果,筛选难度很大,因为 sign 是以 JSON 形式返回的数据,所以可以直接搜索 sign 加冒号,筛选难度大大降低,通过观察会发现 sign 的值在 request.js 中被加密了:

 点击进入 request.js,可以看到以下加密代码内容:

td 值为 0,now 为时间戳,所以 sign 参数的加密方式即为:时间戳 + 字符串(bdc739ff2dcf)经过 MD5 摘要算法进行加密,然后转换为字符串形式,最后转换成大写,我们可以通过 python 对其进行复现:

import time
import hashlib

timestamp = str(int(time.time() * 1000))
sign = hashlib.md5((timestamp + 'bdc739ff2dcf').encode('utf-8')).hexdigest().upper()
print(sign)

password 参数

方法一:直接跟栈调试

        加密参数 password 的具体加密位置如果通过全局搜索的方式来确认的话,工作量会相当繁杂,如下可以看到搜索到了很多结果:

        好在办法总比困难多,这里讲解两种方法解决,首先我们可以通过直接跟栈来尝试找到加密的位置 ,切换到 Network,找到 account 数据包,会看到 initiator(发起请求的对象或进程)选项下有个 js 文件样式(index.esm.js):

        鼠标悬停在上面,可以看到出现如下图所示的一个个堆栈,并且名称被混淆了,以下 Y 函数,它位于调用栈的最顶层,表示经过此函数后,浏览器就会发送登录的请求,密码的加密过程已经处理完毕,从下到上即执行流程:

  • 通过观察堆栈的调用可以回溯逻辑的执行流程,堆栈调用先进后出,就像是一条道路堵塞了,越后过来排队的车越能先倒出去

也可以点击打开 account,右侧 initiator 选项中也能查看到堆栈调用过程:

点击 Y 后链接,即可跳转到它定义的位置,在第 710 行打下断点,再次点击登录,即可成功断住, 这里是登录完成前的最后一步,密码已经被加密完毕了,所以要想找到密码的具体加密位置,就需要接着一步步往下找:

例如在 Call Stack(调用栈)中点击 Y 函数下面的 o 函数,可以看到其断住位置的参数 params 的 data 中密码是已经经过加密了的,所以加密位置还在下面的函数中,我们就需要继续挨个往下找: 

接下来就是挨个往下跟栈的过程,就不赘述了,最后我们能在 utils.ts 中的第 174 行找到 password 的加密位置,在这行打断点再次点击登录,鼠标悬停在 requestParams.password 上可以看到这里就是密码未加密前的明文状态:

可以看到明文是通过 passwordEncrypt( ) 方法进行了加密处理,所以鼠标悬停其上,进一步跟进到加密的位置 encode.ts 中:

这里是很明显的 AES 加密:

复制下来,通过 nodejs 对其复现,代码如下:

var CryptoJS = require('crypto-js');


function encryptPwd(word){
    const key = CryptoJS.enc.Utf8.parse("20171109124536982017110912453698");
    const iv = CryptoJS.enc.Utf8.parse('2017110912453698');
    let srcs = CryptoJS.enc.Utf8.parse(word);
    let encrypted = CryptoJS.AES.encrypt(srcs, key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
    });
return encrypted.ciphertext.toString().toUpperCase();
}


console.log(encryptPwd(123456))
// A7428361DEF118911783F446A129FFCE

结果匹配: 

方法二:Hook 注入

        Hook 技术又称为钩子技术,他是一种特殊的消息处理机制,可以监视系统或者进程中的各种事件消息,截获发往目标窗口的消息并进行处理,因为 Windows 操作系统是建立在事件驱动机制上的,通过消息传递实现,而 Hook 相当于对其的拦截。

相关资料可参考:

Hook 的方式有很多种这里通过 Fiddler 中的插件完成 Hook 注入,通过观察可以发现 password 是以字符串形式传输的:

我们知道在 JavaScript 中 JSON.stringify() 方法用于将 JavaScript 对象或值转换为 JSON 字符串,某些站点在向 web 服务器传输用户名密码时,会用到这个方法,在本案例中,就用到了 JSON.stringify() 方法,针对该方法,可以写一个 Hook 注入脚本:

(function() {
    var stringify = JSON.stringify;
    JSON.stringify = function(params) {
        console.log("Hook JSON.stringify ——> ", params);
        debugger;
        return stringify(params);
    }
})();

以下是 Fiddler 中插件使用的基本流程:

添加好代码后,F12 启动抓包,然后如下清除浏览器 Cookies,刷新页面即可完成注入: 

这里刷新页面后发现直接就被断住了,证明页面生成之前就会响应生成一些字符串数据,我们可以点击上方蓝色箭头  或者 F8 跳过这个断点,即可进入到页面: 

在输入账户密码进行登陆之前遇到 JSON.stringify() 方法就会执行 debugger 语句,立即断下,比如上述情况,输入账户密码点击登录,如下图所示成功断下,可以从 params 中看到加密后的密码内容:

接着在 Call Stack 处从 JSON.stringify 依次往下跟栈,直到找到加密位置,之后的操作跟方法一一样:

模拟执行

完整代码

注意:如果使用了 Hook 注入,运行前停止 Fiddler 抓包操作

以下代码 url 部分进行了脱敏处理(Base64),同时需要输入自己的用户名密码,不能直接运行:

import time

import execjs
import requests
import json
import hashlib


def encrypt_sign():
    timestamp = str(int(time.time() * 1000))
    sign = hashlib.md5((timestamp + 'bdc739ff2dcf').encode('utf-8')).hexdigest().upper()
    return sign


def encrypt_pwd(pwd):
    with open('e.js', 'r', encoding='utf-8') as f:
        pwd_encrypt = f.read()
    pwd_result = execjs.compile(pwd_encrypt).call('encryptPwd', pwd)
    return pwd_result


def login(user, enc_pwd, sign):
    timestamp = str(int(time.time() * 1000))
    headers = {
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
        # content-type 必须加
        "content-type": "application/json;charset=UTF-8",
        "timestamp": timestamp,
        "sign": sign,
    }
    url = "aHR0cHM6Ly9nYXRld2F5LmV3dDM2MC5jb20vYXBpL2F1dGhjZW50ZXIvdjIvb2F1dGgvbG9naW4vYWNjb3VudA=="
    data = {
        "platform": 1,
        "userName": user,
        "password": enc_pwd,
        "autoLogin": True
    }
    data = json.dumps(data)
    session = requests.session()
    response = session.post(url=url, headers=headers, data=data)
    print(response.text)


def main():
    user = '你的用户名'
    pwd = '你的密码'
    enc_pwd = encrypt_pwd(pwd)
    sign = encrypt_sign()
    login(user, enc_pwd, sign)


if __name__ == '__main__':
    main()

运行结果:

总结

        以上是对某升学助考网加密参数的逆向分析,如有任何见解欢迎评论区或私信指正交流~       

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值