华为商城秒杀时加密验证 device_data 的算法研究

前言

  • 之前华为商城放出 Mate60 手机时, 想给自己和家人抢购一两台,手动刷了好几天无果后,决定尝试编写程序,直接发送 POST 请求来抢。
  • 通过抓包和简单重放发送后,始终不成功。仔细研究,发现 Cookie 中有一个名为 device_data 的数据比较可疑,看起来是加密后的 base64, 很有可能服务器是使用这些值进行了验证。于是决定研究一下,看是否可以破解并用于秒杀。
  • 最后虽然研究出加密算法,并尝试用于秒杀,但由于仍然有其他的限制,暂时放弃,并将相关信息开源。
  • 华为的兄弟姐妹们需要辛苦更改算法了 😃
    在这里插入图片描述

查找 device_data 的生成位置

通过搜索,定位到名为 cp_20230815/…/ars_event.js 的文件,里面有对 ‘device_data’ 赋值的操作,那么加密算法就在这里了。
补充信息: 最新版本的地址已经是 cp_20231215/…/ars_event.js, 但内容没有改变。
在这里插入图片描述

算法分析

初看 ars_event.js , 是进行过混淆的, 简直和天书一样, 而且以前也没怎么用过 js, 不知道怎么下手。不过好在知道 js 的所有源码都在里面, 只要肯花一点时间, 必然是能解析出来的。

于是开始分析, 此处省略一万字。。。

功夫不负有心人,断断续续经过一两周的时间,总算把算法反推出来,并且编写了 java 代码进行验证和 POST 秒杀。虽然事后证明,服务器还有其他验证方式没有破解(比如 IP、UID 验证?),直接给我返回非法请求。。。)

算法解释

  • device_data 的数据分两部分:
    • 最前面的 *2k 常量 + 从 479752 中选出的两个数(间隔 3)
    • 要加密的字符串先 base64, 按 4 个字符进行编码
      • 然后在前面加上 8 个随机字符
      • 最后再按 8 个字符为一组的方式编码.

源码

  • 因为源码是从 js 中反推出来的, 主要是为了满足和原有的算法一致, 命名和写法上就比较乱.
/**
 * device_data 的加密/解密 算法
 * https://res.vmallres.com/cp_20230815/js/common/risk/ars_event.js
 * https://res.vmallres.com/cp_20231215/js/common/risk/ars_event.js
 */
@Slf4j
public class ArsEventCrack {

  //获取华为 function _0x272042 函数中,对字符串加密时采用的位置索引列表
  public LinkedList<Integer> GetEncodeStringIndexArray(int strLength, int blockSize){
    //最后4字节保持原样
    int charArrayLength = strLength - 4;
    //String[] charArray = strInput.substring(0, strInput.length() - 4).split("");
    LinkedList<LinkedList<Integer>> tmpArray = new LinkedList<>();
    for(int i = 0; i < blockSize; i++){
      tmpArray.add(new LinkedList<>());
      for(int j = 0; j < charArrayLength; j++){
        int _tmpVal_1 = j * 2 * (blockSize - 1) + i;
        int _tmpVal_2 = 0;
        if (_tmpVal_1 < charArrayLength){
          tmpArray.get(i).add(_tmpVal_1);
        }
        if (i != 0) {
          _tmpVal_2 = j * 2 * (blockSize - 1) - i;
          if (_tmpVal_2 < charArrayLength && _tmpVal_2 > 0) {
            tmpArray.get(i).add(_tmpVal_2);
          }
        }
        if(_tmpVal_1 > charArrayLength || _tmpVal_2 > charArrayLength){
          break;
        }
      }
    }
    //log.info("tmpArray={}", tmpArray);
    //排序和删除重复数据
    List<List<Integer>> sortedDistinctLists = tmpArray.stream().map(
            linked -> linked.stream().sorted().distinct().collect(Collectors.toList()))
        .collect(Collectors.toList());

    LinkedList<Integer> allPositions = new LinkedList<>();
    sortedDistinctLists.forEach(integers -> allPositions.addAll(integers));

    return allPositions;
  }

