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

news2025/1/21 22:07:32

前言

  • 之前华为商城放出 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.coloradmin.cn/o/1344370.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

提升效率:使用注解实现精简而高效的Spring开发

IOC/DI注解开发 1.0 环境准备1.1 注解开发定义bean步骤1:删除原XML配置步骤2:Dao上添加注解步骤3:配置Spring的注解包扫描步骤4&#xff1a;运行程序步骤5:Service上添加注解步骤6:运行程序知识点1:Component等 1.2 纯注解开发模式1.2.1 思路分析1.2.2 实现步骤步骤1:创建配置类…

基于ssm的教师上课系统+vue论文

目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容 2 2 系统开发环境 3 2.1 vue技术 3 2.2 JAVA技术 3 2.3 MYSQL数据库 3 2.4 B/S结构 4 2.5 SSM框架技术 4 3 系统分析 5 3.1 可行性分析 5 3.1.1 技术可行性 5 3.1.2 操作可行性 5 3…

117基于matlab的短时傅里叶变换(STFT)、小波变换(WT)、同步压缩变换(SST)、瞬态提取变换(TET)进行时频分析

基于matlab的短时傅里叶变换&#xff08;STFT&#xff09;、小波变换&#xff08;WT&#xff09;、同步压缩变换&#xff08;SST&#xff09;、瞬态提取变换&#xff08;TET&#xff09;进行时频分析。程序已调通&#xff0c;可直接运行。 117时频分析短时傅里叶变换 (xiaohong…

eve环境虚拟机和电脑如何传送文件

一.桥接 &#xff08;实现电脑和虚拟机在同一网段&#xff09; 虚拟机上网盘设置 二.属性---文件共享设置 1打开属性&#xff0c;点击共享 2.添加共享人为全部人&#xff0c;并修改权限为读写模式 3.点击高级共享&#xff0c;选定此文件夹 4.点击网络和共享中心&#xff0c;划…

c语言:把二维数组降至一维|练习题

一、题目 把二维数组降为一围数组 如图&#xff1a; 二、代码截图【带注释】 三、源代码【带注释】 #include <stdio.h> int main() { int arr2[3][3];//设置二维数组 int arr1[10];//设置一维数组 int z0;//一维数组自增量 printf("输入一个二维数…

【源码】-MyBatis-如何系统地看源码

写在前面 前段时间做过一个项目&#xff0c;期间用到了动态数据源dynamic-datasource&#xff0c;经历了dbcp2的数据库连接池没有生效到排查定位、MyBatis多种数据库产品兼容、手写MyBatis拦截器等事情。 花费了好久&#xff0c;一直在打磨这篇文章&#xff08;不知道花费这么长…

DevEco Studio4.0 Beta2集成ArkUI-X(开发鸿蒙,安卓.ios应用)/ACE Tools脚手架

ArkUI-X简介 ArkUI-X进一步将ArkUI扩展到了多个OS平台&#xff1a;目前支持OpenHarmony、HarmonyOS、Android、 iOS&#xff0c;后续会逐步增加更多平台支持。开发者基于一套主代码&#xff0c;就可以构建支持多平台的精美、高性能应用 该框架对应的IDE版本为 4.0 Beta2 &…

代码随想录刷题 | Day1

今日学习目标 一、基础 数组 array类 模板类vector 数组是存放在连续内存空间上的相同类型数据的集合。 数组可以方便的通过下标索引的方式获取到下标下对应的数据。 需要两点注意的是 数组下标都是从0开始的。 数组内存空间的地址是连续的 而且大家如果使用C的话&…

Leetcode的AC指南 —— 哈希法/双指针:15. 三数之和

摘要&#xff1a; Leetcode的AC指南 —— 15. 三数之和。题目介绍&#xff1a;给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且…

腱鞘囊肿,不就是个水泡嘛?不!可别小瞧了它

