吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 123|回复: 0
上一主题 下一主题
收起左侧

[Android 原创] 某饰品交易平台订单创建协议逆向

[复制链接]
跳转到指定楼层
楼主
qn233 发表于 2026-5-14 21:25 回帖奖励

抓包分析

!


在订单创建的时候抓包

从url可以直接找到是倒数第二个, 带有create的的包

可以看到请求体和响应体都被加密了,本文的目的是逆向加密和解密的过程. 显然, 请求体的加密过程一定发生在客户端, 响应体需要被本地识别, 那么解密的过程也一定发生在客户端, 所以技术上逆向是可行的. 但是解密请求体的私钥和响应体加密的过程都发生在服务端, 显然不可能逆出来.

然后用算法助手抓取一下加密算法


在搜索框搜索一下加密后的请求体

首先发现是个AES加密算法, 其次可以发现加密的内容就是原来的jaso格式, 加密的结果也对的上. 最关键的是调用堆栈里面的com.uu898.uuhavequality.retrofit.a.interceptsg.a, 这两个显然就是加密算法的位置和方法名, 并且有混淆, 这为我们接下来的分析省去了很多寻找的过程.

这里我又搜索了一下刚刚AES算法的密钥, 想看看这个密钥是其他算法生成的还是硬编码的, 没想到意外发现了另一个RSA算法. 可以看到这个RSA是把刚刚的密钥作为加密内容来加密, 并且在抓到的请求头中有这个加密的结果, 也发送到了服务端. 所以可以推测app用AES加密请求体, 再用RSA加密AES的密钥, 并且把密钥也发送给服务段, 服务端用RSA的私钥解出AES的密钥, 最后用AES的密钥解出原来的请求体.

在调用堆栈中又发现了com.uu898.uuhavequality.retrofit.a.intercept, 这次的方法是sg.b

包加解密

请求包加密


经过刚刚的分析, 我们直接来到对应的类下面, 确实非常方便.
现在直接详细看一下加密过程

if (bVar.d().contains(request.url().encodedPath()) && di.b.b("k_api_use_encrypt", true)) 

bVar.d().contains(request.url().encodedPath())
bVar 是 kj.b 的单例实例,d() 方法返回一个 List< String >,里面存着所有需要加密的 API 路径。request.url().encodedPath() 获取当前请求的路径部分,比如 /youpin/bff/trade/v1/order/sell/create。contains() 判断当前路径是否在加密白名单中。只有敏感接口(创建订单、支付等)才在这个列表里,普通的列表查询类接口不需要加密。
di.b.b("k_api_use_encrypt", true)
从远程配置中心读取名为 k_api_use_encrypt 的开关,第二个参数 true 是默认值。正常情况下这个开关始终为 true,但服务器可以随时下发 false 来关闭加密。

String strReplaceAll = UUID.randomUUID().toString().replaceAll(Constants.SPLIT, "");

调用 java.util.UUID 的静态方法,生成一个 随机 UUID(版本4) 的对象。UUID 是 128 位的全局唯一标识符,然后用toString转换成32个16进制字符串, 最后用replaceAll(Constants.SPLIT, "")把中间的横杠换成空. 这个过程就是随机生成一个字符串作为接下来的AES加密的密钥, 这说明密钥还是生成的, 说明之前的硬编码的推断是错的.

builderNewBuilder.addHeader("sk", sg.b.b(strReplaceAll, sg.a.h()));

这一步是用RSA加密算法加密刚刚生成的密钥, 并且一起发过去.


sg.a.h返回的这一段字符串显然就是RSA的公钥, 可以看到这段公钥才是硬编码在程序里的, 而且开头的 MIIBIj 是 X509 公钥格式的固定标识. 上面还有个sg.a.g, 猜测是解密响应包的过程中会用到的客户端私钥.

sg.b.b就是RSA加密函数, 第一个参数就是刚刚随机生成的密钥, 而且cipher.init(1, rSAPublicKey);里面的1也表明是加密模式. 同样的, 上面的2代表解密模式, 大概率也是解密相应包的时候用到的.  具体的RSA算法过程就不多看了.  加密完成之后通过addHeader把结果和sk放到请求头里面.

builderNewBuilder.method(
    request.method(), 
    RequestBody.create(
        MediaType.parse("application/json"), 
        sg.a.e(JSONObjectInstrumentation.toString(jSONObject2), strReplaceAll)
    )
);

jSONObject2这是前面创建的json对象, 先用toString转化为字符串,


