_msgsender()绕过

存在漏洞的代码

    function _msgSender() internal override virtual view returns (address payable ret) {
        if (msg.data.length >= 24 && isTrustedForwarder(msg.sender)) {
            // At this point we know that the sender is a trusted forwarder,
            // so we trust that the last bytes of msg.data are the verified sender address.
            // extract sender address from the end of msg.data
            assembly {
                ret := shr(96,calldataload(sub(calldatasize(),20)))
            }
        } else {
            return msg.sender;
        }
    }

我们看到_msgSender()的实现上,并不总是返回msg.sender。在某一条件下,会返回一个奇怪的汇编代码 (通过注释也能看出,这段代码返回的是calldata中最后一个地址变量) ret := shr(96,calldataload(sub(calldatasize(),20)))

Note: calldatasize 返回的是 msg.data 的字节长度,比如 call(addr1, addr2) 的 calldatasize = 4 + 20 + 20 = 44 bytes

calldataload(i) 返回从i开始的一个 uint256,即 msg.data[i:i+32] (这里的32是bytes=256bit), 所以 calldataload(sub(calldatasize(),20)) 返回的是 msg.data 的后 20bytes (一个address的长度)

以call(0x70873211cb64c1d4ec027ea63a399a7d07c4085b, 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)为例: 返回的就是: res = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc200000000000…

接着 shr(96, res),将 res 右移96位(12bytes),变成 0x00000….C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2

Note: 关于Forwarder以及本合约中的_MsgSender实现,是为了兼容GSN(https://opengsn.org/) Token

GSN Token可以帮助用户,在没有原生代币 (native token) 的情况下发送交易。我们知道发送交易是需要Gas费的,一般Gas费都是用某个链的原生代币来支付 (比如: ETH)

那为什么存在没有原生代币,还需要发交易的情况呢?这个如果有过经历的很容易理解。比如我刚入行区块链的时候,参加过一次Web3活动,刚好某个项目现场发放aridrop。于是,我第一次下载钱包 (入坑开始..),生成地址,项目方把他们的代币转到我这个新开的地址

但是,由于新的账户,里面一个ETH都没有,这些代币完全没法交易或提走。这时,GSN作为一个第三方的服务,可以代替我支付Gas费,相应的,我用我空投得到代币支付给他。

简单来说,我对我要做的事情进行签名,通过网络发送给GSN,GSN生成一笔交易,这笔交易内部完成了我的交易。问题来了,由于GSN代我发送的交易,msg.sender是GSN,如何将内部我的地址暴露出来作为这笔交易的"msg.sender"呢

这时,支持GSN的合约,就会使用_msgSender这样的方法,对数据进行一次剥离,暴露出真实的msg.sender

参考http://101.37.161.120/defi/docs/rekt/2022-01-18-crosswise/

Last updated