Pre:
逆向分析以太坊智能合约(part2) 学习记录+Demo测试原文地址:https://arvanaghi.com/blog/reversing-ethereum-smart-contracts-pt2/
在part1中,我们初步逆向分析了Greeter.sol
合约。我们仔细研究了Greeter.sol
的dispatcher
。作为合约的一部分,dispatch可以接收交易数据,决定应该发送哪个函数。
Greeter.sol
:
part1原文里的
Greeter.sol
是稍微简单一些的,但我是直接去github找的代码,是跟part2里的一样的
1 | pragma solidity >=0.4.22 <0.6.0; |
这次让我们分析一下kill()
方法。
每份智能合约中都存在dispatcher
。kill()
的函数标识符为0x41c0e1b5
,这是因为该ID是kill()
方法 keccak256哈希
的前4个字节:
1 | keccak256("kill()") = 41c0e1b5... |
Dispatcher
会检查发往合约的交易数据,决定是否要与kill()
函数进行通信。大家可以回顾之前那篇文章,详细了解我们分解过的那些指令。回顾一下。
这里我们分析下当dispatcher把我们带到这个函数时会发生什么情况。
kill():
Greeter.sol
中的kill()
函数实际上继承自上一层的mortal
合约:
1 | contract mortal { |
由于greeter is mortal
,因此greeter
可以访问mortal
的所有函数以及成员。即便我们只是把greeter
的字节码加载到Binary Ninja
中,由于存在这种继承关系,该字节码中也会包含mortal
的所有函数。
kill()
函数有以下执行逻辑:
1、检查发送交易的地址是否与合约的address owner
成员相匹配。
2、如果相匹配,kill()
就会调用内置的selfdestruct
函数,将owner
地址以参数形式传入。
selfdestruct
实际上是一种操作码(opcode),因此其实已经内置在EVM(以太坊虚拟机)中。理论上讲,这是我们从以太坊区块链上删除智能合约的唯一方法。如果你的合约接收以太币(ether),那么你以参数形式传递给selfdestruct
的那个地址会在合约代码被删除前接收存储在你合约中的所有以太币。
selfdestruct
(EIP6之前称为suicide
)的功能是允许人们通过删除旧的或者未使用的合约来清理区块链。如果有人将以太币发送给已经销毁的合约,那么这些以太币将永远丢失,因为合约地址已经不再具备将以太币转移到另一个地址的任何代码。大家可以访问此链接了解关于selfdestruct
的更多信息。
反汇编kill()函数:
接下来让我们反汇编kill()
,检查相关操作码。
与原文的图不同,以我自己反编译出来的图为准,参考思路,自己推导
优化:
作为一门高级语言,在编写智能合约这样艰巨的任务方面Solidity已经表现得非常不错。然而,由于这门语言仍属于较新颖的一门语言(对于以太坊来说也是如此),因此Solidity编译器solc在编译出来的字节码中仍然会产生冗余的指令。
solc
编译器有一个optimizer
标志,可以很好地解决这些冗余问题。
1 | solc --bin-runtime --optimize --optimize-runs 200 Greeter.sol |
1 | $ solc --bin-runtime --optimize --optimize-runs 200 Greeter.sol |
优化后的opcode确实短一些,相应的图也简洁一些了。我们会继续分析经过优化后的字节码。
进入kill()函数:
继续分析kill()
的指令:
模拟栈的操作过程中,暂时搞不清楚左位移怎么计算,暂时先跳过。直接看online decompile的输出好了
1 | // Inputs[2] |
EQ
那里会检查 调用者地址 是否等于 合约所有者的地址。
其实这对应于kill()
函数中if (msg.sender == owner)
这条语句。
1 | /* Function to recover the funds on the contract */ |
1 | CALLER // msg.sender压入栈顶,经过前面的判断也就是owner了 |
这个指令块的最后一条指令是SELFDESTRUCT
,该指令会将栈顶元素当成存储以太币的所有合约的目的地址,然后删除所有合约的代码。现在我们的合约代码已经被删除,存储在合约中的所有以太币已经发送到owner。Over…
Summary:
原文没有给出编译器的版本,我用的0.5.17,比较新升级过的编译器应该是采用了EIP-145
的建议:
-
EVM 缺少按 位移位运算符,但支持其他逻辑和算术运算符。
-
移位操作可以通过算术运算符实现,但成本更高,并且需要主机更多的处理时间。
-
实现SHL和SHR使用算术每 35 个 gas,而建议的指令需要 3 个 gas。
所以,新编译器的结果少了很多指令,与原文的汇编代码和图都有很大的出入。但思路是差不多的,可以自己参考推导,模拟栈的操作。
evm的很多优化升级都是围绕着gas
进行的,不断追求地去降低交易成本。每个opcode都有它对应的gas消耗量,新版编译器(带优化参数)能编译出更短的opcode,可大大降低gas。每个函数都降低一些gas,那成千上万的交易就会省下很多gas.
Refs:
-
Understanding Solidity Assembly: Using
shr
andshl
for Byte Manipulation -
https://github.com/CoinCulture/evm-tools/blob/master/analysis/guide.md (每个opcode的出入参个数)
-
https://www.ethervm.io/#MSTORE (这里可以看每个opcode对stack的操作)