sg.a.e就是AES加密算法, 这里把转化的字符串和生成的随机密钥传进去, 得到密文. RequestBody.create() 是 OkHttp 的 API,作用是创建一个请求体对象, 并且第一个参数MediaType.parse("application/json")的作用是告诉服务器这个请求体的类型是 JSON(虽然实际内容已经是加密后的 hex 字符串了,但 Content-Type 头仍然标记为 JSON). 最后用builderNewBuilder.method(request.method(), 上面创建的RequestBody)保持原来的请求方法, 只是把请求体换成了加密后的内容.

响应包解密

String strString = responseProceed.body().string();

接收响应体并转化为字符串, 存在strString

if (!TextUtils.isEmpty(responseProceed.headers().get("sk")) 
    && kj.b.f59586a.d().contains(request.url().encodedPath())) 

判断是否需要解密, 第一个条件获取响应头的sk判断是否为空, 第二个条件是白名单检查

strString = sg.a.c(strString, sg.b.a(responseProceed.headers().get("sk"), sg.a.g()));

sg.a.g就是本地硬编码的RSA算法私钥, sg.b.a(sk, privateKey)把sk解密, 显然这是AES的密钥, 而sg.b.a显然就是RSA解密函数, 然后把响应体用AES解密解出来. 可以发现这个解密过程和加密过程是完全对称的, 所以客户段和服务端的加密过程基本就是一样的.

请求包内容

加解密过程搞定了, 接下来只需要搞清楚请求头和加密的的请求体是怎么生成的.

这里我选择抓了4次包, 然后对比包里面相同和不同的内容, 一样的直接硬编码在代码里面, 不一样的就去逆向生成逻辑.
具体抓包操作不过多展示, 这里直接展示我的结论

请求头一共26个内容, 其中tracestate, traceparent没什么用, 不发也可以. 然后在变化的值一共4个, 包括requestTag, sk, Device-Info,signature, 其他的都是固定的(至少在本次登陆时不变的) 其中sk是刚刚分析过用RSA加密的AES密钥, Device-Info里面包含requestTag所以会变化. 所以要继续分析requestTagsignature的生成逻辑.


Deviceid加上冒号, 再加上时间戳, 传入sg.c.b函数

使用md5加密, 转成16进制, 然后全部大写. requestTag分析完成.


这里搜索signature居然搜不到

在添加请求头的用例中去找, 发现com.umeng.ccg.a.A这里没有对应字符, 有点问题.

进去一看确实是这里.

结合外边传进来的参数,一共6个, 分别是deviceId, 系统版本号, 平台标识, 请求 URL 的 path 部分,请求 body 的排序后 JSON 字符串, 秒级时间戳. 其中的body就是加密之前的请求体, 而且是排序过的, 但是加密的请求体的body是没有排序的. 这里说明服务端在解密请求体之后还会验签. body的内容并不固定, 根据具体接口不同往里写入的字段都不一样, 等会应该还要逆向一下接口.

不仅如此, 还发现调用了native的函数


加载的是libnative.so. 本来打算继续分析里面的生成算法, 但是这个文件不仅有混淆, 具体的执行的过程还是加密的, 只有运行的时候才动态解密, 我使用ai来dump动态文件然后静态分析, 但是没什么成果. 所以这里我选择使用unidbg挂载这个so文件的服务, 然后在py里面访问这个服务, 传入参数然后模拟运行, 直接生成签名结果, 加到最后的请求体里面.

目前还差具体加密的请求体内容和signature生成过程没有解决. 这两个都指向body这段json. 之前我试验过frida不会被检查, 所以我这里选择frida注入直接获取body.

Java.perform(function () {
    var AESUtil = Java.use("sg.a");
    AESUtil.e.implementation = function (plaintext, key) {
        console.log("\n=== 加密前明文 body ===");
        console.log(plaintext);
        console.log("AES key: " + key);
        return this.e(plaintext, key);
    };
});

sg.a.e就是AES加密函数, 直接把传进去的参数泄露.


body里面有什么内容已经知道, 但是这些内容肯定不能只靠frida去拦截获取, 所以要去获取对应的接口, 我想的是获取搜索接口, 然后终端输入饰品的名称, 然后自动获取第一个皮肤的具体信息, 然后发包创建订单.

接口分析

这里我直接抓了一大堆包交给ai分析, 找到两个重要的url
第一个是/api/homepage/search/new/list这是搜索功能的api


请求体里面包含keyWords, 就是我们搜索皮肤输入的内容

响应体里面的Id:1414就是对应皮肤种类的id

第二个是/api/homepage/v3/detail/commodity/list/sell, 这个是获取在售皮肤列表


请求体里包含刚刚获得的皮肤种类id1414

响应体里的id就是具体的商品的id, 也就是body里面的commodityId.

至此, 分析终于完成

python复现

基础函数


#发包函数
def get_request_tag(timestamp : int)-> str:
    #时间是毫秒
    return hashlib.md5(f'{DEVICE_ID}:{timestamp}'.encode('utf-8')).hexdigest().upper()

