0x00 前言git
下拉最後看演示效果。項目地址程序員
原本這應該是一個很和諧的感恩節假期,原本我能夠很清閒的寫完全部做業而後隨便看點論文打發時間,原本能夠很美好的,僞裝本身不是個程序員。然鵝,一切都由於一篇論文而起,那是篇今年(2018)頂會的論文,內容竟然是以太訪智能合約的逆向。因而好奇心就起來了,以太訪有逆向工具了,爲了追上歷史的潮流,NEO是否是也應該有一個逆向工具呢?其實在幾個月前我寫合約的時候就有這種想NEO本身定義了一套指令集,想要經過這些指令去分析交易簡直讓人崩潰。因而,因此,所以,一股子衝動上來,我就開始做死的坑本身。github
0x01 探索安全
假期有五天,個人第一天計劃是用一天時間來研究這個逆向工具,有頭緒就好,不着急動手。而後第二第三第四以及第五天就寫做業啊,看論文啊之類的。因而第一天。函數
我在網上搜了不少,基本上針對以太訪的合約逆向工具都是17年左右出來的。比較完整的是Porosity,也是號稱第一個以太訪反編譯工具,因而我下載了這個傢伙,而後把反編譯以外的文件所有刪掉。研究以後發現這個項目其實仍是蠻原始的,跟咱們熟悉的C#或者Java的反編譯工具相比,這個Porosity能作的其實頗有限,基本上屬於一個輔助工具,畢竟這個工具的做者彷佛是打CTF的,因此人家開發工具固然是最適合本身就行了。另一個號稱比較完整甚至號稱能夠反編譯ETH/NEO/EOS/BTC的工具是OCTOPUS,因而我也跑去看了,可是感受整體完成度不是很高,連指令解析都還處於TODO狀態,因而提交了下本身在研究的時候發現的bug以後就轉去找別的了。 雖然這兩個項目對個人直接幫助並不大(能夠抄代碼)那種,可是研究完這兩個項目個人衝動更強烈了。好奇心使我滅亡。工具
0x02 思考開發工具
網上搜了不少,也看了關於棧虛擬機分析的文章,可是依然感受全部的信息在個人腦子裏都是碎片化的,我不知道如何入手,徹底沒有頭緒。優化
我起初的想法是,由於AVM是經過C#的字節碼翻譯過來的,若是我把這個對應着翻譯回去,豈不是能夠直接用C#的反編譯工具,這簡直是美滋滋。因而我開始研究李總的NEOVM和NEOCompiler源碼。年初的時候,也就是過年那會,我其實研究過李總的這個虛擬機和編譯器的源碼,可是當時對NEO總體還不是很熟悉,啃這個源碼就像嚼糠同樣,含淚嚥下去了,也消化不動。如今將近一年過去,中間又斷斷續續看過一些,如今從新看,感受輕鬆了許多,至少每一個文件幹嗎的都還記得。研究Compiler的方法就是看源碼和打Log,而後根據輸出對應源碼來理解邏輯,蛋是,很快我就發現這樣很蛋疼,由於,Compiler在作轉換的時候,一條C#指令碼可能對應着多條NEO指令,同時多條C#指令也可能對應着相同的NEO指令。再加上C#自己的指令碼晦澀程度遠超NEO,因而我很快繳械投降放棄了Compiler。ui
編譯的過程搞不定,至少還能夠研究下執行的過程,這個虛擬機老是要對每一條指令進行解析的,若是我能搞懂全部的指令,那麼豈不是就能夠針對這些指令相應的生成高級語言代碼!翻譯
0x03 起步
第一天的假期就這麼過去了,我失去了很重要的一天,可是我想了想,剩下來還有四天,擠一擠仍是能夠完成原來的計劃的。因而我決定次日繼續搞這個東東。
在第一天的夜裏,我躺在牀上的時候,忽然就想到其實我徹底能夠沒必要這麼糾結,我徹底不須要作一個像C#反編譯工具那麼牛逼哄哄的工具出來,若是開發一個功能完整的工具備困難,那麼我就從最簡單的開始。因而我決定先不考慮函數調用,系統調用,合約調用等等複雜操做。從最簡單的邏輯代碼逆向開始。
因而次日我終於開始敲代碼了,個人第一步是把李總的對AVM解析的TS項目遷移到C#上來。至少讓個人項目能夠輸出點東西。這個工做純粹是搬磚,把李總的ts代碼拿過來改爲C#,而後加上文件讀寫和解析,到真正能運行的時候,寶貴的上午已通過去了。因而我又遇到了瓶頸。
如何使用這些SAM呢?我開始趴在桌子上抓頭髮,因爲我本人沒有反編譯工具開發的經驗,因此能憑藉的只有大二學計組時學到的逆向和系統知識以及NEOVM的源碼。因此我最後決定,把NEOVM整合到個人反編譯工具裏,具體來講,就是模擬合約執行的過程。
因爲我在這個模擬的過程當中須要定義新變量以及追蹤變量在堆棧中的執行過程,因此我不能使用NEOVM自己自帶的類型也好執行棧也好的全部整套邏輯。大刀闊斧的刪掉全部我不能用的文件以後,NEOVM還剩下一個ExecuteEngine。
好吧,完美,因而我從新定義合約的方法類用來存儲合約裏的函數,從新定義執行棧的元素用來存儲合約執行過程當中對堆棧讀寫的變量。再而後就是對NEO的指令進行一對一的解析和翻譯。
0x04 小成
這個時候其實已經次日的夜裏了,因爲我在的城市夜裏很不安全,我決定先回家。因而第三天,莫名的力量誘使着我:「只是稍微看一小會,稍微優化一點點,而後就開始作做業。」
爲了研究NEO的指令,我首先寫一個空的合約,編譯以後記錄SAM, 而後再定義一個簡單變量並賦值,再記錄SAM,如此反覆,而後反覆對比不一樣版本的合約和相應的SAM,再對照着NEOVM的解析代碼來仔細研究每個指令的執行原理。同理研究函數調用過程。
等到對這些指令大概熟悉以後,開始對每個指令進行解析翻譯。
因爲在NEO中,每個函數都是以指令RET做爲結束標誌符,並且這個指令不會在別的地方出現,所以我以RET爲標記來獲取每個函數的指令並保存在NEOMethod對象中。函數的名字由sub_ 前綴加上函數首地址,Main函數老是在合約的第一個,因此很容易獲取合約入口,Main函數將仍舊以Main命名。
函數調用的指令是CALL,後面跟着目標函數的地址偏移,所以只要計算出目標地址的位置,就能夠直接獲取到目標地址的名稱。此處有個問題就是函數的參數以及返回值,這個問題我尚未想到好的解決方案。
在合約執行過程當中,每個會進入堆棧的變量都會有一個名字,變量起名的規則是由一個variable_count來記錄當前函數的變量個數,而後在前面加上v_ 做爲變量標記符。此外,因爲有些變量可能只會用一次,我對每個變量的引用次數進行記錄,在合約解析完成以後,那些只用了一次的變量將會被移除。
系統調用是️虛擬機提供的那些接口,這些系統調用直接就是接口的名字,所以咱們能夠經過字符串匹配來知道合約在調用哪個接口。在反編譯工具中,我對NEOVM提供的每個接口都進行了整理,記錄了他們須要的參數數量以及輸出的參數數量,所以當解析到SysCall的時候,就能夠直接翻譯爲指定的系統調用並添加輸入和輸出。
其實以上已是兩天的工做量了,沒錯,五天的假期已經被我一時衝動消耗掉了四天。但願最後一天我能即寫完做業又看得完文檔還作的了ppt完得成實驗。
項目地址:https://github.com/Liaojinghui/NEODecompiler
歡迎有興趣的小夥伴和我一塊兒完成這個項目。 最後是反編譯的演示結果:
合約源碼:
public static void Main() { int a = 2; string aa="hello"; string bb = "world"; string cc = aa + bb; // uint b = Blockchain.GetHeight(); }
反編譯結果:
00 static void Main(string[] args) { 01 Array v_array_0 = new Array<?>(5); 04 byte[] v_array_1 = new byte[]("hello "); 11 v_array_0[0] = v_array_1; 12 byte[] v_array_2 = new byte[]("world"); 1e v_array_0[1] = v_array_2; 26 v_array_0[2] = 0; 27 Jump 49 2f v_array_1 = v_array_0[0]; 34 v_array_2 = v_array_0[1]; 35 v_array_1 = v_array_1 + v_array_2 // (hello world) 3c v_array_0[3] = v_array_1; 42 int v_int_0 = v_array_0[2]; 44 v_int_0 = 1 + v_int_0; 4b v_array_0[2] = v_int_0; 50 v_int_0 = v_array_0[2]; 52 bool v_bool_0 = v_int_0 < 10; 59 v_array_0[4] = v_bool_0; 5e v_bool_0 = v_array_0[4]; 5f Jump 27 } 00:PUSH5 01:NEWARRAY 02:TOALTSTACK 03:NOP 04:PUSHBYTES6[0x68656c6c6f20] (hello ) 0b:FROMALTSTACK 0c:DUP 0d:TOALTSTACK 0e:PUSH0(false) 0f:PUSH2 10:ROLL 11:SETITEM 12:PUSHBYTES5[0x776f726c64] (world) 18:FROMALTSTACK 19:DUP 1a:TOALTSTACK 1b:PUSH1(true) 1c:PUSH2 1d:ROLL 1e:SETITEM 1f:PUSH0(false) 20:FROMALTSTACK 21:DUP 22:TOALTSTACK 23:PUSH2 24:PUSH2 25:ROLL 26:SETITEM 27:JMP[37] 2a:NOP 2b:FROMALTSTACK 2c:DUP 2d:TOALTSTACK 2e:PUSH0(false) 2f:PICKITEM 30:FROMALTSTACK 31:DUP 32:TOALTSTACK 33:PUSH1(true) 34:PICKITEM 35:CAT 36:FROMALTSTACK 37:DUP 38:TOALTSTACK 39:PUSH3 3a:PUSH2 3b:ROLL 3c:SETITEM 3d:NOP 3e:FROMALTSTACK 3f:DUP 40:TOALTSTACK 41:PUSH2 42:PICKITEM 43:PUSH1(true) 44:ADD 45:FROMALTSTACK 46:DUP 47:TOALTSTACK 48:PUSH2 49:PUSH2 4a:ROLL 4b:SETITEM 4c:FROMALTSTACK 4d:DUP 4e:TOALTSTACK 4f:PUSH2 50:PICKITEM 51:PUSH10 52:LT 53:FROMALTSTACK 54:DUP 55:TOALTSTACK 56:PUSH4 57:PUSH2 58:ROLL 59:SETITEM 5a:FROMALTSTACK 5b:DUP 5c:TOALTSTACK 5d:PUSH4 5e:PICKITEM 5f:JMPIF[-53] 62:NOP 63:FROMALTSTACK 64:DROP 65:RET