EthEnc

点击此处获得更好的阅读体验


WriteUp来源

官方WP

题目考点

  • 区块链

解题思路

过了程序的pow可以看到有4个菜单:

1
2
3
4
5
6
7
8
9
10
11
12
We design a pretty easy contract game. Enjoy it!
1. Create a game account
2. Deploy a game contract
3. Request for flag
4. Get source code
Game environment: Ropsten testnet

Option 1, get an account which will be used to deploy the contract;
Before option 2, please transfer some eth to this account (for gas);
Option 2, the robot will use the account to deploy the contract for the problem;
Option 3, use this option to obtain the flag after emit OhSendFlag(address addr) event.
You can finish this challenge in a lot of connections.

菜单4可以看到一部分源码,从中可以知道我们需要触发payforflag函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pragma solidity ^0.6.12;

contract EthEnc {
.........................
.........................
.........................
event OhSendFlag(address addr);

modifier auth {
require(msg.sender == owner || msg.sender == address(this), "EthEnc: not authorized");
_;
}

function payforflag() public auth {
require(output == 2282910687825444608285583946662268071674116917685196567156);
emit OhSendFlag(msg.sender);
selfdestruct(msg.sender);
}
.........................
.........................
.........................

}

先通过功能1创建账号:

1
2
3
4
5
[-]input your choice: $ 1
[+]Your game account:0x57BA63AABc5991852A50C6b2361fbe5fEAE49fd1
[+]token: OnvPSX3yroQVfoPezMU5JxTox1qlqP/D/+4Bk/9hMj3TNL/EvDGtDEZX5MApRGh3l/mIrYB9RlB40Q88n87Ogx+x0WCR92vm0iLi4pKJVLEUhL9CJK0YMI6kGMlMuF0oXbhsMopmgHUMD6VNL/WcDgiUf1w++IW586HEXMKGyaU=
[+]Deploy will cost 565486 gas
[+]Make sure that you have enough ether to deploy!!!!!!

给功能1的账号转账后,再通过功能2部署合约:

1
2
3
4
5
[-]input your choice: $ 2
[-]input your token: $ OnvPSX3yroQVfoPezMU5JxTox1qlqP/D/+4Bk/9hMj3TNL/EvDGtDEZX5MApRGh3l/mIrYB9RlB40Q88n87Ogx+x0WCR92vm0iLi4pKJVLEUhL9CJK0YMI6kGMlMuF0oXbhsMopmgHUMD6VNL/WcDgiUf1w++IW586HEXMKGyaU=
[+]new token: sdSA/5Vg6zOGg9imEZ9VZD2gM9kYjvTcLaS7UdqDOIc/aQ20jAuWdM3j5yvu6+gjMagTr7lXYNgAxTobDjH0G6VUm4b/QImRuU3vrRCmLj0OA3B703WGsITM//wgYfe4etYKu2kgdCpAtL6CRaGitq8mIALIfhZ3E7dtpUpgut91COVf7w8cG6VqqGp5lWO+zoTK5jlYse0zPun9IJkflA==
[+]Your goal is to emit OhSendFlag(address addr) event in the game contract
[+]Transaction hash: 0x944d930f504881910ab139a77501c4ea7716b5ed2b50e851dc9e83808b29c058

得到交易id后,我们可以去https://ropsten.etherscan.io查询交易,得到合约的字节码:

