RSS Feed
更好更安全的互联网
  • 以太坊 “后偷渡时代” 盗币之 “拾荒攻击”

    2018-08-29

    作者:Sissel@知道创宇404区块链安全研究团队
    时间:2018年8月20日
    英文版:https://paper.seebug.org/687/

    0x00 前言

    2018年08月01日,知道创宇404区块链安全研究团队发布《金钱难寐,大盗独行——以太坊 JSON-RPC 接口多种盗币手法大揭秘》,针对 偷渡漏洞 和 后偷渡时代的盗币方式 进行了介绍,披露了 后偷渡时代 的三种盗币方式:离线攻击、重放攻击和爆破攻击。

    在进一步的研究中,我们又发现了针对这些攻击方式的补充:拾荒攻击。攻击者或求助于矿工,或本身拥有一定算力以获得将交易打包进区块的权利。在偷渡漏洞中,攻击者在被攻击节点构造gasPrice为 0 的交易,等待用户解锁账户签名广播。攻击者同时设置一个恶意节点,用于接收这笔交易。攻击者将符合条件的交易打包,就可以实现 0 手续费完成转账。通过这种攻击,攻击者可以获取到余额不足以支付转账手续费或勉强足够支付手续费节点上的所有以太币,并在一定程度上可以防止其他攻击者的竞争,可谓是 薅羊毛 的典范。

    除此之外,在薅够以太币残羹之后,攻击者又盯上了这些以太币已被盗光,但账户中残留的代币。直到现在,针对许多智能合约发行的代币,一些被攻击账户中的token,仍在小额地被攻击者以拾荒攻击盗走。

    本文将从一笔零手续费交易谈起,模拟复现盗币的实际流程,对拾荒攻击成功的关键点进行分析。

    0x01 从一笔零手续费交易谈起

    在区块链系统中,每一笔交易都应该附带一部分gas以及相应的gasPrice作为手续费,当该交易被打包进区块,这笔手续费将用来奖励完成打包的矿工。

    《金钱难寐,大盗独行——以太坊 JSON-RPC 接口多种盗币手法大揭秘》中,我们提到了一个利用以太坊JSON-RPC接口的攻击者账号0x957cD4Ff9b3894FC78b5134A8DC72b032fFbC464。该攻击者在公网中扫描开放的RPC端口,构造高手续费的交易请求,一旦用户解锁账户,便会将用户余额转至攻击者的账户或攻击者创建的合约账户。

    在分析该账户交易信息的时候,我们发现了一笔不符合常识的交易,先从这笔交易开始谈起。

    交易地址:0xb1050b324f02e9a0112e0ec052b57013c16156301fa7c894ebf2f80ac351ac22

    0x00a329c0648769a73afac7f9381e08fb43dbea72向合约MinereumToken(攻击者的合约)的交易,虽然用户余额很少,但这笔交易使用了该账户所有余额作为value与合约交互,这笔交易使用了正常数量的gas,但它的gasPrice被设定为0。

    前文提到,攻击者会使用较高的手续费来保证自己的交易成功,矿工会按照本节点的txpool中各交易的gasPrice倒序排列,优先将高gasPrice交易打包进之后的区块。在这个世界上每时每刻都在发生着无数笔交易,在最近七日,成交一笔交易的最低gasPrice是3Gwei。这笔零手续费交易究竟是如何发生,又是如何打包进区块的呢。

    0x02 思路分析

    在区块链系统中,任何人都可以加入区块链网络,成为其中一个节点,参与记账、挖矿等操作。保证区块链的可信性和去中心化的核心便是共识机制

    共识机制

    在以太坊中,矿工将上一区块的哈希值、txpool中手续费较高的交易、时间戳等数据打包,不断计算nonce来挖矿,最先得出符合条件的nonce值的矿工将拥有记账权,得到手续费和挖矿奖励。矿工将广播得到的区块,其他节点会校验这一区块,若无错误,则认为新的区块产生,区块链高度增加。这就是各节点生成新区块保持共识的过程。

    将0 gasPrice交易完成需要确认两个问题

    • 矿工是否会接受这个交易,并将其打包
    • 其余节点接收到含此交易的区块,是否会达成共识

    下面我们来对0 gasPrice交易相关的操作进行测试。了解零手续费的交易如何产生,如何被txpool接受,打包了零手续费交易的区块能否被认可,确认上述问题的答案。

    0x03 零手续费交易测试

    a. 单节点测试

    首先,我们来确认此交易是否可以进入节点的txpool中,启用一个测试链。默认rpc端口是8545,使用python的web3包发起一笔0 gasPrice转账。

    节点一发起转账的脚本,转帐前要解锁账户

    交互结果

    节点一发起的零手续费交易成功,并且挖矿后成功将该交易打包进区块中。

    b. 多节点共识测试

    现在加入另一个节点

    节点一仍使用刚才的脚本发起零手续费交易,节点一的txpool中成功添加,但节点二因为gasPrice非法拒绝了此交易。

    在geth的配置中发现了与此相关的参数

    将其启动时改为0,但节点二的txpool中仍未出现这笔交易。

    阅读源码知,此参数确实是控制txpool增加的交易的最低gasPrice,但不能小于1。

    令节点一(txpool中含0 gasPrice)开始挖矿,将该交易打包进区块后,发现节点二认可了此区块,达成共识,两节点高度均增长了。

    得到结论:

    • 零手续费交易,通常情况下只有发起者的txpool可以接收,其余节点无法通过同步此交易。如若需要,必须进行修改geth源码等操作。
    • 虽然这笔交易无法进入其他节点的txpool,但对于含此交易的区块,可以达成共识。

    我们将进行简要的源代码分析,支持我们的结论。

    0x04 源码分析

    (以下的代码分析基于https://github.com/ethereum/go-ethereum的当前最新提交:commit 6d1e292eefa70b5cb76cd03ff61fc6c4550d7c36)

    以太坊目前最流行的节点程序(Geth/Parity)都提供了RPC API,用于对接矿池、钱包等其他第三方程序。首先确认一下节点在打包txs时,代码的实现。

    i. 交易池

    代码路径:./go-ethereum/core/tx_pool.go

    生成一个tx实例时,发现有对gasPrice的最低要求,具体在这个函数中会拒绝接收此交易。

    ii. 移除低于阈值的交易

    代码路径:./go-ethereum/core/tx_list.go 并且在处理txs中,会将低于阈值的交易删除,但本地的交易不会删除。


    以上部分为区块链网络内一节点,尝试接收或加入 0 gasPrice 的交易时,会有部分过滤或规则限制。但通过修改源码,我们依然可以做到将 0 gasPrice 的交易合法加入到区块中,并进行之后的nonce计算。下面继续源码分析,考察通过此方式得到的区块,是否可以被其他节点接受,达成共识。

    iii. 共识校验

    代码路径:./go-ethereum/consensus/consensus.go 这是geth中,提供的共识算法engine接口

    查看VerifySeal(),发现校验了如下内容:

    • 不同模式下的一些特殊处理
    • 难度是否合法
    • nonce值是否合法
    • gas值是否合法

    可以看到,其他节点针对共识,检查了签名、nonce等内容,对于其中零手续费的交易没有检验。换句话说,零手续费的交易虽然不能激励矿工,但它依然是合法的。

    0x05 利用流程

    攻击者首先以偷渡漏洞利用的方式,构造零手续费,正常的transfer交易。待用户解锁账户后,广播交易。具体流程见下图:

    0x06 小结

    由此我们可以得出,0 gasPrice这样的特殊交易,有如下结论:

    • 通常情况下,0 gasPrice可通过节点自身发起加入至txpool中。
    • 以 geth 为例,修改geth部分源码重新编译运行,该节点方可接受其他节点发出的特殊交易(目标账户发起的0 gasPrice交易)。此为攻击者需要做的事情。
    • 0 gasPrice的交易可以打包进区块,并且符合共识要求。

    因为json-rpc接口的攻击方式中,攻击者可以通过偷渡漏洞签名 0 gasPrice交易并广播。通过收集此类0 gasPrice交易并添加至部分矿工的txpool中,当该矿工挖出一个新的区块,这类交易也将会被打包。即攻击者可能与部分矿工联手,或攻击者本身就有一定的运算能力,让矿工不再遵循诚实挖矿维护区块链系统的原则,

    0x07 利用价值及防御方案

    因为零手续费交易的出现,诸多低收益的攻击都将拥有意义。

    提高收益

    攻击者可以通过此种方式,结合其他的攻击手法,将被攻击账户中的余额全部转出,达到了收益最大化。

    羊毛薅尽

    依照《金钱难寐,大盗独行——以太坊 JSON-RPC 接口多种盗币手法大揭秘》中提到的攻击方式,对于账户余额较少,甚至不足以支付转账手续费的情况,可通过上文提到的薅羊毛式攻击方案,将账户中的残羹收入囊中。由于此交易gasPrice为0,可在一区块中同时打包多个此类型交易,例如此合约下的多组交易:0x1a95b271b0535d15fa49932daba31ba612b52946,此区块中的几笔交易:4788940

    偷渡代币

    在被盗账户已无以太币的情况下,攻击者发现这些账户还存有部分智能合约发行的代币。没有以太币便不能支付gas进行转账,零手续费交易可以完美解决这个问题。直到现在,有诸多无以太币的被攻击账户,仍在被此方式转账代币。

    防御方案

    由于0 gasPrice交易只是扩展其他攻击方案的手法,还应将防御着眼在之前json-rpc接口利用。

    • 对于有被偷渡漏洞攻击的痕迹或可能曾经被偷渡漏洞攻击过的节点,建议将节点上相关账户的资产转移到新的账户后废弃可能被攻击过的账户。
    • 建议用户不要使用弱口令作为账户密码,如果已经使用了弱口令,可以根据1.2节末尾的内容解出私钥内容,再次通过 geth account import 命令导入私钥并设置强密码。
    • 如节点不需要签名转账等操作,建议节点上不要存在私钥文件。如果需要使用转账操作,务必使用 personal_sendTransaction 接口,而非 personal_unlockAccount 接口。

    0x08 影响规模

    我们从上面说到的0 gasPrice的交易入手。调查发现,近期依然有许多交易,以0 gasPrice成交。多数0手续费交易都出自矿池:0xb75d1e62b10e4ba91315c4aa3facc536f8a922f50x52e44f279f4203dcf680395379e5f9990a69f13c,例如区块 61612146160889等。

    我们注意到,这些0 gasPrice交易,仅有早期的少部分交易,会携带较少的以太币,这符合我们对其薅羊毛特性的预计。经统计,从2017年6月起,陆续有748个账户总计24.2eth被零手续费转账。

    在其中也找到了《金钱难寐,大盗独行——以太坊 JSON-RPC 接口多种盗币手法大揭秘》中提到的重放攻击,造成的账户损失:0x682bd7426ab7c7b4b5beed331d5f82e1cf2cecc83c317ccee6b4c4f1ae34d909

    被盗走0.05eth


    在这些0 gasPrice中,更多的是对合约发行的TOKEN,进行的转账请求,将用户账户中的token转移至合约拥有者账户中,例如:

    该账户的tx记录。

    攻击者拥有多个矿池的算力,将众多被攻击账户拥有的多种token,转移到相应的账户中,虽然单笔交易金额较小,但可进行此种攻击方式的账户较多,合约较多,且不需要手续费。积少成多,直到现在,攻击者仍在对这些代币进行着拾荒攻击。

    0x09 结语

    区块链系统基于去中心化能达成交易的共识,一个前提就是,绝大多数的矿工,都会通过诚实挖矿来维持整个比特币系统。当矿工不再诚实,区块链的可信性和去中心化将会大打折扣。当黑客联合矿工,或黑客本身拥有了算力成为矿工,都会在现有攻击手法的基础上,提供更多的扩展攻击方案。0 gasPrice交易的出现,违背了区块链设计初衷,即应对矿工支付手续费作为激励。 区块链技术与虚拟货币的火热,赋予了链上货币们巨大的经济价值,每个人都想在区块链浪潮中分得一杯羹。黑客们更是如此,他们作为盗币者,绞尽脑汁的想着各个角度攻击区块链与合约。当黑客栖身于矿工,他们不但能挖出区块,也能挖出漏洞。


    智能合约审计服务

    针对目前主流的以太坊应用,知道创宇提供专业权威的智能合约审计服务,规避因合约安全问题导致的财产损失,为各类以太坊应用安全保驾护航。

    知道创宇404智能合约安全审计团队: https://www.scanv.com/lca/index.html
    联系电话:(086) 136 8133 5016(沈经理,工作日:10:00-18:00)

    欢迎扫码咨询:

    区块链行业安全解决方案

    黑客通过DDoS攻击、CC攻击、系统漏洞、代码漏洞、业务流程漏洞、API-Key漏洞等进行攻击和入侵,给区块链项目的管理运营团队及用户造成巨大的经济损失。知道创宇十余年安全经验,凭借多重防护+云端大数据技术,为区块链应用提供专属安全解决方案。

    欢迎扫码咨询:

    参考链接

    1. json-rpc接口盗币手法:金钱难寐,大盗独行——以太坊 JSON-RPC 接口多种盗币手法大揭秘
    2. https://www.reddit.com/r/ethereum/comments/7lx1do/a_christmas_mystery_sweepers_and_zero_gas_price/
    3. how-to-create-your-own-private-ethereum-blockchain-dad6af82fc9f
    4. 零手续费交易:https://etherscan.io/tx/0xb1050b324f02e9a0112e0ec052b57013c16156301fa7c894ebf2f80ac351ac22
    5. 慢雾命名的“以太坊黑色情人节”,细节:以太坊生态缺陷导致的一起亿级代币盗窃大案:https://mp.weixin.qq.com/s/Kk2lsoQ1679Gda56Ec-zJg
    6. 揭秘以太坊中潜伏多年的“偷渡”漏洞,全球黑客正在疯狂偷币:https://paper.seebug.org/547/

    Paper本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/673/

    作者:Nanako | Categories:安全研究技术分享 | Tags:
  • Xdebug 攻击面在 PhpStorm 上的现实利用

    2018-08-29

    作者: dawu@知道创宇404实验室
    日期:2018/08/16

    0x00 前言

    在调试 Drupal 远程命令执行漏洞(CVE-2018-7600 && CVE-2018-7602)时,存在一个超大的数组 $form 。在该数组中寻找到注入的变量,可以帮助调试人员确认攻击是否成功。

    但是作为一个安全研究人员,在调试时也保持着一颗发现漏洞的心,所以知道 $form 中的每一个元素的内容就十分重要了。然而 PhpStorm 这款调试工具需要不断的点击才能看到数组中各元素的值,这显然非常低效。

    笔者在官方手册中发现了一种解决方案:

    但是 Evaluate in Console 看上去就具有一定的危险性,所以笔者深入研究了该功能的实现过程并成功通过 PhpStormXdebug 服务器上执行了命令。

    0x01 准备工作

    1.1 Xdebug的工作原理和潜在的攻击面

    Xdebug 工作原理和潜在的攻击面前人已有部分文章总结:

    综合上述参考链接,已知的攻击面有:

    1. eval 命令: 可以执行代码。
    2. property_set && property_get 命令: 可以执行代码
    3. source 命令: 可以阅读源码。
    4. 利用 DNS 重绑技术可能可以导致本地 Xdebug 服务器被攻击。

    就本文而言 PhpStormXdebug 进行调试的工作流程如下:

    1. PhpStorm 开启调试监听,默认绑定 90001013720080 端口等待连接。
    2. 开发者使用 XDEBUG_SESSION=PHPSTORM (XDEBUG_SESSION的内容可以配置,笔者设置的是PHPSTORM) 访问 php 页面。
    3. Xdebug 服务器反连至 PhpStorm 监听的 9000 端口。
    4. 通过步骤3建立的连接,开发者可以进行阅读源码、设置断点、执行代码等操作。

    如果我们可以控制 PhpStorm 在调试时使用的命令,那么在步骤4中攻击面 123 将会直接威胁到 Xdebug 服务器的安全。

    1.2 实时嗅探脚本开发

    工欲善其事,必先利其器。笔者开发了一个脚本用于实时显示 PhpStormXdebug 交互的流量(该脚本在下文截图中会多次出现):

    0x02 通过 PhpStormXdebug 服务器上执行命令

    2.1 通过 Evaluate in Console 执行命令

    通过上文的脚本,可以很清晰的看到我们在执行 Evaluate in Console 命令时发生了什么(红色部分是 base64 解码后的结果):

    如果我们可以控制 $q,那我们就可以控制 eval 的内容。但是在 PHP 官方手册中,明确规定了变量名称应该由 a-zA-Z_\x7f-\xff 组成:

    Variable names follow the same rules as other labels in PHP. A valid variable name starts with a letter or underscore, followed by any number of letters, numbers, or underscores. As a regular expression, it would be expressed thus: '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'

    所以通过控制 $q 来控制 eval 的内容并不现实。但是在 PhpStorm 获取数组中某个元素时,会将该元素的名称带入 eval 的语句中。

    如图所示,定义数组如下: $a = ( "aaa'bbb"=>"ccc"),并在 PhpStorm 中使用 Evaluate in Console 功能。

    可以看到单引号未做任何过滤,这也就意味着我可以控制 eval 的内容了。在下图中,我通过对 $a['aaa\'];#'] 变量使用 Evaluate in Console 功能获取到 $a['aaa'] 的值。

    精心构造的请求和代码如下:

    但在这个例子中存在一个明显的缺陷:可以看到恶意的元素名称。如果用于钓鱼攻击,会大大降低成功率,所以对上述的代码进行了一定的修改:

    在元素名称足够长时,PhpStorm 会自动隐藏后面的部分:

    2.2 通过 Copy Value As 执行命令

    继续研究发现,COPY VALUE AS (print_r/var_export/json_encode) 同样也会使用 Xdebugeval 命令来实现相应的功能:

    再次精心构造相应的请求和代码后,可以再次在 Xdebug 服务器上执行命令:

    2.3 实际攻击探究

    基于上述的研究,我认为可以通过 PhpStorm 实现钓鱼攻击。假设的攻击流程如下:

    1. 攻击者确保受害者可以发现恶意的 PHP 文件。例如安全研究人员之间交流 某大马 具体实现了哪些功能、运维人员发现服务器上出现了可疑的 PHP 文件。
    2. 如果受害者在大致浏览 PHP 文件内容后,决定使用 PhpStorm 分析该文件。
    3. 受害者使用 COPY VALUE AS (print_r/var_export/json_encode)Evaluate array in Console 等功能。命令将会执行。
    4. 攻击者可以收到受害者 Xdebug 服务器的 shell

    精心构造的代码如下(其中的反连IP地址为临时开启的VPS):

    直接执行该 PHP 代码,将只会多次运行 system("echo hello world;")。但是调试人员并不会执行 PHP 代码,他也许会取出 $n[$f] 的值,然后通过 echo XXXXXXXX|base64 -d 解码出具体的内容。

    如果他使用 COPY VALUE BY print_r 拷贝对应的变量,他的 Xdebug 服务器上将会被执行命令。

    在下面这个 gif 中,左边是攻击者的终端,右边是受害者的 debug 过程。

    (GIF中存在一处笔误: decise 应为 decide

    0x03 结语

    在整个漏洞的发现过程中,存在一定的曲折,但这也正是安全研究的乐趣所在。PhpStorm 官方最终没有认可该漏洞,也是一点小小的遗憾。在此将该发现分享出来,一方面是为了跟大家分享思路,另一方面也请安全研究人员使用 PhpStorm 调试代码时慎用 COPY VALUE AS (print_r/var_export/json_encode)Evaluate array in Console 功能。

    0x04 时间线

    2018/06/08: 发现 Evaluate in Console 存在 在 Xdebug 服务器上 执行命令的风险。
    2018/06/31 - 2018/07/01: 尝试分析 Evaluate in Console 的问题,发现新的利用点 Copy Value. 即使 evalXdebug 提供的功能,但是 PhpStorm 没有过滤单引号导致我们可以在 Xdebug 服务器上执行命令,所以整理文档联系 security@jetbrains.com
    2018/07/04: 收到官方回复,认为这是 Xdebug 的问题,PhpStorm 在调试过程中不提供对服务器资源的额外访问权限。
    2018/07/06: 再次联系官方,说明该攻击可以用于钓鱼攻击。
    2018/07/06: 官方认为用户在服务器上运行不可信的代码会造成服务器被破坏,这与 PhpStorm 无关,这也是 PhpStorm 不影响服务器安全性的原因。官方同意我披露该问题。

    2018/08/16: 披露该问题。


    Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/668/

    作者:Nanako | Categories:安全科普技术分享 | Tags:
  • Spring MVC 目录穿越漏洞(CVE-2018-1271)分析

    2018-08-29
    作者: Badcode@知道创宇404实验室
    时间: 2018/08/14

    漏洞简介

    2018年04月05日,Pivotal公布了Spring MVC存在一个目录穿越漏洞(CVE-2018-1271)。Spring Framework版本5.0到5.0.4,4.3到4.3.14以及较旧的不受支持的版本允许应用程序配置Spring MVC以提供静态资源(例如CSS,JS,图像)。当Spring MVC的静态资源存放在Windows系统上时,攻击可以通过构造特殊URL导致目录遍历漏洞。

    漏洞影响

    • Spring Framework 5.0 to 5.0.4.
    • Spring Framework 4.3 to 4.3.14
    • 已不支持的旧版本仍然受影响

    漏洞利用条件

    • Server运行于Windows系统上
    • 要使用file协议打开资源文件目录

    漏洞复现

    复现环境

    环境搭建

    1.下载 spring-mvc-showcase

    修改pom.xml,使用Spring Framework 5.0.0。

    2.修改 Spring MVC 静态资源配置,可参考官方文档

    通过官方文档可知有两种方式配置,可自行选择配置。此处通过重写WebMvcConfigurer中的addResourceHandlers方法来添加新的资源文件路径。在org.springframework.samples.mvc.config.WebMvcConfig添加以下代码即可,使用file://协议指定resources为静态文件目录。

    3.使用 jetty 启动项目

    至此复现环境搭建完毕。

    复现过程及结果

    访问以下链接

    可以看到成功读取到win.ini的内容了。

    漏洞分析

    当外部要访问静态资源时,会调用org.springframework.web.servlet.resource.ResourceHttpRequestHandler:handleRequest来处理,在这里下断点调试。

    跟进org.springframework.web.servlet.resource.ResourceHttpRequestHandler:getResource()

    request中保存的路径是/spring-mvc-showcase/resources/%255c%255c..%255c/..%255c/..%255c/..%255c/..%255c/..%255c/..%255c/..%255c/..%255c/windows/win.ini。在request.getAttribute()函数取值时会进行 url decode操作,此时path的值为%5c%5c..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/windows/win.ini。接下来会对path进行两次校验,将pathpath解码之后的值分别使用isInvalidPath函数检查。看下这个函数

    path包含有..的时候,会调用cleanPath函数对path处理。跟进

    这个函数的作用是把包含..的这种相对路径转换成绝对路径。例如/foo/bar/../经过cleanPath处理后就会变成/foo/

    cleanPath的问题在于String[] pathArray = delimitedListToStringArray(pathToUse, "/");这个是允许空元素存在的,也就是说cleanPath会把//当成一个目录,而操作系统是不会把//当成一个目录的。借用一张Orange大佬的图。

    继续回到流程上,上面说到会对path进行两次校验,第一次调用isInvalidPathpath的值是%5c%5c..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/windows/win.ini,因为path/分割之后没有元素等于..,所以path经过cleanPath处理后的值不变,继续之后的判断,path里面也不包含../,所以最终返回false,也就是通过了校验。

    第二次调用isInvalidPath(URLDecoder.decode(path, "UTF-8")),此时参数值是\\..\/..\/..\/..\/..\/..\/..\/..\/..\/windows/win.ini,经过cleanPath处理后的值是//windows/win.ini,之后继续判断,path里面也不包含../,最终返回false,也通过了校验。

    通过两次校验之后,继续向下执行。获取一个Resource对象

    path的值还是之前,getLocations()获取到的就是之前在配置文件中配置的路径file:./src/main/resources/,继续跟进

    跟进ResourceResolver类的resolveResource

    跟进PathResourceResolverresolveResourceInternal

    进入到org.springframework.web.servlet.resource.PathResourceResolvergetResource()

    此时的resourcePath就是之前的pathlocation就是之前getLocations()获取到的值。继续跟进this.getResource

    调用location.createRelativ拼接得到文件的绝对路径,返回一个UrlResource对象

    返回到getResource函数

    此时,resource是一个UrlResource对象,可以看到值是file:src/main/resources/%5c%5c..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/..%5c/windows/win.ini,之后调用exists()方法检查该文件是否存在,调用isReadable()方法检测该文件是否可读。进去exists()方法

    这里会调用isFileURLurl进行判断,是否以file://协议来读取文件,这也是为什么配置静态目录的时候要使用file://协议。

    通过判断之后,会调用this.getFile()来获取这个文件对象,这个方法在org.springframework.util.ResourceUtils这个方法类里面,跟进

    这里对是否为file://协议又判断了一次,之后进行了一步最重要的操作new File(toURI(resourceUrl).getSchemeSpecificPart());,将resourceUrl转换为URL对象,最后调用URI类的getSchemeSpecificPart()获取到文件路径,而在getSchemeSpecificPart()里面是有一次decode操作的,也就是在这里把%5c解码成了\,跟进

    最后返回到exists(),最终返回true,即文件存在

    之后调用isReadable()方法检测该文件是否可读的时候,同样会调用这个getFile,最终返回true,即文件可读。

    至此,对于resource的判断都结束了。返回到org.springframework.web.servlet.resource.ResourceHttpRequestHandler:handleRequest(),获取到通过校验resource的之后,就开始准备response的内容了,包含获取文件的类型(用于response的Content-type),文件的大小(用于response的Content-length)等等,最后调用this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);获取文件的内容并返回给用户。

    跟进write()

    跟进writeInternal,之后再跳到writeContent

    跟进resource.getInputSream()

    可以看到,这里使用openConnection创建一个URLConnection实例,也是在openConnection方法内,会自动decode,把%5c解码成\,然后返回文件的InputStream对象,最终读取内容返回给用户。

    注意事项

    1. 这个漏洞是可以在 Tomcat 下触发的,因为payload的双URL编码的。
    2. 在Spring Framework 大于5.0.1的版本(我的测试环境5.0.4),双URL编码payload是不行的,单次URL编码的payload的却是可以的,这种情况下该漏洞就无法在Tomcat下触发了,因为在默认情况下Tomcat遇到包含%2f(/)%5c(\)的URL直接http 400,在 jetty 下是可以触发的。

    至于为什么双URL编码不行,是因为org.springframework.web.servlet.resource.PathResourceResolvergetResource()多了一个encode操作。

    如果是双URL编码payload的进来,在获取path的时候解码一次,经过一次isInvalidPath判断,然后进入到PathResourceResolvergetResource(),也就是上图的位置,这里又会重新编码一次,又回到了双编码的情况了。最后在文件判断是否存在exists()方法的时候,getSchemeSpecificPart()只能解码一次,之后是无法读取到文件的,也就是文件不存在。

    所以这里要使用单次编码才行。

    补丁分析

    看官方的补丁,是在ResourceHttpRequestHandlergetResource()里面把processPath重写了

    在进入isInvalidPath之前调用processPath函数对path处理,替换反斜线为斜线,删掉多余斜线,从而导致在isInvalidPath里面校验不通过。如果使用双编码方式的话,会经过isInvalidEncodedPath,里面会先对path解码,然后调用processPath处理,最后经过isInvalidPath,同样无法通过检查。

    漏洞修复

    • Spring Framework 5.*(5.0到5.0.4)版本,建议更新到5.0.5版本
    • Spring Framework 4.3.*(4.3到4.3.14)版本,建议更新到4.3.15版本
    • 不再受支持的旧版本,建议更新到4.3.15版本或5.0.5版本

    参考链接


    Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/665/

    作者:Nanako | Categories:安全研究漏洞通告 | Tags:
  • 以太坊合约审计 CheckList 之“以太坊智能合约规范问题”影响分析报告

    2018-08-29
    作者:LoRexxar'@知道创宇404区块链安全研究团队
    时间:2018年8月10日
    英文版:https://paper.seebug.org/670/

    一、 简介

    在知道创宇404区块链安全研究团队整理输出的《知道创宇以太坊合约审计CheckList》中,把“未触发Transfer事件问题”、“未触发Approval事件问题”、“假充值漏洞”、“构造函数书写错误”等问题统一归类为“以太坊智能合约规范问题”。

    “昊天塔(HaoTian)”是知道创宇404区块链安全研究团队独立开发的用于监控、扫描、分析、审计区块链智能合约安全自动化平台。我们利用该平台针对上述提到的《知道创宇以太坊合约审计CheckList》中“以太坊智能合约规范”类问题在全网公开的智能合约代码做了扫描分析。详见下文:

    二、漏洞详情

    ERC20[1]是一种代币标准,用于以太坊区块链上的智能合约。ERC20定义了一种以太坊必须执行的通用规则,如果在以太坊发行的代币符合ERC20的标准,那么交易所就可以进行集成,在它们的交易所实现代币的买卖和交易。

    ERC20中规定了transfer函数必须触发Transfer事件,transfer函数必须返回bool值,在进行余额判断时,应抛出错误而不是简单的返回错误,approve函数必须触发Approval事件。

    1、未触发Transfer事件

    上述代码在发生交易时未触发Transfer事件,在发生交易时,未产生event事件,不符合ERC20标准,不便于开发人员对合约交易情况进行监控。

    2、未触发Approval事件

    上述代码在发生交易时未触发Approval事件,在发生交易时,未产生event事件,不符合ERC20标准,不便于开发人员对合约情况进行监控。

    3、假充值漏洞

    上述代码在判断余额时使用了if语句,ERC20标准规定,当余额不足时,合约应抛出错误使交易回滚,而不是简单的返回false。

    这种情况下,会导致即使没有真正发生交易,但交易仍然成功,这种情况会影响交易平台的判断结果,可能导致假充值。

    2018年7月9日,慢雾安全团队发布了关于假充值的漏洞预警。

    2018年7月9日,知道创宇404区块链安全研究团队跟进应急该漏洞,并对此漏洞发出了漏洞预警。

    4、构造函数书写错误漏洞

    Solidity0.4.22版本以前,编译器要求,构造函数名称应该和合约名称保持一致,如果构造函数名字和合约名字大小写不一致,该函数仍然会被当成普通函数,可以被任意用户调用。

    Solidity0.4.22中引入了关于构造函数constructor使用不当的问题,constructor在使用中错误的加上了function定义,从而导致constructor可以被任意用户调用,会导致可能的更严重的危害,如Owner权限被盗。

    构造函数大小写错误漏洞

    上述代码错误的将构造函数名大写,导致构造函数名和合约名不一致。这种情况下,该函数被设置为一个普通的public函数,任意用户都可以通过调用该函数来修改自己为合约owner。进一步导致其他严重的后果。

    2018年6月22日,MorphToken合约代币宣布更新新的智能合约[2],其中修复了关于大小写错误导致的构造函数问题。

    2018年6月22日,知道创宇404区块链安全研究团队跟进应急,并输出了《以太坊智能合约构造函数编码错误导致非法合约所有权转移报告》。

    构造函数编码错误漏洞

    上述代码错误的使用function来作为constructor函数装饰词,这种情况下,该函数被设置为一个普通的public函数,任意用户都可以通过调用该函数来修改自己为合约owner。进一步导致其他严重的后果。

    2018年7月14日,链安科技在公众号公布了关于constructor函数书写错误的问题详情[3]。

    2018年7月15日,知道创宇404区块链安全研究团队跟进应急,并输出了《以太坊智能合约构造函数书写错误导致非法合约所有权转移报告》

    三、漏洞影响范围

    使用Haotian平台智能合约审计功能可以准确扫描到该类型问题。

    基于Haotian平台智能合约审计功能规则,我们对全网的公开的共39548 个合约代码进行了扫描,其中共14978个合约涉及到这类问题。

    1、 未触发Transfer事件

    截止2018年8月10日为止,我们发现了4604个存在未遵循ERC20标准未触发Transfer事件的合约代码,其中交易量最高的10个合约情况如下:

    2、 未触发Approval事件

    截止2018年8月10日为止,我们发现了5231个存在未遵循ERC20标准未出发Approval事件的合约代码,其中交易量最高的10个合约情况如下:

    3、假充值漏洞

    2018年7月9日,知道创宇404区块链安全研究团队在跟进应急假充值漏洞时,曾对全网公开合约代码进行过一次扫描,当时发现约3141余个存在假充值问题的合约代码,其中交易量最高的10个合约情况如下:

    截止2018年8月10日为止,我们发现了5027个存在假充值问题的合约代码,其中交易量最高的10个合约情况如下:

    4、构造函数书写问题

    构造函数大小写错误漏洞

    2018年6月22日,知道创宇404区块链安全研究团队在跟进应急假充值漏洞时,全网中存在该问题的合约约为16个。

    截止2018年8月10日为止,我们发现了90个存构造函数大小写错误漏洞的合约代码,其中交易量最高的10个合约情况如下:

    构造函数编码问题

    截止2018年8月10日为止,我们发现了24个存在构造函数书写问题的合约代码,比2018年7月14日对该漏洞应急时只多了一个合约,其中交易量最高的10个合约情况如下:

    四、修复方式

    1)transfer函数中应触发Tranfser事件

    2)approve函数中应触发Approval事件

    3)transfer余额验证时应使用require抛出错误

    4)0.4.22版本之前,构造函数应和合约名称一致

    5)0.4.22版本之后,构造函数不应用function修饰

    五、一些思考

    上面这些问题算是我在回顾历史漏洞中经常发现的一类问题,都属于开发人员没有遵守ERC20标准而导致的,虽然这些问题往往不会直接导致合约漏洞的产生,但却因为这些没有遵守标准的问题,在后期对于合约代币的维护时,会出现很多问题。

    如果没有在transfer和approve时触发相应的事件,开发人员就需要更复杂的方式监控合约的交易情况,一旦发生大规模盗币时间,甚至没有足够的日志提供回滚。

    如果转账时没有抛出错误,就有可能导致假充值漏洞,如果平台方在检查交易结果时是通过交易状态来判断的,就会导致平台利益被损害。

    如果开发人员在构造函数时,没有注意不同版本的编译器标准,就可能导致合约所有权被轻易盗取,导致进一步更严重的盗币等问题。

    我们在对全网公开的合约代码进行扫描和监控时容易发现,有很大一批开发人员并没有注意到这些问题,甚至构造函数书写错误这种低级错误,在漏洞预警之后仍然在发生,考虑到大部分合约代码没有公开,可能还有很多开发者在不遵守标准的情况下进行开发,还有很多潜在的问题需要去考虑。

    这里我们建议所有的开发者重新审视自己的合约代码,检查是否遵守了ERC20合约标准,避免不必要的麻烦以及安全问题。


    智能合约审计服务

    针对目前主流的以太坊应用,知道创宇提供专业权威的智能合约审计服务,规避因合约安全问题导致的财产损失,为各类以太坊应用安全保驾护航。

    知道创宇404智能合约安全审计团队: https://www.scanv.com/lca/index.html
    联系电话:(086) 136 8133 5016(沈经理,工作日:10:00-18:00)

    欢迎扫码咨询:区块链行业安全解决方案

    黑客通过DDoS攻击、CC攻击、系统漏洞、代码漏洞、业务流程漏洞、API-Key漏洞等进行攻击和入侵,给区块链项目的管理运营团队及用户造成巨大的经济损失。知道创宇十余年安全经验,凭借多重防护+云端大数据技术,为区块链应用提供专属安全解决方案。

    欢迎扫码咨询:

    六、REF

    [1] ERC标准
    https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md

    [2] Morpheus官方公告
    https://medium.com/@themorpheus/new-morpheus-network-token-smart-contract-91 b80dbc7655

    [3] 构造函数书写问题漏洞详情
    https://mp.weixin.qq.com/s/xPwhanev-cjHhc104Wmpug


    Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/663/

    作者:Nanako | Categories:安全科普技术分享 | Tags:
  • 金钱难寐,大盗独行——以太坊 JSON-RPC 接口多种盗币手法大揭秘

    2018-08-29
    作者: 知道创宇404区块链安全研究团队
    发布时间: 2018/08/01更新于 2018/08/20 : 修正了原文中的一处错误,感谢 @None在评论区的指正。

    0x00 前言

    2010年,Laszlo 使用 10000 个比特币购买了两张价值25美元的披萨被认为是比特币在现实世界中的第一笔交易。

    2017年,区块链技术随着数字货币的价格暴涨而站在风口之上。谁也不会想到,2010年的那两块披萨,能够在2017年末价值 1.9亿美元

    以太坊,作为区块链2.0时代的代表,通过智能合约平台,解决比特币拓展性不足的问题,在金融行业有了巨大的应用。

    通过智能合约进行交易,不用管交易时间,不用管交易是否合法,只要能够符合智能合约的规则,就可以进行无限制的交易。

    在巨大的经济利益下,总会有人走上另一条道路。

    古人的盗亦有道,在虚拟货币领域也有着它独特的定义。只有对区块链技术足够了解,才能在这场盛宴中 到足够多的金钱。他们似那黑暗中独行的狼,无论是否得手都会在被发现前抽身而去。

    2018/03/20,在 《以太坊生态缺陷导致的一起亿级代币盗窃大案》[19]《揭秘以太坊中潜伏多年的“偷渡”漏洞,全球黑客正在疯狂偷币》[20] 两文揭秘 以太坊偷渡漏洞(又称为以太坊黑色情人节事件) 相关攻击细节后,知道创宇404团队根据已有信息进一步完善了相关蜜罐。

    2018/05/16,知道创宇404区块链安全研究团队对 偷渡漏洞 事件进行预警并指出该端口已存在密集的扫描行为。

    2018/06/29, 慢雾社区 里预警了 以太坊黑色情人节事件(即偷渡漏洞) 新型攻击手法,该攻击手法在本文中亦称之为:离线攻击。在结合蜜罐数据复现该攻击手法的过程中,知道创宇404区块链安全研究团队发现:在真实场景中,还存在 另外两种 新型的攻击方式: 重放攻击爆破攻击,由于此类攻击方式出现在 偷渡漏洞 曝光后,我们将这些攻击手法统一称为 后偷渡时代的盗币方式

    本文将会在介绍相关知识点后,针对 偷渡漏洞后偷渡时代的盗币方式,模拟复现盗币的实际流程,对攻击成功的关键点进行分析。

    0x01 关键知识点

    所谓磨刀不误砍柴功,只有清楚地掌握了关键知识点,才能在理解漏洞原理时游刃有余。在本节,笔者将会介绍以太坊发起一笔交易的签名流程及相关知识点。

    1.1 RLP 编码

    RLP (递归长度前缀)提供了一种适用于任意二进制数据数组的编码,RLP已经成为以太坊中对对象进行序列化的主要编码方式。

    RLP 编码会对字符串和列表进行序列化操作,具体的编码流程如下图:

    在此,也以 3.4.1节eth_signTransaction 接口返回的签名数据为例,解释该签名数据是如何经过 tx 编码后得到的。

    根据 RLP 编码的规则,我们对 tx 字段当作一个列表按顺序进行编码(hash除外)。由于长度必定大于55字节,所以采用最后一种编码方式。

    暂且先抛开前两位,对所有项进行RLP编码,结果如下:

    合并起来就是:01832dc6c083030d4094d4f0ad3896f78e133f7841c3a6de11be0427ed89881bc16d674ec80000801ba0e2e7162ae34fa7b2ca7c3434e120e8c07a7e94a38986776f06dcd865112a2663a004591ab78117f4e8b911d65ba6eb0ce34d117358a91119d8ddb058d003334ba4

    一共是 214 位,长度是 107 字节,也就意味着第二位是 0x6b,第一位是 0xf7 + len(0x6b) = 0xf8,这也是最终 raw 的内容:0xf86b01832dc6c083030d4094d4f0ad3896f78e133f7841c3a6de11be0427ed89881bc16d674ec80000801ba0e2e7162ae34fa7b2ca7c3434e120e8c07a7e94a38986776f06dcd865112a2663a004591ab78117f4e8b911d65ba6eb0ce34d117358a91119d8ddb058d003334ba4

    1.2 keystore 文件及其解密

    keystore 文件用于存储以太坊私钥。为了避免私钥明文存储导致泄漏的情况发生,keystore 文件应运而生。让我们结合下文中的 keystore 文件内容来看一下私钥是被如何加密的:

    在此,我将结合私钥的加密过程说明各字段的意义:

    加密步骤一:使用aes-128-ctr对以太坊账户的私钥进行加密

    本节开头已经说到,keystore 文件是为了避免私钥明文存储导致泄漏的情况发生而出现的,所以加密的第一步就是对以太坊账户的私钥进行加密。这里使用了 aes-128-ctr 方式进行加密。设置 解密密钥初始化向量iv 就可以对以太坊账户的私钥进行加密,得到加密后的密文。

    keystore 文件中的ciphercipherparamsciphertext参数与该加密步骤有关:

    • cipher: 表示对以太坊账户私钥加密的方式,这里使用的是 aes-128-ctr
    • cipherparams 中的 iv: 表示使用 aes 加密使用的初始化向量 iv
    • ciphertext: 表示经过加密后得到的密文

    加密步骤二:利用kdf算法计算解密密钥

    经过加密步骤一,以太坊账户的私钥已经被成功加密。我们只需要记住 解密密钥 就可以进行解密,但这里又出现了一个新的问题,解密密钥 长达32位且毫无规律可言。所以以太坊又使用了一个 密钥导出函数(kdf) 计算解密密钥。在这个 keystore 文件中,根据 kdf 参数可以知道使用的是 scrypt 算法。最终实现的效果就是:对我们设置的密码与 kdfparams 中的参数进行 scrypt 计算,就会得到 加密步骤1 中设置的 解密密钥.

    keystore 文件中的 kdfkdfparams 参数与该加密步骤有关:

    • kdf: 表示使用的 密钥导出函数 的具体算法
    • kdfparams: 使用密钥导出函数需要的参数

    加密步骤三:验证用户密码的正确性

    假设用户输入了正确的密码,只需要通过步骤一二进行解密就可以得到正确的私钥。但我们不能保证用户每次输入的密码都是正确的。所以引入了验算的操作。验算的操作十分简单,取步骤二解密出的密钥的第十七到三十二位和 ciphertext 进行拼接,计算出该字符串的 sha3_256 的值。如果和 mac 的内容相同,则说明密码正确。

    keystore 文件中的 mac 参数与该步骤有关:

    • mac: 用于验证用户输入密码的正确性。

    综上所述,要从 keystore 文件中解密出私钥,所需的步骤是:

    1. 通过 kdf 算法生成解密私钥
    2. 对解密私钥进行验算,如果与 mac 值相同,则说明用户输入的密码正确。
    3. 利用解密私钥解密ciphertext,获得以太坊账户的私钥

    流程图如下:

    如果有读者想通过编程实现从 keystore 文件中恢复出私钥,可以参考How do I get the raw private key from my Mist keystore file?[15]中的最后一个回答。

    其中有以下几点注意事项:

    1. 需要的环境是 Python 3.6+ OpenSSL 1.1+
    2. 该回答在 Decrypting with the derived key 中未交代 key 参数的来历,实际上 key = dec_key[:16]

    1.3 以太坊交易的流程

    根据源码以及网上已有的资料,笔者总结以太坊的交易流程如下:

    1. 用户发起转账请求。
    2. 以太坊对转账信息进行签名
    3. 校验签名后的信息并将信息加入交易缓存池(txpool)
    4. 从交易缓存池中提取交易信息进行广播

    对于本文来说,步骤2:以太坊对转账信息进行签名对于理解 3.4节 利用离线漏洞进行攻击 十分重要。笔者也将会着重分析该步骤的具体实现。

    从上文中我们可以知道,私钥已经被加密在 keystore 文件中,所以在步骤2进行签名操作之前,需要将私钥解密出来。在以太坊的操作中有专门的接口用于解锁账户: personal.unlockAccount

    在解锁对应的账户后,我们将可以进行转账操作。在用私钥进行签名前,存在一些初始化操作:

    • 寻找 from 参数对应地址的钱包
    • 判断必须传入的参数是否正确
    • 将传入的参数和原本的设置参数打包成 Transaction 结构体

    这里可以注意一点:Transaction 结构体中是不存在 from 字段的。这里不添加 from 字段和后面的签名算法有着密切的关系。

    使用私钥对交易信息进行签名主要分为两步:

    1. 对构造的列表进行 RLP 编码,然后通过 sha3_256 计算出编码后字符串的 hash 值。
    2. 使用私钥对 hash 进行签名,得到一串 65 字节长的结果,从中分别取出 rsv

    根据椭圆加密算法的特点,我们可以根据 rsvhash 算出对应的公钥。

    由于以太坊的地址是公钥去除第一个比特后经过 sha3_256 加密的后40位,所以在交易信息中不包含 from 的情况下,我们依旧可以知道这笔交易来自于哪个地址。这也是前文说到 Transaction 结构体中不存在 from 的原因。

    在签名完成后,将会被添加进交易缓存池(txpool),在这个操作中,from 将会被还原出来,并进行一定的校验操作。同时也考虑到交易缓存池的各种极端情况,例如:在交易缓存池已满的情况下,会将金额最低的交易从缓存池中移除。

    最终,交易缓存池中存储的交易会进行广播,网络中各节点收到该交易后都会将该交易存入交易缓存池。当某节点挖到新的区块时,将会从交易缓存池中按照 gasPrice 高低排序交易并打包进区块。

    0x02 黑暗中的盗币方式:偷渡时代

    2.1 攻击流程复现

    攻击复现环境位于 ropsten 测试网络。

    被攻击者IP: 10.0.0.2 ,启动客户端命令为:geth --testnet --rpc --rpcapi eth --rpcaddr 0.0.0.0 console 账户地址为:0x6c047d734ee0c0a11d04e12adf5cce4b31da3921,剩余余额为 5 ether

    攻击者IP: 10.0.0.3 , 账户地址为 0xda0b72478ed8abd676c603364f3105233068bdad

    注:若读者要在公链、测试网络实践该部分内容,建议先阅读 3.2 节的内容,了解该部分可能存在的隐藏问题。

    攻击者步骤如下:

    1. 攻击者通过端口扫描等方式发现被攻击者开放了 JSON-RPC 端口后,调用 eth_getBlockByNumber eth_accounts 接口查询当前节点最新的区块高度以及该节点上已有的账户。
    2. 攻击者调用 eth_getBalance 接口查询当前节点上所有账户的余额。
    3. 攻击者对存在余额的账户持续发起转账请求。

    一段时间后,被攻击者需要进行交易:

    按照之前的知识点,用户需要先解锁账户然后才能转账。当我们使用 personal.unlockAccount 和密码解锁账户后,就可以在终端看到恶意攻击者已经成功发起交易。

    读者可以通过该链接看到恶意攻击者的交易信息。

    攻击的流程图如下所示:

    2.2 攻击成功的关键点解析

    看完 2.1 节 偷渡漏洞 攻击流程,你可能会有这样的疑问:

    1. 攻击者为什么可以转账成功?
    2. 如例子中所示,该地址只有 5 ether,一次被转走了 4.79 ether,如果我们解锁账户后在被攻击前发起转账,转走 1 ether,是否攻击者就不会攻击成功?

    下文将详细分析这两个问题并给出答案。

    2.2.1 攻击者可以通过 rpc 接口转账的原因

    首先,分析一下关键的 unlockAccount 函数:

    在判断传入的解锁时间是否为空、是否大于最大值后,调用 TimedUnlock() 进行解锁账户的操作,而 TimedUnlock() 的代码如下:

    首先通过 getDecryptedKey()keystore 文件夹下的文件中解密出私钥(具体的解密过程可以参考 1.2 节的内容),再判断该账户是否已经被解锁,如果没有被解锁,则将解密出的私钥存入名为 unlocked 的 map 中。如果设置了解锁时间,则启动一个协程进行超时处理 go ks.expire().

    再看向实现转账的函数的实现过程 SendTransaction() -> wallet.SignTx() -> w.keystore.SignTx()

    可以看到,在 w.keystore.SignTx() 中,直接从 ks.unlocked 中取出对应的私钥。这也就意味着如果执行了 unlockAccount() 函数、没有超时的话,从 ipcrpc调用 SendTransaction() 都会成功签名相关交易。

    由于默认参数启动的 Go-Ethereum 设计上并没有对 ipcrpc 接口添加相应的鉴权模式,也没有在上述的代码中对请求用户的身份进行判断,最终导致攻击者可以在用户解锁账号的时候完成转账操作,偷渡漏洞利用成功。

    2.2.2 攻击者和用户竞争转账的问题

    由于用户解锁账户的目的是为了转账,所以存在用户和攻击者几乎同时发起了交易的情况,在这种情况下,攻击者是如何保证其攻击的成功率呢?

    在攻击者账号0x957cD4Ff9b3894FC78b5134A8DC72b032fFbC464的交易记录中,交易0x8ec46c3054434fe00155bb2d7e36d59f35d0ae1527aa5da8ec6721b800ec3aa2能够很好地解释该问题。

    相较于目前主流的 gasPrice 维持在 1 Gwei,该笔交易的 gasPrice 达到了惊人的 1,149,246 Gwei。根据 1.3节 中介绍的以太坊交易流程可知:

    1. 在交易签名完成后,交易就会被存入交易缓存池(txpool),交易会被进行校验。但是由于此时新的交易还没有打包进区块,所以用户和攻击者发起的交易都会存入交易缓存池并广播出去。
    2. 当某节点挖到新的区块时,会将交易从交易缓存池中按照 gasPrice 高低进行排序取出并打包。gasPrice 高的将会优先被打包进区块。由于攻击者的交易的 gasPrice 足够高,所以会被优先被打包进区块,而用户的交易将会由于余额不足导致失败。这是以太坊保证矿工利益最大化所设计的策略,也为攻击者攻击快速成功提供了便利。

    也正是由于较高的 gasPrice,使得该攻击者在与其它攻击者的竞争中(有兴趣的可以看看上图红框下方两笔 dropped Txns)得到这笔 巨款

    2.3 蜜罐捕获数据

    该部分数据截止 2018/03/21

    偷渡漏洞 被曝光后,知道创宇404团队在已有的蜜罐数据中寻找到部分攻击的痕迹。

    下图是 2017/10/012018/03/21 间蜜罐监控到的相关攻击情况:

    被攻击端口主要是 8545端口8546103328555180828585端口等也有少量扫描痕迹。

    攻击来源IP主要集中在 46.166.148.120/196216.158.238.178/186/226 上:

    46.166.148.120/196 攻击者使用的探测 payload 主要是:

    216.158.238.178/186/226 攻击者使用的探测 payload 主要是:

    0x03 后偷渡时代的盗币方式

    在偷渡漏洞被曝光后,攻击者和防御者都有所行动。根据我们蜜罐系统捕获的数据,在后偷渡时代,攻击的形式趋于多样化,利用的以太坊特性越来越多,攻击方式趋于完善。部分攻击甚至可以绕过针对偷渡漏洞的防御方式,所以在说这些攻击方式前,让我们从偷渡漏洞的防御修复方式开篇。

    3.1 偷渡漏洞的已知的防范、修复方式

    在参考链接 101920 中,关于偷渡漏洞的防范、修复方式有:

    • 使用 personal.sendTransaction 功能进行转账,而不是使用 personal.unlockAccounteth.sendTransaction 进行转账。
    • 更改默认的 RPC API 端口、更改 RPC API 监听地址为内网、配置 iptables 限制对 RPC API 端口的访问、账户信息(keystore)不存放在节点上、转账使用 web3sendTransactionsendRawTransaction 发送私钥签名过的 transaction、私钥物理隔离(如冷钱包、手工抄写)或者高强度加密存储并保障密钥的安全
    • 关闭对外暴露的RPC端口,如果必须暴露在互联网,使用鉴权链接地址、借助防火墙等网络防护软件,封堵黑客攻击源IP、检查RPC日志、web接口日志、等待以太坊更新最新代码,使用修复了该漏洞的节点程序

    但是实际的情况却是 关闭对公网暴露的 RPC 接口使用 personal.sendTransaction()进行转账节点上不存放账户信息(keystore) 后,依然可能会被盗币。根据上文,模拟出如下两种情景:

    情景一:对于曾经被盗币,修复方案仅为:关闭对公网暴露的 RPC 接口,关闭后继续使用节点中相关账户或移除了账户信息(keystore)的节点,可能会受到 Geth 交易缓存池的重放攻击离线漏洞 的攻击。

    情景二:对于暂时无法关闭对公网暴露的 RPC 接口,却使用 personal.sendTransaction() 安全转账的节点,可能会受到 爆破账号密码 的攻击。

    我们也将会在 3.2节 - 3.5节 详细的说明这三种漏洞的攻击流程。

    3.2 交易缓存池的重放攻击

    对于曾经被盗币,修复方案仅为:关闭对公网暴露的 RPC 接口,关闭后继续使用节点中相关账户的节点,可能会受到该攻击

    3.2.1 发现经历

    细心的读者也许会发现,在 2.1节 中,为了实现攻击者不停的发送转账请求的功能,笔者使用了 while True 循环,并且在 geth 终端中看到了多条成功签名的交易 hash。由于交易缓存池拥有一定的校验机制,所以除了第一笔交易0x4ad68aafc59f18a11c0ea6e25588d296d52f04edd969d5674a82dfd4093634f6外,剩下的交易应该因为账户余额不足而被移出交易缓存池。

    但是在测试网络中却出现了截然不同的情况,在我们关闭本地的 geth 客户端后,应该被移出交易缓存池的交易在余额足够的情况下会再次出现并交易成功:

    (为了避免该现象的出现,在 2.1节 中,可以在成功转账之后利用 break 终止相关的循环)

    这个交易奇怪的地方在于:在账户余额不足的情况下,查找不到任何 Pendding Transactions

    当账户余额足够支付时,被移出交易缓存池的交易会重新出现,并且是 Pendding 状态。

    在部分 pendding 的交易完成后,剩余的交易将会继续消失。

    这也就意味着,如果攻击者能够在利用 偷渡漏洞 的过程中,在交易被打包进区块,账号状态发生改变前发送大量的交易信息,第一条交易会被立即实行,剩余的交易会在 受害人账号余额 大于 转账金额+gas消耗的金额 的时候继续交易,而且这个交易信息在大多数情况下不会被查到。

    对于这个问题进行分析研究后,我们认为可能的原因是:以太坊在同步交易缓存池的过程中可能因为网络波动、分布式的特点等原因,导致部分交易多次进入交易缓存池。这也导致 部分应该被移出交易缓存池的交易 多次重复进入交易缓存池。

    具体的攻击流程如下:

    3.2.2 本地复现过程

    关于 3.2.1 节中出现的现象,笔者进行了多方面的猜测。最终在低版本的 geth 中模拟复现了该问题。但由于现实环境的复杂性和不可控性,并不能确定该模拟过程就是造成该现象的最终原因,故该本地复现流程仅供参考。

    攻击复现环境位于私链中,私链挖矿难度设置为 0x400000,保证在挖出区块之前拥有足够的时间检查各节点的交易缓存池。geth的版本为 1.5.0

    被攻击者的节点A:通过 geth --networkid 233 --nodiscover --verbosity 6 --ipcdisable --datadir data0 --rpc --rpcaddr 0.0.0.0 console 启动。

    矿机节点B,负责挖矿: 通过 geth --networkid 233 --nodiscover --verbosity 6 --ipcdisable --datadir data0 --port 30304 --rpc --rpcport 8546 console 启动并在终端输入 miner.start(1),使用单线程进行挖矿。

    存在问题的节点C:通过 geth --networkid 233 --nodiscover --verbosity 6 --ipcdisable --datadir data0 --port 30305 --rpc --rpcport 8547 console 启动。

    各节点启动后通过 admin.nodeInfoadmin.addPeer() 相互添加节点。

    1.攻击者扫描到被攻击节点A开放了rpc端口,使用如下代码开始攻击:

    2.节点A的用户由于转账的需求,使用 personal.unlockAccount() 解锁账户,导致偷渡漏洞发生。由于一共进行了三次转账请求并成功广播,所以A、B、C交易缓存池中均存在这三笔交易。

    3.由于网络波动等原因,此时节点 C 与其它节点失去连接。在这里用 admin.removePeer() 模拟节点 C 掉线。节点 B 继续挖矿,完成相应的交易。后两笔交易会因为余额不足从交易缓存池中移除,最终节点 A ,B 的交易缓存池中将不会有任何交易。

    4.上述步骤 1-3 即是前文说到的 偷渡漏洞,被攻击者A发现其节点被攻击,迅速修改了节点A的启动命令,去除了 --rpc --rpcaddr 0.0.0.0,避免 RPC 端口暴露在公网之中。之后继续使用该账户进行了多次转账。例如,使用其它账号给节点A上的账号转账,使的节点A上的账号余额为 1.980065000882e+24

    5.节点 C 再次连接进网络,会将其交易池中的三个交易再次广播,发送到各节点。这就造成已经移除交易缓存池的交易再次回到交易缓存池中。

    6.由于此时节点A的账户余额足够,第二个交易将会被打包进区块,节点A中的余额再次被盗。

    注: 在实际的场景中,不一定会出现节点 C 失去连接的情况,但由于存在大量分布式节点的原因,交易被其它节点重新发送的情况也是可能出现的。这也可以解释为什么在前文说到: 账户余额足够时,会出现大量应该被移除的 pending 交易,在部分交易完成后,pending 交易消失的的情况。当账户余额足够时,重新广播交易的节点会将之前所有的交易再次广播出去,在交易完成后,剩余 pending 交易会因为余额不足再次从交易缓存池中被移除。

    注2: 除了本节说到的现象外,亦不排除攻击者设置了恶意的以太坊节点,接收所有的交易信息并将部分交易持续广播。但由于该猜想无法验证,故仅作为猜测思路提供。

    3.3 unlockAccount接口的爆破攻击

    对于暂时无法关闭对公网暴露的 RPC 接口的节点,在不使用 personal.unlockAccount() 的情况下,仍然存在被盗币的可能。

    3.3.1 漏洞复现

    被攻击节点启动参数为: geth --testnet --rpc --rpcaddr 0.0.0.0 --rpcapi eth,personal console

    攻击者的攻击步骤为:

    1. 偷渡漏洞 攻击 1-3 步类似,攻击者探测到目标开放了 RPC 端口 -> 获取当前节点的区块高度、节点上的账户列表 以及 各账户的余额。根据蜜罐捕获的数据,部分攻击还会通过 personal_listWallets 接口进行查询,寻找当前节点上已经 unlocked 的账户。
    2. 调用 personal_unlockAccount 接口尝试解密用户账户。假如用户使用了弱口令,攻击者将会成功解锁相应账户。
    3. 攻击者可以将解锁账户中的余额全部转给自己。

    攻击流程如下图所示:

    3.3.2 升级的爆破方式

    根据偷渡漏洞的原理可以知道该攻击方式有一个弊端:如果有两个攻击者同时攻击一个节点,当一个攻击者爆破成功,那么这两个攻击者都将可以取走节点中的余额。

    根据 2.3 节中的分析可以知道,谁付出了更多的手续费,谁的交易将会被先打包。这也陷入了一个恶性循环,盗币者需要将他们的利益更多地分给打包的矿工才能偷到对应的钱。也正是因为这个原因,蜜罐捕获到的爆破转账请求从最初的 personal_unlockAccount 接口逐渐变成了 personal_sendTransaction 接口。

    personal_sendTransaction 接口是 Geth 官方在 2018/01 新增了一个解决偷渡漏洞的RPC接口。使用该接口转账,解密出的私钥将会存放在内存中,所以不会引起 偷渡漏洞 相关的问题。攻击者与时俱进的攻击方式不免让我们惊叹。

    3.4 自动签名交易的离线攻击

    对于曾经被盗币的节点,可能会被离线漏洞所攻击。这取决于被盗币时攻击者生成了多个交易签名。

    3.4.1 攻击流程复现

    由于该攻击涉及到的 eth_signTransaction 接口在 pyweb3 中不存在,故攻击流程复现使用 curl 命令与 JSON-RPC 交互

    攻击者IP为:10.0.0.3,账户地址为:0xd4f0ad3896f78e133f7841c3a6de11be0427ed89geth 的启动命令为: geth --testnet --rpc --rpcaddr 0.0.0.0 --rpcapi eth,net,personal

    被攻击者IP为: 10.0.0.4,geth 版本为 1.8.11 (当前最新版本为 1.8.12),账户地址为 0x9e92e615a925fd77522c84b15ea0e8d2720d3234

    1.攻击者扫描到被攻击者开放了 8545 端口后,可以通过多个接口获取被攻击者信息

    账户里余额为0,是因为笔者没有及时同步区块。实际余额是 0.98 ether

    2.通过 eth_getTransactionCount 接口获取节点账户和盗币账户之间的转账次数,用于计算 nonce。等待用户通过 personal.unlockAccount() 解锁。在用户解锁账户的情况下,通过 eth_signTransaction 接口持续发送多笔签名转账请求。例如:签名的转账金额是 2 ether,发送的数据包如下:

    攻击者会在账户解锁期间按照 nonce 递增的顺序构造多笔转账的签名。

    3.至此,攻击者的攻击已经完成了一半。无论被攻击者是否关闭 RPC 接口,攻击者都已经拥有了转移走用户账户里 2 ether 的能力。攻击者只需监控用户账户中的余额是否超过 2 ether 即可。如图所示,在转入 1.2 ether 后,用户的账户余额已经达到 2 ether

    攻击者在自己的节点对已经签名的交易进行广播:

    2 ether 被成功盗走。

    相关交易记录可以在测试网络上查询到。

    攻击流程图示如下:

    3.4.2 攻击成功的关键点解析

    按照惯例,先提出问题:

    1. 为什么签名的交易可以在别的地方广播?
    2. Geth 官方提供的接口 eth_sign 是否可以签名交易?
    3.4.2.1 签名的有效性问题

    从原理上说,离线漏洞的攻击方式亦是以太坊离线签名的一种应用。

    为了保护私钥的安全性,以太坊拥有离线签名这一机制。用户可以在不联网的电脑上生成私钥,通过该私钥签名交易,将签名后的交易在联网的主机上广播出去,就可以成功实现交易并有效地保证私钥的安全性。

    在 1.3 节的图中,详细的说明了以太坊实现交易签名的步骤。在各参数正确的情况下,以太坊会将交易的相关参数:noncegasPricegastovalue 等值进行 RLP 编码,然后通过 sha3_256 算出其对应的 hash 值,然后通过私钥对 hash 值进行签名,最终得到 srv。所以交易的相关参数有:

    由于 hash 可以根据其它值算出来,所以对除 hash 外的所有值进行 RLP 编码,即可得到签名后的交易内容。

    在以太坊的其它节点接受到该交易后,会通过 RLP 解码得到对应的值并算出 hash 的值。由于椭圆曲线数字签名算法可以在知道 hashsrv的情况下得到公钥的值、公钥经过 sha3_256 加密,后四十位就是账户地址,所以只有在所有参数没有被篡改的情况下,才能还原出公钥,计算出账户地址。因此确认该交易是从这个地址签名的。

    根据上述的签名流程,也可以看出,在对应的字段中,缺少了签名时间这一字段,这也许会在区块链落地的过程中带来一定的阻碍。

    3.4.2.2 交易签名流程 与 eth_sign签名流程对比

    根据官网的描述,eth_sign 的实现是 sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message)))

    这与 3.4.2.1 节中交易签名流程有着天壤之别,所以 eth_sign 接口并不能实现对交易的签名!

    注:我们的蜜罐未抓取到离线漏洞相关攻击流量,上述攻击细节是知道创宇404区块链安全团队研究后实现的攻击路径,可能和现实中黑客的攻击流程有一定的出入。

    3.5 蜜罐捕获攻击JSON‐RPC相关数据分析

    在偷渡漏洞曝光后,知道创宇404团队有针对性的开发并部署了相关蜜罐。 该部分数据统计截止 2018/07/14

    3.5.1 探测的数据包

    对蜜罐捕获的攻击流量进行统计,多个 JSON-RPC 接口被探测或利用:

    其中 eth_blockNumbereth_accountsnet_versionpersonal_listWallets 等接口具有很好的前期探测功能,net_version 可以判断是否是主链,personal_listWallets 则可以查看所有账户的解锁情况。

    personal_unlockAccountpersonal_sendTransactioneth_sendTransaction 等接口支持解锁账户或直接进行转账。

    可以说,相比于第一阶段的攻击,后偷渡时代 针对 JSON-RPC 的攻击正呈现多元化的特点。

    3.5.2 爆破账号密码

    蜜罐在 2018/05/24 第一次检测到通过 unlockAccount 接口爆破账户密码的行为。截止 2018/07/14 蜜罐一共捕获到 809 个密码在爆破中使用,我们将会在最后的附录部分给出详情。

    攻击者主要使用 personal_unlockAccount 接口进行爆破,爆破的 payload 主要是:

    在所有的爆破密码中有一个比较特殊:ppppGoogle。该密码在 personal_unlockAccountpersonal_sendTransaction 接口均有被多次爆破的痕迹。是否和《Microsoft Azure 以太坊节点自动化部署方案漏洞分析》案例一样,属于某厂商以太坊节点部署方案中的默认密码,仍有待考证。

    3.5.3 转账的地址

    蜜罐捕获到部分新增的盗币地址有:

    3.5.4 攻击来源IP

    3.6 其它的威胁点

    正如本文标题所说,区块链技术为金融行业带来了丰厚的机遇,但也招来了众多独行的大盗。本节将会简单介绍在研究偷渡漏洞过程中遇到的其它威胁点。

    3.6.1 parity_exportAccount 接口导出账户信息

    3.5.1 节中,蜜罐捕获到对 parity_exportAccount 接口的攻击。根据官方手册,攻击者需要输入账号地址和对应的密码,如果正确将会导出以json格式导出钱包。

    看过 1.21.3 节中的知识点、偷渡漏洞、后偷渡时代的利用方式的介绍,需要意识到:一旦攻击者攻击成功,私钥将会泄漏,攻击者将能完全控制该地址。

    3.6.2 clef 中的 account_export 接口

    该软件是 geth 中一个仍未正式发布的测试软件。其中存在一个导出账户的接口 account_export

    通过 curl -XPOST http://localhost:8550/ -d '{"id": 5,"jsonrpc": "2.0","method" : "account_export","params": ["0xc7412fc59930fd90099c917a50e5f11d0934b2f5"]}' --header "Content-Type: appli cation/json" 命令可以调用该接口导出相关账号信息。值得一提的是,在接口存在一定的安全机制,需要用户同意之后才会导出账号。

    虽然该接口目前仍算安全,但由于不需要密码即可导出keystore文件内容的特性,值得我们持续关注。

    3.7 后偷渡时代的防御方案

    相较于 3.1 节已有的防御方案,后偷渡时代更加关注账户和私钥安全。

    1. 对于有被偷渡漏洞攻击的痕迹或可能曾经被偷渡漏洞攻击过的节点,建议将节点上相关账户的资产转移到新的账户后废弃可能被攻击过的账户。
    2. 建议用户不要使用弱口令作为账户密码,如果已经使用了弱口令,可以根据1.2节末尾的内容解出私钥内容,再次通过 geth account import 命令导入私钥并设置强密码。
    3. 如节点不需要签名转账等操作,建议节点上不要存在私钥文件。如果需要使用转账操作,务必使用 personal_sendTransaction 接口,而非 personal_unlockAccount 接口。

    0x04 总结

    在这个属于区块链的风口上,实际落地仍然还有很长的路需要走。后偷渡时代的离线漏洞中出现的 区块链记录的交易时间不一定是交易签名时间 这一问题就是落地过程中的阻碍之一。

    区块链也为攻击溯源带来了巨大的阻碍。一旦私钥泄漏,攻击者可以在任何地方发动转账。而由于区块链分布式存储的原因,仅仅通过区块链寻找攻击者的现实位置也变得难上加难。

    Go Ethereum JSON-RPC 盗币漏洞而言,涉及到多个方面的多个问题:以太坊底层签名的内容、geth 客户端 unlockAccount 实现的问题、分布式网络导致的重放问题,涉及的范围之广也是单个传统安全领域较难遇到的。这也为安全防御提出了更高的要求。只有从底层了解相关原理、对可能出现的攻击提前预防、经验加技术的沉淀才能在区块链的安全防御方面做到游刃有余。

    虚拟货币价值的攀升,赋予了由算法和数字堆砌的区块链巨大的金融价值,也会让 盗币者 竭尽所能从更多的方面实现目标。金钱难寐,大盗独行,也许会是这个漏洞最形象的描述。


    智能合约审计服务

    针对目前主流的以太坊应用,知道创宇提供专业权威的智能合约审计服务,规避因合约安全问题导致的财产损失,为各类以太坊应用安全保驾护航。

    知道创宇404智能合约安全审计团队: https://www.scanv.com/lca/index.html
    联系电话:(086) 136 8133 5016(沈经理,工作日:10:00-18:00)

    欢迎扫码咨询:区块链行业安全解决方案

    黑客通过DDoS攻击、CC攻击、系统漏洞、代码漏洞、业务流程漏洞、API-Key漏洞等进行攻击和入侵,给区块链项目的管理运营团队及用户造成巨大的经济损失。知道创宇十余年安全经验,凭借多重防护+云端大数据技术,为区块链应用提供专属安全解决方案。

    欢迎扫码咨询:

    参考链接

    1. What is an Ethereum keystore file?
    2. Key_derivation_function
    3. 15.1. hashlib — Secure hashes and message digests
    4. 对比一下ecdsa与secp256k1-py从私钥生成公钥
    5. Ethereum JSON RPC
    6. how-to-create-raw-transactions-in-ethereum-part-1-1df91abdba7c
    7. 椭圆曲线密码学和以太坊中的椭圆曲线数字签名算法应用
    8. Web3-Secret-Storage-Definition
    9. Management-APIs
    10. RPC: add personal_signTransaction: [tx, pw]
    11. Possible BUG - somebody took 50 ETH from my wallet immediately after successful transaction
    12. RLP 英文版
    13. RLP 中文版
    14. Private-network
    15. How do I get the raw private key from my Mist keystore file?
    16. 以太坊源码分析-交易
    17. Ethereum交易详解
    18. Life Cycle of an Ethereum Transaction
    19. 以太坊生态缺陷导致的一起亿级代币盗窃大案
    20. 揭秘以太坊中潜伏多年的“偷渡”漏洞,全球黑客正在疯狂偷币
    21. 慢雾社区小密圈关于以太坊情人节升级攻击的情报
    22. 以太坊离线钱包
    23. 以太坊实战之《如何正确处理nonce》

    附录

    1. 爆破 unlockAccount 接口使用的密码列表

    密码列表


    Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/656/

    作者:Nanako | Categories:安全研究技术分享 | Tags:
  • Sony IPELA E 系列网络摄像头远程命令执行漏洞预警

    2018-08-29
    作者:知道创宇404实验室
    时间:2018年7月24日
    英文版:https://paper.seebug.org/654/

    背景

    索尼是世界视听、电子游戏、通讯产品和信息技术等领域的先导者,是世界最早便携式数码产品的开创者,是世界最大的电子产品制造商之一。

    2018 年 07 月 20 日,Sony IPELA E 系列网络摄像头被曝出存在远程命令执行漏洞, 网上已经公开了漏洞细节。该系列摄像头由于未对用户的输入进行过滤,而直接拼接成命令字符串并执行,攻击者可基于此执行任意命令,进一步完全接管摄像头,该漏洞被赋予编号 CVE-2018-3937。该漏洞的利用难度很低,通过原漏洞详情中的说明,2018 年 07 月 19 日,Sony 官方已发布该漏洞的补丁。 2018 年 07 月 24 日,Seebug 漏洞平台收录了该漏洞。知道创宇404实验室迅速跟进,复现了该漏洞。

    漏洞影响

    通过ZoomEye网络空间搜索引擎对app:”SonyNetworkCamerahttpd” 关键字进行搜索,共得到 6,468 条 IP 历史记录。从本地验证的过程来看,该漏洞的利用难度很低。

    受漏洞影响设备的国家分布如下,主要分布在美国、越南、德国等国家。

    漏洞修复

    根据原漏洞详情的说明,Sony官方已经发布相关补丁修复了该漏洞,请及时根据对应摄像头型号下载安装新版固件


    Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/655/

    作者:Nanako | Categories:技术分享 | Tags:
  • 酷视(NEO Coolcam)网络摄像头登录绕过及多个基于堆栈溢出的远程代码执行漏洞及数据分析报告

    2018-08-29

    作者:知道创宇404实验室
    时间:2018年7月16日
    英文版:https://paper.seebug.org/652

    1. 事件概述

    深圳市丽欧电子有限公司(NEO Coolcam,以下简称酷视)[1],是一家集网络数码产品研发、生产、营销于一体的高新技术企业,是国内最早进入网络摄像头领域的专业厂商之一。2004年成立国内摄像头研发中心,并取得多项国家专利,产品通过了国家质量检测部门的认证和CE、FCC等国际标准认证。

    早在2017年08月02日,Bitdefender公司的安全研究人员就指出酷视旗下的高清网络摄像头NIP-22和Wi-Fi门铃iDoorbell等设备存在多个缓冲区溢出漏洞,十几万暴漏在公网上的相关设备受到潜在的安全威胁,并提供了相关研究报告[2]。2017年9月左右,我们观察到酷视的英文版官网上发布了最新的固件[3],修复了溢出漏洞。

    2018年07月10日,在后续的对网络空间上易受漏洞影响的物联网设备的风险评估中,我们通过ZoomEye网络空间搜索引擎对相关漏洞设备进行搜索,共发现了65万的IP历史记录,其中在中国含该漏洞的设备数量最多,约为16.7万。此外,我们还有以下发现:

    • 从酷视官方发布更新版固件到本文发布约一年的时间里,大部分设备依然没有安装更新版固件。原因有以下几点:1、目标设备本身不具有自动升级机制;2、普通用户不会意识到存在漏洞并手动更新固件;3、更新版固件只发布在英文版官网中;4、其他OEM厂商生产的设备也存在该漏洞。
    • 在目标设备的固件审计过程中,我们发现了登录绕过漏洞,相关细节将在下面的章节中呈现。

    这意味着还有很大数量的目标设备处于风险之中。知道创宇404实验室对酷视NIP-22FX这款摄像头的系列缓冲区溢出漏洞进行了深入研究,并成功从缓冲区溢出到远程代码执行,证实了该漏洞有被黑产利用的潜在风险。同时审计过程中发现了登录绕过漏洞,对用户隐私也是个严重的威胁。

    2.漏洞分析

    2.1 目标设备的相关信息

    设备版本:NeoCoolcam IPCam NIP-22FX
    漏洞二进制文件:MD5 (ipc_server) = 312d924344364620d85099ed279a5f03
    固件版本:V7.7.4.1.1-20160701

    提供Web服务和RTSP服务的主程序为 ipc_server文件,目标系统为ARM、32位小端架构。

    缓冲区溢出缓解措施为全部关闭状态。

    2.2 登录绕过漏洞

    摄像头 Web 服务基于 HTTP 基本认证,存在三组默认凭证,三组凭证对应不同的权限等级,安装时 APP 只会提醒修改 admin 账户的默认密码。三组默认凭证及对用的操作如下:

    1. admin:admin,
    2. user:user;
    3. guest:guest;

    值得一提的是,user 账户和 guest 账户也可以查看视频流,大部分用户不会修改这些账户的默认密码,导致隐私泄漏。

    2.3 Web 服务基于缓冲区溢出的远程代码执行漏洞(无需认证)

    2.3.1 漏洞细节分析

    该溢出点位于地址 0x0007DE80 附近,该函数的处理逻辑是调用libs_parsedata函数解析URL中的usr和pwd,并将其分别存储到本函数栈帧的两块缓冲区中。

    libs_parsedata函数的原型为:

    接受6个参数,从左往右依次为a1:原字符串,a2:原串的长度,needle:匹配待截取字符串的开头,a4:用来截取字符串的分隔符,a6:存储截取后字符串的目标缓冲区。

    该函数的处理逻辑为:使用needle字符串和分隔符a4截取原字符串a1,截取后通过strncpy()函数将截取后的串写入a6所指的缓冲区中,写入的长度为截取字符串的长度,最后写入一个字节’\x00’。由于GET参数长度可控,当攻击者输入超出缓冲区长度的usr或pwd值时,会使缓冲区溢出。

    2.3.2 漏洞利用分析

    二进制文件ipc_server的缓冲区溢出措施皆为关闭状态,利用该缓冲区溢出漏洞的难度很低。利用过程中需要考虑到规避空白符、&、\x00等坏字符,空白符可用 ${IFS} 替代。

    在ipc_server的0x0004E4D8地址处含有如下代码:

    攻击者只需让返回地址指向地址0x0004E4D8,返回地址之后紧跟待执行的命令,即可成功从缓冲区溢出到远程代码执行。由于libs_parsedata函数会在字符串末尾写入一个\x00,可以同时利用两个溢出漏洞分别写入返回地址和待执行命令。

    目标系统不含curl、nc、wget等命令,可将命令执行结果重定向之Web目录,之后访问HTTP接口即可获取执行结果。如果攻击者和摄像头在同一个网络环境,攻击者也可能开启目标系统的telnetd服务,实现对漏洞设备的完全控制。因为目标设备的文件系统以读写方式挂载,有被攻击者恶意篡改的风险。

    在NIP-22FX上的复现结果如下:

    2.3.3 补丁分析

    在最新版的固件(V7.7.4.1.1-20170828)中,libs_parsedata函数加入了第七个参数,用以控制写入目标缓冲区的长度。

    2.4 RTSP 服务基于缓冲区溢出的远程代码执行漏洞(无需认证)

    2.4.1 漏洞细节分析

    该溢出点位于地址0x006C6D4处,利用 sscanf 函数匹配 RTSP Header 中 Authorization: Digest key="value" 中的key和value两部分内容并将之存到本函数堆栈,没有检查这两部分的长度,导致堆栈溢出。

    2.4.2 漏洞利用分析

    该漏洞的利用和2.3.2节中Web服务的缓冲区溢出漏洞利用方法一致,攻击者可利用两个溢出漏洞分别写入待执行的命令和返回地址,很容易的从缓冲区溢出提升到远程代码执行。

    在NIP-22FX的复现结果如下,成功利用RTSP服务的缓冲区溢出开启了目标系统的telnetd服务。

    2.4.3 补丁分析

    在最新版的固件(V7.7.4.1.1-20170828)中,sscanf 的正则匹配表达式中加入了长度限制,最长为255字节,而缓冲区距离栈底为296字节,无法覆盖返回地址。

    3. 漏洞影响范围

    我们通过提取酷视NIP-22高清摄像头设备相关的“关键词”,在ZoomEye网络空间搜索引擎[4]上共发现了651,780个 IP历史数据。

    我们通过对 ZoomEye 网络空间搜索引擎 "Error: username or password error,please input again." 这个关键词得到的651,780条IP历史记录进行确认,发现其中58,413台设备仍然存活。

    存活设备国家分布如下,可以看出这些漏洞设备主要分布在韩国、美国、中国等国家。由于中国的网络IP变化快,在中国的相关存活设备数量实际上不止5,878台。

    存活设备在中国的省份分布如下,主要分布在香港,其次是台湾,ZoomEye网络空间搜索引擎上中国大陆地区的历史IP数据基本都已失效。

    对以上存活的设备进行进一步统计分析,发现大部分设备均至少存在一种默认凭证。由此可见酷视高清摄像头设备普遍存在默认凭证,攻击者可使用默认凭证访问摄像头的视频流,有较大的隐私泄漏风险。值得一提的是,存活的设备中也有很多存在 admin:admin 默认凭证,攻击者可获得管理员身份,并可能通过上传精心制作的设备固件完全接管目标设备。

    在对受漏洞影响的设备进行数据分析的过程中,我们发现存在大量设备是贴牌销售,设备固件存在极大的同源性,有的两个不同厂商之间的设备仅仅是换了个LOGO。

    通过设备页面 ”/web/mainpage.html” 内容的md5值对不同OEM厂商进行区分,统计结果如下:

    除了默认凭证问题,酷视高清摄像头NIP-22还存在无需认证的Web服务及RTSP服务缓冲区溢出漏洞,该溢出漏洞的利用难度很低,攻击者可基于此溢出漏洞远程执行任意命令。溢出发生后,watchdog进程会重启整个系统,攻击者也可利用这点使摄像头拒绝服务。由于固件的同源性,这两个溢出漏洞也有很大可能存在于其他OEM厂商生产的设备中。

    4. 漏洞修复建议

    4.1 对用户的修复建议

    为避免隐私泄漏,建议用户尽快修复系列漏洞。

    首先,用户可登录摄像头Web管理系统,在以下页面中修改三组默认凭证的用户名和密码。

    其次,如果是酷视的设备,建议从酷视官网下载对应设备的最新版固件[3],并手动更新,以修复两个溢出漏洞。如果是其他OEM厂商的设备,可以尝试和厂商联系获取更新固件,并将设备同公网隔离。

    4.2 对厂商的修复建议

    由于这系列漏洞影响国内外几十个OEM厂商,请上表中可能存在漏洞的厂商自查,及时发布补丁固件并通知用户更新设备固件。

    5. 总结

    1. 存活设备中大部分以上都存在默认凭证,对于用户的隐私是个极大的威胁,用户应及时修改默认密码。
    2. 这系列漏洞还可能影响国内外几十个OEM厂商。嵌入式设备固件开发过程中一般都会使用第三方的开源工具或通用软件,这些通用软件又通常由某一特定厂商研发,这就导致很多设备固件存在同源性,不同品牌的设备可能运行相同或者类似的固件以及包含相同的第三方库。漏洞曝出后,由于影响厂商众多,而并不是所有厂商都会发布漏洞补丁,这就导致网络空间上大量漏洞设备无法修复漏洞。
    3. 近年来,路由器、摄像头、摄像机、NAS、智能穿戴设备等 IOT 设备的安全漏洞层出不穷,伴随着越来越多的嵌入式设备连入网络,总体安全形势日益突出,用户的个人隐私受到严重的威胁。一方面,厂商及开发者应不断提高自主研发设备的安全性。另一方面,漏洞是不可避免的。对于用户,应该努力提高自己的安全意识,尽量避免将此类设备直接暴露在网络空间上。对于各 IOT 厂商,针对目前安全漏洞曝出越来越频繁,及时修复漏洞,对产品提供自动升级机制是行之有效的方法。

    6. 相关链接

    [1] NEO Coolcam 官网
    http://www.szneo.com/
    [2] Bitdefender漏洞公告
    https://www.bitdefender.com/box/blog/ip-cameras-vulnerabilities/neo-coolcams-not-cool-buffer-overflow/
    [3] 官方更新固件下载地址
    http://szneo.com/en/service/index.php
    [4] ZoomEye网络空间探测引擎
    https://www.zoomeye.org/searchResult?q=%22Error%3A%20username%20or%20password%20error%2Cplease%20input%20again.%22


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/653/

    作者:Nanako | Categories:安全研究漏洞通告 | Tags:
  • 摄像头漏洞挖掘入门教程(固件篇)

    2018-08-29

    作者:fenix@知道创宇404实验室
    时间:2017年11月27日

    0x00 引言

    据 IT 研究与顾问咨询公司 Gartner 预测,2017 年全球物联网设备数量将达到 84 亿,比 2016 年的 64 亿增长31%,而全球人口数量为 75 亿。2020 年物联网设备数量将达到 204 亿。

    而与如此快的发展速度相对应的,物联网的安全问题也日趋凸显,尤其是网络摄像头、路由器等常见设备。我们可以从以下两个案例大致感受一下物联网设备严峻的安全形势。

    物联网设备数量的快速增长和其安全性的严重滞后形成了鲜明对比。同时也给恶意攻击者和安全研究人员提供了新的土壤,这场正邪的博弈在新的战场上正激烈上演。

    这是一篇详细的入门级别的教程,献给众多想入门智能设备安全的爱好者们。(本文完成于2017年,时隔一年对外发布。)

    0x01 概述

    1.0 固件及其常见获取方式

    固件(Firmware)就是写入 EROM (可擦写只读存储器)或 EEPROM(电可擦可编程只读存储器)中的程序。特殊的,对于市面上大部分的路由器和摄像头来说,固件就是电路板上的 25 系列 Flash 芯片中的程序,其中存放着嵌入式操作系统,通常是 Linux 系统。

    获取固件是分析挖掘固件漏洞的前提,本文将以摄像头为例,介绍如何 Dump Flash 芯片中的固件以及获取固件之后的一些玩法思路。

    通常情况下,有以下几种获取固件的途径。

    本文涉及后两种方式提取固件的方式。

    0x02 概念拓展

    在开始正式的固件提取之前,先来熟悉几个基础概念。

    2.0 串口和串口通信

    串口(Serial port)又称“序列端口”,主要用于串行式逐位数据传输。

    UART(Universal Asynchronous Receiver/Transmitter) 是一种异步串口通信协议。串口遵循 UART 协议按位(bit)异步发送和接收字节,通常情况下需要连接三对针脚,连线方式如下所示(图片来自网络):

    上图中,TX 为接收端,RX 为传输端,GND 为接地端。按照图示方式连接板子的调试串口和 USB 转 TTL 串口线,设置好波特率、数据位、停止位和奇偶校验等重要参数后,双方就可以正常发送 ASCII 码字符,从而进行异步串口通信。

    2.1 u-boot 引导

    u-boot 是一种普遍用于嵌入式系统中的引导程序,它在操作系统运行之前执行,用来初始化软硬件环境,并最终启动系统内核。

    0x03 通过调试串口进入系统

    3.0 研究对象

    本节我们将从一款无线监控摄像头入手,讲解如何通过调试串口获取系统的 Shell。

    使用 nmap 探测该摄像头的开放端口及服务,结果如下

    监听在 100 端口的 Mongoose 是一个嵌入式的 Web 服务器,gSOAP 是一个跨平台的,用于开发 Web Service 服务端和客户端的工具。RTSP(Real Time Streaming Protocol),实时流传输协议,是 TCP/IP 协议体系中的一个应用层协议,该协议定义了一对多应用程序如何有效地通过 IP 网络传送多媒体数据。

    之后可以通过 Fidderwireshark 等工具对服务进行抓包分析,然而这不是我们今天的重点。下面我们将从硬件的角度去分析。

    3.1 需要的工具

    • USB 转 TTL 串口线
    • 电烙铁
    • 螺丝刀
    • ...

    3.2 UART 藏哪了

    制造路由器、摄像头等设备的厂商通常会在设备上留下调试串口方便开发或售后过程中的调试,为了和设备进行通信,我们首先需要找到这些 "后门"。用工具将摄像头拆开,根据主板上芯片上的型号可以识别出芯片的用途。如图,我们找到了处理器和存储器芯片的位置,处理器是国科 IPC 芯片 GK7102,存储器芯片是 25 系列 flash 芯片 IC25LP128 。主板上空闲的接口有三个(右图),左下、右下、右下偏上,经过测试,左下那个是 4 针 debug 串口(波特率 115200),串口的第一个针脚为 Tx,第三个针脚为 Rx,分别与 USB-转-TTLRxTx 连接(USB 转 TTL 串口线和主板由同一个 Hub 供电,VCC 相差不大,没有连接 GND)。

    至于如何找到设备上的调试串口,可参考 reverse-engineering-serial-ports,此处不再赘述。

    minicom 是一款 Linux 平台上的串口工具,在控制台键入以下命令和串口进行通信。

    在这步操作的时候很容易遇到权限的问题,介绍一个很粗暴的方法。

    3.3 嵌入式系统启动流程

    笔记本正确连接主板串口,供电后,在终端可以看到以下系统启动过程中的调试信息。

    Flash 芯片的分区信息如下

    开机后系统启动了以下服务,可能是摄像头服务的主进程。

    系统启动完成后,提供了Shell 的登陆界面。

    通过观察启动流程,我们已经获得了很多有用的信息,对 u-boot 如何引导系统的启动也有了一个大致的认识。

    最后,我们尝试使用弱密码获取系统的 Shell,遗憾的是,经过多次尝试,均已失败告终。

    3.4 登陆绕过

    如果你使用过 Linux 系统,或多或少的经历过忘记系统密码导致无法进入系统的尴尬境地。我们的解决方案也堪称简单粗暴,直接进入 grub 引导修改密码。所以,如果设备触手可及,几乎不存在进不入系统的问题。

    在摄像头这种运行着嵌入式 Linux 操作系统的设备上,也有一个类似 grub 的存在,它就是 u-boot

    重启设备,根据提示键入组合键进入到 u-boot 命令行界面。

    u-boot 命令行内置了很多常用命令供我们使用,键入 h 查看帮助。

    通过 printenv 打印出 u-boot 传递给内核的参数信息。

    从部分参数的内容可以看到 u-boot 引导程序是如何移交控制权给内核的。

    • 首先为内核设置启动参数

    console=${consoledev},${baudrate} noinitrd mem=${mem} rw ${rootfstype} init=linuxrc

    • 将内核从 Flash 加载到内存中
    • 跳转到内存中内核的起始地址并执行

    我们来重点看下启动参数的 init 字段。

    init 字段设置内核执行的初始化进程名,比如上面的 linuxrc,它是位于文件系统根目录下的一段程序代码,负责后续的系统初始化工作。

    是否可以直接修改 init=/bin/sh 从而实现在系统未初始化完成的时候访问根文件系统呢?我们不妨试一下,在 u-boot 命令行中修改参数 sfbootinit 字段的值为 /bin/sh并保存,修改后效果如下。(修改前做好参数的备份)

    重启设备,正如我们所猜想的,修改内核执行的初始进程,我们成功获得了一个 Shell

    由于没有经过 linuxrc 的初始化过程,这样获得的 Shell 功能是很受限的。在该 shell 下编辑 /etc/shadow 文件,擦除或者破解 root 用户的密码,重启到 u-boot 命令行界面中修改回原来的启动参数并保存,再次重启到 Shell 登陆界面,即可获得一个具有完整功能的 Shell。

    3.5 打包上传固件

    经过上面的步骤,我们已经可以登录到一个功能完整的 Shell,使用 tartftp 命令打包上传根文件系统到 tftp 服务器即可。

    3.6 其他技巧

    u-boot 中提供了相关命令操作 Flash 芯片,所以也可以按照如下方式提取固件。(这种 cat 内存的方式只是一种思路,速度是内伤)

    0x04 暴力读写固件存储芯片解锁新功能

    本小节我们以另一款基于 gSOAP 协议的摄像头为例(固件存储芯片型号 MX25LP128),介绍如何用编程器读写 Flash 芯片,从而打开该摄像头的 telnet 服务。

    4.0 需要准备的工具

    • 25 系列芯片编程器
    • 电洛铁
    • ...

    4.1 读取固件

    MX25L128 这款 25 系列 Flash 芯片可以直接在线读取,用 夹子 夹住 Flash 芯片,连接编程器即可读取其中的固件。

    点击 智能识别SmartID,芯片型号识别成功后点击 读取 Read ,最后保存成文件即可。如下图,读取过程非常顺利。

    .2 固件解压

    binwalk 是 devttys0 大神开发的一款固件分析工具,强烈推荐使用 Github 上的教程安装,直接 apt-get 安装会缺少很多依赖。

    使用 binwalk 查看固件结构

    内核编译(make)之后会生成两个文件,一个 Image,一个 zImage,其中 Image 为内核映像文件,而 zImage为内核的一种映像压缩文件。

    那么 uImage 又是什么的?它是 uboot 专用的映像文件,它是在 zImage 之前加上一个长度为 64 字节的头部,说明这个内核的版本、加载位置、生成时间、大小等信息;其 0x40 之后与 zImage 没有区别。

    固件使用的是 squashfs 文件系统,它是一套供 Linux 核心使用的 GPL 开源只读压缩文件系统。所以设备正常运行的时候是不能对固件进行修改的,在前面那部分,我们从串口进去通过修改内核的初始进程的方式进入系统,是由于系统尚未初始化完成,从而获得了对文件系统的读写权限。

    在固件的后一部分,包含一个可以写入的区域。一个 JFFS2 文件系统,它是在闪存上使用非常广泛的读/写文件系统,设备运行过程中修改过的配置信息和其他数据将被写入这个文件系统中。

    squashfs 文件系统开始于 0x3100000, 大小为 6963644 字节, 用 dd 命令提取该文件系统,用 unsquashfs 命令解压。

    4.3 解锁功能

    熟悉文件系统结构和已有的命令

    很明显,该固件的 Shell 是基于 busybox 提供的。从 file 指令的结果可以判断该摄像头是 32位 ARM 指令架构。

    这个 busybox 是静态链接的,不依赖其他的库文件。可以直接利用 qemu-arm 模拟运行。

    当然,我们也可以搭建一个 qemu 虚拟机。

    在这个网站下载 qemu 虚拟机镜像文件,然后按照如下方式启动虚拟机。

    现在我们已经可以确定目标文件系统是存在 telnetd 命令的。在根目录下的 boot.sh 文件末尾添加以下内容,使设备在开启时自动启动 telnet 服务。

    4.4 重新封印

    现在,对文件系统的简单修改已经完成了,我们该如何重新打包固件,以便重新刷回到设备呢?

    还是从固件结构入手,如下

    我们自定义的只是中间的文件系统部分。即 0x3100000 - 0xB00000 这一段。同时,这一段的长度并不等于 squashfs 文件系统的大小 6963644 字节,squashfs 文件系统末至下一段开始之前有一段 0xff的填充部分。

    从 uImage 头信息可以看到,image size2217456, 而 squashfs 文件系统的起始位置为 3670016,没有对 squashfs 文件系统做 CRC 检验。

    根据以上结论判断,我们只需要在不改变原始固件结构的前提下,将修改后的文件系统重新打包成固件。

    利用 cat 将各段连接起来

    4.5 刷回

    Cheers,重新打包完成。利用编程器将修改后的固件离线刷入固件存储芯片即可。(在线刷各种坑,建议离线写入)

    4.6 成果

    可以看到,我们成功开启了该摄像头的 telnet 服务。

    0x05 总结

    对智能设备的软硬件有足够的了解是深入挖掘设备漏洞的基础。本文是在对摄像头等物联网设备研究过程中的一些经验总结,希望对大家有所帮助。

     

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/649/

    作者:Nanako | Categories:安全科普技术分享 | Tags:
  • 以太坊网络架构解析

    2018-08-28

    作者:0x7F@知道创宇404区块链安全研究团队
    时间:2018年7月12日

    0x00 前言

    区块链的火热程度一直以直线上升,其中以区块链 2.0 —— 以太坊为代表,不断的为传统行业带来革新,同时也推动区块链技术发展。

    区块链是一种分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式,这是一个典型的去中心化应用,建立在 p2p 网络之上;本文以学习和分析以太坊运作原理为目的,将以太坊网络架构作为一个切入点,逐步深入分析,最终对以太坊网络架构有个大致的了解。

    通过学习以太坊网络架构,可以更容易的对网络部分的源码进行审计,便于后续的协议分析,来发现未知的安全隐患;除此之外,目前基于 p2p 网络的成熟的应用非常少,借助分析以太坊网络架构的机会,可以学习一套成熟的 p2p 网络运行架构。

    本文侧重于数据链路的建立和交互,不涉及网络模块中的节点发现、区块同步、广播等功能模块。

    0x01 目录

    1. Geth 启动
    2. 网络架构
    3. 共享密钥
    4. RLPXFrameRW 帧
    5. RLP 编码
    6. LES 协议
    7. 总结

    其中第 3、4、5 三个小节是第 2 节「网络架构」的子内容,作为详细的补充。

    0x02 Geth 启动

    在介绍以太坊网络架构之前,首先简单分析下 Geth 的整体启动流程,便于后续的理解和分析。

    以太坊源码目录

    初始化工作

    Geth 的 main() 函数非常的简洁,通过 app.Run() 来启动程序:

    其简洁是得力于 Geth 使用了 gopkg.in/urfave/cli.v1 扩展包,该扩展包用于管理程序的启动,以及命令行解析,其中 app 是该扩展包的一个实例。

    在 Go 语言中,在有 init() 函数的情况下,会默认先调用 init() 函数,然后再调用 main() 函数;Geth 几乎在 ./cmd/geth/main.go#init() 中完成了所有的初始化操作:设置程序的子命令集,设置程序入口函数等,下面看下 init() 函数片段:

    在以上代码中,预设了 app 实例的值,其中 app.Action = geth 作为 app.Run() 调用的默认函数,而 app.Commands 保存了子命令实例,通过匹配命令行参数可以调用不同的函数(而不调用 app.Action),使用 Geth 不同的功能,如:开启带控制台的 Geth、使用 Geth 创造创世块等。

    节点启动流程

    无论是通过 geth() 函数还是其他的命令行参数启动节点,节点的启动流程大致都是相同的,这里以 geth() 为例:

    其中 makeFullNode() 函数将返回一个节点实例,然后通过 startNode() 启动。在 Geth 中,每一个功能模块都被视为一个服务,每一个服务的正常运行驱动着 Geth 的各项功能;makeFullNode() 通过解析命令行参数,注册指定的服务。以下是 makeFullNode() 代码片段:

    然后通过 startNode() 启动各项服务并运行节点。以下是 Geth 启动流程图:

    每个服务正常运行,相互协作,构成了 Geth:

    0x03 网络架构

    通过 main() 函数的调用,最终启动了 p2p 网络,这一小节对网络架构做详细的分析。

    三层架构
    以太坊是去中心化的数字货币系统,天然适用 p2p 通信架构,并且在其上还支持了多种协议。在以太坊中,p2p 作为通信链路,用于负载上层协议的传输,可以将其分为三层结构:

    1. 最上层是以太坊中各个协议的具体实现,如 eth 协议、les 协议。
    2. 第二层是以太坊中的 p2p 通信链路层,主要负责启动监听、处理新加入连接或维护连接,为上层协议提供了信道。
    3. 最下面的一层,是由 Go 语言所提供的网络 IO 层,也就是对 TCP/IP 中的网络层及以下的封装。

    p2p 通信链路层
    从最下层开始逐步分析,第三层是由 Go 语言所封装的网络 IO 层,这里就跳过了,直接分析 p2p 通信链路层。p2p 通信链路层主要做了三项工作:

    1. 由上层协议的数据交付给 p2p 层后,首先通过 RLP 编码。
    2. RLP 编码后的数据将由共享密钥进行加密,保证通信过程中数据的安全。
    3. 最后,将数据流转换为 RLPXFrameRW 帧,便于数据的加密传输和解析。
      (以上三点由下文做分析)

    p2p 源码分析
    p2p 同样作为 Geth 中的一项服务,通过「0x03 Geth 启动」中 startNode() 启动,p2p 通过其 Start() 函数启动。以下是 Start() 函数代码片段:

    上述代码中,设置了 p2p 服务的基础参数,并根据用户参数开启节点发现(节点发现不在本文的讨论范围内),随后开启 p2p 服务监听,最后开启单独的协程用于处理报文。以下分为服务监听和报文处理两个模块来分析。

    服务监听

    通过 startListening() 的调用进入到服务监听的流程中,随后在该函数中调用 listenLoop 用一个无限循环处理接受连接,随后通过 SetupConn() 函数为正常的连接建立 p2p 通信链路。在 SetupConn() 中调用 setupConn() 来做具体工作,以下是 setupConn() 的代码片段:

    setupConn() 函数中主要由 doEncHandshake() 函数与客户端交换密钥,并生成临时共享密钥,用于本次通信加密,并创建一个帧处理器 RLPXFrameRW;再调用 doProtoHandshake() 函数为本次通信协商遵循的规则和事务,包含版本号、名称、容量、端口号等信息。在成功建立通信链路,完成协议握手后,处理流程转移到报文处理模块。

    下面是服务监听函数调用流程:

    报文处理

    p2p.Start() 通过调用 run() 函数处理报文,run() 函数用无限循环等待事务,比如上文中,新连接完成握手包后,将由该函数来负责。run() 函数中支持多个命令的处理,包含的命令有服务退出清理、发送握手包、添加新节点、删除节点等。以下是 run() 函数结构:

    为了理清整个网络架构,本文直接讨论 addpeer 分支:当一个新节点添加服务器节点时,将进入到该分支下,根据之前的握手信息,为上层协议生成实例,然后调用 runPeer(),最终通过 p.run() 进入报文的处理流程中。

    继续分析 p.run() 函数,其开启了读取数据和 ping 两个协程,用于处理接收报文和维持连接,随后通过调用 startProtocols() 函数,调用指定协议的 Run() 函数,进入具体协议的处理流程。

    下面是报文处理函数调用流程

    p2p 通信链路交互流程

    这里整体看下 p2p 通信链路的处理流程,以及对数据包的封装。

    0x04 共享密钥

    在 p2p 通信链路的建立过程中,第一步就是协商共享密钥,该小节说明下密钥的生成过程。

    迪菲-赫尔曼密钥交换
    p2p 网络中使用到的是「迪菲-赫尔曼密钥交换」技术[1]。迪菲-赫尔曼密钥交换(英语:Diffie–Hellman key exchange,缩写为D-H) 是一种安全协议。它可以让双方在完全没有对方任何预先信息的条件下通过不安全信道创建起一个密钥。

    简单来说,链接的两方生成随机的私钥,通过随机的私钥得到公钥。然后双方交换各自的公钥,这样双方都可以通过自己随机的私钥和对方的公钥来生成一个同样的共享密钥(shared-secret)。后续的通讯使用这个共享密钥作为对称加密算法的密钥。其中对于 A、B公私钥对满足这样的数学等式:ECDH(A私钥, B公钥) == ECDH(B私钥, A公钥)

    共享密钥生成
    在 p2p 网络中由 doEncHandshake() 方法完成密钥的交换和共享密钥的生成工作。下面是该函数的代码片段:

    如果作为服务端监听连接,收到新连接后调用 receiverEncHandshake() 函数,若作为客户端向服务端发起请求,则调用 initiatorEncHandshake()函数;两个函数区别不大,都将交换密钥,并生成共享密钥,initiatorEncHandshake() 仅仅是作为发起数据的一端;最终执行完后,调用 newRLPXFrameRW() 创建帧处理器。

    从服务端的角度来看,将调用 receiverEncHandshake() 函数来创建共享密钥,以下是该函数的代码片段:

    共享密钥生成的过程:

    1. 在完成 TCP 连接后,客户端使用服务端的公钥(node_id)加密,发送自己的公钥和包含临时公钥的签名,还有一个随机值 nonce。
    2. 服务端收到数据,获得客户端的公钥,使用椭圆曲线算法从签名中获得客户端的临时公钥;服务端将自己的临时公钥和随机值 nonce 用客户端的公钥加密发送。
    3. 通过上述两步的密钥交换后,对于客户端目前有自己的临时公私钥对和服务端的临时公钥,使用椭圆曲线算法从自己的临时私钥和服务端的临时公钥计算得出共享密钥;同理,服务端按照相同的方式也可以计算出共享密钥。

    以下是共享密钥生成图示:

    得出共享密钥后,客户端和服务端就可以使用共享密钥做对称加密,完成对通信的加密。

    0x05 RLPXFrameRW 帧

    在共享密钥生成完毕后,初始化了 RLPXFrameRW 帧处理器;其 RLPXFrameRW 帧的目的是为了在单个连接上支持多路复用协议。其次,由于帧分组的消息为加密数据流产生了天然的分界点,更便于数据的解析,除此之外,还可以对发送的数据进行验证。

    RLPXFrameRW 帧包含了两个主要函数,WriteMsg() 用于发送数据,ReadMsg()用于读取数据;以下是 WriteMsg() 的代码片段:

    结合以太坊 RLPX 的文档[2]和上述代码,可以分析出 RLPXFrameRW 帧的结构。在一般情况下,发送一次数据将产生五个数据包:

    接收方按照同样的格式对数据包进行解析和验证。

    0x06 RLP 编码

    RLP编码 (递归长度前缀编码)提供了一种适用于任意二进制数据数组的编码,RLP 已经成为以太坊中对对象进行序列化的主要编码方式,便于对数据结构的解析。比起 json 数据格式,RLP 编码使用更少的字节。

    在以太坊的网络模块中,所有的上层协议的数据包要交互给 p2p 链路时,都要首先通过 RLP 编码;从 p2p 链路读取数据,也要先进行解码才能操作。

    以太坊中 RLP 的编码规则[3]。

    0x07 LES 协议层

    这里以 LES 协议为上层协议的代表,分析在以太坊网络架构中应用协议的工作原理。

    LES 服务由 Geth 初始化时启动,调用源码 les 下的 NewLesServer() 函数开启一个 LES 服务并初始化,并通过 NewProtocolManager() 实现以太坊子协议的接口函数。其中 les/handle.go 包含了 LES 服务交互的大部分逻辑。

    回顾上文 p2p 网络架构,最终 p2p 底层通过 p.Run() 启动协议,在 LES 协议中,也就是调用 LES 协议的 Run() 函数:

    可以看到重要的处理逻辑都被包含在 handle() 函数中,handle() 函数的主要功能包含 LES 协议握手和消息处理,下面是 handle() 函数片段:

    handle() 函数中首先进行协议握手,其实现函数是 ./les/peer.go#Handshake(),通过服务端和客户端交换握手包,互相获取信息,其中包含有:协议版本、网络号、区块头哈希、创世块哈希等值。随后用无线循环处理通信的数据,以下是报文处理的逻辑:

    处理一个请求的详细流程是:

    1. 使用 RLPXFrameRW 帧处理器,获取请求的数据。
    2. 使用共享密钥解密数据。
    3. 使用 RLP 编码将二进制数据序列化。
    4. 通过对 msg.Code 的判断,执行相应的功能。
    5. 对响应数据进行 RLP 编码,共享密钥加密,转换为 RLPXFrameRW,最后发送给请求方。

    下面是 LES 协议处理流程:

    0x08 总结

    通过本文的分析,对以太坊网络架构有了大致的了解,便于后续的分析和代码审计;在安全方面来讲,由协议所带的安全问题往往比本地的安全问题更为严重,应该对网络层面的安全问题给予更高的关注。

    从本文也可以看到,以太坊网络架构非常的完善,具有极高的鲁棒性,这也证明了以太坊是可以被市场所认可的区块链系统。除此之外,由于 p2p 网络方向的资料较少,以太坊的网络架构也可以作为学习 p2p 网络的资料。

     

    针对目前主流的以太坊应用,知道创宇提供专业权威的智能合约审计服务,规避因合约安全问题导致的财产损失,为各类以太坊应用安全保驾护航。

    知道创宇404智能合约安全审计团队: https://www.scanv.com/lca/index.html
    联系电话:(086) 136 8133 5016(沈经理,工作日:10:00-18:00)

    欢迎扫码咨询:

    References:


    [1] WIKI.DH: https://en.wikipedia.org/wiki/Diffie–Hellman_key_exchange
    [2] Github.rlpx: https://github.com/ethereum/devp2p/blob/master/rlpx.md
    [3] WIKI.RLP: https://github.com/ethereum/wiki/wiki/RLP
    [4] Github.ZtesoftCS: https://github.com/ZtesoftCS/go-ethereum-code-analysis
    [5] CSDN: https://blog.csdn.net/weixin_41814722/article/details/80680749
    [6] CSDN: https://blog.csdn.net/itcastcpp/article/details/80305636
    [7] ETHFANS: https://ethfans.org/bob/articles/864
    [8] BITSHUO: https://bitshuo.com/topic/5975fbb14a7a061b785db8d5
    [9] Github.go-ethereum: https://github.com/ethereum/go-ethereum

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/642/

    作者:Nanako | Categories:安全科普技术分享 | Tags:
  • 以太坊智能合约OPCODE逆向之理论基础篇

    2018-08-28

    作者:Hcamael@知道创宇404区块链安全研究团队

    在我们对etherscan等平台上合约进行安全审查时,常常会遇到没有公布Solidity源代码的合约,只能获取到合约的OPCODE,所以一个智能合约的反编译器对审计无源码的智能合约起到了非常重要的作用。

    目前在互联网上常见的反编译工具只有porosity[1],另外在Github上还找到另外的反编译工具ethdasm[2],经过测试发现这两个编译器都有许多bug,无法满足我的工作需求。因此我开始尝试研究并开发能满足我们自己需求的反编译工具,在我看来如果要写出一个优秀的反汇编工具,首先需要有较强的OPCODE逆向能力,本篇Paper将对以太坊智能合约OPCODE的数据结构进行一次深入分析。

    基础

    智能合约的OPCODE是在EVM(Ethereum Virtual Machine)中进行解释执行,OPCODE为1字节,从0x00 - 0xff代表了相对应的指令,但实际有用的指令并没有0xff个,还有一部分未被使用,以便将来的扩展

    具体指令可参考Github[3]上的OPCODE指令集,每个指令具体含义可以参考相关文档[4]

    IO

    在EVM中不存在寄存器,也没有网络IO相关的指令,只存在对栈(stack),内存(mem), 存储(storage)的读写操作

    • stack

    使用的push和pop对栈进行存取操作,push后面会带上存入栈数据的长度,最小为1字节,最大为32字节,所以OPCODE从0x60-0x7f分别代表的是push1-push32

    PUSH1会将OPCODE后面1字节的数据放入栈中,比如字节码是0x6060代表的指令就是PUSH1 0x60

    除了PUSH指令,其他指令获取参数都是从栈中获取,指令返回的结果也是直接存入栈中

    • mem

    内存的存取操作是MSTOREMLOAD

    MSTORE(arg0, arg1)从栈中获取两个参数,表示MEM[arg0:arg0+32] = arg1

    MLOAD(arg0)从栈中获取一个参数,表示PUSH32(MEM[arg0:arg0+32])

    因为PUSH指令,最大只能把32字节的数据存入栈中,所以对内存的操作每次只能操作32字节

    但是还有一个指令MSTORE8,只修改内存的1个字节

    MSTORE(arg0, arg1)从栈中获取两个参数,表示MEM[arg0] = arg1

    内存的作用一般是用来存储返回值,或者某些指令有处理大于32字节数据的需求

    比如: SHA3(arg0, arg1)从栈中获取两个参数,表示SHA3(MEM[arg0:arg0+arg1]),SHA3对内存中的数据进行计算sha3哈希值,参数只是用来指定内存的范围

    • storage

    上面的stack和mem都是在EVM执行OPCODE的时候初始化,但是storage是存在于区块链中,我们可以类比为计算机的存储磁盘。

    所以,就算不执行智能合约,我们也能获取智能合约storage中的数据:

    storage用来存储智能合约中所有的全局变量

    使用SLOADSSTORE进行操作

    SSTORE(arg0, arg1)从栈中获取两个参数,表示eth.getStorageAt(合约地址, arg0) = arg1

    SLOAD(arg0)从栈中获取一个参数,表示PUSH32(eth.getStorageAt(合约地址, arg0))

    变量

    智能合约的变量从作用域可以分为三种, 全局公有变量(public), 全局私有变量(private), 局部变量

    全局变量和局部变量的区别是,全局变量储存在storage中,而局部变量是被编译进OPCODE中,在运行时,被放在stack中,等待后续使用

    公有变量和私有变量的区别是,公有变量会被编译成一个constant函数,后面会分析函数之前的区别

    因为私有变量也是储存在storage中,而storage是存在于区块链当中,所以相当于私有变量也是公开的,所以不要想着用私有变量来储存啥不能公开的数据。

    全局变量的储存模型

    不同类型的变量在storage中储存的方式也是有区别的,下面对各种类型的变量的储存模型进行分析

    1. 定长变量

    第一种我们归类为定长变量,所谓的定长变量,也就是该变量在定义的时候,其长度就已经被限制住了

    比如定长整型(int/uint......), 地址(address), 定长浮点型(fixed/ufixed......), 定长字节数组(bytes1-32)

    这类的变量在storage中都是按顺序储存

    上面举的例子,除了address的长度是160bits,其他变量的长度都是256bits,而storage是256bits对齐的,所以都是一个变量占着一块storage,但是会存在连续两个变量的长度不足256bits的情况

    在opcode层面,获取a的值得操作是: SLOAD(0) & 0xffffffffffffffffffffffffffffffffffffffff

    获取b值得操作是: SLOAD(0) // 0x10000000000000000000000000000000000000000 & 0xff

    获取d值得操作是: SLOAD(1) // 0x10000000000000000000000000000000000000000 & 0xffff

    因为b的长度+a的长度不足256bits,变量a和b是连续的,所以他们在同一块storage中,然后在编译的过程中进行区分变量a和变量b,但是后续在加上变量c,长度就超过了256bits,因此把变量c放到下一块storage中,然后变量d跟在c之后

    从上面我们可以看出,storage的储存策略一个是256bits对齐,一个是顺序储存。(并没有考虑到充分利用每一字节的储存空间,我觉得可以考虑把d变量放到b变量之后)

    2. 映射变量

    映射变量就没办法想上面的定长变量按顺序储存了,因为这是一个键值对变量,EVM采用的机制是:

    SLOAD(sha3(key.rjust(64, "0")+slot.rjust(64, "0")))

    比如: a["0xd25ed029c093e56bc8911a07c46545000cbf37c6"]首先计算sha3哈希值:

    我们也可以使用以太坊客户端直接获取:

    还有slot需要注意一下:

    根据映射变量的储存模型,或许我们真的可以在智能合约中隐藏私密信息,比如,有一个secret,只有知道key的人才能知道secret的内容,我们可以b[key] = secret, 虽然数据仍然是储存在storage中,但是在不知道key的情况下却无法获取到secret

    不过,storage是存在于区块链之中,目前我猜测是通过智能合约可以映射到对应的storage,storage不可能会初始化256*256bits的内存空间,那样就太消耗硬盘空间了,所以可以通过解析区块链文件,获取到storage全部的数据。

    上面这些仅仅是个人猜想,会作为之后研究以太坊源码的一个研究方向。

    3. 变长变量

    变长变量也就是数组,长度不一定,其储存方式有点像上面两种的结合

    数组任然会占用对应slot的storage,储存数组的长度(b.length == SLOAD(1))

    比如我们想获取b[1]的值,会把输入的indexSLOAD(1)的值进行比较,防止数组越界访问

    然后计算slot的sha3哈希值:

    在变长变量中有两个特例: stringbytes

    字符串可以认为是字符数组,bytes是byte数组,当这两种变量的长度在0-31时,值储存在对应slot的storage上,最后一字节为长度*2|flag, 当flag = 1,表示长度>31,否则长度<=31

    下面进行举例说明

    当变量的长度大于31时,SLOAD(slot)储存length*2|flag,把值储存到sha3(slot)

    4. 结构体

    结构体没有单独特殊的储存模型,结构体相当于变量数组,下面进行举例说明:

    函数

    两种调用函数的方式

    下面是针对两种函数调用方式说明的测试代码,发布在测试网络上: https://ropsten.etherscan.io/address/0xc9fbe313dc1d6a1c542edca21d1104c338676ffd#code

    整个OPCODE都是在EVM中执行,所以第一个调用函数的方式就是使用EVM进行执行OPCODE:

    第二种方式就是通过发送交易:

    这两种调用方式的区别有两个:

    1. 使用call调用函数是在本地使用EVM执行合约的OPCODE,所以可以获得返回值
    2. 通过交易调用的函数,能修改区块链上的storage

    一个调用合约函数的交易(比如 https://ropsten.etherscan.io/tx/0xab1040ff9b04f8fc13b12057f9c090e0a9348b7d3e7b4bb09523819e575cf651)的信息中,是不存在返回值的信息,但是却可以修改storage的信息(一个交易是怎么修改对应的storage信息,是之后的一个研究方向)

    而通过call调用,是在本地使用EVM执行OPCODE,返回值是存在MEM中return,所以可以获取到返回值,虽然也可以修改storage的数据,不过只是修改你本地数据,不通过发起交易,其他节点将不会接受你的更改,所以是一个无效的修改,同时,本地调用函数也不需要消耗gas,所以上面举例中,在调用信息的字典里,不需要from字段,而交易却需要指定(设置from)从哪个账号消耗gas。

    调用函数

    EVM是怎么判断调用哪个函数的呢?下面使用OPCODE来进行说明

    每一个智能合约入口代码是有固定模式的,我们可以称为智能合约的主函数,上面测试合约的主函数如下:

    PS: Github[5]上面有一个EVM反汇编的IDA插件

    反编译出来的代码就是:

    PS:因为个人习惯问题,反编译最终输出没有选择对应的Solidity代码,而是使用Python。

    从上面的代码我们就能看出来,EVM是根据CALLDATA的前4字节来确定调用的函数的,这4个字节表示的是函数的sha3哈希值的前4字节:

    所以可以去网上找个哈希表映射[6],这样有概率可以通过hash值,得到函数名和参数信息,减小逆向的难度

    主函数中的函数

    上面给出的测试智能合约中只有两个函数,但是反编译出来的主函数中,却有4个函数调用,其中两个是公有函数,另两个是公有变量

    智能合约变量/函数类型只有两种,公有和私有,公有和私有的区别很简单,公有的是能别外部调用访问,私有的只能被本身调用访问

    对于变量,不管是公有还是私有都能通过getStorageAt访问,但是这是属于以太坊层面的,在智能合约层面,把公有变量给编译成了一个公有函数,在这公有函数中返回SLOAD(slot),而私有函数只能在其他函数中特定的地方调用SLOAD(slot)来访问

    在上面测试的智能合约中, test1()函数等同于owner(),我们可以来看看各自的OPCODE:

    owner()函数进行对比:

    所以我们可以得出结论:

    公有函数和私有函数的区别也很简单,公有函数会被编译进主函数中,能通过CALLDATA进行调用,而私有函数则只能在其他公有函数中进行调用,无法直接通过设置CALLDATA来调用私有函数

    回退函数和payable

    在智能合约中,函数都能设置一个payable,还有一个特殊的回退函数,下面用实例来介绍回退函数

    比如之前的测试合约加上了回退函数:

    则主函数的反编译代码就变成了:

    CALLDATA和该合约中的函数匹配失败时,将会从抛异常,表示执行失败退出,变成调用回退函数

    每一个函数,包括回退函数都可以加一个关键字: payable,表示可以给该函数转帐,从OPCODE层面讲,没有payable关键字的函数比有payable的函数多了一段代码:

    反编译成python,就是:

    REVERT是异常退出指令,当交易的金额大于0时,则异常退出,交易失败

    函数参数

    函数获取数据的方式只有两种,一个是从storage中获取数据,另一个就是接受用户传参,当函数hash表匹配成功时,我们可以知道该函数的参数个数,和各个参数的类型,但是当hash表匹配失败时,我们仍然可以获取该函数参数的个数,因为获取参数和主函数、payable检查一样,在OPCODE层面也有固定模型:

    比如上面的测试合约,调动test2函数的固定模型就是: main -> payable check -> get args -> 执行函数代码

    获取参数的OPCODE如下

    函数test2的参数p = CALLDATA[4:4+0x20]

    如果有第二个参数,则是arg2 = CALLDATA[4+0x20:4+0x40],以此类推

    所以智能合约中,调用函数的规则就是data = sha3(func_name)[:4] + *args

    但是,上面的规则仅限于定长类型的参数,如果参数是string这种不定长的变量类型时,固定模型仍然不变,但是在从calldata获取数据的方法,变得不同了,定长的变量是通过调用CALLDATALOAD,把值存入栈中,而string类型的变量,因为长度不定,会超过256bits的原因,使用的是calldatacopy把参数存入MEM

    可以看看function test3(string a) public {}函数获取参数的代码:

    传入的变长参数是一个结构体:

    offset+4表示的是当前参数的length的偏移,length为data的长度,data就是用户输入的字符串数据

    当有多个变长参数时: function test3(string a, string b) public {}

    calldata的格式如下: sha3(func)[:4] + a.offset + b.offset + a.length + a.data + b.length + b.data

    翻译成py代码如下:

    因为参数有固定的模型,因此就算没有从hash表中匹配到函数名,也可以判断出函数参数的个数,但是要想知道变量类型,只能区分出定长、变长变量,具体是uint还是address,则需要从函数代码,变量的使用中进行判断

    变量类型的分辨

    在智能合约的OPCDOE中,变量也是有特征的

    比如一个address变量总会 & 0xffffffffffffffffffffffffffffffffffffffff:

    上一篇说的mapping和array的储存模型,可以根据SHA3的计算方式知道是映射变量还是数组变量

    再比如,uint变量因为等同于uint256,所以使用SLOAD获取以后不会再进行AND计算,但是uint8却会计算& 0xff

    所以我们可以SLOAD指令的参数和后面紧跟的计算,来判断出变量类型

    智能合约代码结构

    部署合约

    在区块链上,要同步/发布任何信息,都是通过发送交易来进行的,用之前的测试合约来举例,合约地址为: 0xc9fbe313dc1d6a1c542edca21d1104c338676ffd, 创建合约的交易地址为: 0x6cf9d5fe298c7e1b84f4805adddba43e7ffc8d8ffe658b4c3708f42ed94d90ed

    查看下该交易的相关信息:

    我们可以看出来,想一个空目标发送OPCODE的交易就是创建合约的交易,但是在交易信息中,却不包含合约地址,那么合约地址是怎么得到的呢?

    智能合约的地址由创建合约的账号和nonce决定,nonce用来记录用户发送的交易个数,在每个交易中都有该字段,现在根据上面的信息来计算下合约地址:

    创建合约代码

    一个智能合约的OPCODE分为两种,一个是编译器编译好后的创建合约代码,还是合约部署好以后runtime代码,之前我们看的,研究的都是runtime代码,现在来看看创建合约代码,创建合约代码可以在创建合约交易的input数据总获取,上面已经把数据粘贴出来了,反汇编出指令如下:

    代码逻辑很简单,就是执行了合约的构造函数,并且返回了合约的runtime代码,该合约的构造函数为:

    因为没有payable关键字,所以开头是一个check代码assert msg.value == 0

    然后就是对owner变量的赋值,当执行完构造函数后,就是把runtime代码复制到内存中:

    最后在把runtime代码返回: return mem[0:0x24f]

    在完全了解合约是如何部署的之后,也许可以写一个OPCODE混淆的CTF逆向题

    总结

    通过了解EVM的数据结构模型,不仅可以加快对OPCODE的逆向速度,对于编写反编译脚本也有非常大的帮助,可以对反编译出来的代码进行优化,使得更加接近源码。

    在对智能合约的OPCODE有了一定的了解后,后续准备先写一个EVM的调试器,虽然Remix已经有了一个非常优秀的调试器了,但是却需要有Solidity源代码,这无法满足我测试无源码的OPCODE的工作需求。所以请期待下篇《以太坊智能合约OPCODE逆向之调试器篇》


    针对目前主流的以太坊应用,知道创宇提供专业权威的智能合约审计服务,规避因合约安全问题导致的财产损失,为各类以太坊应用安全保驾护航。

    知道创宇404智能合约安全审计团队: https://www.scanv.com/lca/ndex.html
    联系电话:(086) 136 8133 5016(沈经理,工作日:10:00-18:00)

    欢迎扫码咨询:

    引用

    1. https://github.com/comaeio/porosity
    2. https://github.com/meyer9/ethdasm
    3. https://github.com/trailofbits/evm-opcodes
    4. http://solidity.readthedocs.io/en/v0.4.21/assembly.html
    5. https://github.com/trailofbits/ida-evm
    6. https://github.com/trailofbits/ida-evm/blob/master/known_hashes.py

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/640/

    作者:Nanako | Categories:安全研究技术分享 | Tags: