難読化とリファクタリングのコンテキストで内省したコードの編成

.NETプラットフォームは、実行時にメタデータにアクセスするための豊富なAPIを提供します。 ただし、イントロスペクションのメカニズムには、対応するデータ構造を介して名前と署名を定義することによるプログラム要素へのレイトバインディングが含まれます。 このようなコードは、メタデータのリファクタリング(名前の変更、パラメーターの順序の変更)または難読化の後、プログラムのロジックを誤ったものに変更する可能性があります。 この問題の解決策は、 Expression TreesテクノロジーとC#で利用可能な構文糖を使用することです。



遠くに行かないために、例として簡単なクラスを書きます。

public class SimpleClass { private readonly string theValue; public SimpleClass(string value, int ratio) { theValue = value; } public string Value { get { return theValue; } } }
      
      





イントロスペクションメカニズムを介してメタデータへのアクセスを提供するプロパティをこのクラスに追加します。

 internal static PropertyInfo ValueProperty { get { return typeof(SimpleClass).GetProperty("Value"); } } internal static FieldInfo ValueField { get { return typeof(SimpleClass).GetField("theValue", BindingFlags.NonPublic | BindingFlags.Instance); } } internal static ConstructorInfo Constructor { get { return typeof(SimpleClass).GetConstructor(new[] { typeof(string), typeof( }); } }
      
      





これらのプロパティの値をコンソールに出力すると、これらのプログラム要素の文字列表現が取得されます。 ただし、IDEに組み込まれたリファクタリングエンジンを使用してコンストラクターパラメーターを再配置する場合(たとえば、Visual Studioの場合は、これは[リファクタリング]メニュー、[パラメーターの並べ替え]アイテムです)、コンストラクターメタデータを反映するプロパティは空のリンク(つまり、null)を返します。 また、アセンブリに難読化も適用すると、このようにフィールドとプロパティを完全に反映できなくなります( ObfuscationAttribute属性が使用されている場合を除く)。

状況から抜け出すために、式ツリー、ラムダ式、および一般化を身につけます。 これらすべてから、ヘルパーメソッドを接着します。

 static TMember Reflect<TDelegate, TMember>(Expression<TDelegate> memberAccess) where TDelegate : class where TMember : MemberInfo { if (memberAccess.Body is MemberExpression) return ((MemberExpression)memberAccess.Body).Member as TMember; else if (memberAccess.Body is NewExpression) return ((NewExpression)memberAccess.Body).Constructor as TMember; else if (memberAccess.Body is MethodCallExpression) return ((MethodCallExpression)memberAccess.Body).Method as TMember; else return null; }
      
      





このメソッドは、2つの型パラメーターを受け入れます。1つ目は、引数として渡されるラムダ式の署名を解決するために使用されるデリゲートを受け入れ、2つ目は、クラスのリフレクトメンバーの型(フィールドのFieldInfoなど)を受け入れます。 引数として渡されるラムダ式は、興味のあるクラスのメンバーへのアクセスを記述します。 メソッド内で、式ツリーとして表されるラムダ式の本体にアピールします。 ボディはクラスのメンバーへのアクセスを表すため、可能なオプションについてボディのタイプを分析します。



このソリューションの主な目的は、アンカーフラグ、プログラム要素の名前、メソッドとコンストラクターのシグネチャを記述する配列を使用する従来のリフレクションメソッドを回避することです。これらのメソッドは、リファクタリングエンジンと難読化ツールによる分析には使用できないためです。

これで、リファクタリング中に自動的に変更され、難読化アルゴリズムに対して安全なリフレクションコードを作成する準備がすべて整いました。

 internal static PropertyInfo ValueProperty { get { return Reflect<Func<SimpleClass, string>, PropertyInfo>(v => v.Value); } } internal static FieldInfo ValueField { get { return Reflect<Func<SimpleClass, string>, FieldInfo>(v => v.theValue); } } internal static ConstructorInfo Constructor { get { return Reflect<Func<string, int, SimpleClass>, ConstructorInfo>((a1, a2) => new SimpleClass(a1, a2)); } }
      
      





このアプローチでは、文字列リテラルを使用してクラスメンバー名を表し、配列の配列を使用してコンストラクターのシグネチャを記述しません。

難読化ツールが動作した後、このコードが機能し続けるのはなぜですか? 答えは、C#コンパイラが式ツリーのコードを生成する方法にあります。 ILDASMでアセンブリを開く場合、LDTOKEN命令を使用して、PEメンバー内の対応するテーブル内のメンバーメタデータの場所を示す数値トークンで動作するクラスメンバーメタデータをロードすることを確認できます。



常にありますが



このメソッドは、現在のレキシカルスコープで利用可能なプログラム要素を反映するためにのみ適しています。




All Articles