Skip to content

Commit 24ab1c6

Browse files
committed
fix(android): chain shared hooks in managed layer
1 parent 77302d3 commit 24ab1c6

1 file changed

Lines changed: 170 additions & 6 deletions

File tree

  • StArray.ModManager.Android/Native

‎StArray.ModManager.Android/Native/Dobby.cs‎

Lines changed: 170 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Diagnostics;
21
using System.Reflection;
32
using System.Runtime.CompilerServices;
43
using System.Runtime.InteropServices;
@@ -14,6 +13,27 @@ namespace StArray.ModManager.Android.Native;
1413
public static class Dobby
1514
{
1615
private const string Lib = "modmanager";
16+
private static readonly object HookLock = new();
17+
private static readonly Dictionary<HookKey, HookRecord> InstalledHooks = new();
18+
private static readonly Dictionary<nint, HookChain> HookChains = new();
19+
private static readonly Dictionary<nint, nint> DetourTargets = new();
20+
private static readonly Dictionary<nint, HookRecord> LatestHooks = new();
21+
22+
private readonly record struct HookKey(nint Target, nint Detour);
23+
24+
private sealed class HookChain(nint target)
25+
{
26+
public nint Target { get; } = target;
27+
public nint Head { get; set; }
28+
public List<HookRecord> Layers { get; } = new();
29+
}
30+
31+
private sealed record HookRecord(
32+
nint Target,
33+
nint PatchPoint,
34+
nint Detour,
35+
nint Origin,
36+
string Owner);
1737

1838
// ========================================================================
1939
// Native externs
@@ -24,8 +44,116 @@ public static class Dobby
2444
/// <param name="replace">替换函数地址(nint 指向 delegate 或函数指针)</param>
2545
/// <param name="origin">[out] 原函数指针(用于调用原逻辑)</param>
2646
/// <returns>0 = 成功,非 0 = 失败</returns>
27-
[DllImport(Lib, EntryPoint = "modmanager_dobby_hook")]
28-
public static extern int Hook(nint address, nint replace, out nint origin);
47+
[DllImport(Lib, EntryPoint = "modmanager_dobby_hook", CallingConvention = CallingConvention.Cdecl)]
48+
private static extern int InstallNativeHook(nint address, nint replace, out nint origin);
49+
50+
/// <summary>
51+
/// 安装 inline hook,并保持旧版 API 兼容。
52+
/// 同一目标的后续 detour 会安装到上一层 detour 上,形成托管侧维护的
53+
/// Hook 链,不依赖新版本 native HookBroker 导出。
54+
/// </summary>
55+
public static int Hook(nint address, nint replace, out nint origin)
56+
=> Hook(address, replace, out origin, "Dobby.Hook");
57+
58+
/// <summary>安装带 owner 诊断信息的 Hook。</summary>
59+
public static int Hook(nint address, nint replace, out nint origin, string? owner)
60+
{
61+
origin = nint.Zero;
62+
if (address == nint.Zero || replace == nint.Zero)
63+
return -1;
64+
if (address == replace)
65+
return -2;
66+
67+
lock (HookLock)
68+
{
69+
var key = new HookKey(address, replace);
70+
if (InstalledHooks.TryGetValue(key, out var existing))
71+
{
72+
origin = existing.Origin;
73+
return 0;
74+
}
75+
76+
// A detour already used as another chain's layer is a patched
77+
// entry point, not an independent target. Patching it as a new
78+
// root would make the two managed registries disagree.
79+
if (DetourTargets.ContainsKey(address))
80+
return -3;
81+
82+
if (DetourTargets.TryGetValue(replace, out var boundTarget) &&
83+
boundTarget != address)
84+
return -3;
85+
86+
HookChains.TryGetValue(address, out var chain);
87+
var patchPoint = chain?.Head ?? address;
88+
if (patchPoint == nint.Zero || patchPoint == replace)
89+
return -2;
90+
91+
var result = InstallNativeHook(patchPoint, replace, out origin);
92+
if (result != 0)
93+
{
94+
origin = nint.Zero;
95+
return result;
96+
}
97+
98+
// A successful native hook must always provide a continuation. Do
99+
// not publish an incomplete layer to the process-wide registry.
100+
if (origin == nint.Zero)
101+
{
102+
RemoveNativeHook(patchPoint);
103+
return -4;
104+
}
105+
106+
chain ??= new HookChain(address);
107+
if (!HookChains.ContainsKey(address))
108+
HookChains.Add(address, chain);
109+
110+
var record = new HookRecord(
111+
address,
112+
patchPoint,
113+
replace,
114+
origin,
115+
string.IsNullOrWhiteSpace(owner) ? "unknown" : owner);
116+
chain.Head = replace;
117+
chain.Layers.Add(record);
118+
InstalledHooks[key] = record;
119+
DetourTargets[replace] = address;
120+
LatestHooks[address] = record;
121+
return 0;
122+
}
123+
}
124+
125+
/// <summary>获取目标地址最近安装的 Hook 层,供诊断使用。</summary>
126+
public static bool TryGetInstalledHook(
127+
nint address,
128+
out string owner,
129+
out nint detour,
130+
out nint origin)
131+
{
132+
lock (HookLock)
133+
{
134+
if (LatestHooks.TryGetValue(address, out var existing))
135+
{
136+
owner = existing.Owner;
137+
detour = existing.Detour;
138+
origin = existing.Origin;
139+
return true;
140+
}
141+
}
142+
143+
owner = string.Empty;
144+
detour = nint.Zero;
145+
origin = nint.Zero;
146+
return false;
147+
}
148+
149+
/// <summary>获取 C# Hook 链的层数。</summary>
150+
public static int GetLayerCount(nint address)
151+
{
152+
lock (HookLock)
153+
return HookChains.TryGetValue(address, out var chain)
154+
? chain.Layers.Count
155+
: 0;
156+
}
29157

30158
/// <summary>
31159
/// 安装 inline hook — 传入 C# Reflection MethodInfo。
@@ -45,7 +173,10 @@ public static int Hook(nint address, MethodInfo replaceMethod, out nint origin)
45173
// 强制 JIT 编译该方法
46174
RuntimeHelpers.PrepareMethod(replaceMethod.MethodHandle);
47175
nint replacePtr = replaceMethod.MethodHandle.GetFunctionPointer();
48-
return Hook(address, replacePtr, out origin);
176+
var owner = replaceMethod.DeclaringType == null
177+
? replaceMethod.Name
178+
: replaceMethod.DeclaringType.FullName + "." + replaceMethod.Name;
179+
return Hook(address, replacePtr, out origin, owner);
49180
}
50181

