<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Hackit</title>
        <link>https://paragraph.com/@hackbot</link>
        <description>Just Hack it</description>
        <lastBuildDate>Thu, 30 Apr 2026 12:41:16 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Hackit</title>
            <url>https://storage.googleapis.com/papyrus_images/5a43e2768b4ea568ad52739811e26e964c490d908ac336c6bf14eb0c55d68ee3.jpg</url>
            <link>https://paragraph.com/@hackbot</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[以太坊智能合约逆向分析与实战：（6）访问动态数据类型 2]]></title>
            <link>https://paragraph.com/@hackbot/6-2</link>
            <guid>HAuyguCtyZfoLDrAYV7g</guid>
            <pubDate>Sun, 09 Oct 2022 06:41:11 GMT</pubDate>
            <description><![CDATA[在之前的文章：以太坊智能合约逆向分析与实战：（3）[实战篇] 访问私有动态数据类型 中，我们以破解链上某“猜数字”游戏为例，讲解了映射这种动态数据类型在 EVM 中的存储与访问，这次我们把目标对准另一种动态数据类型： 动态数组 ，看看 EVM 是如何实现动态数组的存取。首先我们先看一个简单的合约：图1通过之前的学习，你一定能够轻松地判断出 slot 0 里面的值（没错就是 0x666666 ），但是对于动态数组，它的长度是随时变化的，其内容并不会像这些值类型一样固定在某个 slot 里。那么它是如何存储的呢？让我们先调用 record（）函数 ,给动态数组压入一些数值（0xff0000, 0xff0001, 0xff0002，0xff0003）, 此时的动态数组长度应该是4，我们通过其 length() 函数也可获知。然后打开调试器看一下：图2与之前讲的映射相比，我们可以发现动态数组的存储方式和映射有一些相似之处，却也有所不同。在动态数组中，进行变量声明的位置（slot2）存放着数组的长度，而数组内各元素的值，是按照 slot n、slot n+1、slot n+2 … 的顺序进...]]></description>
            <content:encoded><![CDATA[<p>在之前的文章：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/hackbot.eth/oq6e37ApuACqsKGYUcnrTbz26k8P6TsGb-AHBq6_piY">以太坊智能合约逆向分析与实战：（3）[实战篇] 访问私有动态数据类型</a> 中，我们以破解链上某“猜数字”游戏为例，讲解了<strong>映射</strong>这种动态数据类型在 EVM 中的存储与访问，这次我们把目标对准另一种动态数据类型： <strong>动态数组</strong> ，看看 EVM 是如何实现动态数组的存取。首先我们先看一个简单的合约：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/708f33460aa53ffcd11483ce5943ddf01f06b01b7ee6ab143df1bd2b58850892.png" alt="图1" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图1</figcaption></figure><p>通过<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/hackbot.eth/oq6e37ApuACqsKGYUcnrTbz26k8P6TsGb-AHBq6_piY">之前的学习</a>，你一定能够轻松地判断出 slot 0 里面的值（没错就是 0x666666 ），但是对于动态数组，它的长度是随时变化的，其内容并不会像这些值类型一样固定在某个 slot 里。那么它是如何存储的呢？让我们先调用 record（）函数 ,给动态数组压入一些数值（0xff0000, 0xff0001, 0xff0002，0xff0003）, 此时的动态数组长度应该是4，我们通过其 length() 函数也可获知。然后打开调试器看一下：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/4d12eb56f11ae821db593bdaaf30844c53fe95e7ade9752ffd7072c0f5bcaf9c.png" alt="图2" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图2</figcaption></figure><p>与之前讲的<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/hackbot.eth/oq6e37ApuACqsKGYUcnrTbz26k8P6TsGb-AHBq6_piY">映射</a>相比，我们可以发现<strong>动态数组</strong>的存储方式和映射有一些相似之处，却也有所不同。在动态数组中，进行变量声明的位置（slot2）存放着数组的长度，而数组内各元素的值，是按照 slot n、slot n+1、slot n+2 … 的顺序进行排列的。声明的位置我们找到了，但第一个元素所在的 slot n 该怎么找呢？答案是</p><p><code>keccak256(bytes32(1))</code></p><p>其中 bytes32(1)是指该动态数组声明时所占据的 slot 。我们可以计算一下：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ba61b5344f7e7738b86acecc76d5cd00a2a56dd1f1e5b13444c7dfcbe38dd36a.png" alt="图3" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图3</figcaption></figure><p>看，得到的结果与调试器中 codex[0] 对应的 key 相同。具体到本例， codex[0] = slot ( keccak256(bytes32(1)) + 0 )</p><p>codex[1] = slot ( keccak256(bytes32(1)) + 1 )</p><p>codex[2] = slot ( keccak256(bytes32(1)) + 2 )</p><p>……</p><p>然后我们通过读取对应的 slot ，就能够获取到动态数组的元素了。</p><hr><p>本次篇幅较短，趁着还有时间，我们寻找一个被破解的对象：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethernaut.openzeppelin.com/level/0xda5b3Fb76C78b6EdEE6BE8F11a1c31EcfB02b272">ethernaut 的一道题目</a>，作为本次学习的课后习题：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/06af2f62b8d35df5b300b2134019427b4d76ce9a3f7256b64abde76650feb7f2.png" alt="图4" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图4</figcaption></figure><p>从上图可以看到，这是一个很短的合约，合约里 import 了一个 Ownable.sol 文件，这个文件会让部署的合约里面有一个 Owner 变量（可以参考 OpenZeppelin 的 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol">Ownable.sol</a> ）。然而这个合约的 Owner 并不是我们的地址，而且合约也没有提供更改 Owner 的接口（即使引用的文件里实现了 transferOwnership（)，我们也因为不是 Owner 而无法更改）。简单来说：我们的任务，就是要通过对这个合约的 <strong>动态数组</strong> 进行一系列神奇操作，从而获取 Owner 权限。</p><p>在研究动态数组之前，我们先铺垫一下：Owner 是谁呢，怎么查询到它（变量的存放位置在哪里）？</p><p>答：该Solidity 文件中并没有直接出现 Owner 变量，而是以 import 的形式引入的。一般来讲，它的变量存储 slot 是这样排列的：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/808cfcb3199bae1fbc7ad79f411dc596b078f7a3cd43638c1a2f049c31473a6d.png" alt="图5" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图5</figcaption></figure><p>也就是说，合约先继承谁，就把谁的变量放在靠前的 slot 里面，等所有合约继承完毕，再存放本合约的变量。所以在本例中， 其 slot 0 中很可能存放的就是 Owner 的地址。我们拿来前一节用过的脚本来读取一下：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/2722ce1aa8e77f5edefde361e4f4770bd72cc606e82fd533274c27c5fd1f4eb4.png" alt="图6" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图6</figcaption></figure><p>看，果然如此。此处注意：由于 EVM 优化的缘故，address 变量 <strong>owner</strong> 与 bool 变量 <strong>contact</strong> 被打包放进了同一个 slot ，所以我们在一个 slot 里得到了两个变量的值：</p><p><code>owner = 0x3c34a342b2af5e885fcaa3800db5b205fefa3ffb</code></p><p><code>contact = false</code></p><p>然而，我们如何修改它呢？这就需要我们上面讲解的关于<strong>动态数组</strong>的知识了。</p><p>首先我们看图4的合约：变量 <strong>owner</strong>, <strong>contact</strong> 存放在 slot 0, 动态数组 <strong>codex[]</strong> 占用了slot 1 。通过阅读合约中的函数定义可以发现，我们无法修改 owner，但是却可以修改 codex[] 。</p><p>那么我们可以通过修改 codex[] 来实现修改owner 吗？ 对于这个合约来说是可以的，因为它的代码存在漏洞——在 pragma solidity ^0.5.0 环境下，我们可以直接控制 codex[] 的长度（ codex.length-- ），也就是说可以自由读写 slot 1 的数值，而高版本的 solc 就补上了这个漏洞。</p><p>通过本篇开头的讲解我们知道，在修改动态数组内某个元素的值的时候，例如 codex[9] = 0x12345678，实质上是将 0x12345678 写入到 存储槽 slot <strong>(keccak256(bytes32(1)</strong>) + n) 里面，而 keccak256(<strong>bytes32(1)</strong>) 能够被计算出来，等于是向 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6 + 9 这个地址内写入0x12345678，用汇编表示就是</p><p><code>0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cff</code></p><p><code>0x12345678</code></p><p><code>sstore</code></p><p>那么，如果把第一行改为 owner 所在的存储槽地址(slot 0)，第二行改为 我们自己的账户地址，不就可以修改合约 owner 为我们自己的地址了吗？</p><p>上面说过，codex[n] 的值，就是 第 ( <strong>keccak256(bytes32(1)) + n</strong> )个 slot 的值。如果我们通过构造一个 n ,让 keccak256(<strong>bytes32(1)</strong>) + n = 0 ,就可以实现我们的目标了。但 keccak256(<strong>bytes32(1)</strong>) 本身是大于0的，如何让他加上 n 之后 “归零”呢？答案是——<strong>溢出</strong>。slot共有 2^256 个，按照 0 ~ 2^256-1 的顺序排列。我们需要让 keccak256(<strong>bytes32(1)</strong>) + n = 2^256 ，从而出现数据<strong>上溢</strong>，即可实现越过数组下标限制来修改存储槽。</p><p>但这就意味着我们要输入一个很大的数组下标，这就需要有一个很大的数组。那我们如何构造出这么大的数组呢？依靠codex.push() 去填充显然是极其不划算的，答案仍是——<strong>溢出</strong>。在<strong>slot 1</strong> 里面存放着 codex[] 的初始大小：0 , 我们通过操作 retract() 函数，让 0 减去 1 从而实现数据<strong>下溢</strong>,达到构造超大数组的目标。</p><p>让我们开始操作吧。先调用 make_contact(), 使 contact = true。看看 <strong>slot 0</strong> 的值：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/fa589312a81cca6db8de3057a845d473cb7fba27f5d3f6e7e969a0eee43c868e.png" alt="图7" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图7</figcaption></figure><p>可以看到，slot 0 存储的是 bool + address 变量，即 true + OwnerAddress .我们的目标是修改 OwnerAddress 为我们自己的地址。</p><p>再调用 retract() , 让codex的长度减1 。看看 <strong>slot 1</strong> 的值：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ad14f3af20ae0aeb8505da7a31250f699f4738d2940eabc6a697f3da218e8c59.png" alt="图8" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图8</figcaption></figure><p>哦嚯，我们得到了一个“超级大”的动态数组，足足有 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 这么长！意味着我们可以在codex[n]里面存取任意位置的数据了。记得我们的目标吗？是要让keccak256(<strong>bytes32(1)</strong>) + n = 2^256 ，也就是说 n = 2^256 - keccak256(<strong>bytes32(1)</strong>)。我们计算出 n =</p><p>0x10000000000000000000000000000000000000000000000000000000000000000 - 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6 =</p><p>0x4ef1d2ad89edf8c4d91132028e8195cdf30bb4b5053d4f8cd260341d4805f30a</p><p>换算成十进制就是</p><p>35707666377435648211887908874984608119992236509074197713628505308453184860938</p><p>也就是说，我们修改 codex**[<strong>35707666377435648211887908874984608119992236509074197713628505308453184860938</strong>]** 的值为 0x000000000000000000000001AAA…..AAA 就可以了(AAA…..AAA 是我们自己的地址)。我们调用函数 revise（uint，bytes32），参数如下</p><p>参数1：35707666377435648211887908874984608119992236509074197713628505308453184860938</p><p>参数2：0x000000000000000000000001123456…..</p><p>然后再看一下存储槽，第一个 1 是 bool 变量 <strong>true</strong>，而后面的 1234567890…… 说明owner已经成功修改为我们的地址了（0x1234567890…….）：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d4d73fb113e3c72669e29b06f90dc1beb6f6cb13bd9ebe635f6aeda65fbc4f43.png" alt="图9" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图9</figcaption></figure><p>那么，本期节目就到这里啦。</p><p>关于作者：</p><div data-type="embedly" src="https://twitter.com/0xNezha" data="{&quot;provider_url&quot;:&quot;https://twitter.com&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;title&quot;:&quot;JavaScript is not available.&quot;,&quot;url&quot;:&quot;https://twitter.com/0xNezha&quot;,&quot;html&quot;:&quot;&lt;a class=\&quot;twitter-timeline\&quot; href=\&quot;https://twitter.com/0xNezha?ref_src=twsrc%5Etfw\&quot;&gt;Tweets by 0xNezha&lt;/a&gt;\n&lt;script async src=\&quot;https://platform.twitter.com/widgets.js\&quot; charset=\&quot;utf-8\&quot;&gt;&lt;/script&gt;&quot;,&quot;provider_name&quot;:&quot;Twitter&quot;,&quot;cache_age&quot;:3153600000,&quot;type&quot;:&quot;rich&quot;}" format="iframe"></div>]]></content:encoded>
            <author>hackbot@newsletter.paragraph.com (Hackit)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/59e4bc840ad317b0bae906cf3733a866981c82af48d9260184e4d312ef79d14a.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[以太坊智能合约逆向分析与实战：（5）深入EVM之合约的部署与调用]]></title>
            <link>https://paragraph.com/@hackbot/5-evm</link>
            <guid>3CwQfZJSp8Up8YUn5WG6</guid>
            <pubDate>Thu, 29 Sep 2022 11:53:08 GMT</pubDate>
            <description><![CDATA[当我们部署和调用合约的时候，EVM 都在做些什么？ 如果你开发过以太坊智能合约，想必你应该熟悉这样的操作 (此处以remix为例) ： 编写solidity代码 -> 编译 -> 部署-> 交互 。合约的编写与部署似乎并不是一件很麻烦的操作：编写阶段就不说了，Solidity语言大家都应该会；到了编译阶段，本地的 solc 编译器会把 Solidity 代码编译成字节码（bytecodes）；而在部署阶段，部署者通过发起一笔特殊交易（to的地址为空）calldata 带上编译后的字节码，等交易上链之后，就完成了合约的部署；而合约交互，就是call合约里的某个函数，等待函数的响应和返回，一切就是这样的简单。 但是正如开车一样，当你踩住油门后，车辆开始前进。然而这看似简单的操作背后是汽油爆燃、活塞往复、数百个齿轮啮合传动、轮胎与地面滚动摩擦的复杂行为。部署和调用合约也是如此，它涉及到 EVM 的堆栈操作，内存读写，存储访问等一系列底层操作。当部署合约时， EVM 把收到的 calldata 翻译成操作指令，把它们按照给定的长度和参数读入内存；当调用合约时，EVM 又根据收到的 cal...]]></description>
            <content:encoded><![CDATA[<p>当我们部署和调用合约的时候，EVM 都在做些什么？</p><p>如果你开发过以太坊智能合约，想必你应该熟悉这样的操作 (此处以remix为例) ：</p><p>编写solidity代码 -&gt; 编译 -&gt; 部署-&gt; 交互 。合约的编写与部署似乎并不是一件很麻烦的操作：编写阶段就不说了，Solidity语言大家都应该会；到了编译阶段，本地的 solc 编译器会把 Solidity 代码编译成字节码（bytecodes）；而在部署阶段，部署者通过发起一笔特殊交易（to的地址为空）calldata 带上编译后的字节码，等交易上链之后，就完成了合约的部署；而合约交互，就是call合约里的某个函数，等待函数的响应和返回，一切就是这样的简单。</p><p>但是正如开车一样，当你踩住油门后，车辆开始前进。然而这看似简单的操作背后是汽油爆燃、活塞往复、数百个齿轮啮合传动、轮胎与地面滚动摩擦的复杂行为。部署和调用合约也是如此，它涉及到 EVM 的堆栈操作，内存读写，存储访问等一系列底层操作。当部署合约时， EVM 把收到的 calldata 翻译成操作指令，把它们按照给定的长度和参数读入内存；当调用合约时，EVM 又根据收到的 calldata ，通过函数选择器来确定调用哪一段代码，并返回数值。如果只讲理论未免过于枯燥，为了便于讲解，我们这次用 ethernaut 的一道题目作为例子，详细了解 EVM 是如何部署和运行合约的，<s>以及如何充当人肉编译器，徒手编写智能合约</s>。</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethernaut.openzeppelin.com/level/0x200d3d9Ac7bFd556057224e7aEB4161fED5608D0">这个题目</a>是这样的：我们需要部署一个合约，当我们调用合约 **whatIsTheMeaningOfLife()**函数的时候，它需要返回一个数字 “42”。看起来很简单对吧？我们分分钟编写完毕：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a5a1f8e031509f555b2ef758c77ebc0e8b0ac3cabfb16f73ea0e7d0ad6242e44.png" alt="图1" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图1</figcaption></figure><p>慢着，题目后面还有个小小的附加要求：“所部署的合约大小不超过10个操作码”。好吧，这个要求的确够“小”，要知道连合约头部的 “函数选择器” 都不止 10 个操作码好吧？可是“函数选择器” 是什么，为什么会出现在合约里面呢？带着你的疑问，继续向下看。</p><p>我们通过 <code>./solc --asm --bin target.sol</code> 来看看这个合约的最终编译结果：</p><p>608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063650500c114602d575b600080fd5b60336047565b604051603e91906067565b60405180910390f35b6000602a905090565b6000819050919050565b6061816050565b82525050565b6000602082019050607a6000830184605a565b9291505056fea26469706673582212206ef8c7b5177952a701b3b46b69cb3ec296f4c54c946692e8ec901f5e43c1e78a64736f6c63430008110033</p><p>这么一大坨十六进制数据，就是上述 Solidity 程序编译之后的字节码。当我们部署合约时，把这一堆 data 发给以太坊节点，等广播完成后，合约就部署完毕了。这是 solc 编译器编译 Solidity程序得到的代码，看似杂乱无章的的数据，其实都是和 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.ethervm.io/">opcodes</a> 一一对应的。我们来一段一段地看这些代码：</p><p><strong>合约部署代码:</strong></p><p>608060405234801561001057600080fd5b5060b68061001f6000396000f3fe</p><p><strong>合约运行代码:</strong></p><p>6080604052348015600f57600080fd5b506004361060285760003560e01c8063650500c114602d575b600080fd5b60336047565b604051603e91906067565b60405180910390f35b6000602a905090565b6000819050919050565b6061816050565b82525050565b6000602082019050607a6000830184605a565b9291505056fe</p><p><strong>auxdata:</strong></p><p>a26469706673582212206ef8c7b5177952a701b3b46b69cb3ec296f4c54c946692e8ec901f5e43c1e78a64736f6c63430008110033</p><p>我们先简单地把这堆代码分为合约的部署代码、运行代码、auxdata 三部分，如何理解这三种代码呢？我觉得可以理解为向太空发射卫星：“<strong>部署代码</strong>” 就是运载火箭，而“<strong>运行代码</strong>”就是卫星。运载火箭只在发射卫星时才起到作用，一旦卫星进入轨道，火箭就废弃了，只留下卫星在太空中与地球通信。 部署合约也是如此，在部署合约时，部署代码把一些初始化工作作完之后，就把合约的运行代码送入EVM，只留下运行代码在链上与用户进行交互。 （至于<strong>auxdata</strong>，它是紧跟在runtime代码后面的43个字节，相当于源码的指纹，可以用来验证。这只是数据，并不会被EVM执行。）</p><p>那么言归正传，我们题目要求我们合约运行代码的 opcedes 不超过 10 条，那么，这段代码对应的 opcodes 是多少条呢？答：71 条。（通过查看 Remix : ./artifacts/MagicNum.json 中的 bytecode 里的 opcodes 可以看到。而 deployedBytecode 里的 opcodes 却是 92 条，因为它的长度是 部署代码 + 运行代码 ）</p><p>那么问题来了，如何把 71 条 opcodes 精简到 10 条以内呢? 这就需要我们对 EVM 运行智能合约的方式有着一定的了解。如果不了解也没关系，拿起你手边的 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.ethervm.io/"><strong>EVM 指令集</strong></a> ，我们一起来看看吧：</p><p>首先我们要知道，EVM 执行代码时是按照<strong>自上而下</strong>的顺序执行的，代码中没有其他入口点，始终从<strong>顶部</strong> (也就是第一行 opcode ) 开始执行。（这点和 Windows 软件不一样，PE文件是有固定的入口点的,而且不同的 Windows 版本或不同的 PE 文件 入口点也会有所不同）。也就是说，当我们部署合约时， EVM 会从第一个bytecode开始读起。</p><p>所以我们看字节码最前面的部分，也就是它的部署代码：608060405234801561001057600080fd5b5060b68061001f6000396000f3fe</p><p>对照 EVM 指令，我们可以识别出这段代码的含义：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b8423342b92287b217bc3267e34f6c5b857d794072322fe20f56484c9b552be7.png" alt="图2" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图2</figcaption></figure><p>然后我们看合约的运行代码：</p><p>6080604052348015600f57600080fd5b506004361060285760003560e01c8063650500c114602d575b600080fd5b60336047565b604051603e91906067565b60405180910390f35b6000602a905090565b6000819050919050565b6061816050565b82525050565b6000602082019050607a6000830184605a565b9291505056fe</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c29642acdf5f926decb7b8929c9854ac494cda329a4e63c996a823f546d11099.png" alt="图3" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图3</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e5f90e162ebbedf4fed145cc7d946efb5fa75b8ccdd5e205dc6bc27dcfb6cc9e.png" alt="图4" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图4</figcaption></figure><p>综合以上可以发现，合约的运行代码的架构是这样的：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9fb1b0cdd5e61a5b343d38eb2c8272f6f41fc9d491801bc237a16523ef360e28.png" alt="图5" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图5</figcaption></figure><p>初始化操作、函数选择器这些，是 solc 在编译 Solidity 程序的时候自动生成的。如果我们砍掉这些复杂的东西，直接把我们想要的核心功能编码上去，不就可以在 10 条以内opcodes 实现既定功能了吗？</p><p>通过分析 图4 的 whatIsTheMeaningOfLife() 函数调用栈可以得知，让智能合约返回 “42” ( 十六进制 0x2a) 的关键在于 先用 <strong>mstore</strong> 指令将 0x2a 放入 Memory , 再用 <strong>return</strong> 指令将内存里的 0x2a 返回即可。至于那些函数名称和函数签名，只是高级语言的编译产物，直接用汇编实现的话，我们直接用这段代码读写内存，完全没有必要搞那些花里胡哨：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d92ecdbd27a1aacbe1a99bcf1996279d29518cf4869713e775a40fb699ad1144.png" alt="图6" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图6</figcaption></figure><p>以上代码相当于构造了一个十分小的合约“运行代码”。前面我们说过，EVM 执行代码时是按照<strong>自上而下</strong>的顺序执行的，代码中没有其他入口点，始终从<strong>顶部</strong> (也就是第一行 opcode ) 开始执行。而且我们编写的代码并没有函数选择器，也就是说，当外部账户调用该它时，无论传递给它什么样的参数、什么样的函数签名， EVM 都只会从它的 [00] 处开始执行，老老实实地走到 [09]，然后 return 给我们一个 0x20.</p><p>但这只是运行代码，还记得本文开头说的那三段字节码吗？是的，我们还差一个“运载火箭”（部署代码），把这段运行代码给发射出去：</p><p>部署代码的结构基本没怎么变，之前已有解析，此处就不罗嗦了，唯一的区别是把复制到内存的长度由 b6 改为 0a : 608060405234801561001057600080fd5b5060<strong>0a</strong>8061001f6000396000f3fe</p><p>然后把他们拼接到一起，记得部署代码在前、运行代码在后，最后我们把这段代码发射出去就 OK了：</p><p>你将得到一个超级小巧、只有 10 个字节、无论传递什么参数都 <strong>只 会 返 回 42</strong> 的 “智能合约” （这么说看起来并不智能的样子……）</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/29be5c5754c815e603e2267bdb6cdde0396c19f6186197604732b2d6315e742d.png" alt="图7" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图7</figcaption></figure><p>全文完。</p><hr><p>关于作者：</p><div data-type="embedly" src="https://twitter.com/0xNezha" data="{&quot;provider_url&quot;:&quot;https://twitter.com&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;title&quot;:&quot;JavaScript is not available.&quot;,&quot;url&quot;:&quot;https://twitter.com/0xNezha&quot;,&quot;html&quot;:&quot;&lt;a class=\&quot;twitter-timeline\&quot; href=\&quot;https://twitter.com/0xNezha?ref_src=twsrc%5Etfw\&quot;&gt;Tweets by 0xNezha&lt;/a&gt;\n&lt;script async src=\&quot;https://platform.twitter.com/widgets.js\&quot; charset=\&quot;utf-8\&quot;&gt;&lt;/script&gt;&quot;,&quot;provider_name&quot;:&quot;Twitter&quot;,&quot;cache_age&quot;:3153600000,&quot;type&quot;:&quot;rich&quot;}" format="iframe"></div>]]></content:encoded>
            <author>hackbot@newsletter.paragraph.com (Hackit)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/fc76b0d0561c58385988bc7c0a8d1fbb9396143c7a1b6d0933474c06e7a91e90.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[以太坊智能合约逆向分析与实战：（4）复刻黑客的恶意合约
]]></title>
            <link>https://paragraph.com/@hackbot/4</link>
            <guid>XvjdTHBJxFQdSxVAFzDT</guid>
            <pubDate>Mon, 05 Sep 2022 12:07:15 GMT</pubDate>
            <description><![CDATA[据路边社消息，前几天一个刚出校门的“Web3 创业者”发布了一个彩票项目，通过付费 mint NFT 的方式及 “实时开奖” 的玩法，用户在mint NFT 时有 1/2 的概率获得 1.9 倍的费用返还。（中奖率高达 50% ？！实际数学期望E(x)=0.5*1.9 = 0.95，所以说久赌必输啊兄弟们！） 可能项目方对链上项目的运行机制和潜在风险不够了解，导致项目刚一发布便惨遭黑客攻击， 0.6 eth 的奖池被无情撸走，只好宣布创业失败。我们在严正谴责黑客的无良行径之余，不禁会想：这个项目究竟是哪里出问题了呢？今天我们通过阅读项目源码和反编译黑客的恶意合约，来分析此次攻击事件：一、漏洞成因图1从以上源码中我们可以发现，开发者使用了“当前区块难度 （block.difficulty）”和“当前区块时间戳（block.timestamp）”作为随机数种子，并将生成的随机数对 2 进行取模运算，如果结果是偶数则表明未中奖，是奇数则为中奖。 “区块难度”和“时间戳”是两个重要的区块属性，他们的数值很难被人为控制，而且一旦生成便无法修改。乍一看确实很适合当作随机数种子。然而开发者犯下...]]></description>
            <content:encoded><![CDATA[<p>据路边社消息，前几天一个刚出校门的“Web3 创业者”发布了一个彩票项目，通过付费 mint NFT 的方式及 “实时开奖” 的玩法，用户在mint NFT 时有 1/2 的概率获得 1.9 倍的费用返还。（中奖率高达 50% ？！实际数学期望E(x)=0.5*1.9 = 0.95，所以说久赌必输啊兄弟们！）</p><p>可能项目方对链上项目的运行机制和潜在风险不够了解，导致项目刚一发布便惨遭黑客攻击， 0.6 eth 的奖池被无情撸走，只好宣布创业失败。我们在严正谴责黑客的无良行径之余，不禁会想：这个项目究竟是哪里出问题了呢？今天我们通过阅读项目源码和反编译黑客的恶意合约，来分析此次攻击事件：</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">一、漏洞成因</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3930e85e9cc4fab2f61dcc3512920b44c5c2baa02bf11ebe64a231ff3d03add3.png" alt="图1" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图1</figcaption></figure><p>从以上源码中我们可以发现，开发者使用了“<strong>当前区块难度</strong> （block.difficulty）”和“<strong>当前区块时间戳</strong>（block.timestamp）”作为随机数种子，并将生成的随机数对 2 进行取模运算，如果结果是偶数则表明未中奖，是奇数则为中奖。</p><p>“区块难度”和“时间戳”是两个重要的区块属性，他们的数值很难被人为控制，而且一旦生成便无法修改。乍一看确实很适合当作随机数种子。然而开发者犯下的错误在于：他在不恰当的场景下（即时开奖）使用了这种生成方法。由于区块属性是公开可读的，攻击者完全有可能在 mint NFT 的前一刻，读取这两个区块属性并计算随机数，如果运算结果不符合中奖条件，则不发起 mint 或让 mint 中止；如果随机数结果符合中奖条件，则立即发起 mint 甚至在同一区块内大量 mint ，最终抽空奖池。漏洞利用的方法很简单，但我们这次并不直接动手写攻击工具，而是准备从逆向的角度来分析，看看这位黑客的操作是否和我们推测的一样。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">二、逆向分析</h2><p>这是黑客在链上的<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/tx/0x804ff3801542bff435a5d733f4d8a93a535d73d0de0f843fd979756a7eab26af">攻击记录</a>，黑客从布署合约到合约充值，再发起攻击然后卷款跑路，一气呵成。<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/tx/0x804ff3801542bff435a5d733f4d8a93a535d73d0de0f843fd979756a7eab26af">该笔tx</a>在一个区块内 mint 了 50 个 NFT 之多，而且都是中奖的。这就意味着攻击者不仅没为这些 NFT 付费，还获取了额外的奖金。那么，这个邪恶的合约都干了点什么呢？</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d8f977857a4d1d16239f37b70f3f4e7e528f11219c2e93d11f533f0abb77da8b.png" alt="图2" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图2</figcaption></figure><p>很遗憾，合约并没有开源（当然不会开源……）我们只好通过逆向的方式去研究了。这次我们使用一款叫做 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/palkeo/panoramix"><strong>Panoramix decompiler</strong></a> 的工具，它也被很多区块浏览器上所集成，但我选择在本地运行，因为更方便一些。</p><p>（ 如果你没有安装的话： <code>$ pip install panoramix-decompiler</code> ）</p><p>然后指定合约所在的链的 RPC 。我们分析的合约在 ETH 链上，所以使用以下 RPC：</p><p><code>$ export WEB3_PROVIDER_URI = https://rpc.ankr.com/eth</code></p><p>再给出合约地址，软件就开始自动下载二进制文件并开始反编译了：</p><p><code>$ panoramix 0x880df6cc30bb7934d065498ed9163a6e3b5aa67d</code></p><p>过了一会就能看到编译结果。此次反编译的结果比较清楚明了，没有什么需要深入分析的。只有个别没有识别出哈希对应的函数签名，也可以结合 https: //www. 4byte.directory/ 、https: //sig. eth. samczsun.c om/ 等工具来查询。为了节省篇幅。在这里就只画些重点，大家自行阅读吧:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1437db33605064bf8912ff6d0bc52812fe78e87840fd7db2cef373c60eeece32.png" alt="图3" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图3</figcaption></figure><p>由图中可以发现，黑客的操作手法和我们推测的差不多，计算随机数结果、批量循环 mint NFT 这些功能该有的都有，还写了转移 NFT 的方法。需要注意的是，按照 ERC721 标准的要求，如果用合约来调用 NFT 的 _safemint() 方法，该合约要实现 onERC721Received() 才可以成功mint。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">三、代码实现</h2><p>原理和逻辑既然已经搞清楚，那就只剩下编码实现了。这次我们使用 <strong>Foundry</strong> 来进行编写和测试。这是一款用 Rust 编写的构建工具，与其他基于 js 的构建工具相比速度更快一些。</p><p>Foundry 由三个不同的命令行工具（CLI）组成，包括 <strong>forge</strong>（用于构建、测试、部署和验证合约），<strong>cast</strong>（用于进行RPC调用和合约交互），和 <strong>anvil</strong>（用于运行本地EVM区块链节点）。详情请戳<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/">官方文档</a>。</p><p>如果你还没有安装 Foundry ,需要：</p><p><code>$ curl -L https://foundry.paradigm.xyz | bash</code></p><p><code>$ foundryup</code></p><p>如果已经安装，就直接新建项目：</p><p><code>$ forge init luckyHack &amp;&amp; cd luckyHack</code></p><p>删掉项目示例合约、测试合约：</p><p><code>$rm src/Contract.sol</code></p><p><code>$rm test/Contract.sol</code></p><p>在 src/下新建 <strong>luckyTiger.sol</strong> (作为测试目标)、<strong>luckyHack.sol</strong> (作为攻击工具)。</p><p>其中luckyTiger.sol 是照抄项目方的合约，luckyHack.sol 由我们自己编写，核心代码如图：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/5e52041ecc51da4f9138f69740a18eff405909defcda02201fe08b7a7167ad34.png" alt="图4" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图4</figcaption></figure><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">四、实战演练</h2><p>这次测试正好把 Foundry的三大件（forge，cast，anvil）用上一遍，十分方便。把我们先回到项目根目录，用anvil启动本地节点：</p><p><code>$ anvil</code></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/09a2234f233621a8a7a45281b283c2087205bed8f6d88d32f98a590ec025821d.png" alt="图5" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图5</figcaption></figure><p>本地节点会给出 10 个地址及私钥用于开发测试。这里我们假设地址0 是项目方、地址1是黑客，分别用二者的私钥来部署 NFT 项目和攻击合约。</p><p>我们开启另一个终端，用 forge 编译和部署合约。先后编译测试目标 luckyTiger.sol、攻击工具luckyHack.sol：</p><p><code>$ forge build</code></p><p>编译通过之后我们开始部署，luckyTiger.sol 的构造函数需要传递 tokenURI 等参数，记得用 --constructor-args ：</p><p><code>$ forge create src/luckyTiger.sol:luckytiger --private-key=测试私钥0 --constructor-args &quot;AAA&quot; &quot;BBB&quot;</code></p><p><code>$ forge create src/luckyHack.sol:luckyHack --private-key=测试私钥1</code></p><p>一切准备完毕后，测试环境各参数如下：</p><p>项目方地址：0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266</p><p>NFT地址：0x5FbDB2315678afecb367f032d93F642f64180aa3</p><p>黑客地址：0x70997970C51812dc3A010C7d01b50e0d17dc79C8</p><p>攻击合约地址：0x8464135c8F25Da09e49BC8782676a84730C318bC</p><p><strong>测试流程</strong>：</p><p>**1、**项目方调用 <strong>addBonusPool()</strong> 向合约奖池注资，彩票项目开始运行。本例设置为 5 eth。我们使用 cast 与链上合约进行交互：</p><p><code>$ cast send 0x5FbDB2315678afecb367f032d93F642f64180aa3 “addBonusPool()” --value 5ether --private-key=测试私钥0</code></p><p>查看一下合约余额,返回5000000000000000000：</p><p><code>$ cast balance 0x5FbDB2315678afecb367f032d93F642f64180aa3</code></p><p>**2、**攻击者调用 <strong>sendEther()</strong> 向攻击合约注资，作为mint NFT 的成本。本例设置为3 eth:</p><p><code>cast send 0x8464135c8F25Da09e49BC8782676a84730C318bC “sendEther()” --value 3ether --private-key=测试私钥1</code></p><p>查看一下合约余额,返回3000000000000000000：</p><p><code>$ cast balance 0x5FbDB2315678afecb367f032d93F642f64180aa3</code></p><p>**3、**攻击者先通过调用攻击合约的 <strong>getRandom()</strong>，查询当前区块参数是否符合中奖条件。(uint256)约定了返回值的格式，如果不写，会默认返回一长串十六进制字符：</p><p><code>$ cast call 0x8464135c8F25Da09e49BC8782676a84730C318bC &quot;getRandom()(uint256)&quot;</code></p><p>**4、**如果返回值为 1， 说明当前区块参数符合中奖条件。此时攻击者调用 <strong>hack(uint256)</strong> 向NFT项目发起攻击，由于是测试，我们先搞它 50 次 ：</p><p><code>$ cast send 0x8464135c8F25Da09e49BC8782676a84730C318bC &quot;hack(uint256)&quot; &quot;50&quot; --gas-limit 5000000 --private-key=测试私钥1</code></p><p>如此往复几次，我们查看下奖池余额和攻击合约余额：</p><p>奖池余额 ：4500000000000000000 （减少了0.5 eth）</p><p>攻击合约余额：3450000000000000000 （增加了0.45 eth）</p><p>什么？你非要问之间差的0.05 eth到哪去了？NFT 里面写的有啊：</p><p><code>$ cast balance 0x511604E18d63D32ac2605B5f0aF0cF580D21FA49</code></p><p>你看，在项目方的钱包里……</p><p><strong>补充说明：</strong></p><p>在以上实战演练中，为了研究方便和保证测试的全面性，我们搭建了整个测试环境。其实在我们日常测试时，完全不必大费周章地设置整个环境，可以利用 Foundry 这个便利的功能，将主网进行分叉，创造一个真实的链上场景进行演练：</p><p><code>$ anvil --fork-url https://rpc.ankr.com/eth --fork-block-number 15403398</code></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/2ca84d29f51ec3cbfd5ff5ffade74977cb91585a05c4941514612a92fd3c73a6.png" alt="图6" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图6</figcaption></figure><p>如上图所示，我们从以太主网的区块高度 <code>15403398 </code>分叉出了本地测试网，之后的操作与上面一样，但我们只需要专注编写攻击合约就可以了。</p><p><strong>相关代码</strong></p><div data-type="embedly" src="https://github.com/0xNezha/luckyHack" data="{&quot;provider_url&quot;:&quot;https://github.com&quot;,&quot;description&quot;:&quot;针对一起攻击事件的分析及复现. Contribute to 0xNezha/luckyHack development by creating an account on GitHub.&quot;,&quot;title&quot;:&quot;GitHub - 0xNezha/luckyHack: 针对一起攻击事件的分析及复现&quot;,&quot;author_name&quot;:&quot;0xNezha&quot;,&quot;thumbnail_width&quot;:1200,&quot;url&quot;:&quot;https://github.com/0xNezha/luckyHack&quot;,&quot;thumbnail_url&quot;:&quot;https://storage.googleapis.com/papyrus_images/19e0fd308d92fb30bc6c7bf854a3dd5fe6584a6be8fc7c26f0f238ae48934496.png&quot;,&quot;author_url&quot;:&quot;https://github.com/0xNezha&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;provider_name&quot;:&quot;GitHub&quot;,&quot;type&quot;:&quot;link&quot;,&quot;thumbnail_height&quot;:600,&quot;image&quot;:{&quot;img&quot;:{&quot;width&quot;:1200,&quot;height&quot;:600,&quot;src&quot;:&quot;https://storage.googleapis.com/papyrus_images/19e0fd308d92fb30bc6c7bf854a3dd5fe6584a6be8fc7c26f0f238ae48934496.png&quot;}}}" format="small"><link rel="preload" as="image" href="https://storage.googleapis.com/papyrus_images/19e0fd308d92fb30bc6c7bf854a3dd5fe6584a6be8fc7c26f0f238ae48934496.png"/><div class="react-component embed my-5" data-drag-handle="true" data-node-view-wrapper="" style="white-space:normal"><a class="link-embed-link" href="https://github.com/0xNezha/luckyHack" target="_blank" rel="noreferrer"><div class="link-embed"><div class="flex-1"><div><h2>GitHub - 0xNezha/luckyHack: 针对一起攻击事件的分析及复现</h2><p>针对一起攻击事件的分析及复现. Contribute to 0xNezha/luckyHack development by creating an account on GitHub.</p></div><span><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link h-3 w-3 my-auto inline mr-1"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>https://github.com</span></div><img src="https://storage.googleapis.com/papyrus_images/19e0fd308d92fb30bc6c7bf854a3dd5fe6584a6be8fc7c26f0f238ae48934496.png"/></div></a></div></div><p><strong>关于作者</strong></p><div data-type="embedly" src="https://twitter.com/0xNezha" data="{&quot;provider_url&quot;:&quot;https://twitter.com&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;title&quot;:&quot;JavaScript is not available.&quot;,&quot;url&quot;:&quot;https://twitter.com/0xNezha&quot;,&quot;html&quot;:&quot;&lt;a class=\&quot;twitter-timeline\&quot; href=\&quot;https://twitter.com/0xNezha?ref_src=twsrc%5Etfw\&quot;&gt;Tweets by 0xNezha&lt;/a&gt;\n&lt;script async src=\&quot;https://platform.twitter.com/widgets.js\&quot; charset=\&quot;utf-8\&quot;&gt;&lt;/script&gt;&quot;,&quot;provider_name&quot;:&quot;Twitter&quot;,&quot;cache_age&quot;:3153600000,&quot;type&quot;:&quot;rich&quot;}" format="iframe"></div>]]></content:encoded>
            <author>hackbot@newsletter.paragraph.com (Hackit)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/01bdb62a0ee826461f0830e28a36c53d63448e2a59593502434645e14284026e.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[以太坊智能合约逆向分析与实战：（3）[实战篇] 访问私有动态数据类型]]></title>
            <link>https://paragraph.com/@hackbot/3</link>
            <guid>dK9Bc1A3fYnDB4AhjmDk</guid>
            <pubDate>Sun, 24 Jul 2022 12:27:08 GMT</pubDate>
            <description><![CDATA[通过之前的学习，我们了解到在 EVM 中，数据的存储是以”大端“ (bigendian) 的方式存储在”存储槽“ (slot)中的，变量的低位存储在 slot 的低地址中，每个 slot 的长度不超过 32 字节。关于全局变量的存储方式 ，一般来说，静态类型 在合约部署时已经按顺序存到了slot中，并从 slot 0 开始连续排列，按照类型的大小，有的变量单独占据一个slot，有些是好几个变量共用一个slot。然而，像映射、动态数组这些 动态类型 因为所需存储空间无法预计，因此并不是整个的放在某个slot 中，而是随用随存。以映射（MAP）为例，该类型首先会按以上的规则占个slot的位置，再通过一定的计算得到存放value的真实地址。只讲原理有些枯燥，我们举例说明吧！ 本次我们以一个猜数字的游戏为例，为了方便演示，我对游戏合约做了一些简化。游戏很简单：合约提供一个数字，用户提交一个值，系统会提示用户的值是大于或者小于原定数字，直到猜中为止。这个数字存放在一个 privite 映射之中（如下图 item_2 变量），通过区块浏览器是无法查询到这个数字的。但毕竟这是区块链，所有的数据...]]></description>
            <content:encoded><![CDATA[<p>通过<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/hackbot.eth/LvAQD7OjarsLrcRxGPiz-4MtXP0WGwFpwx51LIXSIx4">之前的学习</a>，我们了解到在 EVM 中，数据的存储是以”大端“ (bigendian) 的方式存储在”存储槽“ (slot)中的，变量的低位存储在 slot 的低地址中，每个 slot 的长度不超过 32 字节。关于<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://learnblockchain.cn/article/3510">全局变量的存储方式</a> ，一般来说，<strong>静态类型</strong> 在合约部署时已经按顺序存到了slot中，并从 slot 0 开始连续排列，按照类型的大小，有的变量单独占据一个slot，有些是好几个变量共用一个slot。然而，像映射、动态数组这些 <strong>动态类型</strong> 因为所需存储空间无法预计，因此并不是整个的放在某个slot 中，而是随用随存。以映射（MAP）为例，该类型首先会按以上的规则占个slot的位置，再通过一定的计算得到存放value的真实地址。只讲原理有些枯燥，我们举例说明吧！</p><p>本次我们以一个猜数字的游戏为例，为了方便演示，我对游戏合约做了一些简化。游戏很简单：合约提供一个数字，用户提交一个值，系统会提示用户的值是大于或者小于原定数字，直到猜中为止。这个数字存放在一个 privite 映射之中（如下图 <strong>item_2</strong> 变量），通过区块浏览器是无法查询到这个数字的。但毕竟这是区块链，所有的数据和代码都在链上，”看不到“并不等于”不存在“，我们仍旧可以通过一些方法来获取这个值。</p><p>以下是示例合约代码以及对各种类型存储情况的分析：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3cbe36e10cc5aaec8a73c37310c95e35c88be677eea010dde07174c8ebd2d5f3.png" alt="图1" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图1</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3e2cd0f2632c9edd675af44c0c8eb8339f85ce333ef6383ed93d733c66028224.png" alt="图2" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图2</figcaption></figure><p>由代码可知，游戏中我们需要猜测 映射 <strong>item_2</strong>[ count_A ] 对应的 value。假设合约所有者以某种方式秘密设置了 <strong>item_2</strong>[ count_A ] 的值（为简便起见，我直接在构造函数中设置了），我们可以跳过猜想，直接获取这个值吗？当然可以！</p><p>我们知道，<strong>静态类型</strong>的存储槽都是按顺序固定存放的，可以通过直接读取 slot [n] 的值来获取它们的值。比如 图1 中的 privite <strong>count_B</strong> , 虽然是私有变量，在区块浏览器中无法查阅到，但其对应的值就在 slot 2 中存放，可以通过编程的方式轻松读取出来。但动态类型却不是这样，如果我们直接读取 <strong>item_2</strong> 所占据的 slot 10 ，结果就会让你失望了。那么，映射类型的数据存储是怎样的呢？我们继续向下看。</p><p>合约中各种类型变量的存储和排序情况，可以用 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://remix.ethereum.org/"><strong>Remix</strong></a> 的 debug 功能很直观地看出来：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/18f87bb89ae82051cb56c249274616ee14549ea6a5af5efbdae0b98b4a97e7f6.png" alt="图3" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图3</figcaption></figure><p>可以看到，EVM 的存储方式有点像映射，以 key → value 的形式对应着 slot → 数据 。红色和蓝色方框中的 key 从 0 到 4 依次排开，正如 slot 0 ~ slot 4 分别存储着相应内容。</p><p>但下面的黄色方框是怎么回事呢？它的 slot 编号（key : 0x2cb73cd019c70b24b7128c3a8fa046c2e524595f0f21ef557221be7ab820bc99 ）为什么这么长？而且它存储的数据是 0x3039 , 正是 12345 的十六进制表示。很可能就是我们要寻找的值。</p><p>那这一串数字是怎么来的呢 ？带着这个疑惑，我们来使用<strong>编译器 solc</strong> 看看它的 ”汇编代码“（opcodes）：</p><p>在终端输入指令 <code>./solc -o asmOutputFolder --bin --asm --optimize ./contracts/slotHack.sol </code>从 <strong>OutputFolder</strong> 目录中找到 <strong>slotHack.evm</strong> :</p><p>可以看到，在 汇编代码中，<strong>set_item_1</strong> 处也出现了类似的情况：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/cc0c277201c517e59a3336b9ecf6c6f525290fba95a2e0116349b9eaadcde718.png" alt="图4" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图4</figcaption></figure><p>看来这就是映射类型的存储方式了。通过<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/hackbot.eth/LvAQD7OjarsLrcRxGPiz-4MtXP0WGwFpwx51LIXSIx4">之前的学习</a>，我们可以得知这一长串数字是这样的得来的：<code>n = keecak256(h(k)+p)</code><em>【对于值类型，h(k) 通过填充 0 的方式，将 k 填充为 32 字节; 对于字节或者字符串类型，h(k) 直接计算 k 的 keccak256 哈希。】</em></p><p>在本例的 <strong>set_item_1</strong> [ 0xC0FFEE ] 中，这个数值是这样计算出来的：<code>keccak256(bytes32(0xC0FFEE) + bytes32(9)))</code> 其中 <strong>0xC0FFEE</strong> 是该映射的 key, 而 <strong>9</strong> 是 <strong>set_item_1</strong> 所对应的 slot。</p><p><em>【注：我们之所以能看到编译器可以提前计算 </em><strong><em>set_item_1</em></strong><em> 的 key 的地址，是因为相关的值是常量。如果key使用的是变量（如 </em><strong><em>set_item_2</em></strong><em> ），那么哈希就必须要在汇编代码中完成。】</em></p><p>——也就是说，只要我们能够确定映射类型在合约部署时所占的 <strong>slot</strong> 以及 value对应的 <strong>key</strong>，就能计算并得到 <strong>value</strong> 的真实存放地址, 无论它是 privite 还是 public ，统统能读取出来。原理既然清楚了，那就开始代码实现吧：</p><p><strong>第一步：计算 slot</strong></p><p><strong>set_item_2</strong> [ 0x2560A0256 ] , key 为 <strong>0x2560A0256</strong> ，item_2 的 slot 位置为 <strong>10</strong></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ee8de1e5a3ae2ab66289f1e18c0513eeff382439c568c671034e7e89e6d0ce68.png" alt="图5" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图5</figcaption></figure><p>得到value存储的 slot 为 <code>0x2cb73cd019c70b24b7128c3a8fa046c2e524595f0f21ef557221be7ab820bc99</code></p><p><strong>第二步：读取slot</strong></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0904caa3c1f1a45c94f81336fdeb90d492c7296749ce257d5a52bd9b9365268f.png" alt="图6" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">图6</figcaption></figure><p>如上图，已经读取到了 <strong>item_2</strong> [ 0x2560A0256 ] 的值 0x3039 , 换算成十进制就是 <strong>12345</strong></p><p>大功告成！</p><p>==================================================</p><p><strong>相关代码：</strong></p><div data-type="embedly" src="https://github.com/0xNezha/slotHack" data="{&quot;provider_url&quot;:&quot;https://github.com&quot;,&quot;description&quot;:&quot;计算并读取evm存储槽中映射类型的value. Contribute to 0xNezha/slotHack development by creating an account on GitHub.&quot;,&quot;title&quot;:&quot;GitHub - 0xNezha/slotHack: 计算并读取evm存储槽中映射类型的value&quot;,&quot;author_name&quot;:&quot;0xNezha&quot;,&quot;thumbnail_width&quot;:1200,&quot;url&quot;:&quot;https://github.com/0xNezha/slotHack&quot;,&quot;thumbnail_url&quot;:&quot;https://storage.googleapis.com/papyrus_images/5e5845649f497fbb108b722f1337479778a0e03f0c60785a282b0e62d658fa92.png&quot;,&quot;author_url&quot;:&quot;https://github.com/0xNezha&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;provider_name&quot;:&quot;GitHub&quot;,&quot;type&quot;:&quot;link&quot;,&quot;thumbnail_height&quot;:600,&quot;image&quot;:{&quot;img&quot;:{&quot;width&quot;:1200,&quot;height&quot;:600,&quot;src&quot;:&quot;https://storage.googleapis.com/papyrus_images/5e5845649f497fbb108b722f1337479778a0e03f0c60785a282b0e62d658fa92.png&quot;}}}" format="small"><link rel="preload" as="image" href="https://storage.googleapis.com/papyrus_images/5e5845649f497fbb108b722f1337479778a0e03f0c60785a282b0e62d658fa92.png"/><div class="react-component embed my-5" data-drag-handle="true" data-node-view-wrapper="" style="white-space:normal"><a class="link-embed-link" href="https://github.com/0xNezha/slotHack" target="_blank" rel="noreferrer"><div class="link-embed"><div class="flex-1"><div><h2>GitHub - 0xNezha/slotHack: 计算并读取evm存储槽中映射类型的value</h2><p>计算并读取evm存储槽中映射类型的value. Contribute to 0xNezha/slotHack development by creating an account on GitHub.</p></div><span><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link h-3 w-3 my-auto inline mr-1"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>https://github.com</span></div><img src="https://storage.googleapis.com/papyrus_images/5e5845649f497fbb108b722f1337479778a0e03f0c60785a282b0e62d658fa92.png"/></div></a></div></div><p><strong>测试网合约：</strong></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://rinkeby.etherscan.io/address/0xc81f73EdcA69ac1663d5b2b2E3CBa520d62e5425">https://rinkeby.etherscan.io/address/0xc81f73EdcA69ac1663d5b2b2E3CBa520d62e5425</a></p><p><strong>Twitter：</strong></p><div data-type="embedly" src="https://twitter.com/0xNezha" data="{&quot;provider_url&quot;:&quot;https://twitter.com&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;title&quot;:&quot;JavaScript is not available.&quot;,&quot;url&quot;:&quot;https://twitter.com/0xNezha&quot;,&quot;html&quot;:&quot;&lt;a class=\&quot;twitter-timeline\&quot; href=\&quot;https://twitter.com/0xNezha?ref_src=twsrc%5Etfw\&quot;&gt;Tweets by 0xNezha&lt;/a&gt;\n&lt;script async src=\&quot;https://platform.twitter.com/widgets.js\&quot; charset=\&quot;utf-8\&quot;&gt;&lt;/script&gt;&quot;,&quot;provider_name&quot;:&quot;Twitter&quot;,&quot;cache_age&quot;:3153600000,&quot;type&quot;:&quot;rich&quot;}" format="iframe"></div>]]></content:encoded>
            <author>hackbot@newsletter.paragraph.com (Hackit)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/5a386f75b230e269d7d81a30c983e10b36e71f3639ba8598e12479804b5e00c6.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[以太坊智能合约逆向分析与实战：（2）深入了解以太坊虚拟机]]></title>
            <link>https://paragraph.com/@hackbot/2</link>
            <guid>jQlkKFLUhoaSSdOl4amU</guid>
            <pubDate>Tue, 15 Feb 2022 16:01:39 GMT</pubDate>
            <description><![CDATA[关于以太坊虚拟机的实现原理及操作码分析，我自身掌握的一些知识也比较零散，但网上有很好的资料可以学习，通过阅读一系列文章，有助于梳理自己的知识点，把一个个离散的点串成一条线，为更加深入的学习打下基础。 个人推荐这五篇文章，但由于时间紧张，我也没有工夫逐篇说明了，自己找想要的看吧。（建议前置知识：一些Solidity编程基础） Diving Into The Ethereum Virtual Machine 系列：Introduction to the EVM assembly code.How fixed-length data types are represented.How dynamic data types are represented.How ABI Encodes External Method Calling.What is going on when a new contract is created.有网友对以上文档制作了翻译版：EVM汇编代码的介绍(第1部分)固定长度数据类型的表示方法(第2部分)动态数据类型的表示方法(第3部分)ABI编码外部方法调用的方式...]]></description>
            <content:encoded><![CDATA[<p>关于以太坊虚拟机的实现原理及操作码分析，我自身掌握的一些知识也比较零散，但网上有很好的资料可以学习，通过阅读一系列文章，有助于梳理自己的知识点，把一个个离散的点串成一条线，为更加深入的学习打下基础。</p><p>个人推荐这五篇文章，但由于时间紧张，我也没有工夫逐篇说明了，自己找想要的看吧。（建议前置知识：一些Solidity编程基础）</p><p><strong>Diving Into The Ethereum Virtual Machine 系列：</strong></p><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/@hayeah/diving-into-the-ethereum-vm-6e8d5d2f3c30">Introduction to the EVM assembly code.</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/@hayeah/diving-into-the-ethereum-vm-part-2-storage-layout-bc5349cb11b7">How fixed-length data types are represented.</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/@hayeah/diving-into-the-ethereum-vm-the-hidden-costs-of-arrays-28e119f04a9b">How dynamic data types are represented.</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/@hayeah/how-to-decipher-a-smart-contract-method-call-8ee980311603">How ABI Encodes External Method Calling.</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/@hayeah/diving-into-the-ethereum-vm-part-5-the-smart-contract-creation-process-cb7b6133b855">What is going on when a new contract is created.</a></p></li></ul><p><strong>有网友对以上文档制作了翻译版：</strong></p><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.jianshu.com/p/1969f3761208">EVM汇编代码的介绍(第1部分)</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.jianshu.com/p/9df8d15418ed">固定长度数据类型的表示方法(第2部分)</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.jianshu.com/p/af5721c79505">动态数据类型的表示方法(第3部分)</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.jianshu.com/p/d0e8e825d41b">ABI编码外部方法调用的方式(第4部分)</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.jianshu.com/p/d9137e87c9d3">一个新合约被创建后会发生什么(第5部分)</a></p></li></ul><p>读完之后，相信你会对EVM的原理有深入的认识，或许会解答你的一些疑惑！</p><p>比如：</p><p><strong>EVM汇编代码与 Solidity 的对应关系是什么？</strong></p><p>答：请查阅第一部分。</p><p><strong>为什么有些人的钱包地址会比别人节省Gas?</strong></p><p>答：因为他们钱包地址0比较多（哈哈） 请查阅第一、第二部分。</p><p><strong>为什么有些合约操作这么费Gas？</strong></p><p>答：有些指令（尤其是存储）的Gas价格是很贵的。请查阅第一、第二部分。</p><p><strong>映射、数组这两种看似不同的数据结构，在虚拟机的存储器中是怎样的存在？</strong></p><p>答：其实数组属于是一种有着更加高级特征的映射，具体查阅第三部分。</p><p><strong>调用合约一定要ABI吗？ABI是如何对参数进行编码的？</strong></p><p>答：不一定。 ABI 可以将参数编码为字节序列，以进行交易的发送。详情参阅第四部分。</p><p><strong>当一个新合约被创建时，EVM里究竟发生了什么？</strong></p><p>答：说来话长，看第五部分吧！</p><p><em>注:译文（第4部分）似乎URL打不开了，可尝试到</em><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://blog.csdn.net/qq_21518355/article/details/89306432"><em>CSDN博客</em></a><em>查看。</em></p>]]></content:encoded>
            <author>hackbot@newsletter.paragraph.com (Hackit)</author>
        </item>
        <item>
            <title><![CDATA[智能合约安全初探之撸爆小学生]]></title>
            <link>https://paragraph.com/@hackbot/9l7BOPjjLwunKBmrlbWk</link>
            <guid>9l7BOPjjLwunKBmrlbWk</guid>
            <pubDate>Wed, 09 Feb 2022 08:21:40 GMT</pubDate>
            <description><![CDATA[一、前言 春节假期的最后一天，网上突然出现了这样一条消息：RND 创立者、12岁小学生黄正，在网上发布了智能合约创建和部署的视频教程。别的暂且不谈，但这 RND 是什么呢？原来是小学生在以太坊上发布的一个 ERC20 代币 (0x1c7E83f8C581a967940DBfa7984744646AE46b29) ,合约中制定了一系列空投规则：每个地址都可以领取，领取的越早给的代币越多。这引得网友们争相领取，跑得快的也收益颇丰。这么来看的话，每个钱包都可以领取，如果自己搞100个钱包，则可以领取100次，岂不美哉？但考虑到需要分发以太币及领取空投的GAS费，叠加到一起也不是小数目。然而小学生百密一疏，合约中有一处可以利用的地方，最终导致“科学家”在链上生成众多智能合约，实现了RND代币的“批量领取”。本文基于RND代码及 Robot DAO 群友 GCC (Github地址 ：github.com/GGCCCC )的代码，对本次事件进行复现。 二、RND 代币合约概览 这是一个基于ERC20标准的代币，完整代码可到etherscan上自行查阅。领取空投的具体实现如下(token.s...]]></description>
            <content:encoded><![CDATA[<p><strong>一、前言</strong></p><p>春节假期的最后一天，网上突然出现了这样一条消息：RND 创立者、12岁小学生黄正，在网上发布了智能合约创建和部署的视频教程。别的暂且不谈，但这 RND 是什么呢？原来是小学生在以太坊上发布的一个 ERC20 代币 (0x1c7E83f8C581a967940DBfa7984744646AE46b29) ,合约中制定了一系列空投规则：每个地址都可以领取，领取的越早给的代币越多。这引得网友们争相领取，跑得快的也收益颇丰。这么来看的话，每个钱包都可以领取，如果自己搞100个钱包，则可以领取100次，岂不美哉？但考虑到需要分发以太币及领取空投的GAS费，叠加到一起也不是小数目。然而小学生百密一疏，合约中有一处可以利用的地方，最终导致“科学家”在链上生成众多智能合约，实现了RND代币的“批量领取”。本文基于RND代码及 Robot DAO 群友 GCC (Github地址 ：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://github.com/GGCCCC">github.com/GGCCCC</a> )的代码，对本次事件进行复现。</p><p><strong>二、RND 代币合约概览</strong></p><p>这是一个基于ERC20标准的代币，完整代码可到etherscan上自行查阅。领取空投的具体实现如下(token.sol)：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f77fb894aff191e6f1d9620edab051d799dbcfe640e30562d9c65bd1e066565d.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>可以看到，代码中规定：如果一个地址在360天之内、且之前未曾领取过空投，则按照<code>return_claim_number()</code> 计算出来的数额给予RND代币空投。</p><p>看起来似乎没什么问题，但是小学生忽略了一个方面：外部控制账户（EOA，也就是俗称的“钱包”）与智能合约账户（Contract）都能够参与空投的领取。在有些场景，这算不上什么大问题；而在前段时间 GameFi 火热的时候，各种盲盒、抽卡层出不穷，这时就体现出差异了：用户使用自己钱包发起交易去抽卡，这笔交易广播出去之后，用户就无法再干预了，只能等待抽卡结果；而如果用智能合约发起交易，就可在合约里面加上逻辑判断，如果抽卡结果不是自己想要的，就可以直接revert，除了损失一点gas费之外没有任何支出，直到抽出自己想要的结果。这样的攻击案例层出不穷，也对游戏生态造成了很大破坏。有一种解决方案是加上 <code>msg.sender == tx.origin</code> 的判断，规定项目合约的调用者必须是交易最初发起者，这样就较好地防止了用合约来重复抽卡的行为。</p><p>言归正传，既然RND的代码中没有限制智能合约，那么我们就部署一个合约来批量撸一波空投吧！（本次测试目标为笔者部署在Rinkeby测试网的RND <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://rinkeby.etherscan.io/token/0xcb33f7fb101e377a4b0e19fd647f391fad14d0b5">rinkeby.etherscan.io/token/0xcb33f7fb101e377a4b0e19fd647f391fad14d0b5</a>，并非针对主网RND项目。批量领取合约参考了 GCC 的代码：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://github.com/GGCCCC/airdrop_multi_claim">github.com/GGCCCC/airdrop_multi_claim</a> ）</p><p><strong>三、部署Exploit合约进行批量领取</strong></p><p>开发工具我们选用 Hardhat 。首先要安装 Node.js 。然后打开新的终端，输入以下命令来配置Hardhat 环境：</p><p><code>mkdir rndExploit</code></p><p><code>cd rndExploit</code></p><p><code>npm init --yes</code></p><p><code>npm install --save-dev hardhat</code></p><p>安装完毕后，在 rndExploit 目录中运行：</p><p><code>npx hardhat</code></p><p>用键盘选择 Create a basic sample project ，然后一路按Enter：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9bd8ed6fd8f2bc019fa02cbae03ffb3247871133b055734b64aa456fa67a24a7.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>合约不复杂，实现了claim和transfer。但本次操作的重点在于<code>new</code>这个关键字，我们要用它来创建很多新合约，然后让这些新合约批量领取空投后再转移给我们。我们直接在Harhat的sample代码中修改(./contracts/Greeter.sol)。代码如下：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/38a7128e1f7132d73dcf2d13f8f70144514775d4c6de4981ccdc64f95936ba83.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>然后稍加修改./scripts/sample-script.js。我们把合约部署和调用写到同一个脚本中，连部署带调用，方便 。这里我们向合约传入参数 10 ，意为创建10个子合约来进行空投的领取:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b3e83a4da5e32e399ee90690876c269c7f78ccf845919915eec8290dded0a5b7.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>最后对 ./hardhat.config.js 进行设置 ，以部署到Rinkeby测试网：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3973f9af435c034c8818893b25eef58b5d1752cbe2239100957b613d1de807f3.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>一切设置完毕后，在 rndExploit 目录中运行</p><p><code>npx hardhat run scripts/sample-script.js --network rinkeby</code></p><p>稍等片刻，命令行中显示</p><p>“Compiling 1 file with 0.8.7 Solidity compilation finished successfully Greeter deployed to: 0x0074bdB3da306F9051f172E7646d1dF2959b4443”</p><p>程序部署完毕，我们查看链上记录：</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://rinkeby.etherscan.io/tx/0x343a4e09f91acb5b8178471cfa7f7295765ad135f43acdd2d6e1896f4c3ed222">rinkeby.etherscan.io/tx/0x343a4e09f91acb5b8178471cfa7f7295765ad135f43acdd2d6e1896f4c3ed222</a></p><p>可以发现已经成功Claim了10次空投。</p><p><strong>声明：本文代码均作为演示之用，不对安全性做出承诺。</strong></p><p>HardHat工程源码见：</p><div data-type="embedly" src="https://github.com/0xNezha/mutiClaimRND" data="{&quot;provider_url&quot;:&quot;https://github.com&quot;,&quot;description&quot;:&quot;批量生成合约并Claim空投代币. Contribute to 0xNezha/mutiClaimRND development by creating an account on GitHub.&quot;,&quot;title&quot;:&quot;GitHub - 0xNezha/mutiClaimRND: 批量生成合约并Claim空投代币&quot;,&quot;author_name&quot;:&quot;0xNezha&quot;,&quot;thumbnail_width&quot;:1200,&quot;url&quot;:&quot;https://github.com/0xNezha/mutiClaimRND&quot;,&quot;thumbnail_url&quot;:&quot;https://storage.googleapis.com/papyrus_images/208151a9a0eec0e892aca8a811f61118ad00e824521e98bf119b86d7fdabd087.png&quot;,&quot;author_url&quot;:&quot;https://github.com/0xNezha&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;provider_name&quot;:&quot;GitHub&quot;,&quot;type&quot;:&quot;link&quot;,&quot;thumbnail_height&quot;:600,&quot;image&quot;:{&quot;img&quot;:{&quot;width&quot;:1200,&quot;height&quot;:600,&quot;src&quot;:&quot;https://storage.googleapis.com/papyrus_images/208151a9a0eec0e892aca8a811f61118ad00e824521e98bf119b86d7fdabd087.png&quot;}}}" format="small"><link rel="preload" as="image" href="https://storage.googleapis.com/papyrus_images/208151a9a0eec0e892aca8a811f61118ad00e824521e98bf119b86d7fdabd087.png"/><div class="react-component embed my-5" data-drag-handle="true" data-node-view-wrapper="" style="white-space:normal"><a class="link-embed-link" href="https://github.com/0xNezha/mutiClaimRND" target="_blank" rel="noreferrer"><div class="link-embed"><div class="flex-1"><div><h2>GitHub - 0xNezha/mutiClaimRND: 批量生成合约并Claim空投代币</h2><p>批量生成合约并Claim空投代币. Contribute to 0xNezha/mutiClaimRND development by creating an account on GitHub.</p></div><span><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link h-3 w-3 my-auto inline mr-1"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>https://github.com</span></div><img src="https://storage.googleapis.com/papyrus_images/208151a9a0eec0e892aca8a811f61118ad00e824521e98bf119b86d7fdabd087.png"/></div></a></div></div>]]></content:encoded>
            <author>hackbot@newsletter.paragraph.com (Hackit)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/4b65a09e40999c51e614a86907040aa3d905ce7c036fec71fb35ebca27744eda.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[discordTalker_V1.0： 一个简单的Dsicord水群脚本（内附源码）]]></title>
            <link>https://paragraph.com/@hackbot/discordtalker-v1-0-dsicord</link>
            <guid>k2om6Ta2tknD3XIBnQns</guid>
            <pubDate>Thu, 03 Feb 2022 14:24:32 GMT</pubDate>
            <description><![CDATA[这是个很简单的演示脚本，通过调用Discord的API来实现定时向某个频道内发送指定的文本，额外添加了GUI界面以方便操作，一个实例仅能登录一个账号。（可以复制多份程序到不同的文件夹，以实现“多账号/多频道”发送）Release DiscordTalker_release · 0xNezha/DiscordTalker_V1.0Auto send message to Discord channels. Contribute to 0xNezha/DiscordTalker_V1.0 development by creating an account on GitHub.https://github.com本程序仅供编程学习研究，切勿用于其他用途。如因使用不当造成任何损失，与本程序及作者无关。 程序使用说明： 1、获取Discord账号的登录令牌（即“Token”），可参考该视频： 2、获取想要发送信息的频道号码。方法很多，最简单的办法如图所示： 首先自己的账号要先加入该Discord服务器，然后点击想要发送信息的频道，在浏览器地址栏内找到最后一段数字即为频道号码。（本例：93...]]></description>
            <content:encoded><![CDATA[<p>这是个很简单的演示脚本，通过调用Discord的API来实现定时向某个频道内发送指定的文本，额外添加了GUI界面以方便操作，一个实例仅能登录一个账号。（可以复制多份程序到不同的文件夹，以实现“多账号/多频道”发送）</p><div data-type="embedly" src="https://github.com/0xNezha/DiscordTalker_V1.0/releases/tag/V1.0" data="{&quot;provider_url&quot;:&quot;https://github.com&quot;,&quot;description&quot;:&quot;Auto send message to Discord channels. Contribute to 0xNezha/DiscordTalker_V1.0 development by creating an account on GitHub.&quot;,&quot;title&quot;:&quot;Release DiscordTalker_release · 0xNezha/DiscordTalker_V1.0&quot;,&quot;url&quot;:&quot;https://github.com/0xNezha/DiscordTalker_V1.0/releases/tag/V1.0&quot;,&quot;author_name&quot;:&quot;0xNezha&quot;,&quot;thumbnail_width&quot;:1200,&quot;thumbnail_url&quot;:&quot;https://storage.googleapis.com/papyrus_images/d8cbf6e375e978c778589b37a781d927059be96491639585152166ea9add5b4b.png&quot;,&quot;author_url&quot;:&quot;https://github.com/0xNezha&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;provider_name&quot;:&quot;GitHub&quot;,&quot;type&quot;:&quot;link&quot;,&quot;thumbnail_height&quot;:600,&quot;image&quot;:{&quot;img&quot;:{&quot;width&quot;:1200,&quot;height&quot;:600,&quot;src&quot;:&quot;https://storage.googleapis.com/papyrus_images/d8cbf6e375e978c778589b37a781d927059be96491639585152166ea9add5b4b.png&quot;}}}" format="small"><link rel="preload" as="image" href="https://storage.googleapis.com/papyrus_images/d8cbf6e375e978c778589b37a781d927059be96491639585152166ea9add5b4b.png"/><div class="react-component embed my-5" data-drag-handle="true" data-node-view-wrapper="" style="white-space:normal"><a class="link-embed-link" href="https://github.com/0xNezha/DiscordTalker_V1.0/releases/tag/V1.0" target="_blank" rel="noreferrer"><div class="link-embed"><div class="flex-1"><div><h2>Release DiscordTalker_release · 0xNezha/DiscordTalker_V1.0</h2><p>Auto send message to Discord channels. Contribute to 0xNezha/DiscordTalker_V1.0 development by creating an account on GitHub.</p></div><span><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link h-3 w-3 my-auto inline mr-1"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>https://github.com</span></div><img src="https://storage.googleapis.com/papyrus_images/d8cbf6e375e978c778589b37a781d927059be96491639585152166ea9add5b4b.png"/></div></a></div></div><p><strong>本程序仅供编程学习研究，切勿用于其他用途。如因使用不当造成任何损失，与本程序及作者无关。</strong></p><p>程序使用说明：</p><p>1、获取Discord账号的登录令牌（即“Token”），可参考该视频：</p><div data-type="youtube" videoId="WWHZoa0SxCc">
      <div class="youtube-player" data-id="WWHZoa0SxCc" style="background-image: url('https://i.ytimg.com/vi/WWHZoa0SxCc/hqdefault.jpg'); background-size: cover; background-position: center">
        <a href="https://www.youtube.com/watch?v=WWHZoa0SxCc">
          <img src="{{DOMAIN}}/editor/youtube/play.png" class="play"/>
        </a>
      </div></div><p>2、获取想要发送信息的频道号码。方法很多，最简单的办法如图所示：</p><p>首先自己的账号要先加入该Discord服务器，然后点击想要发送信息的频道，在浏览器地址栏内找到最后一段数字即为频道号码。（本例：933291634093858836）</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e318df6082a13968250c98767ead9863d9ac6c898507ef78391b5fb0fdddb0b4.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>3、将前两步获取的信息填入配置文件 botConfig.ini 并保存：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/bf4f848b310820a5d1f9eece55797debe9b4c60fa640fc7e18ed39e60dd28706.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>4、在talk_list_1.txt 内输入水群信息并保存，每句一行：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/937aa05a221e67aecf0523e8b66ffeb2d6619a4e649ada61e7fb5af5c743dcce.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>5、打开Discord Talker, 点击“启动”即可：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b9b62c5d55e8abed4a7560e1ebded154b24063bec9328778b5600679fc8be977.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><strong>写在后面</strong>：</p><p>这样的简单脚本只适合“半自动“，因为在实际应用中还需要与管理员以及”热心网友“斗智斗勇，以免被当成机器人抓到。</p><p><strong>2022-02-08更新：</strong></p><p>V2.0，实现两个机器人相互引用回复</p><div data-type="embedly" src="https://github.com/0xNezha/DiscordTalker_V2.0/releases/tag/V2.0" data="{&quot;provider_url&quot;:&quot;https://github.com&quot;,&quot;description&quot;:&quot;2 Bots talk || 两人互答 . Contribute to 0xNezha/DiscordTalker_V2.0 development by creating an account on GitHub.&quot;,&quot;title&quot;:&quot;Release DiscordTalker_release · 0xNezha/DiscordTalker_V2.0&quot;,&quot;author_name&quot;:&quot;0xNezha&quot;,&quot;thumbnail_width&quot;:1200,&quot;url&quot;:&quot;https://github.com/0xNezha/DiscordTalker_V2.0/releases/tag/V2.0&quot;,&quot;thumbnail_url&quot;:&quot;https://storage.googleapis.com/papyrus_images/16bb61f58baa3fb731197cdb7a8ddc9d031da2a7e3bfa0a0dae66cfbe59c0af6.png&quot;,&quot;author_url&quot;:&quot;https://github.com/0xNezha&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;provider_name&quot;:&quot;GitHub&quot;,&quot;type&quot;:&quot;link&quot;,&quot;thumbnail_height&quot;:600,&quot;image&quot;:{&quot;img&quot;:{&quot;width&quot;:1200,&quot;height&quot;:600,&quot;src&quot;:&quot;https://storage.googleapis.com/papyrus_images/16bb61f58baa3fb731197cdb7a8ddc9d031da2a7e3bfa0a0dae66cfbe59c0af6.png&quot;}}}" format="small"><link rel="preload" as="image" href="https://storage.googleapis.com/papyrus_images/16bb61f58baa3fb731197cdb7a8ddc9d031da2a7e3bfa0a0dae66cfbe59c0af6.png"/><div class="react-component embed my-5" data-drag-handle="true" data-node-view-wrapper="" style="white-space:normal"><a class="link-embed-link" href="https://github.com/0xNezha/DiscordTalker_V2.0/releases/tag/V2.0" target="_blank" rel="noreferrer"><div class="link-embed"><div class="flex-1"><div><h2>Release DiscordTalker_release · 0xNezha/DiscordTalker_V2.0</h2><p>2 Bots talk || 两人互答 . Contribute to 0xNezha/DiscordTalker_V2.0 development by creating an account on GitHub.</p></div><span><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link h-3 w-3 my-auto inline mr-1"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>https://github.com</span></div><img src="https://storage.googleapis.com/papyrus_images/16bb61f58baa3fb731197cdb7a8ddc9d031da2a7e3bfa0a0dae66cfbe59c0af6.png"/></div></a></div></div>]]></content:encoded>
            <author>hackbot@newsletter.paragraph.com (Hackit)</author>
        </item>
        <item>
            <title><![CDATA[以太坊智能合约逆向分析与实战：（1）以太坊虚拟机原理简析]]></title>
            <link>https://paragraph.com/@hackbot/1</link>
            <guid>0uVPV40TNe6N2M0gOZvV</guid>
            <pubDate>Sat, 08 Jan 2022 05:24:27 GMT</pubDate>
            <description><![CDATA[如果你有二进制逆向的经验，那么以太坊智能合约的逆向是比较容易上手的。但首先我们要知道以太坊智能合约是如何运行的： 以太坊虚拟机（EVM）是一种基于栈的、准图灵完备（quasi-Turing complete）的虚拟机。听起来云里雾里，hum？好吧，EVM是智能合约的执行时环境，它是完全孤立的沙盒：运行在EVM中的代码无法访问网络、文件系统或其他进程。 evm的交易可以看作是从一个帐户发送到另一个帐户的消息（这里的账户，可能是相同的或特殊的零帐户，请参阅下文）。它能包含一个二进制数据（合约负载）和以太币。 如果目标账户含有代码，此代码会被执行，并以 payload 作为入参。 如果目标账户是零账户（账户地址为 0 )，此交易将创建一个 新合约 。 如前文所述，合约的地址不是零地址，而是通过合约创建者的地址和从该地址发出过的交易数量计算得到的（所谓的“nonce”）。 这个用来创建合约的交易的 payload 会被转换为 EVM 字节码并执行。执行的输出将作为合约代码被永久存储。这意味着，为创建一个合约，你不需要发送实际的合约代码，而是发送能够产生合约代码的代码。 注解 在合约创建的...]]></description>
            <content:encoded><![CDATA[<p>如果你有二进制逆向的经验，那么以太坊智能合约的逆向是比较容易上手的。但首先我们要知道以太坊智能合约是如何运行的：</p><p>以太坊虚拟机（EVM）是一种基于栈的、准图灵完备（quasi-Turing complete）的虚拟机。听起来云里雾里，hum？好吧，EVM是智能合约的执行时环境，它是完全孤立的沙盒：运行在EVM中的代码无法访问网络、文件系统或其他进程。</p><p>evm的交易可以看作是从一个帐户发送到另一个帐户的消息（这里的账户，可能是相同的或特殊的零帐户，请参阅下文）。它能包含一个二进制数据（合约负载）和以太币。</p><p>如果目标账户含有代码，此代码会被执行，并以 payload 作为入参。</p><p>如果目标账户是零账户（账户地址为 <code>0</code> )，此交易将创建一个 <strong>新合约</strong> 。 如前文所述，合约的地址不是零地址，而是通过合约创建者的地址和从该地址发出过的交易数量计算得到的（所谓的“nonce”）。 这个用来创建合约的交易的 payload 会被转换为 EVM 字节码并执行。执行的输出将作为合约代码被永久存储。这意味着，为创建一个合约，<strong>你不需要发送实际的合约代码，而是发送能够产生合约代码的代码</strong>。</p><p><s>注解</s></p><p><s>在合约创建的过程中，它的代码还是空的。所以直到构造函数执行结束，你都不应该在其中调用合约自己函数。</s></p><p>需要注意的是：<strong>Solidity这样的高级语言是无法在EVM中被直接执行的，代码会被编译成更加接近机器的低级语言—操作码（opcode）来执行。我们的目的，就是把操作码尽可能地还原成高级语言，以分析其逻辑。</strong></p><p><strong><em>EVM与操作码（Opcode）</em></strong></p><p>EVM是基于栈的虚拟机，为了方便计算，EVM定义了以32个字节为一个词（word）进行操作，栈中最多存储1024个词。</p><p>为了方便计算，EVM定义了以32个字节为一个词（word）进行操作，栈中最多存储1024个词。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/76657e7ad94f1a3d52ca9d87d36e351636a74ae1e4681e1be8bc68991a8ec011.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>在执行上图的加法运算时，EVM的实际流程是：</p><ol><li><p>PUSH b, 将b推入栈</p></li><li><p>PUSH a, 将a推入栈</p></li><li><p>ADD, 栈顶两列相加</p></li></ol><p>上述的PUSH，ADD称为操作码（opcode），是虚拟机可以直接执行的指令，目前EVM定义了大约140个操作码，几乎可以完成所有的计算（图灵完备）。</p><p>操作码可以简单地分为以下几类：</p><p>§ 栈操作码（POP, PUSH, DUP, SWAP）</p><p>§ 算术/比较/位运算操作码（ADD, SUB, GT, LT, AND, OR）</p><p>§ 环境操作码（CALLER, CALLVALUE, NUMBER）</p><p>§ 内存操作码（MLOAD, MSTORE）</p><p>§ 硬盘操作码（SLOAD, SSTORE）</p><p>§ 终止操作码（STOP, RETURN, REVERT, INVALID. SELEFDESTRUCT）</p><p><strong>字节码（Bytecode）</strong></p><p>为了方便存储操作码，EVM采用字节码（Bytecode）来对操作码进行标记。一个字节等于两位16进制数，因此最多存储256个操作码。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/4c58c3e3364bec56eb320f6d5cd0f7a7d15af919771ec786eff35f723ff5c0cf.png" alt="EVM字节码-操作码参照图" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">EVM字节码-操作码参照图</figcaption></figure><p>EVM执行时，字节码将按照字节（即每两位16进制数）划分执行。字节0x60-0x7f（PUSH1 – PUSH32）是特殊的字节，该字节后的字节数将作为输入压入栈中。</p><p>下面来看一组字节码的实际执行步骤：0x6002600101</p><ol><li><p>0x60是特殊的字节码（PUSH1），它取之后的一个字节0x02作为输入。</p></li></ol><p>因此0x6002在EVM中是PUSH1 0x02</p><ol><li><p>同理0x60是特殊的字节码（PUSH1），它取之后的一个字节0x01作为输入。</p></li></ol><p>因此0x6001在EVM中是PUSH1 0x01</p><ol><li><p>0x01对应直接码ADD</p></li></ol><p>因此，上述的字节码在EVM中是对2和1进行求和运算，它先后将0x02和0x01压入栈中，再将栈顶的2个数字弹出栈进行相加，最后将结果0x03压入栈中。</p><p><strong>小结：</strong></p><p>1、创建合约就是往空地址发一笔带合约数据的交易，然后这些数据就固定在了这个地址，变成了一个合约。</p><p>2、EVM执行时，字节码是以字节为单位（即每两位16进制数）读取并执行的。以0x6002600101 为例，执行顺序为 60 02 → 60 01 → 01</p><p>本文参考自：</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://zhuanlan.zhihu.com/p/90487072">https://zhuanlan.zhihu.com/p/90487072</a></p>]]></content:encoded>
            <author>hackbot@newsletter.paragraph.com (Hackit)</author>
        </item>
        <item>
            <title><![CDATA[Hell0  W0r1d]]></title>
            <link>https://paragraph.com/@hackbot/hell0-w0r1d</link>
            <guid>Dm6aeOPujnG1xvifLGzv</guid>
            <pubDate>Fri, 17 Dec 2021 07:59:31 GMT</pubDate>
            <description><![CDATA[const WebSocket = require('ws'); async function main() { var ws = new WebSocket('wss://api.mainnet-beta.solana.com:443'); ws.onopen = function() { console.log('Connect WebSocket...',new Date()); ws.send('{"jsonrpc": "2.0","id": 1,"method": "logsSubscribe","params": [{"mentions": [ "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" ]}]}'); }; ws.onmessage = function(evt) { var data_json = JSON.parse(evt.data) if(data_json.params){ let signature = data_json.params.result.value.signature find_init =...]]></description>
            <content:encoded><![CDATA[<pre data-type="codeBlock" text="const WebSocket = require(&apos;ws&apos;);

async function main() {
    var ws = new WebSocket(&apos;wss://api.mainnet-beta.solana.com:443&apos;);
    ws.onopen = function() {
    console.log(&apos;Connect WebSocket...&apos;,new Date());
    ws.send(&apos;{&quot;jsonrpc&quot;: &quot;2.0&quot;,&quot;id&quot;: 1,&quot;method&quot;: &quot;logsSubscribe&quot;,&quot;params&quot;: [{&quot;mentions&quot;: [ &quot;675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8&quot; ]}]}&apos;);
    };
    ws.onmessage = function(evt) {
    var data_json = JSON.parse(evt.data)

    if(data_json.params){
        let signature = data_json.params.result.value.signature
        find_init = evt.data.indexOf(&quot;process_initialize&quot;)
        if (find_init&gt;=0){
            console.log(new Date(),&apos;Got signature:&apos;,signature)
            console.log(&apos;===========================&apos;)
        }
        console.log(&apos;#&apos;)
    }else{
        console.log(data_json)
    }

    };
    ws.onclose = function(evt) {
    console.log(&apos;connect close,TRY reconnet&apos;);
    //
    main();
    };
}

main()
"><code>const WebSocket <span class="hljs-operator">=</span> <span class="hljs-built_in">require</span>(<span class="hljs-string">'ws'</span>);

async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">main</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">var</span> ws <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> WebSocket(<span class="hljs-string">'wss://api.mainnet-beta.solana.com:443'</span>);
    ws.onopen <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    console.log(<span class="hljs-string">'Connect WebSocket...'</span>,<span class="hljs-keyword">new</span> Date());
    ws.<span class="hljs-built_in">send</span>(<span class="hljs-string">'{"jsonrpc": "2.0","id": 1,"method": "logsSubscribe","params": [{"mentions": [ "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" ]}]}'</span>);
    };
    ws.onmessage <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">evt</span>) </span>{
    <span class="hljs-keyword">var</span> data_json <span class="hljs-operator">=</span> JSON.parse(evt.data)

    <span class="hljs-keyword">if</span>(data_json.params){
        let signature <span class="hljs-operator">=</span> data_json.params.result.<span class="hljs-built_in">value</span>.signature
        find_init <span class="hljs-operator">=</span> evt.data.indexOf(<span class="hljs-string">"process_initialize"</span>)
        <span class="hljs-keyword">if</span> (find_init<span class="hljs-operator">></span><span class="hljs-operator">=</span><span class="hljs-number">0</span>){
            console.log(<span class="hljs-keyword">new</span> Date(),<span class="hljs-string">'Got signature:'</span>,signature)
            console.log(<span class="hljs-string">'==========================='</span>)
        }
        console.log(<span class="hljs-string">'#'</span>)
    }<span class="hljs-keyword">else</span>{
        console.log(data_json)
    }

    };
    ws.onclose <span class="hljs-operator">=</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">evt</span>) </span>{
    console.log(<span class="hljs-string">'connect close,TRY reconnet'</span>);
    <span class="hljs-comment">//</span>
    main();
    };
}

main()
</code></pre>]]></content:encoded>
            <author>hackbot@newsletter.paragraph.com (Hackit)</author>
        </item>
    </channel>
</rss>