Parity钱包事件
知识点预览
delegatecall与call
-
delegatecall函数是一种特殊的函数调用,它允许合约在当前合约的上下文中执行另一个合约的代码。具体来说,delegatecall函数会将当前合约的存储、内存和栈状态传递给被调用合约,并在被调用合约中执行指定的代码。这意味着,在被调用合约中,可以访问当前合约的存储、内存和栈状态,并且可以修改它们。 因此,delegatecall函数通常用于实现库或升级机制。
-
相比之下,call函数是一种普通的函数调用,它允许合约在当前上下文中执行另一个合约的代码。但是,在使用call函数时,被调用合约无法访问当前合约的存储、内存和栈状态。
为何要用bytes4转换
bytes4类型是Solidity中固定长度为4字节(32位)的数据类型。在ABI编码中,bytes4类型用于表示函数签名。
因此,在使用abi.encodeWithSignature()方法生成包含函数签名和参数的字节数组时,需要先将计算得到的函数签名转换为bytes4类型,并将其作为生成字节数组前缀之一。
在外部调用合约中的某个函数时,需要将该函数的签名和参数编码成一个字节数组,并将其作为msg.data传递给合约。
在Solidity内部中,可以使用keccak256()方法计算出函数签名对应的bytes4值。
bytes4 sig = bytes4(keccak256("myFunction(uint256,string)"));
bytes memory data = abi.encodeWithSignature("myFunction(uint256,string)", 123, "hello");
data = abi.encodePacked(sig, data);
- 在上述代码中,我们首先使用keccak256()方法计算出"myFunction(uint256,string)"字符串对应的函数签名,并将其转换为bytes4类型。
- 然后我们使用abi.encodeWithSignature()方法生成包含参数值编码后的字节数组,并将其与函数签名前缀使用abi.encodePacked()方法拼接在一起,得到最终的msg.data字节数组。
事件详情
这次的Parity钱包漏洞是由于代码逻辑不严谨导致的黑客越权攻击行为——delegatecall。
黑客的攻击思路:尝试去修改“Parity”钱包的汇款地址。让所有的人都汇款给自己。
首先在合约中,我们看到了钱包初始化函数。我们知道“Parity”钱包的机制是由多人的私钥进行签名才能够进行汇款等操作,所以这里的地址类型是一个数组。
随着函数的进行,它在初始化的时候会执行initMultiowned(_owners, _required);
函数。此功能是初始化合约钱包,并对钱包所有者的地址进行更新。
下面是钱包合约的内容:
在此合约中,我们能看到支付函数中存在_walletLibrary.delegatecall(msg.data);
。而我们知道倘若我们令其系统执行了此函数,那么我们就可以随心所欲的执行所有_walletLibrary
中的内容了。
此时,我们通过往这个合约地址转账一个value = 0, msg.data.length > 0的交易,以执行_walletLibrary.delegatecall分支。
并将构造的msg.data中传入我们要执行的initWallet ()
函数。而此类函数的特性也就帮助我们将钱包进行了初始化。
又由于钱包初始化函数 initMultiowned()
未做校验,可以被多次调用。所以尽管钱包在最初的时候进行了合法的初始化,但是我攻击者可以将其系统中进行修改,迫使系统代码自行将所有的地址更变为攻击值的地址值。
之后,攻击者执行execute()函数。
而我们可以看到函数中的external onlyowner
。
将这两个修饰符连在一起使用,可以创建只能从合约外部被合约拥有者调用的函数。
此函数使攻击者不能轻易的进行转账操作,但是我们之前所有的操作均是为了将此函数绕过(通过修改owner地址)。
此时我们的黑客就可以收钱了hhh。