程式集包含模組,而模組包含類型,類型又包含成員。反射則提供了封裝程式集、模組和類型的對象。您可以使用反射動態地建立類型的執行個體,將類型綁定到現有對象,或從現有對象中擷取類型。然後,可以調用類型的方法或訪問其欄位和屬性。
反射的用途很大,在上節文章中的“晚期綁定”就是利用了反射的性質,在外掛程式的編寫上更是需要反射。我們常用的反編譯工具Refiector 也是用了反射的性質。
我們今天就來寫一個簡單的類似於Refiector的東西,通過反射來獲得程式集的多種資訊。
1、獲得程式集所有的namespace
public static ArrayList GetAllNameSpace(Assembly assembly)
{
ArrayList namePaceArry = new ArrayList();
foreach(Type type in assembly.GetTypes())
{
if (!namePaceArry.Contains(type.Namespace) && type.Namespace != null)
{
namePaceArry.Add(type.Namespace);
}
}
return namePaceArry;
}
2、獲得程式集的所有類
public static ArrayList GetClassNames(Assembly assembly, string nameSpace)
{
Type[] types = assembly.GetTypes();
ArrayList classArray = new ArrayList();
foreach (Type type in types)
{
if (type.FullName == nameSpace + "." + type.Name && type.BaseType.Name != "Enum")
{
classArray.Add(type.Name);
}
}
return classArray;
}
3、獲得程式集的某一類中的所有方法
public static MethodInfo [] GetMethod(Assembly assembly, string fullName)
{
Type type = assembly.GetType(fullName);
return type.GetMethods();
}
這裡的fullName是包含namespace和類名合在一起的。
4、獲得程式集中的某一類中的所有屬性
public static PropertyInfo[] GetPropertys(Assembly assembly, string fullName)
{
Type type = assembly.GetType(fullName);
return type.GetProperties();
}
5、獲得程式集的枚舉類型
public static ArrayList GetEnums(Assembly assembly, string nameSpace)
{
Type[] types = assembly.GetTypes();
ArrayList classArray = new ArrayList();
foreach (Type type in types)
{
if (type.FullName == nameSpace + "." + type.Name && type.BaseType.Name == "Enum")
{
classArray.Add(type.Name);
}
}
return classArray;
}
6、獲得程式集中枚舉的內容,以及每項的值
public static FieldInfo [] GetSubEnums(Assembly assembly, string fullName)
{
return assembly.GetType(fullName).GetFields();
}
public static string GetSubEnumsData(Assembly assembly, string fullName,string name)
{
FieldInfo info = assembly.GetType(fullName).GetField(name);
return info.GetRawConstantValue().ToString();
}
7、獲得程式集中某一類的所有屬性
public static PropertyInfo[] GetPropertys(Assembly assembly, string fullName)
{
Type type = assembly.GetType(fullName);
return type.GetProperties();
}
8、獲得程式集中某一具體方法的IL代碼
public static string GetMethodData(Assembly assembly, string fullName,string name, Type [] types)
{
Type type = assembly.GetType(fullName);
MethodInfo info = type.GetMethod(name, types);
string data = String.Empty;
if (info.GetMethodBody() != null)
{
byte[] il = info.GetMethodBody().GetILAsByteArray();
if (il != null)
{
int inslength;
for (int currentpos = 0;currentpos < il.Length;currentpos += inslength)
{
inslength = 0;
data += String.Format("{0:X8}: {1}", currentpos,Disassembler.Decode(il, currentpos, out inslength)) + "\r\n";
}
}
return data;
}
return "此函數不做反編譯處理";
}
1、反射加殼
我曾經在06年的《駭客防線》上發表過一篇名為《C# 實現從自身資源提取EXE檔案》的文章,主要講的就是如何將EXE檔案以資源的形式儲存在PE檔案中,然後自我釋放出來(模仿木馬的自我釋放功能),當時就用了反射技術。時隔2年,當年的熬夜奮鬥的情景我還依稀記得,通過該文章得指引,釋放出來後將會得到一個獨立的EXE檔案。而在反射殼中僅僅將託管程式釋放到記憶體中,並找到其進入點,然後執行入口函數(不釋放真實檔案),這便是DONET反射殼的原理了。怎麼樣。貌似很簡單吧。
我們今天就拿一個CrackMe來加一層簡單的反射殼。
首先建立一個CMD項目,將CrackME拷貝到這個專案檔中,並在解決方案中設定成為這個項目的“嵌入式資源”。
然後我們在代碼中將這個資源轉換為的位元組數組:
Stream sr = Assembly.GetExecutingAssembly().GetManifestResourceStream("CiCiPackDemo.CrackMe1.exe");
byte[] fileBytes = new byte[sr.Length];
sr.Read(fileBytes, 0, (int)sr.Length -1);
Assembly assembly = Assembly.Load(fileBytes);
MethodInfo mi = assembly.EntryPoint;
mi.Invoke(null, null);
註:這裡的“CiCiPackDemo”為該項目的命名空間,而“CrackMe1.exe”為嵌入式資源名。
然後我們再找到CrackME1.exe的函數進入點並運行就可以了。
最後我們再把這個項目的編譯類型強制設定為Windows程式,編譯一次,這個最簡單的反射殼就完成了。我們可以看到其流程是:讀取自身資源->轉換資源為Assembly->找到進入點->執行入口函數。
我們用Reflector分別開啟加殼和未加殼的程式查看:
未加殼程式如下:
已經加殼程式如下:
經過對比我們發現,加殼後的程式把CrackMe1.exe作為資源檔儲存了,而我們的Refletor也不能查看其代碼了。起到了簡單的加密保護作用。
這就是反射加殼,很簡單吧^_^!
2、反射脫殼
能加殼就能脫殼,這當然是天經地義的事情。在Win32時代OllyDbg似乎成了該平台下的寵兒,然而對於反射類的DONET平台殼,這款軟體一載入就會將程式跑飛。有一身本領卻施展不出來……..。
那麼如何對付DONET平台下的反射殼呢。再回顧一下加殼原理,我們仔細看看這句代碼:Assembly assembly = Assembly.Load(fileBytes); 這說明不管怎樣系統都會將加殼的程式在記憶體中還原成一個Assembly的對象。那麼我們只要獲得了這個對象,也就可以獲得這個程式相關的資訊,結合上一篇文章我們甚至可以獲得IL代碼。那麼如何獲得這個對象呢。在程式域中有這樣的方法 AppDomain.CurrentDomain.GetAssemblies(),可惜只有在本程式集中才能這樣調用,一番思考後,我們發現可以將Managed 程式碼注射進入程式中,再利用該方法就可以獲得其對象了。
通過上一片文章中我寫的這個工具:“通用Managed 程式碼注射器”。就可以將託管程式注入到任意進程中。根據上篇文章注入cicireflection到上面的加殼的CiCiPackDemo中可以得到Crackme1.exe的完整資訊。
那麼我們如何把這個Crackme1.exe程式集完整的Dump下來呢。在介紹原理前先告訴大家一件失望的事情,這種反射脫殼在目前的DONET解密中並不實用了,更多的則是基於JIT層的脫殼,而不是基於軟體本身,但是作為一種脫殼思路還是有必要和大家分享的。
其一:分享一下RICK大牛的方法,這是我拜讀了RICK大牛的一些文章後自己理解的。(因為牛人寫的文章經常只有代碼並且點到為止,我等菜鳥可是要消化很久的)。用第三方的Dump軟體把這個程式集先DUMP下來,然後根據Assembly對象獲得一些資料來修複Dump後的檔案,就相當於Win32程式脫殼後需要修複輸入輸出表的道理是一樣的。而DONET反射脫殼則是來修複“方法體、標頭檔、中繼資料”之類的。為了不誤導大家或者故意標榜自己的嫌疑,我還是請大家來看看Rick大牛的代碼:http://bbs.pediy.com/showthread.php?t=47330
其二:說完了RICK大牛的原理,再看看我直接注入進去反射獲得資料的辦法。當然我們要用到我的通用Managed 程式碼注射器,自己寫一個外掛程式代碼如下:
if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
{
try
{
Assembly [] assemblies = AppDomain.CurrentDomain.GetAssemblies();
bool isUnpack = false;
foreach (Assembly assembly in assemblies)
{
if (Path.GetFileName(assembly.Location).ToLower() == "cicipackdemo.exe")
{
using (Stream sr =assembly.GetManifestResourceStream("CiCiPackDemo.CrackMe1.exe"))
{
&nb