大麦APP抢票

本文章已经生成可运行项目,

大麦APP抢票技术大揭秘!手把手教你破解核心防护

郑重提醒:本文仅供技术研究,切勿用于非法用途!
在这里插入图片描述

为啥要研究这个?

每次周杰伦、五月天演唱会门票秒没,是不是很抓狂?黄牛用高科技抢票和票源,普通用户只能干瞪眼。今天瞅一瞅大麦APP的防护机制,看看自动抢票工具是怎么运作的。

先看看流程图

在这里插入图片描述

网络请求抓包分析

关键接口长啥样?

用抓包工具Charles查看大麦APP的请求,发现核心接口是这样的:

POST /api/trade/order/build HTTP/1.1
Host: mtop.damai.cn
X-Sign: 7a8f9e0d1c2b3a4f5e6d7c8b9a0f1e2d  # 动态签名
X-T: 1689321600000  # 当前时间戳
X-App-Key: 12574478  # APP固定标识
X-DEVICE-ID: d46f5d7b8a9c0e1f  # 设备指纹
X-UMID: T84f5d7b8a9c0e1f3e2d1c0b9a8f7e6d  # 用户行为ID

data=%7B%22itemId%22%3A%226234567%22%2C%22skuId%22%3A%224567890%22%7D  # 购票数据

重点看这些

  • X-Sign:每次请求都变的动态签名
  • X-DEVICE-ID:手机设备唯一指纹
  • X-UMID:记录你的操作习惯
  • data:包含门票ID和数量的加密数据

签名有啥特点?

  • 同一操作不同时间,签名完全不同
  • 改任意参数签名就失效
  • 固定64位长度(像SHA256加密)

逆向破解核心防护

关键文件在哪?

解包APP后找到这些核心文件:

libmtguard.so - 签名核心
libsgmain.so - 阿里安全组件
libvmp.so - 虚拟机保护

签名算法怎么工作的?

逆向分析发现签名分5步:

def 生成签名(参数, 时间戳, 设备ID, APP密钥):
    # 1. 参数按字母排序
    排序后参数 = '&'.join(f'{k}={参数[k]}' for k in sorted(参数))
    
    # 2. 拼接基础字符串
    基础串 = f"{APP密钥}&{时间戳}&{设备ID}&{排序后参数}"
    
    # 3. 获取动态密钥(每小时变一次)
    动态密钥 = 获取动态密钥(时间戳)
    
    # 4. HMAC-SHA256加密
    h = hmac.new(动态密钥, 基础串.encode(), hashlib.sha256)
    加密结果 = h.digest()
    
    # 5. 二次混淆(异或0x5A)
    for i in range(len(加密结果)):
        加密结果[i] ^= 0x5A
    
    return 加密结果.hex().upper()

动态调试实战

用Frida偷看签名过程

Java.perform(() => {
  // 拦截Java层
  const 安全类 = Java.use('com.damai.security.NativeSecurityGuard');
  安全类.getSign.implementation = function(p1, p2, p3) {
    send(`[拦截] 输入参数: ${p1}, ${p2}, ${p3}`);
    const 结果 = this.getSign(p1, p2, p3);
    send(`[拦截] 签名结果: ${结果}`);
    return 结果;
  };
  
  // 拦截Native层
  const 函数地址 = Module.getExportByName('libmtguard.so', 
    'Java_com_damai_security_NativeSecurityGuard_getSign');
  
  Interceptor.attach(函数地址, {
    onEnter(args) {
      this.参数1 = Java.vm.getEnv().getStringUtfChars(args[2], null).readCString();
      send(`[底层拦截] 输入: ${this.参数1}`);
    },
    onLeave(retval) {
      const 签名 = Java.vm.getEnv().getStringUtfChars(retval, null).readCString();
      send(`[底层拦截] 输出: ${签名}`);
    }
  });
});

发现了啥?

  • 参数格式:时间戳|设备ID|JSON数据
  • 密钥每小时变一次
  • 检测到调试会返回假签名

自动抢票系统实现

核心代码长这样