  public String EncodeString(String strInput, int blockSize){
    LinkedList<Integer> allPositions = GetEncodeStringIndexArray(strInput.length(), blockSize);
    StringBuilder sb = new StringBuilder();

    for (Integer pos : allPositions) {
      sb.append(strInput.charAt(pos));
    }
    sb.append(strInput.substring(strInput.length() - 4));

    String result = sb.toString();
    //log.info("result={}", result);
    return result;
  }

  public String repeatString(String str, int times){
    if (times <= 1){
      return str;
    }
    StringBuilder sb = new StringBuilder();
    for (int i = 0 ; i < times; i++){
      sb.append(str);
    }
    return sb.toString();
  }

  public String DecodeString(String strInput, int blockSize){
    //除了最后4个字节的,生成指定长度, 然后获取随机字符串位置索引, 并对应替换.
    int encodeLength = strInput.length() - 4;
    char[] chars = repeatString("0", encodeLength).toCharArray();
    LinkedList<Integer> allPositions = GetEncodeStringIndexArray(strInput.length(), blockSize);

    int index = 0;
    for (Integer pos : allPositions) {
      chars[pos] = strInput.charAt(index);
      index++;
    }
    String strResult = String.copyValueOf(chars) + strInput.substring(encodeLength);
    return strResult;
  }

  //再次调用就会恢复
  public String BlockString(String strInput, int blockSize){
    String strResult = "";
    String strWithoutLast4 = strInput.substring(0, strInput.length()-4);
    int blockCount = strWithoutLast4.length() / blockSize;
    for (int i = blockSize; i > 0; i--){
        String tmp = strInput.substring((i - 1)*blockCount, i*blockCount);
        strResult += tmp;
    }
    strResult += strInput.substring(strInput.length()-4);
    return strResult;
  }


  public String DecodeDeviceDataString(String strEncodedDeviceData){
    //去除前面的 *2k47 一类的随机开头
    String remove2KHeader = strEncodedDeviceData.substring(5);

    //第一次解码
    String outerDecode = DecodeString(remove2KHeader, 8);

    //解码出来, 前面8个字节是随机值
    String firstUnBlock = BlockString(outerDecode, 4);
    String realFirstBlock = firstUnBlock.substring(8);
    //再次解码,此时解出来的就是 base64
    String innerDecode = DecodeString(realFirstBlock, 4);

    String strOriginal = new String(Base64.getDecoder().decode(innerDecode));

    return strOriginal;
  }
  public String Get2kHeader(){
    //*2k92
    String strInput = "479752";
    int randomIndex = new Random(System.currentTimeMillis()).nextInt(3);
    return "*2k" + strInput.charAt(randomIndex) + strInput.charAt(randomIndex + 3);
  }

  public String EncodeDeviceDataString(String strEncodedDeviceData){
    String strBase64 = Base64.getEncoder().encodeToString(strEncodedDeviceData.getBytes());
    String innerEncode = RandomStringUtils.randomAlphanumeric(8) + EncodeString(strBase64, 4);
    String strBlocked = BlockString(innerEncode, 4);
    String strEncodeDeviceData = Get2kHeader() + EncodeString(strBlocked, 8);
    return strEncodeDeviceData;
  }

  @Test
  public void testAstEventCrack(){
    log.info("Get2kHeader={}", Get2kHeader());

    String strOriginal = "ABCDEFGHIJKLMNOPQRSTWVXYZabcdefghijklmnopqrstuvwxyz123456789";
    int blockSize = 8;

    String strEncoded = EncodeString(strOriginal, blockSize);
    String strDecoded = DecodeString(strEncoded, blockSize);
    log.info("strEncoded={}, strDecoded={}", strEncoded, strDecoded);

    Assert.assertEquals(strOriginal, strDecoded);

    String strBlocked = BlockString(strOriginal, blockSize);
    log.info("strBlocked={}", strBlocked);
    String strUnblocked = BlockString(strBlocked, blockSize);
    log.info("strUnblocked={}", strUnblocked);
    Assert.assertEquals(strOriginal, strUnblocked);
  }