def get_signature( path:str, body_json: str , timestamp: int)-> str:
    #时间是秒
    body=urlencode({
        "deviceId": DEVICE_ID,
        "systemVer" : "14",
        "platform": "android",
        "path": path,
        "body": body_json,
        "timestamp": str(timestamp)
    })
    return urlopen(f'http://localhost:8899/sign?{body}',timeout=10).read().decode('utf-8')

def get_headers (requestTag: str, sk: str, signature: str)-> dict:
    device_info = {
        "deviceType": "Pixel6",
        "systemName ": "Android",
        "hasSteamApp": 0,
        "deviceId": DEVICE_ID,
        "requestTag": requestTag, 
        "systemVersion": "14"
    }
    headers={

        'DeviceToken': DEVICE_TOKEN,
        'DeviceId': DEVICE_ID,
        'requestTag': requestTag,
        'Gameid': 730,
        'deviceType': 0,
        'platform': 'android',
        'User-Agent': 'Android/14 official com.uu898.uuhavequality/5.43.3 okhttp/4.9.3',
        'currentTheme': 'Light',
        'package-type': 'uuyp',
        'Content-Encoding': 'gzip',
        'App-Version': '5.43.3',
        'uk': UK,
        'deviceUk': DEVICEUK,
        'Device-Info': json.dumps(device_info),
        'AppType': 4,
        'Authorization': AUTH,
        'deviceBrand': 'Google',
        'signature': signature,
        'Content-Type': 'application/json; charset=utf-8',
        'Host': 'api.youpin898.com',
        'Connection': 'Keep-Alive',
        'Accept-Encoding': 'gzip'
    }
    if sk:
        headers['sk']=sk
    return headers

三个函数分别是产生requestTag, signature(访问挂载的unidbg直接获取签名), 以及通用的请求头(要发的三个请求头结构基本一样, 只有走加密请求的时候多了个sk). 大写的部分是定值, 直接在抓到的包里取值就可以用.

获取皮肤id


def get_skin_id(session, skin_name: str) -> int:
    #请求体
    body={
    "filterMap": {},
    "gameId": 730,
    "keyWords": skin_name,
    "listSortType": 0,
    "listType": 10,
    "pageCode": "MARKET_PAGE_BUY_COMMODITY_PAGE",
    "pageIndex": 1,
    "pageSize": 20,
    "pageSourceCode": "PG3000002",
    "propertyFilterTags": [],
    "sortType": 0,
    "stickerAbrade": 0,
    "stickersIsSort": False,
    "Sessionid": DEVICE_ID
    }
    body_json=json.dumps(body, separators=(',', ':'),ensure_ascii=False)
    #请求头
    time_sec=time.time()
    time_ms=int(time_sec*1000)
    headers=get_headers(get_request_tag(time_ms), "", get_signature('/api/homepage/search/new/list', body_json, int(time_sec)))
    resp=session.post('https://api.youpin898.com/api/homepage/search/new/list', headers=headers, data=body_json, timeout=10)
    return resp.json()["Data"]["commodityTemplateList"][0]["Id"]

请求体也是直接把抓到的包里面的复制过来, 只把搜索时候输入的字符串给换成skin_id, 这是唯一的变量, 通过我们的输入改变.

获取在售列表

def get_sell_list(session, skin_id: int):
    body={
    "autoDelivery": 0,
    "conditions": [],
    "hasSold": "true",
    "haveBuZhangType": 0,
    "integritySellFilter": 0,
    "isDialogMarket": False,
    "isMultipleZone": 0,
    "listSortType": "1",
    "listType": 10,
    "mergeFlag": 0,
    "pageIndex": 1,
    "pageSize": 50,
    "pageSourceCode": "PG3000003",
    "presaleMoreZones": 2,
    "sortType": "1",
    "sortTypeKey": "",
    "sourceChannel": "",
    "status": "20",
    "stickerAbrade": 0,
    "stickersIsSort": False,
    "templateId": skin_id,
    "ultraLongLeaseMoreZones": 0,
    "userId": "14486551",
    "Sessionid": DEVICE_ID
    }
    body_json=json.dumps(body, separators=(',', ':'),ensure_ascii=False)
    time_sec=time.time()
    time_ms=int(time_sec*1000)
    headers=get_headers(get_request_tag(time_ms), "", get_signature('/api/homepage/v3/detail/commodity/list/sell', body_json, int(time_sec)))
    resp=session.post('https://api.youpin898.com/api/homepage/v3/detail/commodity/list/sell', headers=headers, data=body_json, timeout=10)

    id=resp.json()["data"]["commodityList"][0]["id"]
    price=resp.json()["data"]["commodityList"][0]["price"]

    return id, price

创建订单

