Blockchain签到

Checkin

1
2
3
4
5
一般题目中给出了三个端口,分别是 RPC、水龙头、题目交互端。
其中,浏览器可直接访问的是水龙头,浏览器直接访问报 403 的是 RPC,浏览器无法访问的是题目交互端,需使用 nc 连接。
week-1.hgame.lwsec.cn:30111
week-1.hgame.lwsec.cn:32559
week-1.hgame.lwsec.cn:32428

前期准备

通过判断可以知道题目、RPC、水龙头分别对应的端口

1
week-1.hgame.lwsec.cn:32559		水龙头

水龙头访问结果:

1
2
week-1.hgame.lwsec.cn:30111		题目环境
nc week-1.hgame.lwsec.cn 30111

题目环境访问结果

题目环境有四个选项:

1
2
3
4
5
6
7
8
9
10
11
12
13
We design a pretty easy contract challenge. Enjoy it!
Your goal is to make isSolved() function returns true!
[1] - Create an account which will be used to deploy the challenge contract
[2] - Deploy the challenge contract using your generated account
[3] - Get your flag once you meet the requirement
[4] - Show the contract source code
翻译:
我们设计了一个非常简单的合同挑战。享受它!
您的目标是使isSolved()函数返回true!
[1] -创建一个用于部署挑战合同的帐户
[2] -使用生成的帐户部署挑战合同
[3] -一旦满足要求,就拿旗
[4] -显示合同源代码
创建账户

首先选择1创建账户:

1
2
3
4
[-] input your choice: 1
[+] deployer account: 0x6FfB5cCb379A96b3aB7B1527372Ff90b4201f401
[+] token: v4.local.JE3A5C-HZoI2joiVNAHV4NjBaxM7jyToGUKmx9CeCOSHxCLXEACw61gVdVYSAeL4zJeja0NyJkNFvxfwIYh04JYLFNkdLRimDi6mhC-_crrY4gZu1FkS0oyHu0fnIW_2NLdx3njjF4n51HQJXlEnwhizMBZLvtSf7yHRVNyftzOeqw
[+] please transfer 0.001 test ether to the deployer account for next step

可以看到拿到了一个账户和和一个token,并且我们需要转0.001 test ether到账户中

此时我们如果选择2会报错:

需要我们提前转账才能做后面的操作。

测试币领取

可以使用水龙头进行测试币领取

合约信息

此时再选2

可以拿到远程合约的地址和交易的hash信息:

1
2
[+] contract address: 0xbdFcE62184338C494e4Ea6593c374c57C02279CB
[+] transaction hash: 0xa1b89528e2954356c9cbb0088bceab1f72d151cf96b84e23ba71ecc8848d654c
查看合约源码

我们可以选择4进行源代码查看:

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
[-] input your choice: 4
contracts/checkin.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

contract Checkin {
string greeting;

constructor(string memory _greeting) {
greeting = _greeting;
}

function greet() public view returns (string memory) {
return greeting;
}

function setGreeting(string memory _greeting) public {
greeting = _greeting;
}

function isSolved() public view returns (bool) {
string memory expected = "HelloHGAME!";
return keccak256(abi.encodePacked(expected)) == keccak256(abi.encodePacked(greeting));
}
}

通过源码审计之后可以简单的发现攻击思路。

如果需要使isSolved()放回true,我们需要使keccak256(abi.encodePacked(expected)) == keccak256(abi.encodePacked(greeting));,其中expected的值是HelloHGAME!,所以我们只需要将greeting的值也设置成HelloHGAME!即可满足加密。

继续审计可以看到我们可以通过setGreeting修改greeting的值即可。

首先尝试使用REMIX解题

Remix解题

RPC地址配置

首先第一步我们需要配置小狐狸的RPC地址

1
HTTP://week-1.hgame.lwsec.cn:32428

配置完了之后查看是否生效,可以使用水龙头往自己的小狐狸地址中转账如果可以收到就表示配置成功。

Remix交互

首先把在题目中拿到的源码复制到Remix中,然后选择Injected Provider - MetaMask,这边注意networks的id是否一样,然后在At Address中输入前面的合约地址:0xbdFcE62184338C494e4Ea6593c374c57C02279CB

输入地址之后可以看到下面加载了合约信息,里面有一些函数跟变量

但是我们点击setGreeting没有反应,所以应该是题目没有做remix的交互,在geth启动的时候需要额外的参数

1
2
Note: To use Geth & https://remix.ethereum.org, configure it to allow requests from Remix
geth --http --http.corsdomain https://remix.ethereum.org

所以这边尝试使用web3py合约交互

web3py合约交互

1
官方手册:https://web3py.readthedocs.io/en/stable/web3.eth.html

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
from web3 import Web3, HTTPProvider

# 测试链接
w3 = Web3(HTTPProvider('http://week-1.hgame.lwsec.cn:32475/')) # 此处是RPC地址
assert w3.isConnected() # 测试连接钱包地址
print(w3.isConnected()) # 结果为true表示连接成功

# 创建账户
pk = '0x16e39235b9e8d246db0c59d09139d3f3b5aeee31858eed44a5f57527c5f42d70' # 生成一个账户
# privateKeyToAccount函数可以将给定的私钥转换成一个账户,其中包括账户地址、公钥和私钥。它使用私钥来计算出一个地址,然后使用私钥来计算出相应的公钥。最后,它会返回一个包含地址、公钥和私钥的账户对象。
account = w3.eth.account.privateKeyToAccount(pk)
print(account.address) # 拿到前面生成的地址,此处应该是固定值
# 0x38d921812152C00D2C0304b084b84a0aF75Bf9a8
##在这个时候我们可以用水龙头往里面转测试币

# abi获取
## 然后在本地部署然后在remix里面部署的时候拿到ABI的值,就是下面的这样的信息
abi = '[{"inputs":[{"internalType":"string","name":"_greeting","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"greet","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isSolved","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_greeting","type":"string"}],"name":"setGreeting","outputs":[],"stateMutability":"nonpayable","type":"function"}]'

# 远程信息获取
# 此处是合约部署的地址,可以在选择交易的之后查看到远程合约部署的地址和交易hash值
contract = w3.eth.contract(address='0xF587E2006316F1DF3121Be3ba31dEC1CA558928E', abi=abi) # 应该是合约的地址

# 生成事务,固定写法
tx = contract.functions.setGreeting('HelloHGAME!').buildTransaction({
'from': account.address,
'nonce': w3.eth.getTransactionCount(account.address),
'gas': 1000000,
'gasPrice': w3.toWei('1', 'gwei'),
})

# 交易签名
signed_tx = account.signTransaction(tx)

# 发送数据
tx_hash = w3.eth.sendRawTransaction(signed_tx.rawTransaction)

# 获取交易收据以获取合同地址
tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash)

print(tx_receipt)
print('greet : {}'.format(contract.functions.greet().call())) # call函数只能读取值不能往里面写
print('isSo : {}'.format(contract.functions.isSolved().call())) # call函数只能读取值不能往里面写

可以看到效果: