【JavaScript 逆向】某道翻译接口逆向

本文详细介绍了如何对一个采用MD5加密的在线翻译接口进行逆向分析,包括寻找入口、调试分析和模拟执行的过程。通过JavaScript加密参数的定位和理解,最终实现了加密逻辑的复现,从而能够模拟翻译请求获取翻译结果。
摘要由CSDN通过智能技术生成

前言

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

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

案例目标

网址:aHR0cHM6Ly9mYW55aS55b3VkYW8uY29tLw==

翻译接口:aHR0cHM6Ly9mYW55aS55b3VkYW8uY29tL3RyYW5zbGF0ZV9vP3NtYXJ0cmVzdWx0PWRpY3Qmc21hcnRyZXN1bHQ9cnVsZQ==

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

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

常规做法直接访问接口,会显示 errorCode 50,证明网站存在一定的反爬机制

常规 JavaScript 逆向思路

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

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

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

寻找入口 

        首先随便输入一个单词,可以看到在网页没有刷新的情况下翻译结果就显示出来了,这里初步推断为 Ajax 异步加载数据的结果,一般情况下,我们在爬虫中使用 requests 方法直接获取到的网页响应数据(response) 是 HTML 页面的原始文档,现在网页的实现大多数都采用前后端分离的模式,浏览器中的页面是 JavaScript 渲染后生成的,这样能减少服务器直接渲染页面带来的压力,我们可以简单的测试一下,右击网页选择查看网页源代码:

        以下看到的就是 HTML 网页的原始文档,CTRL + F 搜索我们刚刚输入待翻译的词汇 Rose,可以看到没有筛选到任何结果,这就进一步验证了我们的猜想: 

        F12 打开开发者人员工具,我们第一步要获取的是翻译的接口,而翻译的数据是通过 Ajax 加载的,Ajax 有特殊的请求类型 XHR,我们可以直接在开发者人员工具的筛选栏选择 XHR,抓包到的内容就全部是 Ajax 加载的数据,可以看到抓报到一条数据:

        我们点击进去查看一下内容,通过 preview(控制台会把发送过来的 json 数据自动转换成 javascript 的对象格式,response 为原始响应数据)预览中可以看到,smartResult 为智能翻译的结果,translateResult 是直接翻译显示出来的结果,这样我们就成功找到了翻译的入口,接下来就可以进行第二部调试分析了:

调试分析

        观察 Headers 中的信息,可以发现是以 POST 方式提交的数据,Headers 往下滑(Chrome 浏览器相关内容在新功能 Payload 中)可以看到 Form Data 是以表单形式提交的数据,其中是 JSON 格式的参数数据:

通过观察我们可以大概分析出对应的数据的含义:

  • i:待翻译的字符串
  • version:版本
  • action:实时翻译 FY_BY_REALTlME、手动点击翻译 FY_BY_CLICKBUTTION

        以上内容不足以让我们知道哪些参数是固定值,哪些参数是经过加密得到的值,我们可以重新输入一个单词,这样就会抓包到一个新的数据,通过观察两个 Form Data 中的数据,可以知道,saltsign、lts 的值每次会改变,其他的皆为固定值:

所以我们可以通过全局搜索(如下图)寻找这几个变量出现的位置:

例如搜索 salt,这里没什么干扰项,只存在于一个 js 文件 fanyi.min.js 中:

点击进去查看会发现整个 js 文件内容被压缩成了一行,不利于我们进行分析研究,可以点击左下角的 {} 对其进行格式化操作,得到的就是标准格式便于观察的 js 文件:

我们可以通过 CTRL+F 进一步定位 salt 参数的位置,由上可知,请求数据是以 JSON 格式提交的,所以我们可以直接搜索 salt+冒号(英文冒号)定位,可以看到筛选出了更少的结果:

接下来我们可以通过对照 Form Data 中的参数内容,逐一对比找到所有参数定义及加密的位置,可以看到在 8974 - 8986 行全部匹配:

此时我们可以打下断点进行调试分析,再次点击翻译,可以发现在断点处停住了,证明这就是翻译参数生成的位置:

我们可以看到,加密的几个参数全部是由 r 定义生成的,这时候进一步跟踪 r 的构造位置:

鼠标悬停在 n 上,可以发现 n 就是我们待翻译的字符串,选中整行代码,鼠标悬停后可以观察到,这个方法生成了所有的加密参数值:

所以跟进函数 generateSaltSign() 就可以找到关键的 js 加密位置:

点击以上链接,会跳转到函数的构造位置:

