feat(huya): nonce生成机制完全破解 - 任意账号零设备出完整CK闭环
动态hook实锤(scripts/hook_nonce_chain.py): rnd = XXTEA(pack(st) + pack(counter|st<<16), key=MD5hex(uid_str + k1)[:16]) - st=毫秒时间戳, counter同st内递增 - k1=设备常量865a4924...(不随账号变, evidence/nonce_k1.json) - Python复刻(tools/nonce_forge.py)对设备两轮rnd逐字节MATCH 推翻v2误判(X2'nonce账号绑定令牌'实为nonce内编码uid): - 换账号用目标uid重算nonce而非复用旧nonce! - 纯随机nonce必40020因解不出合法st/counter结构 实测: 同账号+跨账号(hy_300024708从未设备登录)本地nonce铸证bind均通过, full_web_cookie/full_auto_safe 任意账号密码->全套cookie(udb_cred/udb_biztoken)
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python3
|
||||
"""追踪 this+0x10(k1) 的写入者: hook std::string::operator=, 比对目标地址==BusinessCfg+0x10。
|
||||
启动后等初始化完成, 直接读 BusinessCfg 实例的 +0x10 最终值, 并记录所有写到该地址的调用。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import frida
|
||||
|
||||
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
|
||||
|
||||
JS = r"""
|
||||
'use strict';
|
||||
Process.setExceptionHandler(function(d){ send({type:'segv', info:{type:d.type, addr:String(d.address)}}); return true; });
|
||||
var base = Process.getModuleByName('libudbauthunify.so').base;
|
||||
send({type:'armed', base:String(base)});
|
||||
|
||||
function rdStr(p){
|
||||
try{
|
||||
var b0=p.readU8();
|
||||
if((b0&1)===0){ var l=b0>>1; if(l>8192) return {t:'s?',len:l,v:''};
|
||||
return {t:'s',len:l,v:l?Array.from(new Uint8Array(p.add(1).readByteArray(l))).map(x=>('0'+x.toString(16)).slice(-2)).join(''):''}; }
|
||||
var len=parseInt(p.add(8).readU64().toString());
|
||||
if(len>65536) return {t:'l?',len:len,v:''};
|
||||
var dp=p.add(16).readPointer();
|
||||
return {t:'l',len:len,v:Array.from(new Uint8Array(dp.readByteArray(len))).map(x=>('0'+x.toString(16)).slice(-2)).join('')};
|
||||
}catch(e){ return {t:'err',len:-1,v:String(e)}; }
|
||||
}
|
||||
|
||||
// BusinessCfg::getInstance 返回单例 -> 拿 this 地址
|
||||
var cfgAddr = null;
|
||||
try {
|
||||
Interceptor.attach(base.add(0x454310 - 0x454310 + 0x26a880), { // getInstance 附近, 用符号找
|
||||
});
|
||||
} catch(e){}
|
||||
|
||||
// 用 export 查找 getInstance 真实地址
|
||||
var syms = Process.getModuleByName('libudbauthunify.so').enumerateSymbols();
|
||||
var getInst = null;
|
||||
syms.forEach(function(s){ if (s.name.indexOf('BusinessCfg11getInstance') >= 0) getInst = s.address; });
|
||||
send({type:'info', getInst:String(getInst)});
|
||||
|
||||
if (getInst) {
|
||||
Interceptor.attach(getInst, {
|
||||
onLeave: function(r){
|
||||
cfgAddr = r;
|
||||
var s = rdStr(r.add(0x10));
|
||||
send({type:'cfg', addr:String(r), k1: s});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// hook std::string::operator=(string const&) @0x452e40
|
||||
try {
|
||||
Interceptor.attach(base.add(0x452e40), {
|
||||
onEnter: function(a){
|
||||
if (!cfgAddr) return;
|
||||
// a[0] = this(目标), a[1] = src
|
||||
if (a[0].equals(cfgAddr.add(0x10))) {
|
||||
var v = rdStr(a[1]);
|
||||
send({type:'write_k1', val:v,
|
||||
bt:Thread.backtrace(this.context, Backtracer.ACCURATE).map(function(x){
|
||||
return DebugSymbol.fromAddress(x).toString().slice(0,80);}).slice(0,4)});
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch(e){ send({type:'hookerr', where:'op=', msg:''+e}); }
|
||||
|
||||
// 直接读最终值
|
||||
setTimeout(function(){
|
||||
if (cfgAddr) {
|
||||
var s = rdStr(cfgAddr.add(0x10));
|
||||
send({type:'final_k1', k1:s});
|
||||
}
|
||||
}, 60000);
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
d = frida.get_device_manager().add_remote_device('127.0.0.1:31877')
|
||||
pid = d.spawn(['com.duowan.kiwi'])
|
||||
print(f'spawned {pid}')
|
||||
s = d.attach(pid)
|
||||
s.create_script((RE / "evidence/scripts/bypass_msaoaid_maps_art_callsite.js").read_text()).load()
|
||||
s.create_script((RE / "evidence/scripts/mask_frida_maps_only.js").read_text()).load()
|
||||
d.resume(pid)
|
||||
print('resumed')
|
||||
time.sleep(11)
|
||||
s.create_script((RE / "evidence/scripts/patch_guard_block_termination.js").read_text()).load()
|
||||
print('patch_guard on')
|
||||
|
||||
sc = s.create_script(JS)
|
||||
events = []
|
||||
got_armed = []
|
||||
|
||||
def on_msg(m, _):
|
||||
if m.get('type') == 'error':
|
||||
print('JS ERR:', str(m)[:240]); return
|
||||
if m.get('type') != 'send':
|
||||
return
|
||||
p = m.get('payload') or {}
|
||||
t = p.get('type')
|
||||
events.append(p)
|
||||
if t == 'armed':
|
||||
got_armed.append(1); print('[armed]')
|
||||
elif t == 'info':
|
||||
print('[info] getInstance =', p['getInst'])
|
||||
elif t == 'cfg':
|
||||
print(f"[cfg] addr={p['addr']} k1={p['k1']['v'][:40]}({p['k1']['len']})")
|
||||
elif t == 'write_k1':
|
||||
print(f"[WRITE k1] val={p['val']['v'][:60]}")
|
||||
for f in p['bt']:
|
||||
print(" <", f)
|
||||
elif t == 'final_k1':
|
||||
print(f"[FINAL k1] {p['k1']['v'][:40]}({p['k1']['len']})")
|
||||
elif t == 'hookerr':
|
||||
print('[hookerr]', p)
|
||||
|
||||
sc.on('message', on_msg)
|
||||
sc.load()
|
||||
assert got_armed
|
||||
|
||||
deadline = time.time() + 130
|
||||
while time.time() < deadline:
|
||||
time.sleep(2)
|
||||
if any(e.get('type') == 'final_k1' for e in events):
|
||||
time.sleep(3)
|
||||
break
|
||||
Path('/tmp/opencode/k1_writer.json').write_text(json.dumps(events))
|
||||
print('saved /tmp/opencode/k1_writer.json')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for attempt in range(3):
|
||||
print(f'===== 尝试 #{attempt+1} =====')
|
||||
try:
|
||||
main()
|
||||
break
|
||||
except frida.InvalidOperationError as e:
|
||||
print(f'session detached: {e}')
|
||||
subprocess.run(['adb', 'shell', 'am', 'force-stop', 'com.duowan.kiwi'])
|
||||
time.sleep(3)
|
||||
except Exception as e:
|
||||
print(f'err: {e}')
|
||||
subprocess.run(['adb', 'shell', 'am', 'force-stop', 'com.duowan.kiwi'])
|
||||
time.sleep(3)
|
||||
Reference in New Issue
Block a user