class 大麦抢票器:
    def __init__(self, 账号, 目标活动):
        self.会话 = requests.Session()
        self.账号 = 账号
        self.目标活动 = 目标活动
        self.设备ID = self.生成设备ID()
        self.APP密钥 = "12574478"
        
    def 生成设备ID(self):
        """伪造设备指纹"""
        imei = f"86{random.randint(1000000000, 9999999999)}"
        android_id = binascii.hexlify(os.urandom(8)).decode()
        mac = ":".join([f"{random.randint(0x00, 0xff):02x}" for _ in range(6)])
        return hashlib.md5(f"{imei}|{android_id}|{mac}".encode()).hexdigest().upper()
    
    def 提交订单(self):
        """疯狂提交直到成功"""
        参数 = {
            "itemId": self.目标活动["门票ID"],
            "skuId": self.目标活动["场次ID"],
            "buyNum": self.目标活动["购买数量"]
        }
        
        失败次数 = 0
        
        while True:
            # 每5次失败换IP
            if 失败次数 % 5 == 0:
                self.更换代理IP()
            
            时间戳 = str(int(time.time() * 1000))
            签名 = self.生成签名(参数, 时间戳)
            
            请求头 = {
                "X-Sign": 签名,
                "X-T": 时间戳,
                "X-DEVICE-ID": self.设备ID,
                "X-App-Key": self.APP密钥
            }
            
            try:
                响应 = self.会话.post(
                    "https://mtop.damai.cn/api/trade/order/build",
                    json=参数,
                    headers=请求头,
                    timeout=2.0
                )
                
                if "成功" in 响应.text:
                    return True  # 抢到啦!
                else:
                    失败次数 += 1
                    
            except:
                失败次数 += 1
            
            # 失败越多等越久
            等待时间 = min(0.1 * (2 ** 失败次数), 5.0)
            time.sleep(等待时间 + random.uniform(0, 0.3))

分布式集群抢票

from celery import Celery
抢票任务 = Celery('抢票集群', broker='redis://localhost:6379/0')

@抢票任务.task
def 执行抢票(账号信息, 活动信息):
    """多机器同时开抢"""
    抢票器 = 大麦抢票器(账号信息, 活动信息)
    抢票器.登录()
    
    while True:
        库存 = 抢票器.检查库存()
        
        if 库存 > 0:
            结果 = 抢票器.提交订单()
            if 结果: 
                return "抢票成功!"
        
        # 动态调整等待时间
        if 库存 > 10:
            等待 = random.uniform(1.0, 3.0)
        elif 库存 > 0:
            等待 = random.uniform(0.1, 0.5)
        else:
            等待 = random.uniform(3.0, 5.0)
            
        time.sleep(等待)

反检测关键技术

1. 假装真人操作

class 行为模拟器:
    def 模拟点击(self, 元素):
        """生成人类鼠标轨迹"""
        起点 = (random.randint(0, 100), random.randint(0, 100))
        终点 = 元素.位置
        轨迹 = self.生成贝塞尔曲线(起点, 终点)
        
        forin 轨迹:
            self.移动鼠标()
            time.sleep(random.uniform(0.01, 0.05))
        
        time.sleep(random.uniform(0.1, 0.3))  # 点击前犹豫
        self.点击(元素)

2. 设备指纹管理

class 设备管理器:
    def __init__(self):
        self.设备池 = [
            {
                "型号": "小米12",
                "安卓版本": "Android 12",
                "分辨率": "1080x2400",
                "设备ID": "DM1234567890ABCD"
            },
            {
                "型号": "华为P50",
                "安卓版本": "Android 11",
                "分辨率": "1228x2700",
                "设备ID": "DMABCDEF12345678"
            }
        ]
    
    def 切换设备(self):
        """随机换个设备身份"""
        self.当前设备 = random.choice(self.设备池)
        return self.当前设备
    
    def 生成UA(self):
        """伪造浏览器标识"""
        设备 = self.当前设备
        return (f"Dalvik/2.1.0 (Linux; U; Android {设备['安卓版本']}; "
                f"{设备['型号']} Build/SP1A.210812.016) "
                "com.damai/7.3.1")

