逆向方法
从 base.apk(build 8911100)的 38 个 DEX 文件中提取字符串和类名,结合 Frida OkHttp hook 捕获真实上传请求,还原完整上传流程。
核心四步流程
Step 1: Preupload 探测 → 获取 upos_uri / X-Upos-Auth / biz_id
Step 2: 初始化上传 → POST /{upos_path}?uploads
Step 3: 分片上传 → PUT /{upos_path}?partNumber=N&...
Step 4: 合并完成 → POST /{upos_path}?output=json&uploadId=...
Step 5: 投稿提交 → POST /x/vu/app/add
Step 1:Preupload 探测
def preupload_probe():
r = session.get(
'https://member.bilibili.com/preupload',
params={
'r': 'probe', # 带宽探测,选最优节点
'profile': 'ugcupos/st-android',
'build': '8911100',
'mobi_app': 'android',
}
)
return r.json() # 返回 endpoint_list,选最优
def preupload(filename, filesize, upcdn='bos'):
r = session.get(
'https://member.bilibili.com/preupload',
params={
'name': filename,
'size': filesize,
'r': 'upos',
'profile': 'ugcupos/st-android',
'upcdn': upcdn,
'build': '8911100',
}
)
data = r.json()
return {
'upload_id': data['upload_id'],
'upos_uri': data['upos_uri'], # upos://xxx
'endpoint': data['endpoint'],
'auth': data['X-Upos-Auth'],
'biz_id': data['biz_id'],
'chunk_size': data['chunk_size'],
}
Step 2-4:分片上传
def upload_video(filepath, upload_info):
endpoint = upload_info['endpoint']
upos_path = upload_info['upos_uri'].removeprefix('upos:/')
auth = upload_info['auth']
upload_id = upload_info['upload_id']
chunk_size = upload_info['chunk_size']
total = os.path.getsize(filepath)
# Step 2: 初始化
session.post(
f'https://{endpoint}{upos_path}',
params={'uploads': '', 'output': 'json'},
headers={'X-Upos-Auth': auth},
)
# Step 3: 分片上传
with open(filepath, 'rb') as f:
chunks = math.ceil(total / chunk_size)
for i, chunk in enumerate(iter(lambda: f.read(chunk_size), b'')):
session.put(
f'https://{endpoint}{upos_path}',
params={
'partNumber': i + 1,
'uploadId': upload_id,
'chunk': i,
'chunks': chunks,
'size': len(chunk),
'start': i * chunk_size,
'end': i * chunk_size + len(chunk),
'total': total,
},
data=chunk,
headers={'X-Upos-Auth': auth, 'Content-Type': 'application/octet-stream'},
)
print(f'[BiliUpload] Upload chunk {i+1}/{chunks} success')
# Step 4: 合并
session.post(
f'https://{endpoint}{upos_path}',
params={
'output': 'json',
'name': os.path.basename(filepath),
'profile': 'ugcupos/st-android',
'uploadId': upload_id,
'biz_id': upload_info['biz_id'],
},
headers={'X-Upos-Auth': auth},
)
Step 5:投稿提交
def submit_archive(biz_id, cover_url, title, desc, tag, tid=17):
params = sign_app({
'access_key': access_token,
'videos': json.dumps([{
'filename': filename_without_ext,
'title': title,
'cid': 0,
'type': 'normal',
}]),
'cover': cover_url,
'title': title,
'desc': desc,
'tag': tag,
'tid': tid,
'biz_id': biz_id,
}, APP_KEY, APP_SECRET)
r = session.post(
'https://member.bilibili.com/x/vu/app/add',
data=params
)
return r.json() # {'code': 0, 'data': {'aid': ..., 'bvid': ...}}
关键 DEX 字符串
从 38 个 DEX 文件扫描中定位上传流程的日志字符串,帮助快速确认类名和方法:
[BiliUpload] Start to upload chunk=
[BiliUpload] Upload chunk chunkSize=
[BiliUpload] Upload all chunk success!!!
ugcupos/st-android
X-Upos-Auth
CDN 节点选择
preupload probe 返回多个 endpoint,选择延迟最低的节点:
upos-sz-upcdncos.bilivideo.com (COS/腾讯云)
upos-sz-upcdnbos.bilivideo.com (BOS/百度云)
upos-cs-upcdngcs.bilivideo.com (GCS/谷歌云)
upos-sz-upcdnkodo.bilivideo.com (Kodo/七牛云)