这里的请求和响应是加密的, 先把加密和解密的函数和常量准备好

#密码常量
RSA_PU='MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0dPEMUuou9S8aeyxraN3JmZo7VLjsoTLPGdFduQcgbVJRYJMRPbL3ju73afvqvNDURBkFDcfgkeJFfVoFugMz5aLL8Pa/kTH9ClmDYQarhf4JHo8p5XDFlcih6PEU8gKKo+XoqeAld/fOyg3GRQ6+XY5iJ+/xOsoDHuAaKhfsSY2p6Am3tt5YpYeQKFsN1da7ewb+Fp2MvVhxLKhxiazSs27x7XdFlq0KlfWXKm2RqMuR90S59547KFjEkioyF9bLh6mh8OdqTuATC2T6Ne2Q0GeNJP68jeptgzsrlCXdNisQ3Rgv/pimxfF+uSoqJLH+4PeLYwvfTUZo5QzjeMESQIDAQAB'
RSA_PR='MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCL1i+fn62CHFUAEIlsuSU8DM4PhqjqiW4itBCne/MRfKIsDCdevuw1OHCV80ICUjd5ke+Rr7AvwO3gU88M6UjGvCrJK/krElYigJswehgjmRMJAay8tO78h2ABgwvoUfkPOP7IHJaxiUaZGGzifd2cFNNRgRN9H20HTvlzmIhgBOcSVEVmJugo2BjzOzU4/Lu10D/arOn9EeofXXpfEBAo799OTm7nqXzKMA9Egdl/8ZIPhXrbMl6CVg4syYV5t46T0W7frRw7L78b2O3inxeH2LchEjDHKDQTNiexJ/xFQVCh1iVgoIwaKBLC6LMTPh06gZVDURKJB9wnfyTJ+hpNAgMBAAECggEABykkryviGrOQtrwiDWs9uOF++9SNedUnyqcl4y25uL+FHnRQ380vE1qciVE3pB7JsHQErJUulINwqvgfti2MCIFCP6L803PQ7Vtglw7phYklLGTlj5REWLIl/G3VgkQQWPM2ONEd9mFtOBHEIaUIYCHA4H+Xm+SsFJ+6rmy1LxV9ZW5ddDeGRIgZLCIDDJtsaEuc18RbvFE2S98lha1LAdL1Una210Rw70IUH358VzsRf/jfsaFeZZGnF1n7PXw5eor5aEKguCioiTqTyRfPtege2KdcRrhUWP2+JuYhFvA6eFq3/87hHizI6/Yrg2AggYUUY6gA6eS1tjE1gqXTEQKBgQDoed+flyVnqsCX3jWYybpsUy5rBDzVZODcoVbvgEl8Xw5R7nR93+R1g2NYmDPnS5XiEKWtNNYewo6Pv2pmRMbuyKX6B41a0IsDMIG1vAqbdc7Xj91eLCfTVPqS0AiGIFJX5CWFOCs70rglG3h/nZe5hcR11ltOAWqSqRlvcGcrUwKBgQCZ/I4O4T0d7s7BJAkHo41LIo3pVA7IkRn4DQTpF1BxElnWMNv6Vov0YbnXz84cfGmq4bTOX8ueHSTP0a0JR0nMvbWukj9p8C7nGmXJuHXpLJjraHTT+O8OkE30VajEyP2CnPNryfR66oi2XpUpRcXTj7KaVb2UyResIgbWUHiP3wKBgQCQUA+UtzQeHW5/GA73cMrMMfrPrgrBgWThMTqRZHa5wRxXmgowlYrxtAU42wrlWxOJCUJ/uhvtbmMnMvEu2SUQ1/fItWV3aZvR+AudMET5anFjeUg3DHwQgWEnQAL6mBflvZfZEhwsf8uWJW5w8fhcz4A8kjuNue1Za6WBeypgRwKBgB4Ml9g1ggy2TmiIVK7F7suruY+/1Ia1MiEiwUOPRiZak2dl73eBrhwJeg+wQKN0b9Zl5zeioASB4W4gl6jI3ZDzsGGZroBI245Dq3ta4L+Y8Vp27t1ypYvtAxlcIewM4NO9Nw9gwLG/1N/pwyfjssAfOZY+hxliyJjRpw3pdC13AoGAETuOBJDCHiGqBAFL7L0ctGdynOKOC4aMxbCeWYLQWYr3FtMJ11rCib4XxfGN6k/dXAcF97ivZjs8xCSHHx4ruzAFJyF9dshpMMeZB6pWDa0NX/jB9zYMGEbFiGz6uBqCYeL028xh8Nn2tmoXl5hEK5/QuUM0XSRt9ELLjFYAm4s='

