dnlib使用

# 前言

看了老外的白皮书兴高采烈去挖洞了,然后发现人家挖洞是黑名单,我挖洞是白名单。需要自己找gadget,可是一个一个找构造函数、getter、setter是真的慢,干脆学一下 https://github.com/0xd4d/dnlib 看能不能整个.net的tabby出来算求。

接下来我将以下面这个类作为测试的程序集,讲解dnlib的使用。

 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
using System;
using System.Collections.Generic;

namespace TestApp
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("asd");
        }
    }
    abstract class A
    {
        public int AProp { get; set; }
        public abstract void M();
    }
    interface ITest
    {
        void IMethod();
        void IMethod(string s);
        int IProp { get; set; }
    }
    class Test
    {
        public int MyProperty { get; set; }
        private int MyValue { get; set; }
        private int Count;
        public delegate void Handler();
        public event Handler Change;

        public static string Hello(string s)
        {
            Console.WriteLine(s);
            return "hello";
        }

        public Test() { Console.WriteLine("ctor"); }
        public Test(string s) { Console.WriteLine("ctor string " + s); }

    }
    class Container
    {
        public class Nested
        {
            private Container parent;

            public Nested()
            {
            }
            public Nested(Container parent)
            {
                this.parent = parent;
            }
        }
    }
    enum E
    {
        None = 0,
    }

    class G : Comparer<string>
    {
        public override int Compare(string x, string y)
        {
            return x.CompareTo(y);
        }
    }
}

# dnlib的常用类

  • AssemblyDef assembly程序集类
  • ModuleDef 模块类
  • TypeDef 类型定义 包含了事件、属性、字段等
  • EventDef 事件类
  • FieldDef 字段类
  • PropertyDef 属性类

会发现,都是以XXDef命名,还有一些是以XXRef命名的类,表示引用类,这里暂不讨论。

XXDef为抽象类,真正实现类在XXDefMD,如ModuleDef

image.png

其实现类为ModuleDefMD,我们用也是用它。

# 载入程序集

首先要先引入命名空间

1
2
using dnlib.DotNet;
using dnlib.DotNet.Emit;

dnlib中提供了多种读取dotnet程序集的方式

1
2
ModuleContext modCtx = ModuleDef.CreateModuleContext();
ModuleDefMD module = ModuleDefMD.Load(@"C:\path\to\file.exe", modCtx);

Load有多个重载,提供从文件中、byte中、内存中或者是System.Reflection.Module示例读取程序集

image.png

比如以下代码就将从type void拿到System.Reflection.Module实例,从而加载mscorlib.dll

1
2
3
System.Reflection.Module reflectionModule = typeof(void).Module;	// Get mscorlib.dll's module
ModuleContext modCtx = ModuleDef.CreateModuleContext();
ModuleDefMD module = ModuleDefMD.Load(reflectionModule, modCtx);

# 读取程序集

在dnlib的Example1 https://github.com/0xd4d/dnlib/blob/master/Examples/Example1.cs 中给了例子

 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
using System;
using dnlib.DotNet;

namespace dnlib.Examples {
	// This example will open mscorlib.dll and then print out all types
	// in the assembly, including the number of methods, fields, properties
	// and events each type has.
	public class Example1 {
		public static void Run() {
			// Load mscorlib.dll
			var filename = typeof(void).Module.FullyQualifiedName;
			var mod = ModuleDefMD.Load(filename);

			int totalNumTypes = 0;
			// mod.Types only returns non-nested types.
			// mod.GetTypes() returns all types, including nested types.
			foreach (var type in mod.GetTypes()) {
				totalNumTypes++;
				Console.WriteLine();
				Console.WriteLine("Type: {0}", type.FullName);
				if (type.BaseType is not null)
					Console.WriteLine("  Base type: {0}", type.BaseType.FullName);

				Console.WriteLine("  Methods: {0}", type.Methods.Count);
				Console.WriteLine("  Fields: {0}", type.Fields.Count);
				Console.WriteLine("  Properties: {0}", type.Properties.Count);
				Console.WriteLine("  Events: {0}", type.Events.Count);
				Console.WriteLine("  Nested types: {0}", type.NestedTypes.Count);

				if (type.Interfaces.Count > 0) {
					Console.WriteLine("  Interfaces:");
					foreach (var iface in type.Interfaces)
						Console.WriteLine("    {0}", iface.Interface.FullName);
				}
			}
			Console.WriteLine();
			Console.WriteLine("Total number of types: {0}", totalNumTypes);
		}
	}
}

通过mod.GetTypes()拿到type定义后,我们可以获取type的method、field、prop、event等等属性,我们写一下代码解析下我们的TestApp.exe

 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
ModuleContext modCtx = ModuleDef.CreateModuleContext();
ModuleDefMD module = ModuleDefMD.Load(@"E:\code\ConsoleApp2\TestApp\bin\Debug\TestApp.exe", modCtx);
AssemblyDef assembly = module.Assembly;

foreach (ModuleDef m in assembly.Modules)
{
    Console.WriteLine("Module: " + m.FullName);
    //遍历类
    foreach (TypeDef type in m.Types)
    {
        Console.WriteLine("Class: " + type.Name);

        //遍历方法
        foreach (MethodDef method in type.Methods)
        {
            Console.WriteLine("Method: " + method.Name);
        }

        //遍历字段
        foreach (FieldDef field in type.Fields)
        {
            Console.WriteLine("Field: " + field.Name);
        }
    }
}
Console.ReadKey();

输出

 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
