flying_bat 发表于 2024-9-2 11:15

某安卓助手脚本的下载脚本

本帖最后由 flying_bat 于 2024-9-2 11:25 编辑

两三年前搞的,已经通知过此公司。
主要分两部分,脚本列表的下载在java部分(有混淆,应该是使用AS的默认混淆),脚本的DES解密在so部分(无加密,未去除函数名)。
以下是java部分的功能(域名和IP修改过),前期通过`wireshark`进行初步的分析,再通过`frida`插桩`okhttp`的下载函数和`java`的字符串和RSA加密函数来获取对应的KEY。
```python
#! /usr/bin/env python3
from urllib.request import quote, unquote
from hashlib import md5
from json import tool
from urllib.parse import urlencode
import requests
import json
import random
import base64
import uuid
import sys
import urllib
import time
from Crypto.Cipher import DES
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import MD5
from Crypto.Hash import SHA1
import zipfile
import os
G_PROXIES = {
    'http': 'http://192.168.0.100:8080',
    'https': 'http://192.168.0.100:8080'
}
G_HEADERS = {
    'Accept-Encoding': 'gzip',
    'User-Agent': 'okhttp/3.12.0',
}
G_DEVICE = {
    'IMEI': '357537081683237',
    'MODEL': 'Pixel 2',
    'SDK_INT': 28
}
SIGN8 = [
    "239bed96-f728-4f2f-b718-8f38dd592951",
    "edd87ac6-1620-4faf-8184-e28a42af6456",
    "fef4b024-9f0f-4373-b15d-e29f7dea7907",
    "0a53aef6-e4bf-41fb-85af-633ed959b0eb",
    "fe8b35ef-7ecf-4d2d-9dc6-bcf6de01553e",
    "88e18ba5-9eca-4480-b24e-fd241a32fad3",
    "8ed432e5-acdf-47ea-9668-c38f216dcdfd",
    "f7b9b929-b74d-4833-bb42-4bc14a7999c9",
]
RSA_PRIVATE_KEY = '''-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAPHzUSeup3iG1NdO
adtST0JGkXqg4xiVktOIZx+oqip6dzSxZN1DsxVcnFZ0sBl3uyySb5n6/l1Zhsmd
Y0hiobR45j3ZpoJSGSMHK6xIE3ETRjehKj/RypvH/8cIFqNzrKeAD7HW1COFoZhA
fsnSi4CTtXw75sUnVVoItHA3e0q5AgMBAAECgYAfK4SKAzMqENy8vCphCfKSDDtT
vARlt2dJ/ymh5328qThQPoZLBxy8JMkzTicdsNIZROhBwxcf+Zemw7L1sVc5g7JX
mVdibgjxmtsBKo8jORwcJaijZ+bzV10eNoRAzIhAavjSalnHZFq1kPvXIZfGNCfr
GRu77q6h5jWHUucDjQJBAP32f54XYZPEa/6t5k3NU2wDgwg5FnAg4r/Wr9JnV5gl
m29ZWvcesCCFJ6/MZ6+VAz+upqOyjY4SK31CZx8ddPsCQQDz5CbJ+ir6sEuU62nQ
qg90pQ8VDWN1yQl2wevU2xH7Nh84qwST1v5aGfxx+CX/N6AMQNSnXprL2dwEDKBD
MyjbAkEA9nviupZozxkp08IGL2bj24bBCx8VJvjT+msstGI2guWlKXopLoVrXfS+
YwJZc/GFeK5S77ghaU07RlRHb8yQNwJAJ1EfY64doZ4sAQzWWRohNbeqL8WhxAot
qWRnpT/PrUSzUcpYCZE+Hb5UsPbFAVixR2hoH7cjkztCubBjYZHswwJBALXMo/tw
msX12qru9HFMiIrnFtLkjTlghhkqfoW0A2NP0DsW/uWMD8fIpGVhBM2WpKaSSc+2
48OImdoJj4kVGb8=
-----END PRIVATE KEY-----
'''
RSA_PUBLIC_KEY = '''-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEHan4MLT48S5bredVgr8WOnk6
y4bN3nUvegA/aH/qKYYFTDT3QV8Af1TqlePSnH+RkBrAmfWOTWMHsCZ6rsfEHQVU
y7KeEa7i9RBUVdbBD+fa+5Ybi3cIYRYjfY+oO3pbTLu2RTukrGKjknTbZYORTTg4
4NaiNlIRn7rA7yvUBwIDAQAB
-----END PUBLIC KEY-----
'''
G_IP = ["103.198.223.195", "103.198.223.199"]
BaseRequest = {
    'a': 'Android',
    'aa': G_DEVICE['SDK_INT'],#Build.VERSION.SDK_INT;
    'ab': '1231233123',
    'ad': 'default',
    'b': G_DEVICE['IMEI'], # IMEI
    'bc': '1000', #Channel
    'd': 2,
    'de': 0, #System.currentTimeMillis();
    'isVa': 1, #不同的URL不一样 MODEL_KEY
    'pg': 'com.angel.helper',
    'pv': '福建福州',
    'vc': 136,
    'vs': '1.3.6'
}
def sort_by_key(d:dict):
    new_d = {}
    keys = sorted(d.keys())
    for k in keys:
      new_d = str(d)
    return new_d
def sign(values:str, idx:int) -> str:
    ct = values+SIGN8
    return md5(ct.encode('utf-8')).hexdigest()
def sort_and_sign(query:dict) -> dict:
    query['de'] = int(time.time()*1000)
    sorted_query = sort_by_key(BaseRequest | query)
    r = random.randint(0, 7)
    k = random.randint(0, 7)
    values = ''.join(sorted_query.values())
    sorted_query['Sign'] = sign(values, r)
    sorted_query['R'] = r
    sorted_query['K'] = k
    return sorted_query
def pkcs7(data:bytes, block:int) -> bytes:
    end = (block - len(data) % block)
    return data + end * chr(end).encode()
def pkcs7_clear(data:bytes, block:int) -> bytes:
    if(data[-1] > block or data[-1] > len(data)):
      return data
    return data]
def des_cbc_encrypt(data:bytes, key:bytes, iv:bytes) -> bytes:
    cipher = DES.new(key=key, iv=iv, mode=DES.MODE_CBC)
    return cipher.encrypt(pkcs7(data, 8))
def des_cbc_decrypt(data:bytes, key:bytes, iv:bytes) -> bytes:
    cipher = DES.new(key=key, iv=iv, mode=DES.MODE_CBC)
    return pkcs7_clear(cipher.decrypt(data), 8)
def read_file(filename:str) -> bytes:
    with open(filename, 'rb') as f:
      return f.read()
    return None
def write_file(filename:str, data:bytes) -> None:
    with open(filename, 'wb') as f:
      f.write(data)
def rsa_sign(data:bytes) -> str:
    priKey = RSA.import_key(RSA_PRIVATE_KEY)
    signer = PKCS1_v1_5.new(priKey)
    digest = MD5.new(data)
    bs = signer.sign(digest)
    return base64.b64encode(bs).decode('utf-8')
   
def rsa_verify(data:bytes, sign:str) -> bool:
    pubKey = RSA.import_key(RSA_PUBLIC_KEY)
    verifier = PKCS1_v1_5.new(pubKey)
    digest = MD5.new(data)
    signbs = base64.b64decode(sign)
    return verifier.verify(digest, signbs)
def key_xor(key:str, sessionId:str) -> bytes:
    keybytes = base64.b64decode(key)
    sessionIdBytes = sessionId.encode('utf-8')
    x = sessionIdBytes
    return bytes()
def app_init() -> str:
    '''
    获取api的IP
    '''
    resp = requests.post('http://www.helper001.com/api/app/init', data=urlencode(sort_and_sign({
      'pv': quote('福建福州'),
    })), headers=G_HEADERS, proxies=G_PROXIES)
    if(resp.status_code != 200):
      return None
   
    respStr = resp.content.decode('utf-8')
    respJson = json.loads(respStr)
    return respJson['data']['rdata']['IPpool']
def register(username:str, password:str) -> bool:
    '''
    注册
    '''
    resp = requests.get('http://'+G_IP+':5054/al', params=sort_and_sign({
      'devicecode': G_DEVICE['IMEI'],
      'devicemodel': G_DEVICE['MODEL'],
      'username': username,
      'password': md5(password.encode('utf-8')).hexdigest()
    }), headers=G_HEADERS, proxies=G_PROXIES)
    if(resp.status_code != 200):
      return False
   
    respStr = resp.content.decode('utf-8')
    respJson = json.loads(respStr)
    return respJson['data'] != None and respJson['data']['UserId'] > 0
def login(username:str, password:str) -> dict:
    '''
    登录
    '''
    resp = requests.get('http://'+G_IP+':5054/x', params=sort_and_sign({
      'devicecode': G_DEVICE['IMEI'],
      'devicemodel': G_DEVICE['MODEL'],
      'logintype': 0,
      'username': username,
      'password': md5(password.encode('utf-8')).hexdigest(),
      'uuid': str(uuid.uuid1())
    }), headers=G_HEADERS, proxies=G_PROXIES)
    if(resp.status_code != 200):
      return None
    respStr = resp.content.decode('utf-8')
    respJson = json.loads(respStr)
    if(respJson['data'] == None or respJson['data']['UserInfo'] == None):
      return None
      
    return {
      'userName': respJson['data']['UserInfo']['UserName'],
      'userId': respJson['data']['UserInfo']['UserID'],
      'userSessionId': respJson['data']['UserInfo']['UserSessionId'],
      'userSecret': respJson['data']['UserInfo']['ToolSecret'],
      'userToken': respJson['data']['AutoLoginToken']
    }
def auto_login(old_token:int) -> dict:
    '''
    使用token自动登录
    '''
    resp = requests.get('http://'+G_IP+':5054/c', params=sort_and_sign({
      'devicecode': G_DEVICE['IMEI'],
      'devicemodel': G_DEVICE['MODEL'],
      'token':    old_token,
    }), headers=G_HEADERS, proxies=G_PROXIES)
    if(resp.status_code != 200):
      return None
    respStr = resp.content.decode('utf-8')
    respJson = json.loads(respStr)
    return {
      'userName': respJson['data']['UserInfo']['UserName'],
      'userId': respJson['data']['UserInfo']['UserID'],
      'userSessionId': respJson['data']['UserInfo']['UserSessionId'],
      'userSecret': respJson['data']['UserInfo']['ToolSecret'],
      'userToken': respJson['data']['AutoLoginToken']
    }
def query_all_topics(maxTopicId:int=780) -> str:
    '''
    查看所有TopicId的脚本列表
    '''
    resp = requests.get('http://'+G_IP+':5054/m', params=sort_and_sign({
      'authorid': 0,
      'topicid':maxTopicId,
    }), headers=G_HEADERS, proxies=G_PROXIES)
    return resp.content.decode('utf-8')
def query_topic(userId:int, topicId:int) -> list:
    '''
    查看TopicId的脚本列表
    '''
    resp = requests.get('http://'+G_IP+':5054/t', params=sort_and_sign({
      'isVa': 2,
      'authorid': 0,
      'currentpage':0,
      'pagesize': 999,
      'topicid':topicId,
      "userid":   userId
    }), headers=G_HEADERS, proxies=G_PROXIES)
    if resp.status_code != 200:
      return None
    respStr = resp.content.decode('utf-8')
    respJson = json.loads(respStr)
    return respJson['data']['rdata']
def query_script_info(userId:int, sessionId:str, toolKey:str, topicId:int, scriptId:int, onlyId:str) -> dict:
    '''
    查看脚本的下载路径及解密key
    '''
    desKey = key_xor(toolKey, sessionId)
    jsondata = json.dumps({
      "AppId":"default",
      "ApppSign":"786927904D5FDB182E46B66018D658DD082BC766",
      "ChannelName":"1000",
      "CpuInfo":" Qualcomm Technologies, Inc MSM8998\n",
      "DeviceType":"Android",
      "IsInternalTool":0,
      "IsNeedChooseGold":1,
      "IsVa":1,
      "KernelInfo":"",
      "MemoryInfo":3744240,
      "NetworkType":1,
      "OnlyId":onlyId,
      "PackageName":"com.angel.helper",
      "SessionId":sessionId,
      "ToolId":scriptId,
      "ToolKey":"",
      "TopicId":topicId,
      "UserID":userId,
      "UserKey":"0",
      "VersionCode":136,
      "VersionName":"1.3.6",
      "imei":G_DEVICE['IMEI']
    }, separators=(',',':'))+'\n'
    resp = requests.post('http://'+G_IP+':5055/api/GetRunPerm',
      data=sessionId.encode('utf-8') + des_cbc_encrypt((json.dumps({
            'data': jsondata,
            'sign': rsa_sign(jsondata.encode('utf-8'))
      }, separators=(',',':'))+'\n').encode('utf-8'), desKey, desKey) + sessionId.encode('utf-8'), headers=G_HEADERS, proxies=G_PROXIES)
    if(resp.status_code != 200 or resp.content == None or len(resp.content) == 0):
      return None
   
    respBytes = des_cbc_decrypt(resp.content, desKey, desKey)
    respStr = respBytes.decode('utf-8')
    respJson = json.loads(respStr)
    return json.loads(respJson['data'])
def download_script(url:str, referer:str, outzip:str):
    '''
    下载脚本
    '''
    resp = requests.get(url, headers={ 'Referer': referer })
    write_file(outzip, resp.content)
    print('下载脚本成功: ' + url)
if __name__ == '__main__':
    outzip = os.path.dirname(os.path.realpath(__file__)) + '/script_dir/script.zip'
    topicId = 89195
    username = '-------'
    password = '-------'
    # app_init()#获取IP
    # topicstr = query_all_topics() #获取topicId, 部分topic存在assets文件里 t.txt\xs.txt\zsc.txt
    # register(username, password) #注册
    user = login(username, password) #登录
    # 查询topicId下面的脚本信息
    scriptList = query_topic(user['userId'], topicId)
    script = scriptList
    # 查询脚本下载路径及解密key
    scriptInfo = query_script_info(user['userId'], user['userSessionId'], user['userSecret'],
      topicId, script['ScriptID'], script['OnlyID'])
    scriptInfo = scriptInfo['ScriptInfo']
    # 下载脚本
    download_script(scriptInfo['ScriptPath'], scriptInfo['Referer'], outzip)
```

asdasxzca 发表于 2024-9-2 16:11

牛逼!!!

shepherdglen 发表于 2024-9-2 21:17

全是干货

tianya0908 发表于 2024-9-13 10:15

不明觉厉
页: [1]
查看完整版本: 某安卓助手脚本的下载脚本