1
0x6080604052600436106100435760003560e01c8063234fbf321461011e57806380e10aa514610135578063e7aab2901461014c578063f20eaeb81461021457610119565b366101195760003414156100b6573073ffffffffffffffffffffffffffffffffffffffff166380e10aa56040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561009957600080fd5b505af11580156100ad573d6000803e3d6000fd5b50505050610117565b3073ffffffffffffffffffffffffffffffffffffffff1663234fbf326040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156100fe57600080fd5b505af1158015610112573d6000803e3d6000fd5b505050505b005b600080fd5b34801561012a57600080fd5b5061013361023f565b005b34801561014157600080fd5b5061014a61054a565b005b34801561015857600080fd5b506102126004803603602081101561016f57600080fd5b810190808035906020019064010000000081111561018c57600080fd5b82018360208201111561019e57600080fd5b803590602001918460018302840111640100000000831117156101c057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506106cd565b005b34801561022057600080fd5b506102296106e7565b6040518082815260200191505060405180910390f35b60006102e4600a8054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102da5780601f106102af576101008083540402835291602001916102da565b820191906000526020600020905b8154815290600101906020018083116102bd57829003601f168201915b50505050506106ed565b905060008063ffffffff60015460601c1660055563ffffffff60015460401c1660065563ffffffff60015460201c1660075563ffffffff600154166008556001805b60048110156105435763ffffffff85600860046008850260180301021c16935063ffffffff856008808402601803021c169250600060045560005b60208110156105175760008060008063ffffffff8863ffffffff60208b041663ffffffff60108c02161801169350600363ffffffff6004541616600081146103cf57600181146103eb57600281146104075763ffffffff806008541663ffffffff600454160116935061041f565b63ffffffff806005541663ffffffff600454160116935061041f565b63ffffffff806006541663ffffffff600454160116935061041f565b63ffffffff806007541663ffffffff60045416011693505b5063ffffffff8385188a0116985063ffffffff60025460051c63ffffffff60045416011660045563ffffffff8963ffffffff60208c041663ffffffff60108d02161801169150600363ffffffff8060045416600b1c1616600081146104aa57600181146104c657600281146104e25763ffffffff806008541663ffffffff60045416011691506104fa565b63ffffffff806005541663ffffffff60045416011691506104fa565b63ffffffff806006541663ffffffff60045416011691506104fa565b63ffffffff806007541663ffffffff60045416011691505b5063ffffffff818318890116975050505050600181019050610361565b50826040820260c0031b846020840260c0031b0160035401600355600282019150600181019050610326565b5050505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806105cf57503073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b610641576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f457468456e633a206e6f7420617574686f72697a65640000000000000000000081525060200191505060405180910390fd5b775d1ab31f6a103c8f364d33e96dbdd5cdbd40d15e55c232746003541461066757600080fd5b7f8b177f28772b136d199ffba089065a411729f108f8d6c93aec14389ed904d00133604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a13373ffffffffffffffffffffffffffffffffffffffff16ff5b80600a90805190602001906106e3929190610715565b5050565b60035481565b60008060208301519050680100000000000000008160001c8161070c57fe5b04915050919050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061075657805160ff1916838001178555610784565b82800160010185558215610784579182015b82811115610783578251825591602001919060010190610768565b5b5090506107919190610795565b5090565b5b808211156107ae576000816000905550600101610796565b509056fea2646970667358221220a9e07df1b7e0cb341d8070f34f6912ff2ba7e42490326fed5ba8d7c8d716bfab64736f6c634300060c0033

ropsten自带的反编译结果:

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
#
# Panoramix v4 Oct 2019
# Decompiled source of ropsten:0x97575FA5A283eD45ddDddCFF45DC65869Ac62243
#
# Let's make the world open source
#
#
# I failed with these:
# - _fallback()
# All the rest is below.
#

def storage:
stor0 is addr at storage 0
stor1 is uint32 at storage 1 offset 32
stor1 is uint32 at storage 1 offset 96
stor1 is uint32 at storage 1 offset 64
stor1 is uint32 at storage 1
stor2 is uint256 at storage 2
output is uint256 at storage 3
stor4 is uint32 at storage 4
stor4 is uint8 at storage 4 offset 11
stor4 is uint256 at storage 4
stor4 is uint8 at storage 4
stor4 is uint256 at storage 4 offset 32
stor5 is uint32 at storage 5
stor5 is uint256 at storage 5
stor6 is uint32 at storage 6
stor6 is uint256 at storage 6
stor7 is uint32 at storage 7
stor7 is uint256 at storage 7
stor8 is uint32 at storage 8
stor8 is uint256 at storage 8
stor10 is array of struct at storage 10

def output(): # not payable
return output

#
# Regular functions
#

