不能hook非公开的方法,无法创建带有非公开类型参数的hook,魔改了一下Monitor.cs
xiangyuecn opened this issue · 11 comments
举个例子
我要hook A类里面的两个私有方法aa、bb:
- aa算是比较正常的一个私有方法;
- bb是一个带外部不能访问类型参数的方法。
例子代码
namespace ConsoleApplication1 {
class Program {
static void Main(string[] args) {
Monitor.Install();
new A().CallB();
Console.ReadLine();
}
}
class A {
public A() { aa(); }
//需要hook的方法1
private void aa() {
Console.WriteLine("A.aa()");
}
//需要hook的方法2
private void bb(B b) {
if (b == null) {
Console.WriteLine("A.bb(null)");
} else {
Console.WriteLine("A.bb(B)");
b.Run();
}
}
/********到此为止*****************/
public void CallB() {
bb(new B());
}
private class B{
public void Run() {
Console.WriteLine("B.Run()");
}
}
}
public class AHook : IMethodMonitor {
[Monitor("ConsoleApplication1", "A")]
private void aa() {
Console.WriteLine("hook aa()");
org();
}
[MethodImpl(MethodImplOptions.NoInlining)]
[Original]
private void org() {
return;
}
}
public class BHook : IMethodMonitor {
[Monitor("ConsoleApplication1", "A")]
private void bb(object b) {
Console.WriteLine("hook bb()");
org(b);
org(null);
}
[MethodImpl(MethodImplOptions.NoInlining)]
[Original]
private void org(object b) {
return;
}
}
}
未修改前执行结果
两个方法都无法hook
A.aa()
A.bb(B)
B.Run()
魔改后的结果
两个方法成功hook,并且调用未发现异常
hook aa()
A.aa()
hook bb()
A.bb(B)
B.Run()
A.bb(null)
Monitor.cs改动
Install()
两处GetMethods()
调用,改成加上flags
参数,flags
=
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static
。
InstallInternal()
GetExportedTypes()
调用改成GetTypes()
。
两处GetMethod(methodName, paramTypes)
调用,换一种方式查找Method,否则私有类型参数的Hook 函数无法编写;示例代码如下:
源代码(有两处要改):
src = type.GetMethod(methodName, paramTypes);
改成
src = getFn(type, methodName, paramTypes);
------
//查找最优函数,忽略参数匹配
private static MethodInfo getFn(Type type, string name, Type[] paramTypes) {
var all = type.GetMethods(AllFlag);
MethodInfo rtv = null;
var find = 0;
foreach (var item in all) {
if (item.Name == name) {
rtv = item;
find++;
}
}
if (find == 0) {
return null;
}
if (find == 1) {
return rtv;
}
//获取最佳的
var list = new List<fnItem>();
foreach (var item in all) {
if (item.Name == name) {
int score = 0;
var ps = item.GetParameters();
score += 100 - Math.Abs(paramTypes.Length - ps.Length);
//参数数量不一样基本忽略
if (score == 100) {
for (int i = 0, len = paramTypes.Length; i < len; i++) {
if (paramTypes[i] == ps[i].ParameterType) {
score += 100;
}
}
}
list.Add(new fnItem() { fn = item, score = score });
}
}
list.Sort((a, b) => {
return b.score - a.score;
});
return list[0].fn;
}
private class fnItem {
public MethodInfo fn;
public int score;
}
我也改了这里,并且已支持构造函数的hook。遇到很多属性hook失败,目前尝试一个System_Net_WebHeaderCollection的CheckBadChars也不行了。有内存访问错误
@kissstudio 有些属性hook失败,也许是编译后被优化掉了,压根没有调用这个get方法,试一下hook最原始取值的地方,比如:
//AClass
string Val1{get{return BClass.Val2;}}
//BClass
string Val2{get{return val;}} //要是hook上面无效,hook这里说不定有效
我遇上了一个这样的,hook直接调用的属性get方法没有效果,hook中间返回值的属性的get也没有效果,hook最深一个返回值的get就成功了,非常奇怪。
我找到方法调用内存访问错误的原因了,是我写的方法和要hook的方法签名不一致造成的,我在hook时把静态方法声明为实例方法了。CLR对不同签名的方法的调用最后生成的汇编可能不一致。
属性反正经常遇到内存访问错误,我已经放弃了。
我还增加了内部类作为参数的方法的优雅hook,使用object声明并对参数进行特征标记:
private void PostProgressChanged(AsyncOperation asyncOp, [NonPublicParameterTypeAttribute("System.Net.WebClient+ProgressData")] object progress)
提交pull request吧,我合并下代码
@bigbaldy1128 不知道怎么开始写这个PR,感觉对当前版本代码基础上那几处改动有点伤筋动骨的感觉,Monitor换了一半血那种。
另外测试也是个比较纠结的事,刚才重新clone了一份代码,没有做任何改动,测试发现那个系统方法通不过,开调试又通的过:
测试名称: SystemMethod
测试全名: Test.MonitorTest.SystemMethod
测试源: ..... MonitorTest.cs: 第 34 行
测试结果: 未通过
测试持续时间: 0:00:00.0471841
结果 的消息:
测试方法 Test.MonitorTest.SystemMethod 引发了异常:
System.AccessViolationException: 尝试读取或写入受保护的内存。这通常指示其他内存已损坏。
环境.NET Framework 4.5。
批量时测试不过是有原因的。仔细想想。😁
批量时测试不过是有原因的。仔细想想。😁
通过对patch的汇编代码的比对发现,也不是批量执行引起的。我的比对代码如下
public virtual bool IsDetourInstalled()
{
byte[] v = new byte[newInstrs.Length];
for (int i = 0; i < v.Length; i++)
{
v[i] = *(rawMethodPtr + i);
}
return v.SequenceEqual(newInstrs);
}
patch代码修改成这样子
public virtual bool Patch(MethodBase rawMethod/*要hook的目标函数*/,
MethodInfo customImplMethod/*用户定义的函数,可以调用占位函数来实现对原函数的调用*/,
MethodInfo placeholder/*占位函数*/)
{
//确保jit过了
var typeHandles = rawMethod.DeclaringType.GetGenericArguments().Select(t => t.TypeHandle).ToArray();
RuntimeHelpers.PrepareMethod(rawMethod.MethodHandle, typeHandles);
rawMethodPtr = (byte*)rawMethod.MethodHandle.GetFunctionPointer().ToPointer();
var customImplMethodPtr = (byte*)customImplMethod.MethodHandle.GetFunctionPointer().ToPointer();
//生成跳转指令,使用相对地址,用于跳转到用户定义函数
fixed (byte* newInstrPtr = newInstrs)
{
*(uint*)(newInstrPtr + 1) = (uint)customImplMethodPtr - (uint)rawMethodPtr - 5;
}
//因测试项目的特殊性,确保测试项目代码不会重入
if (IsDetourInstalled())
{
return false;
}
//将对占位函数的调用指向原函数,实现调用占位函数即调用原始函数的功能
if (placeholder != null)
{
MakePlacholderMethodCallPointsToRawMethod(placeholder);
}
//并且将对原函数的调用指向跳转指令,以此实现将对原始目标函数的调用跳转到用户定义函数执行的目的
Patch();
return true;
}
vs的测试功能会启动一个执行引擎,其默认选项是复用执行引擎。
修改汇编指令会对其造成影响,这个影响不能从汇编字节对比(IsDetourInstalled方法)获取到。
因此从菜单关闭该选项Test->Test Settings ->Keep Test Execution Engine Running ,
经我测试,目前没有发现问题了。
@kissstudio 厉害了,设置一下测试就没有问题了,你那边改动的厉害点,看到你改动的代码了,也许你那可以PR 😃
提了PR,外围改动比较大。。不知道收不收😈
尝试在azure Devops进行build,有一个测试未通过。。。