ward 发表于 2012-7-16 13:10:36

調用機械碼的技巧與 MemoryDLL 的故事

AutoIt,原先只是 Windows 的自動化小工具,但在第三版之後蛻變成完整的程式語言。其不僅具有腳本語言簡單易學的特性,再加上強大的內建函式與豐富的外部函式庫,讓我們可以輕易地完成日常的自動化工作,進而開發各種程式與軟體。

然而執行速度慢向來是腳本語言的缺點。當然一般性作業,如檔案操作等,並不會感到明顯差異。不過講到演算法,比如簡單的 MD5,或是一些要求高速的關鍵運算,用 AutoIt 來寫就會力不從心。解決的辦法之一,是使用外部的 DLL 函式庫,畢竟 AuotIt 提供了完善的機制讓呼叫 DLL 非常容易。但 DLL 畢竟是獨立的檔案,看在一個有潔癖的程式設計者眼中,無法編譯成單一檔案就是一種不完美。至於用 FileInstall 之類臨時寫入硬碟的笨方法就別說了,不過在早期除了笨方法以外,也沒別的方法了,所以內建的 SQLite.dll.au3 就是這樣做的。

那有什麼方法可以不用臨時寫入硬碟,不需額外檔案,又能有 DLL 函式的速度呢?有的,答案就是直接調用機械碼。什麼是機械碼呢?其實就是 C 或 ASM 所編譯或組譯出的二進位碼。不論是 OBJ、EXE、DLL 檔,都是內含機械碼的。在適當的設計下,AutoIt 中可用 Hex 字串儲存機械碼並寫入內存,之後透過呼叫內存位址來調用。如此一來,執行的速度就跟呼叫 C 或 ASM 寫成的 DLL 一樣快了。

此一呼叫內存位址調用機械碼的技巧,在官方論壇上最早由 Lazycat 所提出,方式是使用 Windows 的 CallWindowProc API。當然這是一個很天才的想法,但無奈限制很多,比如僅允許四個參數,且只能呼叫 stdcall 函式,最麻煩的是機械碼中不能有固定內存位址的呼叫或存取。但這至少是一個開始,一道曙光,由此開啟了 AutoIt 無限的可能性。

在 Lazycat 提出做法之後,第一個將調用機械碼發揚光大的,正是區區在下。我用 CallWindowProc 的方式寫了一系列的機械碼函式庫,包含 MD5、SHA1、CRC32、RC4、Base64、XXTEA 等。當然源碼多半是網上下載的,但要改造成適合 CallWindowProc 調用的機械碼卻費了一翻苦心。這些機械碼函式跟原本一些用純 AutoIt 寫的相同函式比起來,同樣的易用但速度飛快。很快就獲得廣大使用者的喜愛,至今仍是官方論壇下載次數最多的函式庫之一。AutoIt 中文版中內建的 ACN_HASH.au3,也是此一函式庫的重命名版。但我不敢居功,因為發現這個方法的 Lazycat 大大,才是真正該被喝彩的人。

不過這種方式還是有問題的,主要就是上面已經提到,適合 CallWindowProc 的機械碼函式限制很多。要依照這些限制生成適當的機械碼,並不是一個簡單的工作。一般從 C 源碼編譯出來的結果,若沒有在機械碼的尺度上做修改,通常是不適用的。能正確修改出所需機械碼的人,大概都在看雪論壇玩 ShellCode,很少會用 AutoIt 寫程式了。所以在寫了上述幾個函式後,我就一直在想如何將這過程簡單化,讓一般使用者也能開發自己的機械碼函式。

回過頭來想,生成可直接調用的機械碼並不容易,但生成 DLL 就容易的多。大部份的程式設計師都知道如何製作 DLL 函式庫,不然至少也知道如何搜尋或下載。所以山不轉路轉,內嵌機械碼的函式製作不易,不如開發內嵌 DLL 檔案的技術吧。你有份 C 源碼想應用在 AutoIt 裡,卻不知道怎樣製作成機械碼函式......沒關係,把它編譯成 DLL 總會了吧,然後內嵌到 AutoIt 裡調用。瞧,既沒有額外檔案,又執行速度飛快,問題不都解決了!

這就是 MemoryDLL 函式庫的開始。內嵌 DLL 不是什麼困難的技術,就模擬系統的 DLL Loader 做的事就可以了。網路上查了 Joachim Bauch 大大的教學,非常簡單,照著做就行了。難就難在 AutoIt 中如何調用內存位址的老問題上。上面提到,舊的內存位址調用技巧,由 Lazycat 所提出的 CallWindowProc,只能調用四個參數的 stdcall 函式。但 DLL 檔案的函式卻可能是任意參數或 cdecl 規範的。原本嘗試的做法是由 CallWindowProc 先調用一段堆疊變換小程式,再轉到真正要執行的內存位址上,但仍有一些惱人的限制無法解決,如指標變數無法回傳等,我為這問題頭大了很久。