def unknown80e10aa5(): # not payable
if stor0 != caller:
if this.address != caller:
revert with 0, 'EthEnc: not authorized'
require output == 0x5d1ab31f6a103c8f364d33e96dbdd5cdbd40d15e55c23274
log 0x8b177f28: caller
selfdestruct(caller)

def unknowne7aab290(array _param1): # not payable
require calldata.size - 4 >= 32
require _param1 <= 4294967296
require _param1 + 36 <= calldata.size
require _param1.length <= 4294967296 and _param1 + _param1.length + 36 <= calldata.size
stor10[].field_0 = Array(len=_param1.length, data=_param1[all])

def unknown234fbf32(): # not payable
idx = 128
s = 0
while stor10.length + 96 > idx:
mem[idx + 32] = stor10[s].field_256
idx = idx + 32
s = s + 1
continue
uint256(stor5) = uint32(stor1.field_96)
uint256(stor6) = uint32(stor1.field_64)
uint256(stor7) = uint32(stor1.field_32)
uint256(stor8) = uint32(stor1.field_0)
idx = 1
s = 1
t = 0
t = 0
while idx < 4:
uint256(stor4.field_0) = 0
t = 0
u = 0
v = 0
while t < 32:
uint32(stor4.field_0) = uint32(uint32(stor4.field_0) + (Mask(251, 0, stor2) * 0.03125))
Mask(224, 0, stor4.field_32) = 0
if not stor4.field_0 % 4:
......

stor1和stor4的两个值感觉比较重要,看反编译代码里是找不到这两个值的,可以通过两种方式,方法一是反编译此次交易的input data:

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
contract disassembler {

function main() public return ()
{
mstore(0x40,0x80);
sstore(0x4,(~0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000 & sload(0x4)));
sstore(0x5,(~0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000 & sload(0x5)));
sstore(0x6,(~0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000 & sload(0x6)));
sstore(0x7,(~0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000 & sload(0x7)));
sstore(0x8,(~0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000 & sload(0x8)));
sstore(0x9,0x944AA7BA1B02649C534F16833BFBD82F);
sstore(0x1,0x746869745F69735E5F746573746B6579);
sstore(0x2,0xB3C6EF3720);
callcodecopy(0x0,0x1E3,0x7E8);
RETURN(0x0,0x7E8);
}

}

或者通过API查看:

1
2
3
4
5
6
7
8
#!/usr/bin/env python
# coding=utf-8
from web3 import Web3

contract_address = '0x97575fa5a283ed45dddddcff45dc65869ac62243'
w3 = Web3(Web3.HTTPProvider(""))
key = w3.eth.getStorageAt(Web3.toChecksumAddress(contract_address), 1)
print(key)

stor1的值为thit_is^_testkey,计算stor4:

1
2
>>> hex(int(0xb3c6ef3720 * 0.03125) & 0xffffffff)
'0x9e3779b9'

根据0x9e3779b9常量和反编译代码,可以推断出是XTEA加密算法,stor1为key,密文十进制为2282910687825444608285583946662268071674116917685196567156,因此可以解密:

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
#include <stdio.h>
#include <stdint.h>
#include <string.h>

/* take 64 bits of data in v[0] and v[1] and 128 bits of key[0] - key[3] */

void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9;
for (i=0; i < num_rounds; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
sum += delta;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
}
v[0]=v0; v[1]=v1;
}

void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds;
for (i=0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}
v[0]=v0; v[1]=v1;
}

int main()
{
unsigned int i;
uint32_t const k[4]={0x74686974,0x5f69735e,0x5f746573,0x746b6579};
unsigned int r=32;

// 2282910687825444608285583946662268071674116917685196567156 = 0x5d1ab31f6a103c8f364d33e96dbdd5cdbd40d15e55c23274
uint32_t enc1[2] = {0x5d1ab31f, 0x6a103c8f};
uint32_t enc2[2] = {0x364d33e9, 0x6dbdd5cd};
uint32_t enc3[2] = {0xbd40d15e, 0x55c23274};
char flag[50];
decipher(r, enc1, k);
decipher(r, enc2, k);
decipher(r, enc3, k);
printf("解密后的数据:%x %x %x %x %x %x\n",enc1[0],enc1[1],enc2[0],enc2[1],enc3[0],enc3[1]);

// 0x5f5f6f685f66616e74616e73697469635f626162795f5f5f
int s[24] = {0x5f, 0x5f, 0x6f, 0x68, 0x5f, 0x66, 0x61, 0x6e, 0x74, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x63, 0x5f, 0x62, 0x61, 0x62, 0x79, 0x5f, 0x5f, 0x5f};
for (i=0; i<24; i++) {
printf("%c", s[i]);
}

return 0;
}