#密码函数
def aes_encrypt(plain: str, key: str) -> str:
    cipher=AES.new(key.encode('utf-8'), AES.MODE_ECB)
    return cipher.encrypt(pad(plain.encode('utf-8'),AES.block_size)).hex()

def rsa_encrypt(plain: str, pub_key: str) -> str:
    key=RSA.import_key(base64.b64decode(pub_key))
    cipher=PKCS1_v1_5.new(key)
    return base64.b64encode(cipher.encrypt(plain.encode('utf-8'))).decode('utf-8')

def aes_decrypt(ciphertext: str, key: str) -> str:
    cipher=AES.new(key.encode('utf-8'), AES.MODE_ECB)
    return unpad(cipher.decrypt(bytes.fromhex(ciphertext)), AES.block_size).decode('utf-8')

def rsa_decrypt(ciphertext: str, priv_key: str) -> str:
    key=RSA.import_key(base64.b64decode(priv_key))
    cipher=PKCS1_v1_5.new(key)
    return cipher.decrypt(base64.b64decode(ciphertext), b"ERROR").decode("utf-8")

然后order

def order(session, commodity_id: int, price: int):
    body={
        "commodityId": commodity_id,
        "sellAmount": price,
        "paySellAmount": price,
        "orderSubType":1,
        "Sessionid": DEVICE_ID
    }
    body_json=json.dumps(body, separators=(',', ':'),ensure_ascii=False)
    aes_key=uuid.uuid4().hex
    body_json_encrypted=aes_encrypt(body_json, aes_key)
    sk=rsa_encrypt(aes_key, RSA_PU)

    time_sec=int(time.time())
    time_ms=int(time_sec*1000)
    headers=get_headers(get_request_tag(time_ms), sk, get_signature("/api/youpin/bff/trade/v1/order/sell/create", body_json, time_sec))
    resp=session.post('https://api.youpin898.com/api/youpin/bff/trade/v1/order/sell/create', headers=headers, data=body_json_encrypted, timeout=10)
    sk=resp.headers.get('sk')
    aes_key_decrypted=rsa_decrypt(sk, RSA_PR)
    data_decrypted=aes_decrypt(resp.text, aes_key_decrypted)
    return json.loads(data_decrypted)    

主函数

if __name__ == '__main__':
    session=requests.Session(impersonate='chrome')
    #第一次发包获取皮肤的id
    skin_name = input("皮肤名字: ")
    skin_id = get_skin_id(session,skin_name)
    print(f"皮肤ID: {skin_id}")
    #第二次发包获得在售列表

    id,price= get_sell_list(session, skin_id)
    print(f"最低价: {price}, 订单ID: {id}")

    #第三次发包创建订单
    resp=order(session, id, price)
    print(f"订单创建结果: {resp}")

完整代码


#常量
import base64
import hashlib
import json
import time
from urllib.parse import urlencode
from urllib.request import urlopen
import uuid

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5

from curl_cffi import requests

#发包信息常量
DEVICE_TOKEN = ''
DEVICE_ID = ''
UK=''
DEVICEUK=''
AUTH=''