由上图可以明显看到,t 和 sign 是经过 MD5 加密的,我们进一步分析这段代码的其他加密参数的含义,打下断点进行调试,可以看到 e 就是待翻译的字符串:

var r = function(e) {
    # navigator.appVersion 浏览器的版本号
    # t:浏览器版本号经过 MD5 加密的结果
    var t = n.md5(navigator.appVersion)
        # r:时间戳
      , r = "" + (new Date).getTime()
        # i:ts 的值加上一个 0-9 的随机整数
      , i = r + parseInt(10 * Math.random(), 10);
    return {
        ts: r,
        bv: t,
        salt: i,
        # 两个固定的字符串 + e(待翻译的字符串)+ salt 值
        sign: n.md5("fanyideskweb" + e + i + "Y2FYu%TNSbMCxc3t2u^XT")
    }
};
  • bv:浏览器版本号经过 MD5 加密的结果
  • ts:时间戳
  • salt:ts 的值加上一个 0-9 的随机整数
  • sign:两个固定的字符串 + e(待翻译的字符串)+ salt 值

以上找到的加密方法的具体位置,就可以通过代码进行复现了,一般复现的方式有两种:

  1. 通过 python 直接复现
  2. JavaScript 文件写入本地调用复现

这里使用第二种更常规的方式进行逻辑复现:

JavaScript 代码:

// 引用 crypto-js 加密模块
// Crypto 谷歌开发的一个纯 JavaScript 的加密算法类库,可以非常方便的在前端进行其所支持的加解密操作
// require 是 node 用来加载并执行其它文件导出的模块的方法
var CryptoJS = require('crypto-js')

function getParams(input, ua) {
    var bv = CryptoJS.MD5(ua).toString(),
        lts = "" + (new Date).getTime(),
        salt = lts + parseInt(10 * Math.random(), 10),
        sign = CryptoJS.MD5('fanyideskweb' + input + salt + ']BjuETDhU)zqSxf-=B#7m').toString()
    return { bv: bv, lts: lts, salt: salt, sign: sign }
}

以上 sign 中的 salt 相当于 MD5 的加盐操作 :

加盐(the-Salt):常用口令的 MD5 值 + 复杂字符串,防止黑客通过彩虹表(反推表,简单密码口令的 MD5 值进行对比),加强账户保护。

我们需要获取的是页面上看到的翻译出来的内容,从 Preview 中层层打开获取到的 json 响应内容,可以看到需要的内容在 translateResult 第二层的 tgt 中: 

模拟执行 

完整代码(url、Referer 均经过脱敏处理,代码不可以直接运行,解密方式在文章开头):

# @Author  :  Y
# !/usr/bin/env python3
# -*- coding: utf-8 -*-
import execjs
import requests

url = 'aHR0cHM6Ly9mYW55aS55b3VkYW8uY29tL3RyYW5zbGF0ZV9vP3NtYXJ0cmVzdWx0PWRpY3Qmc21hcnRyZXN1bHQ9cnVsZQ=='
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'


def get_translation_result(parameters):
    headers = {
        'User-Agent': user_agent,
        'Referer': 'aHR0cHM6Ly9mYW55aS55b3VkYW8uY29tLw==',
        'Cookie': 'OUTFOX_SEARCH_USER_ID="-1848382357@10.169.0.84"; ___rl__test__cookies=1625907853887; OUTFOX_SEARCH_USER_ID_NCOO=132978720.55854891'
    }
    response = requests.post(url=url, headers=headers, data=parameters)
    result = response.json()['translateResult'][0][0]['tgt']
    return result


def get_parameters_by_javascript(query):
    with open('youdao_encrypt.js', 'r', encoding='utf-8') as f:
        youdao_js = f.read()
    params = execjs.compile(youdao_js).call('getParams', query, user_agent)
    parameters = {
        'i': query,
        'from': 'AUTO',
        'to': 'AUTO',
        'smartresult': 'dict',
        'client': 'fanyideskweb',
        'salt': params['salt'],
        'sign': params['sign'],
        'lts': params['lts'],
        'bv': params['bv'],
        'doctype': 'json',
        'version': '2.1',
        'keyfrom': 'fanyi.web',
        'action': 'FY_BY_REALTlME'
    }
    return parameters


def main():
    query = input('请输入要翻译的内容:')
    param = get_parameters_by_javascript(query)
    result = get_translation_result(param)
    print('翻译的结果为:', result)


if __name__ == '__main__':
    main()

总结

        以上是对某道翻译的翻译接口逆向分析,如有见解欢迎评论区指正交流~

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值