Android 比赛

分析流程 #

这个软件呢,其实分析主要就是想要看他拉人获得vip的操作

抓包可以找到一个api

api/user/traveler

这之前token还没有得到,参数

{"deviceId": device, "dev": 随机3字符, "code": '{"p":"PDTVPD"}'}

这个code参数其实开始我也不知道是啥,走了一遍拉人的流程,发现app给我们的邀请链接有p参数,就是邀请码

https://z6mhir31k.xyz?p=PDTVPD

在进到网页中点击下载软件会将邀请码复制到我们的粘贴板上

{"p":"PDTVPD"}

当你进入app时,读取粘贴板的第一条数据

 public static String u() {
        ClipData v0 = FragmentAnim.f.getSystemService("clipboard").getPrimaryClip();
        String v1 = "";
        if(v0 != null && v0.getItemCount() > 0) {
            CharSequence v0_1 = v0.getItemAt(0).getText();
            if(TextUtils.isEmpty(v0_1)) {
                return v1;
            }
            else {
                return v0_1.toString();
            }
        }

        return v1;
    }

deviceId #

顺着上去就发现了deviceId的生成

public final void C() {
        String v0_2;
        String v3_1;
        String v1 = "everb";
        String v2 = "";
        if(a.a.exists()) {
            try {
                ObjectInputStream v0_1 = new ObjectInputStream(new FileInputStream(a.a));
                Object v3 = v0_1.readObject();
                try {
                    v0_1.close();
                    goto label_21;
                }
                catch(Exception v0) {
                }
            }
            catch(Exception v0) {
                v3_1 = v2;
            }

            Log.d(v1, v0.toString());
        }
        else {
            v3_1 = v2;
        }

    label_21:
        if(v3_1 == null) {
            Log.d(v1, "data == null");
            v3_1 = v2;
        }

        if(TextUtils.isEmpty(((CharSequence)v3_1))) {
            v0_2 = String.valueOf(System.currentTimeMillis() + UUID.randomUUID().getLeastSignificantBits());
            try {
                byte[] v0_4 = MessageDigest.getInstance("MD5").digest(v0_2.getBytes());
                StringBuilder v1_1 = new StringBuilder();
                int v5;
                for(v5 = 0; v5 < v0_4.length; ++v5) {
                    String v6 = Integer.toHexString(v0_4[v5] & 0xFF);
                    if(v6.length() < 2) {
                        v1_1.append(0);
                    }

                    v1_1.append(v6);
                }

                v3_1 = v1_1.toString();
            }

他会打开是个名为.CAD的文件,把deviceid写进去,如果像要看一遍生成新device,记得删除这个文件

但是我却没有搞懂这个deviceID的鉴别方法,你自己随便改一两位他都不会返回,看不懂

下面是用python模拟生成的deviceid

import uuid,hashlib,unpack
t = int(time.time()*1000)
uuid_bytes = uuid.uuid1().bytes
leastbit = struct.unpack('>q', uuid_bytes[8:])[0]
print(leastbit)
v4 = hashlib.md5(str(t+leastbit).encode()).digest()
v2 = []
for i in v4:
    v1 = hex(i & 0xFF)[2:]
    if len(v1) < 2:
        v1 = "0"+v1
    v2.append(v1)
device = ''.join(v2)

这个几把最低64位真的搞得烦,没见过

里面其实还有个chcode不知道是啥

    public JSONObject c(String arg5, String arg6) {
        this.a("deviceId", arg5);
        Random v5 = new Random();
        StringBuffer v0 = new StringBuffer();
        int v1;
        for(v1 = 0; v1 < 3; ++v1) {
            v0.append("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".charAt(v5.nextInt(62)));
        }

        this.a("dev", v0.toString());
        if(arg6 != null) {
            this.a("code", arg6);
        }

        try {
            arg5 = FragmentAnim.f.getPackageManager().getApplicationInfo(FragmentAnim.f.getPackageName(), 0x80).metaData.getString("UMENG_CHANNEL");
        }
        catch(PackageManager$NameNotFoundException ) {
            arg5 = "";
        }

        StringBuilder v6 = new StringBuilder();
        v6.append("-------------->");
        v6.append(arg5);
        String v0_1 = "渠道号:";
        Log.e(v0_1, v6.toString());
        if(!TextUtils.isEmpty(((CharSequence)arg5)) && !arg5.equals(FragmentAnim.A())) {
            this.a("chCode", arg5);
            Log.e(v0_1, "chCode-------------->" + arg5);
        }

        return b.b;
    }

在上面那个api中header中的s和t

import time,hashlin

t=str(int(time.time()*1000))
s=hsahlib.md5(t[3:8].encode()).hesdigest()

到最后我发现妈的这软件好像也不加头,我的vip啊,也有可能是我哪里遗漏了些什么

注意ua,没带或者ua有问题都不会返回token,我在网上找的爬虫ua就不行,用模拟器自己生成的ua就行

个人里面那个填写invitecode的地方好像也是摆设

encData #

在后面返回数据里encData是有加密的,aes/cbc/pkcs7,key和iv都是一样的

JhbGciOiJIUzI1Ni

m3u8解密 #

m3u8文件解密,得到的m3u8文件里的key是假的

看了几个关于m3u8的类

会找到getkeyPath这个方法

key是在本地文件kdbc/kb里,把key copy出来

配合N_m3u8DL-CLI可以下载m3u8文件

打印N_m3u8DL-CLI的命令,视频存放在Videos目录

def download():
    id = '1570'
    # 143811
    # 24323
    t = str(int(time.time()*1000))
    header = {'deviceid': 'ac6819db96c1892587d63f353bd9a51c',
              't': t,
              's': hashlib.md5(t[3:8].encode()).hexdigest(),
              'authorization': 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMzMzNjQwNyIsImlzcyI6IiIsImlhdCI6MTY3MTI3NTY5NCwibmJmIjoxNjcxMjc1Njk0LCJleHAiOjE4Mjg5NTU2OTR9.766BQyjE6ZO5ZPC5Y6J0i5BZKmezUk7CXYo-cStNp_w'
              }
    res = requests.get(
        f'https://mhapp.vxxsred.xyz/api/video/getVideoById?videoId={id}', headers=header).json()
    # print(res)
    data = res['encData']
    data = AES_decrypt(data, 'JhbGciOiJIUzI1Ni', 'JhbGciOiJIUzI1Ni')
    print(data)
    data = json.loads(data)
    print(data['videoUrl'])
    print('"https://nwretbns.yongchengge.cn/' +
          data['videoUrl']+r'" --workDir "C:\Users\xx\Videos" --useKeyFile "C:\Users\xx\Desktop\N_m3u8DL-CLI_v3.0.2_with_ffmpeg_and_SimpleG\key\kb"')
    # https://videossy.tsk12.top/lfb/5c/5h/8n/rv/fb43cc643014465380e4c222aa60fb30.m3u8/
    # lfb/5c/5h/8n/rv/fb43cc643014465380e4c222aa60fb30.m3u8
    # "https://nwretbns.yongchengge.cn/lfb/5c/5h/8n/rv/fb43cc643014465380e4c222aa60fb30.m3u8" --workDir "E:\tools\other\N_m3u8DL-CLI_v3.0.2_with_ffmpeg_and_SimpleG" --useKeyFile "C:\Users\jinchuan\Desktop\N_m3u8DL-CLI_v3.0.2_with_ffmpeg_and_SimpleG\key\kb"
    # https://nwretbns.yongchengge.cn/lfb/5c/5h/8n/rv/fb43cc643014465380e4c222aa60fb30.m3u8

全部代码

import hashlib
import time
import requests
from Crypto.Cipher import AES
import base64
import json
import struct
import uuid
import random
import string

def add(text, L=16):
    l = len(text)
    if l < L:
        text = text+'\0'*(L-l)
    elif l > L:
        text = text+'\0'*(L-l % L)
    return text.encode()


def AES_decrypt(cipher, key, iv):
    aes = AES.new(add(key), AES.MODE_CBC, add(iv))
    return aes.decrypt(base64.b64decode(cipher.encode())).decode().replace('\x08', '').replace('\x0f', '')


def download():
    id = '1570'
    # 143811
    # 24323
    t = str(int(time.time()*1000))
    header = {'deviceid': 'ac6819db96c1892587d63f353bd9a51c',
              't': t,
              's': hashlib.md5(t[3:8].encode()).hexdigest(),
              'authorization': 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMzMzNjQwNyIsImlzcyI6IiIsImlhdCI6MTY3MTI3NTY5NCwibmJmIjoxNjcxMjc1Njk0LCJleHAiOjE4Mjg5NTU2OTR9.766BQyjE6ZO5ZPC5Y6J0i5BZKmezUk7CXYo-cStNp_w'
              }
    res = requests.get(
        f'https://mhapp.vxxsred.xyz/api/video/getVideoById?videoId={id}', headers=header).json()
    # print(res)
    data = res['encData']
    data = AES_decrypt(data, 'JhbGciOiJIUzI1Ni', 'JhbGciOiJIUzI1Ni')
    print(data)
    data = json.loads(data)
    print(data['videoUrl'])
    print('"https://nwretbns.yongchengge.cn/' +
          data['videoUrl']+r'" --workDir "C:\Users\jinchuan\Videos" --useKeyFile "C:\Users\jinchuan\Desktop\N_m3u8DL-CLI_v3.0.2_with_ffmpeg_and_SimpleG\key\kb"')
    # https://videossy.tsk12.top/lfb/5c/5h/8n/rv/fb43cc643014465380e4c222aa60fb30.m3u8/
    # lfb/5c/5h/8n/rv/fb43cc643014465380e4c222aa60fb30.m3u8
    # "https://nwretbns.yongchengge.cn/lfb/5c/5h/8n/rv/fb43cc643014465380e4c222aa60fb30.m3u8" --workDir "E:\tools\other\N_m3u8DL-CLI_v3.0.2_with_ffmpeg_and_SimpleG" --useKeyFile "C:\Users\jinchuan\Desktop\N_m3u8DL-CLI_v3.0.2_with_ffmpeg_and_SimpleG\key\kb"
    # https://nwretbns.yongchengge.cn/lfb/5c/5h/8n/rv/fb43cc643014465380e4c222aa60fb30.m3u8


def r(n):
    return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(n))
ua='Mozilla/5.0 (Linux; Android 7.1.2; HD1910 Build/N2G48H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/68.0.3440.70 Mobile Safari/537.36;SuiRui/mh/ver=1.1.5'
def com(url):
    result=requests.get(url,proxies={'https':'127.0.0.1:7890'},headers={'user-agent':ua})
def traveler():
    api = 'https://mhapp.vxerfxs.shop/api/user/traveler'
    t = int(time.time()*1000)
    uuid_bytes = uuid.uuid1().bytes
    leastbit = struct.unpack('>q', uuid_bytes[8:])[0]
    print(leastbit)
    v4 = hashlib.md5(str(t+leastbit).encode()).digest()
    v2 = []
    for i in v4:
        v1 = hex(i & 0xFF)[2:]
        if len(v1) < 2:
            v1 = "0"+v1
        v2.append(v1)
    device = ''.join(v2)
    data = {"deviceId": device, "dev": r(3), "code": '{"p":"PDTVPD"}'}
    print(data)
    t=int(time.time()*1000)
    header = {
        'deviceid': device, 't': str(t), 
        's': hashlib.md5(str(t)[3:8].encode()).hexdigest(),
        'user-agent':ua
        }
    result = requests.post(api, json=data, headers=header,proxies={'https':'127.0.0.1:7890'})
    print(result.status_code, result.text)

com('https://z6mhir31k.xyz?p=PDTVPD')
traveler()
# https://z6mhir31k.xyz?p=LVMC5Y