来自:绿盟科技研究通讯(微信号:nsfocus_research)
内容编辑:物联网安全实验室 魏佩儒 张星 责任编辑:肖晴
比特币(Bitcoin)的概念最初由中本聪在2009年提出,是一种P2P形式的数字货币,可以用来兑换成大多数国家的货币。
区块链诞生自中本聪的比特币,自2009年以来,出现了各种各样的类比特币的数字货币,都基于公有区块链。
由于以比特币为代表的数字货币具备兑现的能力,与数字货币相关的恶意事件(如勒索病毒、恶意浏览器脚本挖矿以及IoT僵尸网络挖矿等)逐渐增多。因此,对数字货币的节点进行识别,有助于提供威胁情报,减缓和预防相关恶意事件的影响。
本文将以比特币为例,就节点识别做相关的介绍。第一章为概述,介绍比特币的概念以及节点识别的思路。第二章以新节点加入比特币网络为例,介绍比特币网络的通信过程。第三章介绍比特币的通信协议。第四章介绍了本文所述方法的节点识别实现过程。第五章对节点识别的结果做了分析。第六章为小结。
比特币是一系列概念和技术,构成了数字货币生态系统的基础。它是一个分布式的点对点系统。因此,没有“中央”服务器或控制点。比特币是通过称为“挖矿”的过程创建的,这涉及到在处理比特币交易时竞争找到数学问题的解决方案。比特币网络中的任何参与者(即使用设备的任何人)都可以运行完整的比特币协议栈,利用其计算机的处理能力来验证和记录交易,这样的节点称之为“矿工”,而验证和记录交易的过程,称之为“挖矿”。平均每10分钟,比特币矿工能够验证过去10分钟的交易,作为奖励,将获得全新的比特币。从本质上讲,比特币采矿分散了中央银行的货币发行和结算功能,取代了对任何中央银行的需求。
对于特有协议的识别,一般的思路为全网扫描协议相关端口,模拟协议的通信方式,如果目的节点响应的内容符合协议的格式,则认为识别出开放该协议的节点。在比特币协议中,比特币常用的端口为8333,因此,可以全网扫描8333端口,从而确定大部分的比特币节点。但这种方式存在一些问题:1)端口扫描比较耗费时间。2)存在一部分比特币节点,没有使用8333端口,端口扫描难以对这部分节点进行识别。
更进一步,比特币网络为P2P网络,比特币节点均具备发现其他节点的能力(比特币协议中的“getaddr”消息,具有返回该节点周围活跃节点的功能),因此,从一个已知的比特币节点出发,通过模拟比特币协议的节点发现过程,即可获取其相邻节点的地址,之后,对得到的节点,依次通过模拟节点发现过程递归遍历,最终即可遍历整个比特币网络。该方法的优势在于,仅对比特币网络中的节点进行遍历,扫描速度快。因此,本文将对这种方式进行介绍。
比特币网络基于TCP连接,默认端口8333,在套接字之上直接将数据打包成二进制流进行传输。
若一个新的比特币节点,希望加入到比特币网络,总体上流程分为两步,1)获取到当前比特币网络中网络状况正常的节点列表(获取种子节点列表)。2)运行比特币的协议栈,与比特币网络中的其他节点通信。
本章将以获取一个节点周围的缓存节点为例,对比特币节点之间的通信过程做相应的说明。
如图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节的步骤后,正常情况下,可以获取到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消息)。
比特币节点之间的握手过程,建立在已经完成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 握手包含有的数据(部分)
完成正常的握手后,节点识别的关键,即为获取对方节点周围活跃的节点地址。比特币协议中规定了用于获取一个节点周围活跃的节点getaddr消息,在完成握手后,通过向对方节点发送getaddr消息,即可获取其周围活跃节点,通信过程如图2.5所示。
图2.5getaddr过程时序
比特币的协议是在TCP层之上,直接封装了自己的协议,因此,通信时,直接通过套接字,将协议转为二进制数据流进行传输。比特币网络中,连接均使用TCP的方式。本章将对比特币的通信协议做简单的描述,第四章识别的实现,是在本章的基础上完成的,对比特币节点的识别,需要实际上是对通信数据包的过滤和解析,第四章的实现中,对数据包进行处理,实际上是根据协议中规定的数据包的长度以及特定的字段,进行过滤和打包。
比特币协议中,数据包的结构如表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
|
如图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]
|
version ack的包相对简单,仅有数据包的头,没有payload,如图3.3所示。
图3.3 version ack包
getaddr消息向节点发送请求,询问一个节点周围缓存的活跃节点情况,以帮助查找网络中的潜在节点。getaddr消息的响应是响应节点发送一个或多个addr消息(上限为1000)。典型的假设是,如果节点在过去三小时内发送消息,则该节点可能处于活动状态。此消息与version ack包一样,没有payload。
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).
|
文本所描述的对比特币节点识别的实现,使用了python 3.6.6。整体思路如下:
1)从DNS种子获取IP种子列表。
2)与IP种子列表中的所有节点建立TCP连接,完成握手。
3)获取种子节点周围的缓存节点,存储到缓存节点列表中。
4)与缓存节点列表中的每一个节点通信,获得新的缓存节点,添加到当前缓存节点列表中并去重。
5)重复第四步,直到处理完所有的缓存节点列表。