CTF
https://capturetheether.com/
Не проходил как положено, потому что нужно подключение к Ропстену, а он уже мертв. Для некоторых заданий даже критично чтобы посмотреть саму сеть (а сейчас даже сканер не запускается). Поэтому решал как задачи в учебнике, иногда с эксперементами в Ремиксе.
Есть резюме по всем заданиям.
Есть по каждому заданию отдельное описание от меня, как оно у меня шло в деталях.
Некоторые задания не получались, пришлось смотреть в решения других людей и усваивать.
Задание | Сделал ли | Насколько сложно было
--- | ---| ---
Guess the number | Да | Легко
Guess the secret number | Да | Сложно
Guess the random number | Да | Легко
Guess the new number | Да | Легко
Predict the future | Да | Легко
Predict the block hash | Нет | Сложно
Token Sale | Да | Легко
Token whale | Да | Легко
Retirement fund | Да | Легко
Mapping | Почти | Сложно
Donation | Почти | Сложно
Fifty years | Почти | Сложно
Fuzzy Identity | Да | Легко
Public Key | Нужен доступ Ропстен | Средне
Account Takeover | Нужен доступ Ропстен | Легко
Assume ownership | Да | Легко
Token bank | Да | Легко
# 1) Guess the number
Тут нужно угадать номер, который просто в коде контракта захардкожен.
#### Сложно ли было:
Совсем нет
# 2) Guess the secret number
Тут я очень долго тупил, не понимал как это вообще возможно подобрать хэш.
```
contract GuessTheSecretNumberChallenge {
bytes32 answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365;
...
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
if (keccak256(n) == answerHash) {
msg.sender.transfer(2 ether);
}
}
}
```
Потом осенило что в инпуте uint8, а значит мало вариантов которых надо просто перебрать.
#### Сложно ли было:
Для меня было Сложно
# 3) Guess the random number
Я даже не понял в чем сложность, похоже на задание 1. Видимо переменная приватная, и это классическое задание на прочитание слота в сторедже.
````
contract GuessTheRandomNumberChallenge {
uint8 answer;
function GuessTheRandomNumberChallenge() public payable {
require(msg.value == 1 ether);
answer = uint8(keccak256(block.blockhash(block.number - 1), now));
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
if (n == answer) {
msg.sender.transfer(2 ether);
}
}
}
````
#### Сложно ли было:
Легко
# 4) Guess the new number
Тут похожий перебор uint8, но внутри в качестве исходных параметров используются block.number и block.timestamp
```solidity
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now));
if (n == answer) {
msg.sender.transfer(2 ether);
}
}
```
Поэтому задача заключается в подборе правильного тайминга.
Проще послать в сеть много транзакций, ревертить неудачные, а удачная рано или поздно пройдет.
Можно на будущее расчитать, куда сделать транзакцию, если есть уверенность попадания именно в этот момент.
Ничего иного не придумал.
#### Сложно ли было:
Легко
# 5) Predict the future
Очень похожая история.
Только надо сперва залочить средства, и потом их вытащить в нужный момент в будущем.
Формула тоже очень похожая.
```solidity
uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now<)) % 10;
```
Но тут есть взятие модуля.
Поэтому надо тоже сперва залочить, а дальше посылать много транзакций на попытку вывода - какая-то точно пройдет в вероятность 1/10.
#### Сложно ли было:
Легко
# 6) Predict the block hash
Тут не решил.
Пришлось подсматривать.
Тут надо знать оказывается, что взятие blockhash начинает возвращать 0 после 256 блока.
Как будто совсем непрактичное знание.
#### Сложно ли было:
Сложно
# 7) Token sale
Нужно оверфлоу сделать указав большое число в инпут, устарело
```solidity
uint256 constant PRICE_PER_TOKEN = 1 ether;
function buy(uint256 numTokens) public payable {
require(msg.value == numTokens * 1 ether);
balanceOf[msg.sender] += numTokens;
}
```
#### Сложно ли было:
Легко
# 8) Token whale
Напутали msg.sender и from, у меня уже глаз наточен такое замечать
```solidity
function _transfer(address to, uint256 value) internal {
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
}
function transferFrom(address from, address to, uint256 value) public {
require(balanceOf[from] >= value);
require(balanceOf[to] + value >= balanceOf[to]);
require(allowance[from][msg.sender] >= value);
allowance[from][msg.sender] -= value;
_transfer(to, value);
}
```
#### Сложно ли было:
Легко
# 9) Retirement fund
Легчайший, там надо кинуть эфиры на адрес который не хочет их принимать, надо через selfdestruct.
И в итоге будет андерфлоу и можно пройти проверку.
```
uint256 withdrawn = startBalance - address(this).balance;
require(withdrawn > 0);
```
#### Сложно ли было:
Легко
# 10) Mapping
Это интересный уровень, я долго тупил.
Надо в этом контракте перезаписать первую переменную.
Я в целом угадал правильное направление - чтобы слот хранящий элемент списка был 0. Через эдакий оверфлоу. И сходил в статью Миксбайтс.
```solidity
pragma solidity ^0.4.21;
contract MappingChallenge {
bool public isComplete;
uint256[] map;
function set(uint256 key, uint256 value) public {
// Expand dynamic array as needed
if (map.length <= key) {
map.length = key + 1;
}
map[key] = value;
}
function get(uint256 key) public view returns (uint256) {
return map[key];
}
}
```
А детали уже подсмотрел в ответах.
#### Сложно ли было:
Сложно
# 11) Donation
Очень похоже но посложнее - здесь еще появляются struct + хитрость с округлением (очевиднейшая), я угадал с направлением мысли и куда копать, но а детальное решение подлягел в ответах.
#### Сложно ли было:
Сложно
# 12) Fifty years
Похож на предыдущий, но еще сложнее. Все то же самое, просто контракт запутанее.
Просто прочитал готовое решение.
#### Сложно ли было:
Сложно
# 13) Fuzzy Identity
Тут надо забрутфорсить свой адрес смарт-контракта в котором будут подряд идти символы badc0de. Не быстро, но выполнимо.
#### Сложно ли было:
Легко
# 14) Public Key
Сложно, но я понял куда надо капать - найти транзакции в сети подписанные от целевого адреса, посмотреть подпись. Почитал готовое решение.
```
pragma solidity ^0.4.21;
contract PublicKeyChallenge {
address owner = 0x92b28647ae1f3264661f72fb2eb9625a89d88a31;
bool public isComplete;
function authenticate(bytes publicKey) public {
require(address(keccak256(publicKey)) == owner);
isComplete = true;
}
}
```
#### Сложно ли было:
Сложно
# 15) Account Takeover
Тут надо смотреть историю подписей, а ропстен недоступен. Но почитал в чем решение - там якобы одинаковый `r` у двух подписей, это я знаю.
```
pragma solidity ^0.4.21;
contract AccountTakeoverChallenge {
address owner = 0x6B477781b0e68031109f21887e6B5afEAaEB002b;
bool public isComplete;
function authenticate() public {
require(msg.sender == owner);
isComplete = true;
}
}
```
#### Сложно ли было:
Легко
# 16) Assume ownership
Старое упражнение со времен когда конструктур назывался именем контракта, и была допущена ошибка в этом названии конструктора.
#### Сложно ли было:
Легко
# 17) Token bank
Тут все просто, хукабл токен + нарушенный CEI паттерн = можно через реентранси весь баланс вывести
#### Сложно ли было:
Легко