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