得到明文为__oh_fantansitic_baby___看题目给出的部分源代码,payforflag有modifier auth的检查,所以我们不能直接调用,利用 https://ethervm.io/decompile 反编译合约看到:

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
......
} else if (msg.data.length) { revert(memory[0x00:0x00]); }
else if (msg.value != 0x00) {
var var0 = address(this);
var var1 = 0x234fbf32;
var temp0 = memory[0x40:0x60];
memory[temp0:temp0 + 0x20] = (var1 & 0xffffffff) << 0xe0;
var var2 = temp0 + 0x04;
var var3 = 0x00;
var var4 = memory[0x40:0x60];
var var5 = var2 - var4;
var var6 = var4;
var var7 = 0x00;
var var8 = var0;
var var9 = !address(var8).code.length;

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

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

if (!var3) { stop(); }

var temp2 = returndata.length;
memory[0x00:0x00 + temp2] = returndata[0x00:0x00 + temp2];
revert(memory[0x00:0x00 + returndata.length]);
} else {
var0 = address(this);
var1 = 0x80e10aa5;
var temp3 = memory[0x40:0x60];
memory[temp3:temp3 + 0x20] = (var1 & 0xffffffff) << 0xe0;
var2 = temp3 + 0x04;
var3 = 0x00;
var4 = memory[0x40:0x60];
var5 = var2 - var4;
var6 = var4;
var7 = 0x00;
var8 = var0;
var9 = !address(var8).code.length;

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

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

if (!var3) { stop(); }

var temp5 = returndata.length;
memory[0x00:0x00 + temp5] = returndata[0x00:0x00 + temp5];
revert(memory[0x00:0x00 + returndata.length]);
}

..

如果转账金额为0的话,就会调用0x80e10aa5函数,即payforflag。 于是先调用e7aab290函数设置明文,字符串参数可以用如下方法生成:

1
2
from eth_abi import encode_abi
print(encode_abi(['string'], ['__oh_fantansitic_baby___']).hex())

调用脚本如下:

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
#!/usr/bin/env python
# coding=utf-8
from web3 import Web3

contract_address = '0x97575fa5a283ed45dddddcff45dc65869ac62243'
w3 = Web3(Web3.HTTPProvider(""))

private = bytes.fromhex("")

def get_txn(data):
txn = {
"nonce": w3.eth.getTransactionCount('0xfe80C412340e57305bc85C4692F853E10c69e186'),
"from": '0xfe80C412340e57305bc85C4692F853E10c69e186',
"to": Web3.toChecksumAddress(contract_address),
"gasPrice": w3.eth.gasPrice,
"gas": 3000000,
"value": Web3.toWei(0, 'ether'),
"data": data
}
return txn

data1 = '0xe7aab290000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000185f5f6f685f66616e74616e73697469635f626162795f5f5f0000000000000000'
data2 = '0x234fbf32'
data3 = '0x'

signed_txn = w3.eth.account.signTransaction(get_txn(data1), private)
txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash)
print(txn_hash)
print(txn_receipt)

然后调用0x234fbf32函数加密:

1
2
3
4
5
signed_txn = w3.eth.account.signTransaction(get_txn(data2), private)
txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash)
print(txn_hash)
print(txn_receipt)

触发payforflag:

1
2
3
4
5
signed_txn = w3.eth.account.signTransaction(get_txn(data3), private)
txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash)
print(txn_hash)
print(txn_receipt)

然后通过功能3 Request for flag即可得到flag。

Flag

1
flag{0h_Fantansitic_B4by_Congratulatiuon_t0_u_}