後來靈機一動,其實 AutoIt 內建的 DllCall 就可以適用任意參數的函式,又能正確回傳指標變數,不過只限於調用 DLL 檔案罷了。那何不直接 Hack 這個函式來達到目的呢?此方式研究成功後,我興奮的差點跳了起來,因為這絕對是 AutoIt 語言的革命性發明。由此,MemoryDll 函式庫正式完工了,而內建的 MemoryFuncCall,即任意內存調用函式,也被應用在許多函式庫中。此一函式可說是把 AutoIt 的發展帶入新的紀元,比如頂頂大名的 AutoItObject 函式庫裡,位址呼叫函式就是由 MemoryFuncCall 的程式碼修改而來。

然而,MemoryDll 雖然好用,也被用來內嵌各式的 DLL 函式庫,但我還是醉心於純機械碼函式的開發。畢竟製作 DLL 雖然簡單,仍比不上機械碼函式的簡潔與美麗。幾年過去了,我陸續推出了各式的機械碼函式庫。如各式 Hash、加密、壓縮等多達數十種演算法(包含 32/64 位元版本的 MD5、SHA1、SHA2、DES、AES、LZMA、zLib 等與其它太多),和內嵌本文即時編譯並執行的 C 和 ASM 工具(以 TCC 和 FASM 為基礎),此系列函式破除了腳本語言速度就一定慢的迷思,也被各路高手廣範地應用。比如說 AutoItObject 函式庫裡的 Base64 解碼函式就是出自我手。終於 AutoIt 團隊也注意到了內存調用的重要性,在新的版本中加入了 DllCallAddress 函式,其實也就是 MemoryFuncCall 的內建版。不得不說,此函式的加入完全就是受到我的影響,但從此我開發的 MemoryFuncCall 也可以說是功成身退了。

一直以來,官方論壇上除了我以外少有人能推出其它的機械碼函式,究其原因就是生成可調用的機械碼困難度太高。但隨著經驗的累積,我已經找到了一條簡單的路,把這一過程的難度降到最低。只要照著我的教學做,初學者也能輕易將 C 源碼轉換成 AutoIt 裡可調用的機械碼。當然,希望學會的人也把你們的成果分享出來,讓每個 AutoIt 愛用者的資源也跟著豐富起來。

人老了總是多話的。原本想說簡介一下但不知不覺就拉裡拉雜的講了一堆,正式的教學就在下次開始吧。當然,有空的話就回覆一下,至少讓我知道各位有沒有興趣學啊~

xiaochuan 发表于 2012-7-16 13:25:56

期待楼主下篇大作{:face (280):}

wsfda 发表于 2012-7-16 13:45:03

顶你楼主,期待中.....

wsfda 发表于 2012-7-16 13:47:37

一个小小的建议,如果楼主写简体中文方便的话,希望楼主能写简体,我看繁体比较吃力
阅读起来稍慢,有时候需要查字典,不知道其他网友怎么样,
只是个人建议,当然,如果楼主简体吃力那还是繁体吧,呵呵

3mile 发表于 2012-7-16 14:38:29

期待楼主的教学.

lpxx 发表于 2012-7-16 16:25:53

繁体字看着还真不习惯。

ashfinal 发表于 2012-7-16 18:48:02

看上去不错支持 支持 {:face (125):}

zldfsz 发表于 2012-7-16 20:16:10

繁体字虽然看起来费劲,不过只要有楼主的大作,慢慢看也是很乐意的,实在不行我们还可以转换为简体再看

whitehead 发表于 2012-7-16 22:36:23

期待楼主的教学.

xiaowo 发表于 2012-7-16 23:59:34

希望LZ继续,我排队学习

xms77 发表于 2012-7-18 21:55:53

回复 4# wsfda
读繁体字完全没有压力

xms77 发表于 2012-7-18 21:56:22

回复 1# ward
顶楼主,期待楼主的大作

penguinl 发表于 2012-7-21 22:42:17

很期待之后的教学!

都市浪子666 发表于 2012-7-21 22:53:46

坐等楼主开课。。。谢谢楼主了。。。

test10942 发表于 2012-7-26 10:29:36

好教程,赞一个!希望很快看到2
页: [1] 2
查看完整版本: 調用機械碼的技巧與 MemoryDLL 的故事