#密码常量
RSA_PU='MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0dPEMUuou9S8aeyxraN3JmZo7VLjsoTLPGdFduQcgbVJRYJMRPbL3ju73afvqvNDURBkFDcfgkeJFfVoFugMz5aLL8Pa/kTH9ClmDYQarhf4JHo8p5XDFlcih6PEU8gKKo+XoqeAld/fOyg3GRQ6+XY5iJ+/xOsoDHuAaKhfsSY2p6Am3tt5YpYeQKFsN1da7ewb+Fp2MvVhxLKhxiazSs27x7XdFlq0KlfWXKm2RqMuR90S59547KFjEkioyF9bLh6mh8OdqTuATC2T6Ne2Q0GeNJP68jeptgzsrlCXdNisQ3Rgv/pimxfF+uSoqJLH+4PeLYwvfTUZo5QzjeMESQIDAQAB'
RSA_PR='MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCL1i+fn62CHFUAEIlsuSU8DM4PhqjqiW4itBCne/MRfKIsDCdevuw1OHCV80ICUjd5ke+Rr7AvwO3gU88M6UjGvCrJK/krElYigJswehgjmRMJAay8tO78h2ABgwvoUfkPOP7IHJaxiUaZGGzifd2cFNNRgRN9H20HTvlzmIhgBOcSVEVmJugo2BjzOzU4/Lu10D/arOn9EeofXXpfEBAo799OTm7nqXzKMA9Egdl/8ZIPhXrbMl6CVg4syYV5t46T0W7frRw7L78b2O3inxeH2LchEjDHKDQTNiexJ/xFQVCh1iVgoIwaKBLC6LMTPh06gZVDURKJB9wnfyTJ+hpNAgMBAAECggEABykkryviGrOQtrwiDWs9uOF++9SNedUnyqcl4y25uL+FHnRQ380vE1qciVE3pB7JsHQErJUulINwqvgfti2MCIFCP6L803PQ7Vtglw7phYklLGTlj5REWLIl/G3VgkQQWPM2ONEd9mFtOBHEIaUIYCHA4H+Xm+SsFJ+6rmy1LxV9ZW5ddDeGRIgZLCIDDJtsaEuc18RbvFE2S98lha1LAdL1Una210Rw70IUH358VzsRf/jfsaFeZZGnF1n7PXw5eor5aEKguCioiTqTyRfPtege2KdcRrhUWP2+JuYhFvA6eFq3/87hHizI6/Yrg2AggYUUY6gA6eS1tjE1gqXTEQKBgQDoed+flyVnqsCX3jWYybpsUy5rBDzVZODcoVbvgEl8Xw5R7nR93+R1g2NYmDPnS5XiEKWtNNYewo6Pv2pmRMbuyKX6B41a0IsDMIG1vAqbdc7Xj91eLCfTVPqS0AiGIFJX5CWFOCs70rglG3h/nZe5hcR11ltOAWqSqRlvcGcrUwKBgQCZ/I4O4T0d7s7BJAkHo41LIo3pVA7IkRn4DQTpF1BxElnWMNv6Vov0YbnXz84cfGmq4bTOX8ueHSTP0a0JR0nMvbWukj9p8C7nGmXJuHXpLJjraHTT+O8OkE30VajEyP2CnPNryfR66oi2XpUpRcXTj7KaVb2UyResIgbWUHiP3wKBgQCQUA+UtzQeHW5/GA73cMrMMfrPrgrBgWThMTqRZHa5wRxXmgowlYrxtAU42wrlWxOJCUJ/uhvtbmMnMvEu2SUQ1/fItWV3aZvR+AudMET5anFjeUg3DHwQgWEnQAL6mBflvZfZEhwsf8uWJW5w8fhcz4A8kjuNue1Za6WBeypgRwKBgB4Ml9g1ggy2TmiIVK7F7suruY+/1Ia1MiEiwUOPRiZak2dl73eBrhwJeg+wQKN0b9Zl5zeioASB4W4gl6jI3ZDzsGGZroBI245Dq3ta4L+Y8Vp27t1ypYvtAxlcIewM4NO9Nw9gwLG/1N/pwyfjssAfOZY+hxliyJjRpw3pdC13AoGAETuOBJDCHiGqBAFL7L0ctGdynOKOC4aMxbCeWYLQWYr3FtMJ11rCib4XxfGN6k/dXAcF97ivZjs8xCSHHx4ruzAFJyF9dshpMMeZB6pWDa0NX/jB9zYMGEbFiGz6uBqCYeL028xh8Nn2tmoXl5hEK5/QuUM0XSRt9ELLjFYAm4s='

#密码函数
def aes_encrypt(plain: str, key: str) -> str:
    cipher=AES.new(key.encode('utf-8'), AES.MODE_ECB)
    return cipher.encrypt(pad(plain.encode('utf-8'),AES.block_size)).hex()

def rsa_encrypt(plain: str, pub_key: str) -> str:
    key=RSA.import_key(base64.b64decode(pub_key))
    cipher=PKCS1_v1_5.new(key)
    return base64.b64encode(cipher.encrypt(plain.encode('utf-8'))).decode('utf-8')

def aes_decrypt(ciphertext: str, key: str) -> str:
    cipher=AES.new(key.encode('utf-8'), AES.MODE_ECB)
    return unpad(cipher.decrypt(bytes.fromhex(ciphertext)), AES.block_size).decode('utf-8')

def rsa_decrypt(ciphertext: str, priv_key: str) -> str:
    key=RSA.import_key(base64.b64decode(priv_key))
    cipher=PKCS1_v1_5.new(key)
    return cipher.decrypt(base64.b64decode(ciphertext), b"ERROR").decode("utf-8")

#发包函数
def get_request_tag(timestamp : int)-> str:
    #时间是毫秒
    return hashlib.md5(f'{DEVICE_ID}:{timestamp}'.encode('utf-8')).hexdigest().upper()

def get_signature( path:str, body_json: str , timestamp: int)-> str:
    #时间是秒
    body=urlencode({
        "deviceId": DEVICE_ID,
        "systemVer" : "14",
        "platform": "android",
        "path": path,
        "body": body_json,
        "timestamp": str(timestamp)
    })
    return urlopen(f'http://localhost:8899/sign?{body}',timeout=10).read().decode('utf-8')