Module: TestApp.exe
Class: <Module>
Class: Program
Method: Main
Method: .ctor
Class: A
Method: get_AProp
Method: set_AProp
Method: M
Method: .ctor
Field: <AProp>k__BackingField
Class: ITest
Method: IMethod
Method: IMethod
Method: get_IProp
Method: set_IProp
Class: Test
Method: get_MyProperty
Method: set_MyProperty
Method: get_MyValue
Method: set_MyValue
Method: add_Change
Method: remove_Change
Method: Hello
Method: .ctor
Method: .ctor
Field: <MyProperty>k__BackingField
Field: <MyValue>k__BackingField
Field: Count
Field: Change
Class: Container
Method: .ctor
Class: E
Field: value__
Field: None
Class: G
Method: Compare
Method: .ctor

# 解析call关系

解析call关系其实是解析method body的opcode,比如静态方法调用的OpCodes.Call https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.calli?view=net-7.0

 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
using System;
using dnlib.DotNet;
using dnlib.DotNet.Emit;

namespace tabby
{
    internal class Program
    {
        static void Main(string[] args)
        {
            ModuleContext modCtx = ModuleDef.CreateModuleContext();
            ModuleDefMD module = ModuleDefMD.Load(@"E:\code\ConsoleApp2\TestApp\bin\Debug\TestApp.exe", modCtx);
            AssemblyDef assembly = module.Assembly;

            foreach (ModuleDef m in assembly.Modules)
            {
                Console.WriteLine("Module: " + m.FullName);
                //遍历类
                foreach (TypeDef type in m.Types)
                {
                    Console.WriteLine("Class: " + type.Name);

                    //遍历方法
                    foreach (MethodDef method in type.Methods)
                    {
                        Console.WriteLine("Method: " + method.Name);

                        if (method.HasBody && method.Body.HasInstructions)
                        {
                            var instructions = method.Body.Instructions;

                            foreach (var ins in instructions)
                            {
                                if (ins.OpCode == OpCodes.Call || ins.OpCode == OpCodes.Callvirt)
                                {
                                    // 获取调用的方法
                                    IMethod calledMethod = (IMethod)ins.Operand;

                                    // 输出调用关系
                                    Console.WriteLine("{0} {1} {2}", method.FullName, ins.OpCode, calledMethod.FullName);
                                }
                            }
                        }
                    }

                    //遍历字段
                    foreach (FieldDef field in type.Fields)
                    {
                        Console.WriteLine("Field: " + field.Name);
                    }
                }
            }
            Console.ReadKey();
        }
    }
}

输出

 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
Module: TestApp.exe
Class: <Module>
Class: Program
Method: Main
System.Void TestApp.Program::Main(System.String[]) call System.Void System.Console::WriteLine(System.String)
Method: .ctor
System.Void TestApp.Program::.ctor() call System.Void System.Object::.ctor()
Class: A
Method: get_AProp
Method: set_AProp
Method: M
Method: .ctor
System.Void TestApp.A::.ctor() call System.Void System.Object::.ctor()
Field: <AProp>k__BackingField
Class: ITest
Method: IMethod
Method: IMethod
Method: get_IProp
Method: set_IProp
Class: Test
Method: get_MyProperty
Method: set_MyProperty
Method: get_MyValue
Method: set_MyValue
Method: add_Change
System.Void TestApp.Test::add_Change(TestApp.Test/Handler) call System.Delegate System.Delegate::Combine(System.Delegate,System.Delegate)
System.Void TestApp.Test::add_Change(TestApp.Test/Handler) call TestApp.Test/Handler System.Threading.Interlocked::CompareExchange<TestApp.Test/Handler>(TestApp.Test/Handler&,TestApp.Test/Handler,TestApp.Test/Handler)
Method: remove_Change
System.Void TestApp.Test::remove_Change(TestApp.Test/Handler) call System.Delegate System.Delegate::Remove(System.Delegate,System.Delegate)
System.Void TestApp.Test::remove_Change(TestApp.Test/Handler) call TestApp.Test/Handler System.Threading.Interlocked::CompareExchange<TestApp.Test/Handler>(TestApp.Test/Handler&,TestApp.Test/Handler,TestApp.Test/Handler)
Method: Hello
System.String TestApp.Test::Hello(System.String) call System.Void System.Console::WriteLine(System.String)
Method: .ctor
System.Void TestApp.Test::.ctor() call System.Void System.Object::.ctor()
System.Void TestApp.Test::.ctor() call System.Void System.Console::WriteLine(System.String)
Method: .ctor
System.Void TestApp.Test::.ctor(System.String) call System.Void System.Object::.ctor()
System.Void TestApp.Test::.ctor(System.String) call System.String System.String::Concat(System.String,System.String)
System.Void TestApp.Test::.ctor(System.String) call System.Void System.Console::WriteLine(System.String)
Field: <MyProperty>k__BackingField
Field: <MyValue>k__BackingField
Field: Count
Field: Change
Class: Container
Method: .ctor
System.Void TestApp.Container::.ctor() call System.Void System.Object::.ctor()
Class: E
Field: value__
Field: None
Class: G
Method: Compare
System.Int32 TestApp.G::Compare(System.String,System.String) callvirt System.Int32 System.String::CompareTo(System.String)
Method: .ctor
System.Void TestApp.G::.ctor() call System.Void System.Collections.Generic.Comparer`1<System.String>::.ctor()

# 修改method body

这个案例我们直接看Example2.cs就行了 https://github.com/0xd4d/dnlib/blob/master/Examples/Example2.cs

image.png

用dnspy看看

image.png

确实多了这个类

# 总结

简单写了写怎么用,和javassist差的还是比较远,需要实现IL的翻译。

不过只是为了找到call关系的话,足够了,优化下加个多线程,加个neo4j就是下一个tabby for dotnet。

文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。