94 lines
3.7 KiB
Python
94 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
|
"""抓 hyCred 签发链: 挂 saveLoginData(落盘) + LoginCred handler(vtable探测)。
|
|
|
|
用法: 脚本跑起来后, 在手机上 手动退出登录 -> 重新登录 一次。
|
|
捕获: 登录态JSON明文(含cred字段) / saveLoginData 入参 / LoginCred handler 触发。
|
|
"""
|
|
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"""var base = Process.getModuleByName('libudbauthunify.so').base;
|
|
send({type:'armed'});
|
|
var hooked=0;
|
|
var fns=[];
|
|
try{ fns=DebugSymbol.findFunctionsNamed('SSL_write'); }catch(e){}
|
|
if(!fns.length){ try{ var r=new ApiResolver('module'); fns=r.enumerateMatchesSync('exports:*!SSL_write').map(function(x){return x.address;}); }catch(e2){} }
|
|
fns.slice(0,6).forEach(function(p,i){
|
|
Interceptor.attach(p,{ onEnter:function(a){
|
|
var len=a[2].toInt32(); if(len<40||len>20000) return;
|
|
var head=''; try{ head=a[1].readCString(Math.min(len,400)); }catch(e){ return; }
|
|
if(head.indexOf('wupudbrequest')<0 && head.indexOf('huyaudbwebui')<0) return;
|
|
var arr=new Uint8Array(a[1].readByteArray(len));
|
|
var hex=''; for(var j=0;j<arr.length;j++) hex+=('0'+arr[j].toString(16)).slice(-2);
|
|
send({type:'wire', idx:i, len:len, head:head.slice(0,300), hex:hex});
|
|
}});
|
|
hooked++;
|
|
send({type:'hooked', idx:i, at:String(p)});
|
|
});
|
|
if(hooked===0) send({type:'nowrite'});"""
|
|
|
|
|
|
def main():
|
|
d = frida.get_device_manager().add_remote_device('127.0.0.1:31877')
|
|
pid = d.spawn(['com.duowan.kiwi'])
|
|
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)
|
|
time.sleep(11)
|
|
s.create_script((RE / "evidence/scripts/patch_guard_block_termination.js").read_text()).load()
|
|
print('armed. >>> 请在手机上退出登录并重新登录 <<<')
|
|
|
|
sc = s.create_script(JS)
|
|
events = []
|
|
def on_msg(m, _):
|
|
if m.get('type') == 'error':
|
|
print('JS ERR:', str(m)[:200]); return
|
|
if m.get('type') != 'send':
|
|
return
|
|
p = m.get('payload') or {}
|
|
t = p.get('type')
|
|
events.append(p)
|
|
if t == 'vt_attached':
|
|
print(f"[vt] {p['name']} slot{p['slot']} @+{p['fp']}")
|
|
elif t == 'saveLD_str':
|
|
print(f"[saveLD_str] str={p['str']['t']}:{p['str']['len']}\n bean={p['bean_head']}\n bt={p['bt']}")
|
|
elif t == 'saveLD_bean':
|
|
print(f"[saveLD_bean] flag={p['flag']}\n bean={p['bean_head']}\n bt={p['bt']}")
|
|
elif t == 'cred_unpack':
|
|
print(f"[cred_unpack] in={p['in']['t']}:{p['in']['len']}:{p['in']['v'][:160]} <- {p['ra']}")
|
|
elif t == 'bigaes':
|
|
print(f"[bigaes] len={p['x2']['len']} head={p['x2']['v'][:120]}")
|
|
elif t == 'vtable':
|
|
print(f"[VT!] {p['name']}#{p['slot']} a1={p['a1']} bt={p['bt']}")
|
|
sc.on('message', on_msg)
|
|
sc.load()
|
|
deadline = time.time() + 300
|
|
while time.time() < deadline:
|
|
time.sleep(3)
|
|
if any(e.get('type') == 'saveLD_bean' for e in events):
|
|
time.sleep(10)
|
|
break
|
|
Path('/Users/yml/codes/douyu_login_py/evidence/sslwrite.json').write_text(json.dumps(events))
|
|
from collections import Counter
|
|
print('saved:', Counter(e.get('type') for e in events))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
for attempt in range(4):
|
|
try:
|
|
main()
|
|
break
|
|
except frida.InvalidOperationError as e:
|
|
print('detached:', e)
|
|
subprocess.run(['adb', 'shell', 'am', 'force-stop', 'com.duowan.kiwi'])
|
|
time.sleep(3)
|