腱鞘囊肿&#xff0c;很多人并不陌生&#xff0c;因为它的发病率比较高&#xff0c;不少人都有过腱鞘囊肿的经历。 有的人觉得不美观&#xff0c;有的人害怕癌变&#xff0c;有的人担心影响功能能&#xff0c;有的人经医生用力挤破好转&#xff0c;有的人经穿刺抽液治愈&#x…

使用element中el-cascader级联选择器动态懒加载以及回显 (单选)

<template><!-- 新增||修改弹框 --><el-dialog :close-on-click-modal"false" :close-on-press-escape"false" :title"title" :visible.sync"open"width"800px" append-to-body><el-form ref"for…

Unity | 快速修复Animation missing错误

目录 一、背景 二、效果 三、解决办法 一、背景 最近在做2D 骨骼动画相关的Demo&#xff0c;我自己使用Unity引擎进行骨骼绑定并创建了anim后&#xff0c;一切正常&#xff0c;anim也能播放。但是昨天我修改Obj及子物体的名称&#xff08;由中文改为英文&#xff0c;如&…

RabbitMQ 核心概念(交换机、队列、路由键),队列类型等介绍

RabbitMQ 核心概念(交换机、队列、路由键)&#xff0c;队列类型等介绍 RabbitMQ 是一个消息队列系统&#xff0c;它的核心概念包括交换机&#xff08;Exchange&#xff09;、队列&#xff08;Queue&#xff09;和路由键&#xff08;Routing Key&#xff09;&#xff0c;它们一起…

OpenStack云计算(-) 简介与部署Keystone

一.OpenStack简介 什么是云计算:云计算是一种按使用量付费的模式,这种模式提供可用的、便捷的、按需的网络访问,进入可配置的计算资源共享池(资源包括网络,服务器,存储,应用软件,服务) 云计算所包含的几个层次服务&#xff1a; SaaS ( Software as a Service ) :把在线软件作…

LLM提示词工程学习_Day01

LLM提示词工程学习_Day01 安装学习环境基础Conda环境安装安装Python安装所需的包Jupyter Notebook 安装获取OpenAI API KEY&#xff0c;并写入工程目录里的.env文件进入Jupyter&#xff0c;先跑一段代码 安装学习环境 基础Conda环境安装 conda环境安装&#xff0c;miniconda也…

omlox定位标准(二)——定位核心

上一篇文章中介绍了关于omlox hub相关内容&#xff0c;可以用于整合多种API接口&#xff0c;便于实现统一的应用&#xff0c;本文中介绍omlox core&#xff0c;介绍了基础设施、定位技术、定位引擎等内容。 2.omlox core zone and air-interface 随着越来越多的业务应用基于室…

基于ssm的航空票务推荐系统的设计与实现论文

摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;航班信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行处理不能满足广大…

[C++] : 贪心算法专题(第一部分)

1.柠檬水找零&#xff1a; 1.思路一&#xff1a; 柠檬水找零 class Solution { public:bool lemonadeChange(vector<int>& bills) {int file0;int ten 0;for(auto num:bills){if(num 5) file;else if(num 10){if(file > 0)file--,ten;elsereturn false;}else{i…

读算法霸权笔记07_筛选

1. 美国残疾人法案 1.1. 1990年 1.2. 公司在招聘时使用身体检查是非法的 1.3. 有某些心理健康问题的人被“亮了红灯”&#xff0c;他们因此没能找到一份正常的工作&#xff0c;过上正常的生活&#xff0c;这就使其进一步被社会孤立&#xff0c;而这正是《美国残疾人法案》要…

万界星空低代码云MES-才是工业MES的未来

万界星空科技作为一家在云MES系统的研发、生产自动化方面拥有很多年行业经验的科技型企业&#xff0c;多年来专注于云MES系统的研发与技术支持服务&#xff0c;目前已成为国内知名的智能制造整体解决方案提供商。 近几年&#xff0c;万界星空科技发掘制造行业生产及物流难点、…