移除LC_CODE_SIGNATURE

首先保证是二进制文件是 thin的,不能是fat binary(multiarchitecture binary)fat

减肥方法

lipo -thin armv7  old.bin -o new.bin
或者
ditto --arch armv7 bin

1. 修改load命令的数目,位于0×10处,4个字节

     比如,如果load命令的数目是 0x2c,就改为0x2B

2. 修改load命令的大小,位于0×14处, 4个字节

       大小减去0×10

3.   从load命令 条目 中修改16个字节,用0×00替换

                条目以1D 00 00 00标志开始, 实际上就是修改 8个字节的 load命令和 8个字节的load命令内容

      在intel i386和arm中,载入命令总是 1D 00 00 00 10 00 00 00

 

4. 移除实际的代码签名

          代码签名以  FA DE 0C C0 开始

5. 修改 LINKEDIT命令
vm size 和 file size都要减去 签名的大小

filesize是实际的文件大小
vm size必须以4KB 字节对齐,所以一般比file size大一些

 

 

另外:i386 thin binary文件是以  CE FA ED FE 开始的

而x64 thin binary文件是以  CF FA ED FE 开始的

armv7 thin binary也是以  CE FA ED FE 开始的

容器内手动就可以 docker exec -it ks_sig3 bash -lc 'frida -H 192.168.50.66:27042 -f com.kuaishou.nebula -l /app/frida/ks_sig_rpc_full.js' 自动就失败 这是代码 #!/usr/bin/env python3 # -*- coding: utf-8 -*- # server.py — KS 本地签名服务(Frida Python API 版:attach 优先 + 进程探测 + 实时签名) # # 运行前准备: # pip install fastapi uvicorn frida==17.* frida-tools==12.* # 确保设备 frida-server 与 frida 主版本一致,且可通过 TCP 访问(FRIDA_HOST:FRIDA_PORT) # # 关键环境变量(可选,均有默认值): # FRIDA_HOST=192.168.50.66 # FRIDA_PORT=27042 # FRIDA_APP_ID=com.kuaishou.nebula # FRIDA_PROCESS=com.kuaishou.nebula # 精确进程名(优先 attach 到它) # FRIDA_JS=/app/frida/ks_sig_rpc_full.js # KS_INJECT_MODE=attach # attach|spawn,推荐 attach # SIG_TIMEOUT=5 # /sig3 现算超时秒 # LOG_LEVEL=INFO # DEBUG|INFO|WARNING|ERROR # VERBOSE_FRIDA=false # # 路由: # POST/GET /sig3 (推荐,用于签名三件套) # POST/GET /sig (同上) # POST/GET /compute_signature (别名) # POST /inject (手动注入,可选,启动时也会自动注入) # GET /status # GET /logs?count=50 # POST /logs/level { "level": "DEBUG" } import os, time, json, threading, asyncio from typing import Optional, Dict, Any, List from fastapi import FastAPI, Request, HTTPException from fastapi.responses import JSONResponse import uvicorn from dataclasses import dataclass from datetime import datetime from collections import deque # ====== Frida Python API ====== import frida app = FastAPI(title="ks-sig3 (Frida API Attach-First)") # ---------- LOGGING ---------- LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper() VERBOSE_FRIDA = os.getenv("VERBOSE_FRIDA", "false").lower() == "true" MAX_LOG_HISTORY = int(os.getenv("MAX_LOG_HISTORY", "200")) LEVELS = {"DEBUG": 0, "INFO": 1, "SUCCESS": 1, "WARNING": 2, "ERROR": 3} current_level = LEVELS.get(LOG_LEVEL, 1) class LogMgr: def __init__(self): self.buf = deque(maxlen=MAX_LOG_HISTORY) self.last = {} self.suppressed = 0 def _ok(self, lvl, msg, thr): if LEVELS.get(lvl, 1) < current_level: return False if thr > 0: k = f"{lvl}:{hash(msg)}" now = time.time() if k in self.last and (now - self.last[k]) < thr: self.suppressed += 1 return False self.last[k] = now return True def log(self, *args, level="INFO", throttle=0): msg = " ".join(str(a) for a in args) if not self._ok(level, msg, throttle): return ts = datetime.now().strftime("%H:%M:%S") color = {"DEBUG":"\033[37m","INFO":"\033[94m","SUCCESS":"\033[92m","WARNING":"\033[93m","ERROR":"\033[91m"}.get(level,"\033[94m") if level in ("ERROR","WARNING") or LOG_LEVEL=="DEBUG": print(f"{color}[{ts}] {level}: {msg}\033[0m", flush=True) elif level=="SUCCESS" and current_level<=1: print(f"{color}[{ts}] {level}: {msg}\033[0m", flush=True) elif level=="INFO" and current_level==0: print(f"{color}[{ts}] {level}: {msg}\033[0m", flush=True) self.buf.append({"t": time.time(), "level": level, "msg": msg}) def recent(self, n=50): return list(self.buf)[-n:] log = LogMgr().log get_logs = LogMgr().recent # not used; we keep one instance below logmgr = LogMgr() def LOG(*args, **kw): logmgr.log(*args, **kw) def RECENT(n=50): return logmgr.recent(n) # ---------- ENV ---------- FRIDA_HOST = os.getenv("FRIDA_HOST", "127.0.0.1") FRIDA_PORT = int(os.getenv("FRIDA_PORT", "27042")) FRIDA_APP_ID = os.getenv("FRIDA_APP_ID", "com.kuaishou.nebula") FRIDA_PROCESS = os.getenv("FRIDA_PROCESS", "").strip() # 优先 attach 到它 FRIDA_JS = os.getenv("FRIDA_JS", "/app/frida/ks_sig_rpc_full.js") KS_INJECT_MODE = os.getenv("KS_INJECT_MODE", "attach").lower() # attach|spawn DEFAULT_TIMEOUT = float(os.getenv("SIG_TIMEOUT", "5")) # ---------- STATE ---------- @dataclass class SignatureData: sig: Optional[str] = None sig3: Optional[str] = None nssig: Optional[str] = None xfalcon: Optional[str] = None ts: float = 0.0 source: str = "empty" @dataclass class SysStats: requests_total: int = 0 signatures_generated: int = 0 errors: int = 0 last_activity: float = 0.0 class State: def __init__(self): self.ready = False self.java_ready = False self.hooks_installed = False self.start = time.time() self.sign = SignatureData() self.stats = SysStats() self.lock = threading.Lock() def set_sig(self, k, v, src="rpc"): with self.lock: if k == "sig": self.sign.sig = v elif k == "__NS_sig3": self.sign.sig3 = v elif k == "__NStokensig": self.sign.nssig = v elif k == "__NS_xfalcon": self.sign.xfalcon = v self.sign.ts = time.time() self.sign.source = src self.stats.signatures_generated += 1 self.stats.last_activity = self.sign.ts def snapshot(self): with self.lock: age = time.time()-self.sign.ts if self.sign.ts else None return { "Sig": self.sign.sig or "", "Sig3": self.sign.sig3 or "", "NsSig": self.sign.nssig or "", "xfalcon": self.sign.xfalcon or "", "timestamp": self.sign.ts, "age": age, "source": self.sign.source } STATE = State() # ---------- FRIDA HANDLES ---------- G = {"device":None, "session":None, "script":None, "pid":None, "lock": threading.Lock()} def _on_frida_message(message, data): try: typ = message.get("type") if typ == "send": payload = message.get("payload") if isinstance(payload, dict): if payload.get("type") == "ks-hooks-installed": STATE.hooks_installed = True LOG("Hook 安装成功", level="SUCCESS") elif payload.get("type") in ("rpc_exports","script_loaded"): # JS 侧脚本就绪 pass else: if VERBOSE_FRIDA or LOG_LEVEL=="DEBUG": LOG(f"[FRIDA] {payload}", level="DEBUG") else: s = str(payload) if "Java.available true" in s: STATE.java_ready = True LOG("Java 就绪", level="SUCCESS") elif "MessageDigest" in s or "atlasSign" in s or "doCommandNative" in s: if VERBOSE_FRIDA or LOG_LEVEL=="DEBUG": LOG(s, level="DEBUG", throttle=1) elif typ == "error": LOG(f"[FRIDA-ERR] {message}", level="ERROR") else: if LOG_LEVEL=="DEBUG": LOG(f"[FRIDA] {message}", level="DEBUG") except Exception as e: LOG(f"[FRIDA-MSG-HANDLER] {e}", level="ERROR") def _pick_target_pid(dev, pkg: str, hint: str, timeout=15.0): """在超时时间内不断枚举进程,优先: 1) 精确 FRIDA_PROCESS 2) 包名同名进程 3) 包名前缀的子进程(:ads/:web/:mini 等)""" t0 = time.time() while time.time() - t0 < timeout: try: procs = dev.enumerate_processes() except Exception: time.sleep(0.2); continue # 1) 精确 hint if hint: for p in procs: if p.name == hint or getattr(p, "identifier", "") == hint: return p.pid # 2) 同名(主进程) for p in procs: if p.name == pkg or getattr(p, "identifier", "") == pkg: return p.pid # 3) 子进程,按常见优先序靠前 cands = [p for p in procs if isinstance(p.name, str) and p.name.startswith(pkg + ":")] prio = ("ads","ad","mini","web","nebula","main","push") def rank(n: str): if ":" not in n: return 999 suf = n.split(":",1)[1] for i,k in enumerate(prio): if k in suf: return i return 500 cands.sort(key=lambda x: rank(x.name)) if cands: return cands[0].pid time.sleep(0.3) return None def inject_via_frida() -> bool: """ 自动探测 Java 进程并注入: 1) 若设置了 FRIDA_PROCESS:优先尝试该进程; 2) 否则遍历所有 com.kuaishou.nebula* 进程,逐个 attach + 加载“探针脚本”检测 Java.available; 3) 找到 Java 可用的进程后,卸载探针脚本,在同一 session 里加载你的 ks_sig_rpc_full.js; 4) waitjava/status 置位 ready。 """ with G["lock"]: if G["script"] is not None: return True host, port, pkg, script_path = FRIDA_HOST, FRIDA_PORT, FRIDA_APP_ID, FRIDA_JS dev = frida.get_device_manager().add_remote_device(f"{host}:{port}") # 1) 枚举候选进程 try: procs = dev.enumerate_processes() except Exception as e: LOG(f"[FRIDA] enumerate_processes failed: {e}", level="ERROR") return False # 候选优先级:FRIDA_PROCESS(如果提供)> 主进程(同名)> 子进程(:ads/:mini/:web…) candidates = [] if FRIDA_PROCESS: for p in procs: if p.name == FRIDA_PROCESS or getattr(p, "identifier", "") == FRIDA_PROCESS: candidates.append(p) break else: for p in procs: if p.name == pkg or getattr(p, "identifier", "") == pkg: candidates.append(p) break # 同名没找到或还需兜底:把所有子进程也加进来 subs = [p for p in procs if isinstance(p.name, str) and p.name.startswith(pkg + ":")] # 常见优先序 order = ("ads", "ad", "mini", "web", "nebula", "main", "push") def rank(n: str): if ":" not in n: return 999 suf = n.split(":", 1)[1] for i, key in enumerate(order): if key in suf: return i return 500 subs.sort(key=lambda x: rank(x.name)) candidates.extend(subs) if not candidates: LOG(f"[FRIDA] 未发现 {pkg} 相关进程。请先在手机上打开 App,再调用 /inject。", level="WARNING") return False # 2) 探针脚本(极简,快速判断 Java.available) probe_source = r""" rpc.exports = { probe: function () { return Java.available; } }; """ chosen = None session = None for p in candidates: try: sess = dev.attach(p.pid) except Exception as e: # 有些进程会 attach 失败,跳过 continue try: probe = sess.create_script(probe_source) ok_holder = {"ok": False} def _on_msg(m, d): pass probe.on("message", _on_msg) probe.load() # 调 probe try: available = probe.exports.probe() except Exception: available = False probe.unload() if available: chosen = p session = sess break else: # 不是 Java 进程,分离 try: sess.detach() except Exception: pass except Exception: try: sess.detach() except Exception: pass if not chosen or session is None: LOG("[FRIDA] 没有任何候选进程的 Java 可用。请确认 App 在前台/对应页面。", level="WARNING") return False # 3) 在同一 session 里加载你的主脚本 with open(script_path, "r", encoding="utf-8") as f: source = f.read() script = session.create_script(source) script.on("message", _on_frida_message) script.load() # 4) 等待 Java / hooks try: if hasattr(script.exports, "waitjava"): script.exports.waitjava(); STATE.java_ready = True except Exception as e: LOG(f"[FRIDA] waitjava error: {e}", level="WARNING") try: if hasattr(script.exports, "status"): t0 = time.time() while time.time() - t0 < 10.0: st = {} try: st = script.exports.status() except Exception: pass if st.get("java"): STATE.java_ready = True if st.get("hooks"): STATE.hooks_installed = True if STATE.java_ready and STATE.hooks_installed: break time.sleep(0.2) except Exception as e: LOG(f"[FRIDA] status polling error: {e}", level="WARNING") G.update({"device": dev, "session": session, "script": script, "pid": chosen.pid}) STATE.ready = bool(STATE.java_ready and STATE.hooks_installed) LOG(f"[FRIDA] attach 到 Java 进程成功:{chosen.name} (pid={chosen.pid}) ready={STATE.ready}", level="SUCCESS") return STATE.ready def compute_via_frida(path: str, query: str, post: str, salt: str) -> Dict[str,str]: """直接调用 JS 导出的 computesig,统一字段名返回""" if G["script"] is None: raise RuntimeError("frida not injected") res = G["script"].exports.computesig(path, query, post, salt) if not isinstance(res, dict): res = {} out = { "Sig": res.get("Sig") or res.get("sig") or "", "Sig3": res.get("Sig3") or res.get("__NS_sig3") or res.get("sig3") or "", "NsSig": res.get("NsSig") or res.get("__NStokensig") or res.get("tokensig") or "", } if out["Sig"]: STATE.set_sig("sig", out["Sig"], src="rpc") if out["Sig3"]: STATE.set_sig("__NS_sig3", out["Sig3"], src="rpc") if out["NsSig"]: STATE.set_sig("__NStokensig", out["NsSig"], src="rpc") return out # ---------- HELPERS ---------- async def parse_params(request: Request) -> Dict[str,Any]: qp = request.query_params path = qp.get("path","") query= qp.get("query","") post = qp.get("post","") salt = qp.get("salt","") timeout = float(qp.get("timeout", DEFAULT_TIMEOUT) or DEFAULT_TIMEOUT) if request.method == "POST": ctype = (request.headers.get("content-type") or "").lower() raw = (await request.body()).decode("utf-8", errors="ignore") data = {} if "application/json" in ctype: try: data = json.loads(raw or "{}") except: data = {} elif "application/x-www-form-urlencoded" in ctype or "multipart/form-data" in ctype: try: form = await request.form() data = dict(form) except: data = {} path = data.get("path", path) or "" query= data.get("query", query) or "" post = data.get("post", post) or "" salt = data.get("salt", salt) or "" try: timeout = float(data.get("timeout", timeout) or timeout) except: pass return {"path":str(path), "query":str(query), "post":str(post), "salt":str(salt), "timeout":float(timeout)} # ---------- ROUTES ---------- @app.get("/") async def root(): return JSONResponse({ "service":"ks-sig3 (Frida API Attach-First)", "env":{"FRIDA_HOST":FRIDA_HOST,"FRIDA_PORT":FRIDA_PORT,"APP_ID":FRIDA_APP_ID,"MODE":KS_INJECT_MODE}, "endpoints":["/sig","/sig3","/compute_signature","/inject","/status","/logs","/logs/level"] }) @app.post("/inject") async def inject(): ok = inject_via_frida() return JSONResponse({"ok": ok, "pid": G.get("pid"), "ready": STATE.ready}) @app.get("/status") async def status(): snap = STATE.snapshot() return JSONResponse({ "system":{ "frida_running": G["session"] is not None, "java_ready": STATE.java_ready, "hooks_installed": STATE.hooks_installed, "ready": STATE.ready, "uptime": time.time()-STATE.start }, "signatures":{ "count": int(bool(snap["Sig"]))+int(bool(snap["Sig3"]))+int(bool(snap["NsSig"])), "has_valid": bool(snap["Sig"] or snap["Sig3"] or snap["NsSig"]), "age": snap["age"], "sample": (snap["Sig"] or snap["Sig3"] or snap["NsSig"] or "")[:16] }, "statistics":{ "requests_total": STATE.stats.requests_total, "signatures_generated": STATE.stats.signatures_generated, "errors": STATE.stats.errors } }) @app.get("/logs") async def logs(count: int = 50): return JSONResponse({ "logs": RECENT(count), "suppressed": logmgr.suppressed, "level": LOG_LEVEL }) @app.post("/logs/level") async def set_level(request: Request): global current_level body = (await request.body()).decode("utf-8", errors="ignore") try: lvl = json.loads(body or "{}").get("level","INFO").upper() except: lvl = "INFO" if lvl in LEVELS: current_level = LEVELS[lvl] LOG(f"日志级别已调整为: {lvl}", level="INFO") return JSONResponse({"ok": True, "level": lvl}) return JSONResponse({"ok": False, "error": "invalid level"}, status_code=400) @app.api_route("/sig", methods=["GET","POST"]) async def sig(request: Request): return await _compute(request, "sig") @app.api_route("/sig3", methods=["GET","POST"]) async def sig3(request: Request): return await _compute(request, "sig3") app.add_api_route("/compute_signature", sig3, methods=["GET","POST"]) async def _compute(request: Request, name: str): STATE.stats.requests_total += 1 try: p = await parse_params(request) path, query, post, salt, timeout = p["path"], p["query"], p["post"], p["salt"], p["timeout"] if LOG_LEVEL=="DEBUG": LOG(f"/{name}: path={path[:64]} qlen={len(query)} plen={len(post)}", level="DEBUG") if not STATE.ready or G["script"] is None: return JSONResponse({ "Sig":"","Sig3":"","NsSig":"", "error":"system_not_ready", "java_ready": STATE.java_ready, "hooks_installed": STATE.hooks_installed }, status_code=503) async def run(): loop = asyncio.get_event_loop() return await loop.run_in_executor(None, compute_via_frida, path, query, post, salt) try: res = await asyncio.wait_for(run(), timeout=max(0.5, float(timeout))) except asyncio.TimeoutError: STATE.stats.errors += 1 return JSONResponse({"Sig":"","Sig3":"","NsSig":"","error":"compute_timeout"}, status_code=504) if not (res.get("Sig") or res.get("Sig3") or res.get("NsSig")): STATE.stats.errors += 1 return JSONResponse({"Sig":"","Sig3":"","NsSig":"","error":"no_signature_fresh"}, status_code=502) LOG(f"/{name} 成功: Sig?{bool(res['Sig'])} Sig3?{bool(res['Sig3'])} NsSig?{bool(res['NsSig'])}", level="SUCCESS", throttle=2) return JSONResponse({"Sig":res["Sig"], "Sig3":res["Sig3"], "NsSig":res["NsSig"]}) except Exception as e: STATE.stats.errors += 1 LOG(f"/{name} 异常: {e}", level="ERROR") return JSONResponse({"Sig":"","Sig3":"","NsSig":"","error":"internal_error","detail":str(e)}, status_code=500) # ---------- STARTUP/SHUTDOWN ---------- @app.on_event("startup") async def on_startup(): LOG("ks-sig3 服务启动", level="INFO") LOG(f"模式: {KS_INJECT_MODE}, 目标: {FRIDA_APP_ID}, 进程提示: {FRIDA_PROCESS or 'N/A'}", level="INFO") def auto(): time.sleep(1.5) ok = inject_via_frida() if ok: LOG("自动注入完成", level="SUCCESS") else: LOG("自动注入失败(可尝试:先打开App前台 + KS_INJECT_MODE=attach + /inject)", level="WARNING") threading.Thread(target=auto, daemon=True).start() @app.on_event("shutdown") async def on_shutdown(): LOG("服务关闭中...", level="INFO") try: if G["script"]: G["script"].unload() except Exception: pass try: if G["session"]: G["session"].detach() except Exception: pass G.update({"device":None,"session":None,"script":None,"pid":None}) if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8088, log_level="warning")
09-29
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值