51182
/// <summary>安装动态指令插桩。</summary>
@@ -58,8 +189,41 @@ public static int Hook(nint address, MethodInfo replaceMethod, out nint origin)
58189
/// <summary>移除 hook 并恢复原函数。</summary>
59190
/// <param name="address">被 hook 的函数地址</param>
60191
/// <returns>0 = 成功</returns>
61-
[DllImport(Lib, EntryPoint = "modmanager_dobby_destroy")]
62-
public static extern int Destroy(nint address);
192+
[DllImport(Lib, EntryPoint = "modmanager_dobby_destroy", CallingConvention = CallingConvention.Cdecl)]
193+
private static extern int RemoveNativeHook(nint address);
194+
195+
/// <summary>
196+
/// 移除单层 Hook。包含多层的链不能安全地从中间拆除,因此保留到进程结束。
197+
/// </summary>
198+
public static int Destroy(nint address)
199+
{
200+
lock (HookLock)
201+
{
202+
if (HookChains.TryGetValue(address, out var chain) && chain.Layers.Count > 1)
203+
return -5;
204+
if (DetourTargets.ContainsKey(address))
205+
return -5;
206+
207+
// Keep the native operation inside the same lock as registry
208+
// updates so a concurrent install cannot attach to a hook while
209+
// it is being restored.
210+
var result = RemoveNativeHook(address);
211+
if (result != 0)
212+
return result;
213+
214+
if (!HookChains.Remove(address, out var removedChain))
215+
return result;
216+
217+
foreach (var layer in removedChain.Layers)
218+
{
219+
InstalledHooks.Remove(new HookKey(layer.Target, layer.Detour));
220+
DetourTargets.Remove(layer.Detour);
221+
}
222+
LatestHooks.Remove(address);
223+
224+
return result;
225+
}
226+
}
63227

64228
/// <summary>按动态库名和符号名解析函数地址。</summary>
65229
/// <param name="imageName">动态库名,如 "libil2cpp.so"</param>

0 commit comments

Comments
 (0)