def get_headers (requestTag: str, sk: str, signature: str)-> dict:
    device_info = {
        "deviceType": "Pixel6",
        "systemName ": "Android",
        "hasSteamApp": 0,
        "deviceId": DEVICE_ID,
        "requestTag": requestTag, 
        "systemVersion": "14"
    }
    headers={

        'DeviceToken': DEVICE_TOKEN,
        'DeviceId': DEVICE_ID,
        'requestTag': requestTag,
        'Gameid': 730,
        'deviceType': 0,
        'platform': 'android',
        'User-Agent': 'Android/14 official com.uu898.uuhavequality/5.43.3 okhttp/4.9.3',
        'currentTheme': 'Light',
        'package-type': 'uuyp',
        'Content-Encoding': 'gzip',
        'App-Version': '5.43.3',
        'uk': UK,
        'deviceUk': DEVICEUK,
        'Device-Info': json.dumps(device_info),
        'AppType': 4,
        'Authorization': AUTH,
        'deviceBrand': 'Google',
        'signature': signature,
        'Content-Type': 'application/json; charset=utf-8',
        'Host': 'api.youpin898.com',
        'Connection': 'Keep-Alive',
        'Accept-Encoding': 'gzip'
    }
    if sk:
        headers['sk']=sk
    return headers

def get_skin_id(session, skin_name: str) -> int:
    #请求体
    body={
    "filterMap": {},
    "gameId": 730,
    "keyWords": skin_name,
    "listSortType": 0,
    "listType": 10,
    "pageCode": "MARKET_PAGE_BUY_COMMODITY_PAGE",
    "pageIndex": 1,
    "pageSize": 20,
    "pageSourceCode": "PG3000002",
    "propertyFilterTags": [],
    "sortType": 0,
    "stickerAbrade": 0,
    "stickersIsSort": False,
    "Sessionid": DEVICE_ID
    }
    body_json=json.dumps(body, separators=(',', ':'),ensure_ascii=False)
    #请求头
    time_sec=time.time()
    time_ms=int(time_sec*1000)
    headers=get_headers(get_request_tag(time_ms), "", get_signature('/api/homepage/search/new/list', body_json, int(time_sec)))
    resp=session.post('https://api.youpin898.com/api/homepage/search/new/list', headers=headers, data=body_json, timeout=10)
    return resp.json()["Data"]["commodityTemplateList"][0]["Id"]

def get_sell_list(session, skin_id: int):
    body={
    "autoDelivery": 0,
    "conditions": [],
    "hasSold": "true",
    "haveBuZhangType": 0,
    "integritySellFilter": 0,
    "isDialogMarket": False,
    "isMultipleZone": 0,
    "listSortType": "1",
    "listType": 10,
    "mergeFlag": 0,
    "pageIndex": 1,
    "pageSize": 50,
    "pageSourceCode": "PG3000003",
    "presaleMoreZones": 2,
    "sortType": "1",
    "sortTypeKey": "",
    "sourceChannel": "",
    "status": "20",
    "stickerAbrade": 0,
    "stickersIsSort": False,
    "templateId": skin_id,
    "ultraLongLeaseMoreZones": 0,
    "userId": "14486551",
    "Sessionid": DEVICE_ID
    }
    body_json=json.dumps(body, separators=(',', ':'),ensure_ascii=False)
    time_sec=time.time()
    time_ms=int(time_sec*1000)
    headers=get_headers(get_request_tag(time_ms), "", get_signature('/api/homepage/v3/detail/commodity/list/sell', body_json, int(time_sec)))
    resp=session.post('https://api.youpin898.com/api/homepage/v3/detail/commodity/list/sell', headers=headers, data=body_json, timeout=10)

    id=resp.json()["data"]["commodityList"][0]["id"]
    price=resp.json()["data"]["commodityList"][0]["price"]

    return id, price

def order(session, commodity_id: int, price: int):
    body={
        "commodityId": commodity_id,
        "sellAmount": price,
        "paySellAmount": price,
        "orderSubType":1,
        "Sessionid": DEVICE_ID
    }
    body_json=json.dumps(body, separators=(',', ':'),ensure_ascii=False)
    aes_key=uuid.uuid4().hex
    body_json_encrypted=aes_encrypt(body_json, aes_key)
    sk=rsa_encrypt(aes_key, RSA_PU)

    time_sec=int(time.time())
    time_ms=int(time_sec*1000)
    headers=get_headers(get_request_tag(time_ms), sk, get_signature("/api/youpin/bff/trade/v1/order/sell/create", body_json, time_sec))
    resp=session.post('https://api.youpin898.com/api/youpin/bff/trade/v1/order/sell/create', headers=headers, data=body_json_encrypted, timeout=10)
    sk=resp.headers.get('sk')
    aes_key_decrypted=rsa_decrypt(sk, RSA_PR)
    data_decrypted=aes_decrypt(resp.text, aes_key_decrypted)
    return json.loads(data_decrypted)                      

