TGViewer
Из Solidity в AI и дальше Из Solidity в AI и дальше @solidityset · 2.47K subscribers
Post #435 201
Ethernaut. Задача 24. Puzzle Wallet

В этой задаче нам нужно завладеть правами owner прокси контракта.
Ссылка на задачу

В чем суть?

Дааа, заставила эта задача посидеть над ней... Особенно с функцией multicall() в PuzzleWallet.

Хотя, если я правильно понял, на сегодняшний день контракта UpgradeableProxy в openzeppelin нет. Базовые прокси контракты там делятся на два типа: TransparentUpgradeableProxy и UUPSUpgradeable. Об этом был урок на канале ранее.

В прокси контрактах нужно понимать, как работают слоты памяти для переменных, которые вы объявляете в начале. Крайне важно, чтобы они были не только одинаковыми, но и располагались в одинаковом порядке.

В задаче же порядок нарушен:

address public pendingAdmin;
address public admin;

идут в прокси контракте и в PuzzleWallet:

address public owner;
uint256 public maxBalance;

Следовательно, использовав функцию в прокси контракте proposeNewAdmin(), мы можем не только предложить нового админа для прокси, но и установить админские права в PuzzleWallet.

Далее, по той же логике, нам нужно вызвать функцию в PuzzleWallet setMaxBalance(), которая, установив новый максимальный баланс у себя же, установит нового админа в прокси. Но как это сделать?

Просто вызвать ее не получится, так как на балансе контракта уже лежит 0.001 Эфира. Следовательно, нам нужно вызвать deposit() и пополнить наш баланс.

Однако, прямой вызов deposit() будет пополнять и наш счет и счет контракта, так что баланс контракт всегда будет чуть больше нашего. Поэтому напрямую его делать нельзя.

Обратимся к самой навороченной функции multicall(). Она принимает в себя массив байтов, проходит по нему, создает флаг depositCalled, и в конце через delegatecall() вызывает deposit(). Вот тут и кроется уязвимость.

Флаг depositCalled позволяет вызвать deposit() только один раз. Это защищает вызов от повторного использования. Это может затормозить решение задачи, если не знать некоторых нюансов.

А нюанс заключается в том, что мы можем внутри нашего multicall() вызвать еще один multicall, который в свою очередь будет вызывать deposit(), на который уже не будет ругаться проверка флага.

Разберем примерную логику:

bytes[] memory insideCall = new bytes[](1);
insideCall[0] = abi.encodeWithSelector(PuzzleWallet.deposit.selector);

bytes[] memory calls = new bytes[](2);
calls[0] = abi.encodeWithSelector(PuzzleWallet.deposit.selector);
calls[1] = abi.encodeWithSelector(PuzzleWallet.multicall.selector, insideCall);

Мы создаем массив байтов с фиксированной длиной, и туда с помощью abi.encodeWithSelector кладем функцию deposit().

Затем создаем уже наш "боевой" массив байтов с фиксированной длинной 2.

В первый слот массива просто также кладем зашифрованную функцию deposit(), а во второй - еще раз эту же функцию плюс наш insideCall. Далее сделаем вызов:

puzzleWallet.multicall{value: 0.001 ether}(calls);

Поняли логику? Мы передадим в multicall() наш массив байтов, в котором сможем выполнить deposit() дважды, и немного Эфира.

При этом уже внутри deposit() произойдет небольшая ошибка: так как мы дважды вызвали данную функцию, но передав всего 0.001 Эфира, то баланс контракта будет увеличен на 0.001, а наш личный баланс на 0.002.

Таким образом, после всех этих действий мы сможем вызвать функцию execute(), чтобы обнулить баланс контракта, и потом обратиться к функции setMaxBalance, чтобы захватить права админа в прокси контракте.

Фух, понимаю, пост получился длинный, но задачу по другому не разберешь.

Итак, код может выглядеть так:
More from @solidityset
  1. Oct 8, 2026Промо видео для SaaS и других ваших проектов Хочу поделить с вами одним из своих открытий…
  2. Oct 8, 2026video post
  3. Oct 2, 2026Вторая сложность — как доказать, что установилось именно опубликованное. В первом варианте…
  4. Oct 2, 2026Задача с полуоткрытым кодом На днях, в процессе создания одного приложения, столкнулся с и…
  5. Sep 22, 2026Какой язык программирования учить сейчас? На днях в Твиттере увидел небольшой пост о разви…
  6. Sep 18, 2026Интересная модель Jev Буквально пару дней назад в Твиттере многие начали обсуждение новой…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →