上篇我們一起看了附加到進程這個功能實現后的樣子,這篇我們就來講一下他的實現原理。如果你還沒有看過上一篇里的功能介紹的話,建議回去掃一眼,花不了二分鍾的時間,要不然你繼續往下看的話,會一頭霧水的 。
從上篇的演示中,我們不難看出,要實現附加到進程的功能,至少需要解決兩個問題 。
一.如何把HibernatingRhinos.Profiler.Appender.dll送到目標進程,並在這個進程里調用HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize() 讓NhibernatePorfiler 實現初始化
二. 如何給NHProf加一個熱鍵,可以方便的呼出我們的附加到進程。
下面我們就來一個一個的解決這兩個問題。
解決第一個問題用到的主要 技術就是進程注入, 要實現進程注入無非就是三種 方法 1.利用全局鈎子。2 .利用 CreateRemoteThread和LoadLibrary把你的DLL注入。 3.用 CreateRemoteThread和WriteProcessMemory 直接往目標進程 寫指令(這個其實和第二種用的技術是一樣的)。 在這里我們采用的是第一種方式 ,這是三種方式中最安全的方式,當然也是適應范圍最小的,只能用於使用了USER32的進程中 ,也就是必須是有界面的程序。
進程注入的代碼,我們就不細講了,注釋寫的已經很詳細了,你們可以自已看一看源碼,我已經將他們封裝在了Injector這個工程里,這是一個VC CLR Library的工程,如果你的VS沒有裝C++語言的話,就直接引用DLL。編譯后的DLL,我也拷貝了一份放在了附錄根目錄下的Libs文件夾里。名字就叫 Injector.dll 。在這里我們只簡單的提一下他的用法。
Injector.dll 的使用很簡單,不過在講它如何用之前,我們還需要做一些假設。
首先我們假設,有一個名為UI的工程,他編譯后生成的程序正是實現進程注入的主程序,在這個程序里,可以列出來系統里的所有進程,並且可以選則一個進程然后點擊附加到進程按鈕后就可以實現進程注入。
然后我們再假設,還有一個名為ProcessViewer的的工程,這是一個類庫(Class Library)工程,我們正是要把這個工程編譯后生成的DLL,注入到目標進程。
說到這里,如果已經打開了源碼的朋友,可能已經看出什么了,是的,這些其實並不是什么假設,而是我們源代碼里實際的項目 命名 。
選中的那個就是UI工程了,他上面的那個就是ProcessViewer工程。
從上圖還可以看到,除了這兩個工程外,還有好幾個工程,這里我們來對他們作個簡單的介紹。
DotNetHookLibrary : UI調用了他里面的AssembleViewer的IsDotNet方法來驗證目標進程是否.Net程序 。
Injector : 這個就是上面介紹的實現注入的核心DLL了。其實就是先把這個工程生成的DLL注入到目標進程,再由這個DLL在目標進程里將我們要注入的DLL再加載進目標進程。
NHProfLancher : 這個是為了解決上面講的第二個問題:加熱鍵而寫的一個NHProf的加載程序。這里就先不講了,后面會提到。
Win32SDK : 封裝API的一個類庫工程,本來我的意思是 慢慢的把所有API都封裝到這里面,那么以后只要引用這個工程就可以方便的調用API了,不過細想后發現其實意義不大,因為完全沒有必要因為只使用了一個API,就引用這么一個龐大的類庫,不過已經寫了而且引用了,就放在這里了。
看完了上面的所有工程,細心的可能已經發現了,上節里我們使用的那個測試用的被注入的程序的工程這里並沒有,是的,為了方便同時調用,我把那個工程放在了另外一個解決方案里,就在附錄的根目錄下的 測試 文件夾里,測試文件里有一個Test.sln 。打開它 。
這個就是測試用的被注入的目標程序了。
OKAY 描述到這里,基本上可以講Injector.dll 的使用方法了。
要利用Injector.dll實現進程的注入,首先你得在你的主程序里添加對他的引用(Add Reference)了 ,在我們這里,當然是在UI這個工程里添加對他的引用了。 能打開VC CLR Library類型工程的,直接引用工程,不行的,Browser選中Libs下的 Injector.dll 。引用后,只要在需要的地方調用App.Security.Injector.Launch就可以實現注入了。
我們先來看看 App.Security.Injector.Launch 的定義
//----------------------------------------------------------------------------- // 方法描述 : 啟動注入,此函數執行成功,注入便已成功 , 並會在目標進程執行 className.methodName // 參數 : // windowHandle 目標進程主窗體的句柄 // assembly 要注入到目標進程的Assembly // className 注入后,要執行的方法的類名 // methodName 注入后,要執行的方法名 // 返回值 : void //----------------------------------------------------------------------------- void Injector::Launch(System::IntPtr windowHandle, System::Reflection::Assembly^ assembly, System::String^ className, System::String^ methodName)
再看看我們UI工程里的實際代碼 (這段代碼就在附加進程按鈕的單擊事件里:))
App.Security.Injector.Launch( targetProcess.MainWindowHandle, typeof(ProcessViewer.ProcessViewer).Assembly, typeof(ProcessViewer.ProcessViewer).FullName, "Entry" );
那么結合前面所講的。這句執行完成后。ProcessViewer.ProcessViewer所在的Assembly(就是我們的ProcessViewer工程編譯后生成的DLL了) 就已經被注入到 主窗體句柄為 targetProcess.MainWindowHandle 的目標進程里了。並且會在目標進程里執行ProcessViewer.ProcessViewer類的Entry 方法 。
當然要明白剛才講的,還有二個疑問需要解決,第一個就是 targetProcess.MainWindowHandle 從那里來的。 這個其實不難理解,前面也講了,UI有列出所有進程的功能,而且還可以選擇一個進程實現注入,那么這個targetProcess自然就是你選中的那個進程的對象了, targetProcess的類型就是Process 。第二個剛才你也沒講過引用了ProcessViewer那個工程, 直接這樣typeof(ProcessViewer.ProcessViewer).Assembly 不會出錯嗎 ??? 這個嗎,當然是要先引用一下ProcessViewer工程的,那么又有問題了,ProcessViewer是要被注入目標進程執行的,那么有必要還要先要加載到我們這個進程里嗎?答案是原則上是沒有必要的,事實上我們只要知道了這個Assembly的路徑就完全可以實現注入了,但在我們的工程里,把傳給的Lancher的第二個參數,寫成了 要傳遞一個Assembly對象,所以在我們的工程里是要引入一下的,這樣寫起來的代碼,看起來 顯得 更優雅一點。 :)
那么依次類推,要把HibernatingRhinos.Profiler.Appender.dll注入到目標進程,並在目標進程里執行HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize() 應該怎么做呢
App.Security.Injector.Launch( targetProcess.MainWindowHandle, typeof(HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler).Assembly, typeof(HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler).FullName, "Initialize" );
你的第一反應一定是這樣寫,原則上(汗~~~,又是原則上)這樣寫應該也是可以的,但實際上我們並沒有這樣做(事實上也不能這么,你試試就知道了),我們還是如前面描述的那些那樣先把ProcessViewer送到了目標進程,然后在目標進程執行了ProcessViewer的Entry方法。然后才在Entry方法里加載了HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler ,並利用反射原理執行了他的Initialize 方法。我們來看看 ProcessViewer的Entry代碼。
public void Entry() { string path = this.OriginalPath + "\\HibernatingRhinos.Profiler.Appender.dll"; string f = System.Environment.CurrentDirectory + "\\HibernatingRhinos.Profiler.Appender.dll"; if (!File.Exists(f)) { File.Copy(path, f); } string className = "HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler"; Assembly assembly = Assembly.LoadFile(f) ; Type type = assembly.GetType(className); MethodInfo mi = type.GetMethod( "Initialize", BindingFlags.Public | BindingFlags.Static, null, Type.EmptyTypes, null ); mi.Invoke(null, null); }
上面的 this.OriginalPath 是在Injector.dll 里傳過來,實際就是ProcessViewer.dll所在的目錄。
在這段代碼先將this.OriginalPath 目錄下的HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.dll 先拷貝到目標進程所在的目錄里(所以你必須在UI的工程里也引用一下HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.dll ,這樣才能保證this.OriginalPath 的目錄下有這個DLL),這是必須的,因為只有在目標進程里才有Nhibernate的相關DLL,而HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler的初始化是需要這些的,這也是我們不直接將HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.dll送到目標進程的原因之一 , 另外一個原因是你會發現如果有了ProcessViewer的這個Entry的話那么在將HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.dll加載到目標進程前后,可以更加方便的做一些其它處理,當然這里我們 沒有做處理了,但是你要想的話,是很方便的,例如,注入后, 提示一下什么的 …… 當然這是閑話了,將HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.dll拷貝到目標進程的目錄后,就是加載它,然后 就是 利用 反射,調用他的Initialize方法。 注意ProcessViewer的Entry 是在目標進程執行的,所以這里的一切都是在目標進程執行 的
至此,我們的第一個問題就算解決了。 下面我們解決第二個問題。
其實剛才在描述我們源碼的目錄時,已經提到了,實現第二個問題的方法就是為NHProf.exe寫一個Lancher , 也就是我們的那個NHProfLancher 工程了。
代碼也不細講了,看一看就明白了,不是很麻煩,在這里只要知道,在這個Lancher里主要做了哪些工作就可以了。
這個Lancher的只要有兩方面的工作 1,注冊熱鍵,並捕獲熱鍵來打開我們的附加到進程工具。 2. 在另外一個線程里,加載執行NHProf.exe 並且一直等到NHPorf.exe退出為止,NHProf.exe一旦退出,此線程立馬執行this.close 關閉本程序。然后在Form_Closed事件的處理函數里, 取消熱鍵 。
OKAY, 實現原理介紹完了 , 具體的可以看源碼,當然源碼編譯后,是需要把編譯后的文件,全部拷貝到NHibernateProfiler的根目錄的,拷過去后還要修改NHProfLancher.exe.config 。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="NHProfPath" value="NHProf.exe"/> <add key="AttachToProcessPath" value="AttachToProcess.exe"/> </appSettings> </configuration>
其中NHProfPath對應的是NHProf.exe的路徑,可以是相對的,也可以是絕對的,AttachToProcessPath對應的就是附加到進程工具的路徑了,同樣的也是可以相對,也可以絕對