  @Test
  public void TestDeviceData(){
    if(true){
      //只解密
      String strEncodedDeviceData = "*2k75xxxxxx";  // 此处输入通过 F12 或抓包获取的 device_data 字符串,运行后即可解密
      String strDeviceData = DecodeDeviceDataString(strEncodedDeviceData);
      log.info("strDeviceData: {}", strDeviceData);
    }

    if(false){
      //加密后再解密
      String strOriginalData = "";  //输入想要加密的字符串
      //String strOriginalData = GetDeviceFingerPrint() + "_" + "[object Object]";
      String strEncode = EncodeDeviceDataString(strOriginalData);
      String strDecode = DecodeDeviceDataString(strEncode);
      log.info("strDecode:{}", strDecode);
      Assert.assertEquals(strOriginalData, strDecode);
    }
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/282911.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HTML5+CSS3③——无语义布局标签、画盒子、CSS定义、CSS引入方式

目录 无语义布局标签 画盒子 CSS定义 小结 CSS引入方式 小结 无语义布局标签 画盒子 CSS定义 小结 CSS引入方式 小结

Vue2 - Vue.observable 介绍

目录 1&#xff0c;介绍2&#xff0c;使用场景和 Vue 实例的区别 1&#xff0c;介绍 官网参考 可以让一个对象变成响应式数据。在 Vue 内部就是用它来处理传递给 Vue 的 data 对象&#xff0c;或是在单文件组件中 data() 返回的对象。 var vm new Vue({data: {count: 0} })…

MAC 中多显示器的设置(Parallels Desktop)

目录 一、硬件列表&#xff1a; 二、线路连接&#xff1a; 三、软件设置&#xff1a; 1. 设置显示器排列位置及显示参数 2. 分别设置外接显示器为&#xff1a;扩展显示器&#xff0c;内建显示器为主显示器 3. 设置Parallels Desktop屏幕参数 四、结果 一、硬件列表&a…

Spark SQL简介与基本用法

Apache Spark是一个强大的分布式计算框架&#xff0c;Spark SQL是其组件之一&#xff0c;用于处理结构化数据。Spark SQL可以使用SQL查询语言来查询和分析数据&#xff0c;同时还提供了与Spark核心API的无缝集成。本文将深入探讨Spark SQL的基本概念和用法&#xff0c;包括数据…

MongoDB的基本使用

MongoDB的引出 使用Redis技术可以有效的提高数据访问速度&#xff0c;但是由于Redis的数据格式单一性&#xff0c;无法操作结构化数据&#xff0c;当操作对象型的数据时&#xff0c;Redis就显得捉襟见肘。在保障访问速度的情况下&#xff0c;如果想操作结构化数据&#xff0c;…

STM32F407-14.3.10-表73具有有断路功能的互补通道OCx和OCxN的输出控制位-00x10

如上表所示&#xff0c;MOE0&#xff0c;OSSI0&#xff0c;CCxE1&#xff0c;CCxNE0时&#xff0c;OCx与OCxN的输出状态取决于GPIO端口上下拉状态。 ---------------------------------------------------------------------------------------------------------------------…

学生管理系统(vue + springboot)

学生管理系统&#xff08;vuespringboot&#xff09;资源-CSDN文库 项目介绍 这是一个采用前后端分离开发的项目&#xff0c;前端采用 Vue 开发、后端采用 Spring boot Mybatis 开发。 项目部署 ⭐️如果你有 docker 的话&#xff0c;直接 docker compose up 即可启动&#…

PyTorch常用工具(1)数据处理

文章目录 前言1 数据处理1.1 Dataset1.2 DataLoader 前言 在训练神经网络的过程中需要用到很多的工具&#xff0c;最重要的是数据处理、可视化和GPU加速。本章主要介绍PyTorch在这些方面常用的工具模块&#xff0c;合理使用这些工具可以极大地提高编程效率。 由于内容较多&am…

五、Spring AOP面向切面编程

本章概要 场景设定和问题复现解决技术代理模式面向切面编程思维&#xff08;AOP&#xff09;Spring AOP框架介绍和关系梳理 5.1 场景设定和问题复现 准备AOP项目 项目名&#xff1a;spring-aop-annotation pom.xml <dependencies><!--spring context依赖--><…

关于LayUI表格重载数据问题

目的 搜索框搜索内容重载数据只显示搜索到的结果 遇到的问题 在layui官方文档里介绍的table属性有data项,但使用下列代码 table.reload(test, {data:data //data为json数据}); 时发现&#xff0c;会会重新调用table.render的url拿到原来的数据&#xff0c;并不会显示出来传…

C语言实验4:指针

目录 一、实验要求 二、实验原理 1. 指针的基本概念 1.1 指针的定义 1.2 取地址运算符&#xff08;&&#xff09; 1.3 间接引用运算符&#xff08;*&#xff09; 2. 指针的基本操作 2.1 指针的赋值 2.2 空指针 3. 指针和数组 3.1 数组和指针的关系 3.2 指针和数…

Linux系统使用yum安装MySQL

部署MySQL数据库有多种部署方式&#xff0c;常用的部署方式就有三种&#xff1a;yum安装、rpm安装以及编译安装。每一种安装方式都有自己的优势&#xff0c;那么企业当中通常情况下采用的是rpm和二进制安装的方式。 MySQL官网下载地址 Mysql 5.7的主要特性 更好的性能&#xf…

C++实现定积分运算

文章目录 题目代码 题目 代码 #include <iostream> #include <cmath> #include <functional>using namespace std;// 定积分函数 double integrate(function<double(double)> func, double a, double b, int num_intervals) {double h (b - a) / num…

【c++————————构造函数和析构函数】

【c————————构造函数和析构函数】 欢迎阅读新一期的c模块————构造函数和析构函数 ✒️个人主页&#xff1a;-Joker- &#x1f3f7;️专栏&#xff1a;C &#x1f4dc;代码仓库&#xff1a;c_code &#x1f339;&#x1f339;欢迎大佬们的阅读和三连关注&#xff0c…

idea 出现Cannot resolve symbol ‘springframework‘解决方法

Maven手动重新加载 1&#xff09;File–>Invalidate Caches / Restart… 清理缓存&#xff0c;重启idea客户端 2&#xff09;File–>Maven–>Reload project重新从maven中加载工程依赖的组件

医院安全(不良)事件报告系统源码 支持二次开发、支持源码交付

医疗不良事件报告系统源码旨在建立全面的、统一的医疗不良事件标准分类系统和患者安全术语&#xff0c;使不良事件上报管理更加标准化和科学化。通过借鉴国内外医疗不良事件报告系统的先进经验&#xff0c;根据医疗不良事件的事件类型、处理事件的不同部门&#xff0c;灵活设置…

【FileZilla的安装与使用(主动与被动模式详解,以及如何利用FileZilla搭建FTP服务器并且进行访问)】

目录 一、FileZilla介绍 1.1 简介 1.2 重要信息和功能 二、FileZilla的安装与使用 2.1 FileZilla服务端安装与配置 2.1.1 安装步骤 2.1.2 新建组 2.1.3 新建用户 2.1.4 新建目录 2.1.5 权限分配 &#xff08;1&#xff09;用户Milk权限分配 &#xff08;2&#xff…

MCS接口技术----定时/计数,中断

目录 一.中断系统相关寄存器 1.51单片机中断系统的总体结构&#xff1a; 2.中断源的中断级别&#xff08;由高到低&#xff09;&#xff1a; 3.与中断有关的四个寄存器&#xff1a; &#xff08;1&#xff09;TCON---定时控制寄存器 &#xff08;2&#xff09;IE---中断允…

【算法】哈希算法和哈希表

一、哈希算法 哈希算法是一种将任意长度的数据&#xff08;也称为“消息”&#xff09;转换为固定长度字符串&#xff08;也称为“哈希值”或简称“哈希”&#xff09;的数学函数或算法。这个固定长度的字符串是由输入数据通过一系列的运算得到的&#xff0c;并且具有一些重要…

docker里面不能使用vim的解决办法

docker里面不能使用vim的解决办法 目录 docker里面不能使用vim的解决办法 1.在使用时会出现 2.在使用这些都不能解决的时候考虑 3.测试是否可用 1.在使用时会出现 bash: vim: command not found 出现这种错误时首先考虑使用 apt-get update 然后在用 apt-get install …
最新文章