3. IP代理池

class 代理管理器:
    def __init__(self):
        self.代理池 = [
            "http://user:pass@192.168.1.1:8080",
            "socks5://user:pass@192.168.1.2:1080"
        ]
        self.当前代理 = None
        
    def 切换代理(self):
        """随机换个IP地址"""
        self.当前代理 = random.choice(self.代理池)
        return self.当前代理

对抗高级防护

绕过反调试检测

// 让调试器隐身
Interceptor.replace(Module.findExportByName(null, "ptrace"), 
    new NativeCallback(() => 0, 'int', []));

// 屏蔽Frida检测
const fopen = Module.findExportByName(null, "fopen");
Interceptor.replace(fopen, new NativeCallback((路径) => {
    if (路径.includes("frida")) return 0;
    return fopen(路径);
}, 'pointer', ['pointer']));

破解虚拟机保护

大麦使用阿里自研的虚拟机保护技术(VMP),关键函数被编译为自定义字节码:

破解步骤

  1. 定位虚拟机入口

    void vmp_main(vmp_context *ctx, const uint8_t *bytecode, size_t len) {
        // 虚拟机主循环
        while (ctx->pc < len) {
            uint8_t opcode = bytecode[ctx->pc++];
            switch (opcode) {
                case 0xA1: // 加载密钥
                    vmp_load_key(ctx);
                    break;
                case 0xC3: // 哈希运算
                    vmp_hash(ctx);
                    break;
                // ...
            }
        }
    }
    
  2. 逆向指令集

    操作码指令功能描述
    0xA1LOAD_KEY加载动态密钥
    0xB2LOAD_DATA加载待签名数据
    0xC3HASHSHA256哈希运算
    0xD4XOR_OBF异或混淆(0x5A)
    0xE5ROTATE循环移位操作
  3. 字节码反编译

    def disassemble_vmp(bytecode):
        pc = 0
        instructions = []
        
        while pc < len(bytecode):
            opcode = bytecode[pc]
            pc += 1
            
            if opcode == 0xA1:  # LOAD_KEY
                key_len = bytecode[pc]
                pc += 1
                key = bytecode[pc:pc+key_len]
                pc += key_len
                instructions.append(f"LOAD_KEY {key.hex()}")
                
            elif opcode == 0xB2:  # LOAD_DATA
                data_len = int.from_bytes(bytecode[pc:pc+2], 'big')
                pc += 2
                data = bytecode[pc:pc+data_len]
                pc += data_len
                instructions.append(f"LOAD_DATA {data.hex()}")
                
            # 其他指令处理...
            
        return instructions
    
  4. 算法重建

    def vmp_sign(bytecode, params):
        # 模拟虚拟机执行
        ctx = VMPContext()
        ctx.set_data(json.dumps(params).encode())
        
        pc = 0
        while pc < len(bytecode):
            opcode = bytecode[pc]
            pc += 1
            
            if opcode == 0xA1:  # LOAD_KEY
                key_len = bytecode[pc]
                pc += 1
                key = bytecode[pc:pc+key_len]
                pc += key_len
                ctx.load_key(key)
                
            elif opcode == 0xC3:  # HASH
                ctx.hash()
                
            elif opcode == 0xD4:  # XOR_OBF
                ctx.xor_obf(0x5A)
                
        return ctx.get_result()
    
// 虚拟机指令示例
0xA1 // 加载密钥
0xC3 // 执行SHA256
0xD4 // 异或混淆
def 破解虚拟机(字节码, 参数):
    # 创建虚拟执行环境
    上下文 = 虚拟机上下文()
    上下文.设置数据(参数)
    
    # 模拟执行字节码
    for 指令 in 字节码:
        if 指令 == 0xA1: 
            上下文.加载密钥()
        elif 指令 == 0xC3:
            上下文.执行哈希()
        elif 指令 == 0xD4:
            上下文.执行混淆()
    
    return 上下文.获取结果()

最后强调:本文已做脱敏处理,仅供学习交流!

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AALoveTouch

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值