第一章 概述
比特币(Bitcoin)的概念最初由中本聪在2009年提出,是一种P2P形式的数字货币,可以用来兑换成大多数国家的货币。
区块链诞生自中本聪的比特币,自2009年以来,出现了各种各样的类比特币的数字货币,都基于公有区块链。
由于以比特币为代表的数字货币具备兑现的能力,与数字货币相关的恶意事件(如勒索病毒、恶意浏览器脚本挖矿以及IoT僵尸网络挖矿等)逐渐增多。因此,对数字货币的节点进行识别,有助于提供威胁情报,减缓和预防相关恶意事件的影响。
本文将以比特币为例,就节点识别做相关的介绍。第一章为概述,介绍比特币的概念以及节点识别的思路。第二章以新节点加入比特币网络为例,介绍比特币网络的通信过程。第三章介绍比特币的通信协议。第四章介绍了本文所述方法的节点识别实现过程。第五章对节点识别的结果做了分析。第六章为小结。
1 什么是比特币?
比特币是一系列概念和技术,构成了数字货币生态系统的基础。它是一个分布式的点对点系统。因此,没有“中央”服务器或控制点。比特币是通过称为“挖矿”的过程创建的,这涉及到在处理比特币交易时竞争找到数学问题的解决方案。比特币网络中的任何参与者(即使用设备的任何人)都可以运行完整的比特币协议栈,利用其计算机的处理能力来验证和记录交易,这样的节点称之为“矿工”,而验证和记录交易的过程,称之为“挖矿”。平均每10分钟,比特币矿工能够验证过去10分钟的交易,作为奖励,将获得全新的比特币。从本质上讲,比特币采矿分散了中央银行的货币发行和结算功能,取代了对任何中央银行的需求。
2 比特币节点识别的方式
对于特有协议的识别,一般的思路为全网扫描协议相关端口,模拟协议的通信方式,如果目的节点响应的内容符合协议的格式,则认为识别出开放该协议的节点。在比特币协议中,比特币常用的端口为8333,因此,可以全网扫描8333端口,从而确定大部分的比特币节点。但这种方式存在一些问题:1)端口扫描比较耗费时间。2)存在一部分比特币节点,没有使用8333端口,端口扫描难以对这部分节点进行识别。
更进一步,比特币网络为P2P网络,比特币节点均具备发现其他节点的能力(比特币协议中的“getaddr”消息,具有返回该节点周围活跃节点的功能),因此,从一个已知的比特币节点出发,通过模拟比特币协议的节点发现过程,即可获取其相邻节点的地址,之后,对得到的节点,依次通过模拟节点发现过程递归遍历,最终即可遍历整个比特币网络。该方法的优势在于,仅对比特币网络中的节点进行遍历,扫描速度快。因此,本文将对这种方式进行介绍。
第二章 比特币网络的通信过程
比特币网络基于TCP连接,默认端口8333,在套接字之上直接将数据打包成二进制流进行传输。
若一个新的比特币节点,希望加入到比特币网络,总体上流程分为两步,1)获取到当前比特币网络中网络状况正常的节点列表(获取种子节点列表)。2)运行比特币的协议栈,与比特币网络中的其他节点通信。
本章将以获取一个节点周围的缓存节点为例,对比特币节点之间的通信过程做相应的说明。
1 获取种子节点列表
如图2.1所示,比特币的Wiki[4]上提供了8种发现种子节点IP列表的方式,本文采用了第三种方式(比特币网络中存在一部分节点使用IPv6的地址,因此,通过该方式获取种子节点列表时,会获取到一部分使用IPv6地址的节点,本文仅对使用IPv4地址的节点做相关说明),即通过DNS请求获取IP地址。
图2.1 获取种子节点IP列表的方式
截至2017年12月,比特币源码的chainparams.cpp[5]文件中,一共包含了6个DNS域名(见表2.1),可以用于新节点加入比特币网络时获取种子节点列表。
表2.1 比特币获取种子节点IP的DNS域名
seed.bitcoin.sipa.be |
dnsseed.bluematt.me |
dnsseed.bitcoin.dashjr.org |
seed.bitcoinstats.com |
seed.bitcoin.jonasschnelli.ch |
seed.btc.petertodd.org |
2 比特币节点的通信流程
在完成2.2节的步骤后,正常情况下,可以获取到140个左右比特币节点的地址(IPv4),之后即可通过比特币协议进行通信,比特币节点的通信流程如图2.2所示。
图2.2 比特币节点的通信过程
节点A做为通信的发起者,首先与节点B完成握手的过程。需要着重说明的是,节点之间进行握手时,若一方发送的握手包有问题,则另一方节点不做任何回应。握手无误后,节点B将向节点A发送大量的数据包,如获取节点A的头、区块信息等数据包。
若节点A希望获取到节点B的信息(以获取节点B周围的缓存节点为例),则发送相应的消息即可(图2.2中,节点A发送了一个getaddr消息以获取节点B周围的缓存节点),节点B收到后,将继续向节点A发送消息,以获取自己希望获取到的信息,一段时候后,回应节点A发送消息的结果(图2.2中即为最后的addr消息)。
2.2.1 握手过程比特币节点之间的握手过程,建立在已经完成TCP连接的基础上。握手的发起者会向目标节点发起TCP连接,默认为8333端口(部分节点不使用8333端口),连接建立成功后,双方需要根据比特币协议的规定,完成握手的过程,主要是确认双方版本、IP地址、端口等信息。
如图2.3所示,节点A为握手的发起者,希望与节点B进行握手,首先向B节点发送一个version消息(含有自己的协议版本等),节点B收到节点A的version消息后,会对数据包进行验证,若验证无误,则会向A回应自己的version消息,否则,节点B将忽略掉节点A的消息。节点B在回应了自己的version消息之后,会再回应一个version ack的消息。节点A在收到B节点的version ack消息后,向节点B回应一个version ack,握手过程即正常结束。
图2.3比特币节点握手过程
握手期间,version消息包含的内容如图2.4所示。有协议的版本号、提供的服务、时间戳、双方的IP地址、客户端描述以及自身当前区块的高度。
图2.4 握手包含有的数据(部分)
2.2.2 获取节点周围缓存节点
完成正常的握手后,节点识别的关键,即为获取对方节点周围活跃的节点地址。比特币协议中规定了用于获取一个节点周围活跃的节点getaddr消息,在完成握手后,通过向对方节点发送getaddr消息,即可获取其周围活跃节点,通信过程如图2.5所示。
图2.5getaddr过程时序
第三章 通信协议
比特币的协议是在TCP层之上,直接封装了自己的协议,因此,通信时,直接通过套接字,将协议转为二进制数据流进行传输。比特币网络中,连接均使用TCP的方式。本章将对比特币的通信协议做简单的描述,第四章识别的实现,是在本章的基础上完成的,对比特币节点的识别,需要实际上是对通信数据包的过滤和解析,第四章的实现中,对数据包进行处理,实际上是根据协议中规定的数据包的长度以及特定的字段,进行过滤和打包。
1 数据包结构
比特币协议中,数据包的结构如表3.1所示(payload长度可变,用“?”表示),由magic、command、length、checksum以及payload组成。
表3.1 比特币节点数据包格式
Field Size | Description | Data type | Comments |
4 | magic | uint32_t | Magic value indicating message origin network, and used to seek to next message when stream state is unknown |
12 | command | char[12] | ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) |
4 | length | uint32_t | Length of payload in number of bytes |
4 | checksum | uint32_t | First 4 bytes of sha256(sha256(payload)) |
?(variable) | payload | uchar[] | The actual data |
比特币的网络有主网络,也有用于测试的网络,通过magic即可区分各个网络,如表3.2所示
表3.2 比特币主网络与测试网络的magic
Network | Magic value | Sent over wire as |
main | 0xD9B4BEF9 | F9 BE B4 D9 |
testnet | 0xDAB5BFFA | FA BF B5 DA |
testnet3 | 0x0709110B | 0B 11 09 07 |
namecoin | 0xFEB4BEF9 | F9 BE B4 FE |
如表3.3所示(payload值可变,用“?”表示),为一个比特币的主网络中的数据包结构。
表3.3 比特币主网络中数据包的示例
f9beb4d9 | -magic |
76657273696f6e0000000000 | -command “version” |
55000000 | -- lenth. Payload lenth is 85 |
0d0fe5c6 | -- checksum |
?(variable) | Payload |
2 version包
如图3.1所示,为比特币节点在通信的握手过互发的一个version消息数据包。
图3.1 version包
version数据包由数据包的头和payload两部分组成。数据包的头包含magic、command、payloadlength和checksum,如表3.4所示。
表3.4 version包的头
F9 BE B4 D9 | Main network magic bytes |
76 65 72 73 69 6F 6E 00 00 00 00 00 | "version" command |
64 00 00 00 | Payload is 100 bytes long |
3B 64 8D 5A | payload checksum |
version数据包的payload如表3.5所示,主要包含了协议的版本号、节点提供的服务、时间戳,接收者的地址、发送者的地址、随机生成的id(用于区分不同的连接)、客户端软件的描述以及该节点当前最新的块。
表3.5 version包的payload
62 EA 00 00 | - 60002 (protocol version 60002) |
01 00 00 00 00 00 00 00 | - 1 (NODE_NETWORK services) |
01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF 00 00 00 00 00 00 | - Tue Dec 18 10:12:33 PST 2012 |
01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF 00 00 00 00 00 00 | - Recipient address info - see Network Address |
01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF 00 00 00 00 00 00 | - Sender address info - see Network Address |
3B 2E B3 5D 8C E6 17 65 | - Node ID |
0F 2F 53 61 74 6F 73 68 69 3A 30 2E 37 2E 32 2F | - "/Satoshi:0.7.2/" sub-version string (string is 15 bytes long) |
C0 3E 03 00 | - Last block sending node has is block #212672 |
version包payload中不同字段的类型和长度非常重要,对于比特币节点的识别,均是根据数据包字段的类型和长度进行过滤,类型和长度如表3.6所示(user_agent为客户端的描述,长度可变,用“?”表示)。
表3.6 version包不同字段的类型和长度
Field Size | Description | Data type | Comments |
4 | version | int32_t | Identifies protocol version being used by the node |
8 | services | uint64_t | bitfield of features to be enabled for this connection |
8 | timestamp | int64_t | standard UNIX timestamp in seconds |
26 | addr_recv | net_addr | The network address of the node receiving this message |
Fields below require version ≥ 106 | |||
26 | addr_from | net_addr | The network address of the node emitting this message |
8 | nonce | uint64_t | Node random nonce, randomly generated every time a version packet is sent. This nonce is used to detect connections to self. |
? | user_agent | var_str | User Agent (0x00 if string is 0 bytes long) |
4 | start_height | int32_t | The last block received by the emitting node |
Fields below require version ≥ 70001 | |||
1 | relay | bool | Whether the remote peer should announce relayed transactions or not |
verison包中包含的节点提供的服务,如表3.7所示。
表3.7 节点提供的服务
Value | Name | Description |
1 | NODE_NETWORK | This node can be asked for full blocks instead of just headers. |
2 | NODE_GETUTXO | See BIP 0064[6] |
4 | NODE_BLOOM | See BIP 0111[7] |
8 | NODE_WITNESS | See BIP 0144[8] |
1024 | NODE_NETWORK_LIMITED | See BIP 0159[9] |
3 version ack包
version ack的包相对简单,仅有数据包的头,没有payload,如图3.3所示。
图3.3 version ack包
4 getaddr包
getaddr消息向节点发送请求,询问一个节点周围缓存的活跃节点情况,以帮助查找网络中的潜在节点。getaddr消息的响应是响应节点发送一个或多个addr消息(上限为1000)。典型的假设是,如果节点在过去三小时内发送消息,则该节点可能处于活动状态。此消息与version ack包一样,没有payload。
5 addr包
addr包用于节点之间传输地址信息。非广播节点,通常在三小时后被丢弃。
payload结构如表3.8所示(addr_list长度可变,用“?”表示),count表示该数据包中含有多少条地址,数值在1-1000之间。addr_list为一个地址列表,单条地址的长度为30。
表3.8 addr包payload结构
Field Size | Description | Data type | Comments |
1+ | count | var_int | Number of address entries (max: 1000) |
30*? | addr_list | (uint32_t + net_addr)[] | Address of other nodes on the network. version < 209 will only read the first one. The uint32_t is a timestamp (see note below). |
第四章 实现方式
1 总体思路
文本所描述的对比特币节点识别的实现,使用了python 3.6.6。整体思路如下:
1)从DNS种子获取IP种子列表。
2)与IP种子列表中的所有节点建立TCP连接,完成握手。
3)获取种子节点周围的缓存节点,存储到缓存节点列表中。
4)与缓存节点列表中的每一个节点通信,获得新的缓存节点,添加到当前缓存节点列表中并去重。
5)重复第四步,直到处理完所有的缓存节点列表。
2 脚本的总体流程
脚本的流程如图4.1所示,脚本启动后,首先通过DNS种子获取到种子IP的列表,之后,启动10个线程,通过getaddr扫描种子IP的列表,将扫描出的结果存入一个缓存字典中。
脚本启动的同时,另一个线程,每隔1s,对脚本当前的扫描结果进行判断,若新扫描出1000个地址,则开启40个线程,对这1000个地址进行二次的验证确认,验证成功,记录入输出的比特币节点字典中。开放100个线程,通过getaddr,扫描这1000个地址,以获取更多的地址。
图4.1 脚本的总体流程
3 getaddr扫描
getaddr用于获取一个节点周围活跃的节点,脚本处理getaddr的流程如下:
1) 完成握手。若握手出现超时或其他情况,则直接跳过这个节点。
2) 发送getaddr消息,等待0.2秒,再次发送getaddr消息,以防丢包。
3) 对收到的数据包进行初次的过滤,过滤出addr的数据包。若40个包依然过滤不出addr包,或10秒内解析不出addr包,则跳过这个节点。
4) 过滤出addr包后,对addr包进行解析,由于一个addr包中包含的地址数量不同,所以需要根据payload中含有的地址数量,动态解析数据包的内容。4 二次确认
二次确认的作用,是对getaddr扫描出来的地址,做第二次确认。通常情况下,getaddr扫描出来的地址,大部分是超时或者连接拒绝的,比特币P2P网络的特点本身如此,网络不是十分稳定。二次确认的流程如下:
1) 建立TCP连接,若连接出现错误,则记录相应的原因。
2) TCP连接建立成功后,进行握手,若超过5秒,无法完成握手,则记录其为超时。
3) 握手成功后,初步过滤出version包。
4) 对version包进行解析。5 其他(性能方面的改进)
脚本一共经历了四个大的版本:
第一版,使用一个线程进行,造成了资源的空闲,12小时可以扫描10万个节点,得到7000个左右正常的节点。
第二版,初次使用了多线程,6小时可以扫描出20万个未作二次确认的节点,但地址池中的数量已经有60万个,主要是有一部分重复的节点。发现扫描时CPU占用率极高。
第三版,优化脚本中的数据结构,通过如将列表替换为字典等方式减少时间复杂度。20分钟扫描出7000个正常节点。
第四版,使用了系统定时器管理getaddr模块和二次确认模块线程的开放情况。重构了日志输出的逻辑,10分钟扫描出7000个正常的节点,且CPU占用率较低。
如图4.2所示,红色线条为getaddr模块扫描的节点总数,黑色线条为二次确认模块扫描出的超时节点数量。图中,当一条曲线斜率大时,另一条曲线斜率区域稳定,从侧面说明了多线程的调度比较理想。
图4.2 扫描节点总数与超时节点数量变化折线图
如图4.3所示,红色曲线为扫描出当前活跃的比特币节点数量。只需要10分钟左右,数量即趋于稳定,达到7500个左右。
图4.3 比特币节点数量扫描趋势折线图
第五章 扫描结果的分析
如图5.1所示,就扫描出节点的协议版本号做统计,可以发现,目前比特币网络中,大部分的节点运行70015版本的协议栈。
图 5.1 比特币节点版本号分布
如图5.2所示,对扫描出节点的客户端版本进行统计,可以发现,比特币节点的客户端软件,大部分使用了GitHub上官方的开源软件,Satoshi即为比特币的创始人中本聪,GitHub上,比特币客户端的版本命名,以它的名字加版本号组成。
图5.2 比特币节点User Agent分布
如图5.3所示,对扫描出节点的地区分布情况做统计(单次扫描的节点数量在7500-8000左右,此处对三次扫描结果做了合并),可以看出,美国和中国比特币节点数量非常多,欧洲因为国家比较多,所以分散到各个国家的数量相对较少,但欧洲比特币节点总量也很多,且比较密集。
图5.3 比特币节点地区分布情况
我们在进行比特币节点识别的过程中,发现Bitnodes网站也提供了对于比特币节点的识别。因此,我们爬取了Bitnodes网站的数据,对比两者的识别结果,从图5.4中可以看出,约有2/3的节点是重叠的,说明通过本文所述的识别方法扫描出的结果与Bitnodes总体相近,可信度较高。
图5.4 与Bitnodes网站数据的重叠度
在图5.5中,我们对本文所述方法扫描出的数据以及Bitnodes数据的国家分布进行了统计。从图中可以看出,我们所扫描到的中国的节点数量要明显高于Bitnodes的,而Bitnodes所识别到的中国外的大部分国家的节点数量要高于我们识别的数据。我们认为造成这一现象的原因为,我们的扫描VPS位于中国,而Bitnodes的位于国外。因此,我们有一个推测,在国内外同时部署VPS进行扫描,并将结果进行合并去重所得到的比特币节点的数据会更全。
图5.5 比特币节点的国家分布情况
第六章 小结
本文对比特币协议以及比特币节点识别方法进行了介绍,并对扫描到的结果进行了分析。我们发现,单次扫描(1小时左右),比特币网络中稳定提供服务的节点数量在7500到8500个(IPv4)之间,而三小时内(比特币协议规定,getaddr获取到的节点是每个节点缓存的三小时内活动过的节点)在比特币网络中活动过的节点数量在20万到30万(IPv4)之间,之所以出现差异,是因为比特币网络中除了存在负责打包区块、挖矿等行为,需要长时间运行协议栈的节点外,还存在大量轻量钱包节点,这些节点在短暂运行协议栈后就停止了协议栈。
同时,通过与Bitnodes网站的数据进行对比,我们发现双方数据的重叠度较高,说明双方的识别方式相近,数据可信度高。关于二者差异的部分,我们推测在国内外同时部署VPS进行扫描,并将结果进行合并去重所得到的比特币节点的数据会更加全面。
参考文献:
[1] Andreas M. Antonopoulos. MasteringBitcoin[J]. 2015.
[2] 比特币官网P2P网络部分介绍.https://bitcoin.org/en/developer-guide#p2p-network[3] 比特币协议.https://en.bitcoin.it/wiki/Protocol_documentation[4] 获取比特币种子节点.https://en.bitcoin.it/wiki/Satoshi_Client_Node_Discovery[5] 比特币客户端源中的chainparams.cpp .https://github.com/bitcoin/bitcoin/blob/master/src/chainparams.cpp#L128[6]BIP 0064. https://github.com/bitcoin/bips/blob/master/bip-0064.mediawiki[7]BIP 0111. https://github.com/bitcoin/bips/blob/master/bip-0111.mediawiki[8]BIP 0144. https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki[9]BIP 0159. https://github.com/bitcoin/bips/blob/master/bip-0159.mediawiki内容编辑:物联网安全实验室 魏佩儒 张星 责任编辑:肖晴
声明:本文来自绿盟科技研究通讯,版权归作者所有。文章内容仅代表作者独立观点,不代表安全内参立场,转载目的在于传递更多信息。如有侵权,请联系 anquanneican@163.com。