-
Notifications
You must be signed in to change notification settings - Fork 59
Expand file tree
/
Copy pathDotNetHook.cs
More file actions
173 lines (163 loc) · 8.29 KB
/
DotNetHook.cs
File metadata and controls
173 lines (163 loc) · 8.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
namespace BitMono.Protections;
public class DotNetHook : Protection
{
private readonly Renamer _renamer;
private readonly RandomNext _randomNext;
public DotNetHook(Renamer renamer, RandomNext randomNext, IBitMonoServiceProvider serviceProvider) : base(serviceProvider)
{
_renamer = renamer;
_randomNext = randomNext;
}
public override Task ExecuteAsync()
{
var module = Context.Module;
var runtimeModule = Context.RuntimeModule;
var runtimeHookingType = runtimeModule.ResolveOrThrow<TypeDefinition>(typeof(Hooking));
var runtimeRedirectStubMethod = runtimeHookingType.Methods.Single(x => x.Name!.Equals(nameof(Hooking.RedirectStub)));
var listener = new ModifyInjectTypeClonerListener(ModifyFlags.All, _renamer, module);
var memberCloneResult = new MemberCloner(module, listener)
.Include(runtimeHookingType)
.CloneSafely(module, runtimeModule);
var redirectStubMethod = memberCloneResult.GetClonedMember(runtimeRedirectStubMethod);
var factory = module.CorLibTypeFactory;
var systemVoid = factory.Void;
var moduleType = module.GetOrCreateModuleType();
var moduleCctor = moduleType.GetOrCreateStaticConstructor();
foreach (var method in Context.Parameters.Members.OfType<MethodDefinition>())
{
if (method.CilMethodBody is not { } body)
{
continue;
}
for (var i = 0; i < body.Instructions.Count; i++)
{
var instruction = body.Instructions[i];
if (instruction.OpCode.FlowControl != CilFlowControl.Call)
{
continue;
}
if (instruction.Operand is not IMethodDescriptor callingOperandMethod)
{
continue;
}
var callingMethod = callingOperandMethod.Resolve();
if (callingMethod == null)
{
continue;
}
if (callingMethod.CilMethodBody == null || callingMethod.Signature == null || callingMethod.Managed == false)
{
continue;
}
if (callingMethod.ParameterDefinitions.Any(p => p.IsIn || p.IsOut))
{
continue;
}
// Skip generic methods. The cloned signature carries
// GenericParameterCount > 0, but we would not attach
// corresponding GenericParameter definitions to the
// generated dummyMethod. AsmResolver then rejects the
// assembly during WriteModule with:
// "Method defines 0 generic parameters but its signature
// defines N parameters."
// Fully cloning the GenericParameters (including their
// constraints) is non-trivial, so we conservatively skip
// these calls.
if (callingMethod.Signature.GenericParameterCount > 0)
{
continue;
}
if (module.TryLookupMember(callingMethod.MetadataToken, out var callingMethodMetadata) == false)
{
continue;
}
// If the original method is an instance method, its signature
// carries the HasThis flag. Previously callingMethod.Signature
// was reused by reference while the dummy method was forced to
// IsStatic = true, which produced inconsistent metadata
// ("Method is static but its signature has the HasThis flag
// set."). Under .NET 10 / ASP.NET Core this triggers hundreds
// of WriteModuleAsync errors. We now rebuild the signature
// explicitly as static, prepending "this" as a regular first
// parameter so the IL call stack at the caller stays the same.
var originalSignature = callingMethod.Signature;
MethodSignature dummySignature;
bool prependThis = originalSignature.HasThis;
if (prependThis)
{
var paramTypes = new List<TypeSignature>(originalSignature.ParameterTypes.Count + 1)
{
callingMethod.DeclaringType!.ToTypeSignature()
};
paramTypes.AddRange(originalSignature.ParameterTypes);
dummySignature = MethodSignature.CreateStatic(
originalSignature.ReturnType,
originalSignature.GenericParameterCount,
paramTypes.ToArray());
}
else
{
dummySignature = originalSignature;
}
// The attributes have to be correct at construction time
// because AsmResolver verifies them against the signature
// inside MethodDefinition's constructor. Setting
// "IsStatic = true" later via an object initializer is too
// late and would throw "An instance method requires a
// signature with the HasThis flag set." Strip all
// visibility/static-related flags from the original method
// and set Assembly + Static directly.
const MethodAttributes visibilityMask =
MethodAttributes.MemberAccessMask |
MethodAttributes.Static |
MethodAttributes.Virtual |
MethodAttributes.Final |
MethodAttributes.Abstract |
MethodAttributes.NewSlot;
var dummyAttributes = (callingMethod.Attributes & ~visibilityMask)
| MethodAttributes.Assembly
| MethodAttributes.Static;
var dummyMethod = new MethodDefinition(_renamer.RenameUnsafely(), dummyAttributes, dummySignature)
.AssignNextAvailableToken(module);
moduleType.Methods.Add(dummyMethod);
if (prependThis)
{
dummyMethod.ParameterDefinitions.Add(new ParameterDefinition(1, "this", 0));
}
foreach (var actualParameter in callingMethod.ParameterDefinitions)
{
var parameter = new ParameterDefinition(
prependThis ? (ushort)(actualParameter.Sequence + 1) : actualParameter.Sequence,
actualParameter.Name, actualParameter.Attributes);
dummyMethod.ParameterDefinitions.Add(parameter);
}
var dummyMethodBody = dummyMethod.CilMethodBody = new CilMethodBody();
if (callingMethod.Signature.ReturnsValue)
{
dummyMethodBody.Instructions.Add(new CilInstruction(CilOpCodes.Ldnull));
}
dummyMethodBody.Instructions.Add(new CilInstruction(CilOpCodes.Ret));
var signature = MethodSignature.CreateStatic(systemVoid);
var initializationMethod = new MethodDefinition( _renamer.RenameUnsafely(), MethodAttributes.Assembly | MethodAttributes.Static, signature)
{
CilMethodBody = new CilMethodBody
{
Instructions =
{
new CilInstruction(CilOpCodes.Ldc_I4, dummyMethod.MetadataToken.ToInt32()),
new CilInstruction(CilOpCodes.Ldc_I4, callingMethodMetadata.MetadataToken.ToInt32()),
new CilInstruction(CilOpCodes.Call, redirectStubMethod),
new CilInstruction(CilOpCodes.Ret)
}
}
};
moduleType.Methods.Add(initializationMethod);
instruction.Operand = dummyMethod;
var randomIndex = _randomNext(0, moduleCctor.CilMethodBody!.Instructions.CountWithoutRet());
moduleCctor.CilMethodBody.Instructions.Insert(randomIndex,
new CilInstruction(CilOpCodes.Call, initializationMethod));
}
}
return Task.CompletedTask;
}
}