if __name__ == '__main__':
    session=requests.Session(impersonate='chrome')
    #第一次发包获取皮肤的id
    skin_name = input("皮肤名字: ")
    skin_id = get_skin_id(session,skin_name)
    print(f"皮肤ID: {skin_id}")
    #第二次发包获得在售列表

    id,price= get_sell_list(session, skin_id)
    print(f"最低价: {price}, 订单ID: {id}")

    #第三次发包创建订单
    resp=order(session, id, price)
    print(f"订单创建结果: {resp}")


还有unidbg的代码, 这个是纯ai的

package com.uu898;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpExchange;

import java.io.File;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

public class YoyoSign extends AbstractJni {

    private final AndroidEmulator emulator;
    private final VM vm;
    private final DvmClass nativeLibClass;
    private final DvmObject<?> nativeLibInstance;

    public YoyoSign() throws Exception {
        emulator = AndroidEmulatorBuilder
                .for64Bit()
                .setProcessName("com.uu898.uuhavequality")
                .build();

        Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));

        vm = emulator.createDalvikVM();
        vm.setJni(this);
        vm.setVerbose(false);

        vm.loadLibrary(new File(YoyoSign.class.getClassLoader().getResource("yoyo/libc++_shared.so").getPath()), false);
        DalvikModule dm = vm.loadLibrary(new File(YoyoSign.class.getClassLoader().getResource("yoyo/libnative.so").getPath()), false);
        dm.callJNI_OnLoad(emulator);

        nativeLibClass = vm.resolveClass("com/uu898/uuencryption/shared/NativeLib");
        nativeLibInstance = nativeLibClass.newObject(null);
    }

    public synchronized String generateSign(String deviceId, String systemVer, String platform,
                                            String path, String body, long systemTimeSeconds) {
        StringObject result = nativeLibInstance.callJniMethodObject(
                emulator,
                "generateSign(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)Ljava/lang/String;",
                new StringObject(vm, deviceId),
                new StringObject(vm, systemVer),
                new StringObject(vm, platform),
                new StringObject(vm, path),
                new StringObject(vm, body),
                systemTimeSeconds
        );
        return result == null ? "" : result.getValue();
    }

    // ===== 以下是新增的 HTTP 服务部分 =====

    private static Map<String, String> parseQuery(String query) {
        Map<String, String> params = new HashMap<>();
        if (query == null || query.isEmpty()) return params;
        for (String pair : query.split("&")) {
            String[] kv = pair.split("=", 2);
            try {
                String key = URLDecoder.decode(kv[0], "UTF-8");
                String val = kv.length > 1 ? URLDecoder.decode(kv[1], "UTF-8") : "";
                params.put(key, val);
            } catch (Exception e) { e.printStackTrace(); }
        }
        return params;
    }

    private void handleSign(HttpExchange exchange) {
        try {
            Map<String, String> p = parseQuery(exchange.getRequestURI().getQuery());
            String sig = generateSign(
                    p.getOrDefault("deviceId", ""),
                    p.getOrDefault("systemVer", "14"),
                    p.getOrDefault("platform", "android"),
                    p.getOrDefault("path", ""),
                    p.getOrDefault("body", ""),
                    Long.parseLong(p.getOrDefault("time", "0"))
            );
            byte[] resp = sig.getBytes("UTF-8");
            exchange.sendResponseHeaders(200, resp.length);
            OutputStream os = exchange.getResponseBody();
            os.write(resp);
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                byte[] err = e.getMessage().getBytes("UTF-8");
                exchange.sendResponseHeaders(500, err.length);
                exchange.getResponseBody().write(err);
                exchange.getResponseBody().close();
            } catch (Exception ignored) {}
        }
    }

    public static void main(String[] args) throws Exception {
        YoyoSign sign = new YoyoSign();

        // 测试一次
        String test = sign.generateSign(
                "afr75XnGam0DAOmMHNsnT8L+", "14", "android",
                "/api/youpin/bff/common/v1/ip/location",
                "{\"Sessionid\":\"afr75XnGam0DAOmMHNsnT8L+\"}",
                1778337168L
        );
        System.out.println("[+] 测试签名: " + test.substring(0, 32) + "... (len=" + test.length() + ")");

        // 启动 HTTP 服务
        HttpServer server = HttpServer.create(new InetSocketAddress(8899), 0);
        server.createContext("/sign", sign::handleSign);
        server.start();
        System.out.println("[+] 签名服务已启动: http://localhost:8899/sign");
        System.out.println("[+] 等待 Python 请求... (Ctrl+C 退出)");
    }
}

免费评分

参与人数 1吾爱币 +2 热心值 +1 收起 理由
qwe30245868913 + 2 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-5-16 04:01

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表