以P2P的方式追踪 DDG 僵尸网络(下)

阅读量    73005 |   稿费 400

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

本系列文章从 Botnet(僵尸网络)的基础概念说起,围绕实现了 P2P 特性的 DDG.Mining.Botnet,一步一步设计一个基于 P2P 的僵尸网络追踪程序,来追踪 DDG。DDG 是一个目前仍十分活跃的 Botnet,读懂本文,再加上一些辅助分析工作,就可以自行实现一套针对 DDG 的 P2P 僵尸网络跟踪程序。内容分 上、下 两部分:

  1. 半部分写本人理解的 Botnet 相关概念,然后介绍 DDG Botnet,着重介绍其涉及的 P2P 特性;
  2. 半部分写如何根据 DDG.Mining.Botnet 的 P2P 特性,来设计一个僵尸网络跟踪程序 DDG.P2P.Tracker,用以遍历 Botnet 中的节点、及时获取最新的云端配置文件、及时下载到 Botnet 中最新的恶意样本、及时获知 Botnet 中最新启用的 C&C 服务器。

上半部分传送门以P2P的方式追踪 DDG 僵尸网络(上)

 

3. 追踪程序设计

3.1 追踪程序的执行流程

前文说过,设计追踪程序的最终目标,有 4 个,其中涉及到 Peer 信息的获取和保存、样本与配置数据的解析和保存、记录最新启用的 C&C Server ……这样一来,就不可避免地将相关数据和文件保存到本地或数据库中。

我们可以把最新一次探测到的 P2P 节点信息存储到数据库中,把样本文件、配置数据、最新的 C&C Server 列表保存到本地文件中。根据 Memberlist 框架的实现,程序要调用 memberlist.Join() 函数来加入一个已存在的 P2P 网络,而这个函数需要一个 IP List( Go 变量 [] string ,下文简称 init_peers) 来作为加入 P2P 网络的“介绍人”。当然,这个 IP List 中的 IP,应该是当前已加入 P2P 网络的 IP (按照这个概念,这些 IP 应该是对应常规 P2P 网络中的 Node,P2P 网络中的 NodePeer 的概念可以自行了解,为了简化描述,本文把 P2P 网络中的节点统称为 Peer)。

前文还说过,ddg 主样本中有一份内置硬编码的 HUB IP List。其实,这一份 HUB IP List 就可以拿来当做 memberlist.Join() 函数的参数,即 init_peers。为了方便程序运行,我们可以把这一份 IP List 提前保存到数据库中,追踪程序每次运行,都要先从数据库中读取最新的 init_peers,通过 init_peers 加入 ddg 的 P2P 网络。

这里先说一下追踪程序的概要执行流程,后面分步骤详细说明:

  1. 从数据库中读取 init_peers IP List ,并调用 memberlist.Join() 加入 ddg 的 P2P 网络;
  2. 成功加入 P2P 网络后,调用 memberlist.Members() 获取当前网络中的最新 Peers List;
  3. 解析获取到的 Peers List 中的 Peers 信息,将每个 Peer 信息拆解成 IP:Port:Versioin:Hash:DateTime 5 元组,存到数据库中;
  4. 将每个 Peer IP ,拼接 URL 串 http://<peer_ip>:8000/slave ,并向该 URL 发送 Post 请求,以获取经过 msgPack 编码的配置数据;
  5. 如果成功从某个 Peer 上获取到了配置数据,则:
    • 将该 Peer IP 暂存到一个非重复的、并发安全的 IP List 结构中;
    • 保存 RAW 格式的配置数据到本地;
    • 用该 Peer IP 拼接 URL 串 http://<peer_ip>:8000/i.sh ,并用 HTTP GET 请求的方式尝试获取最新的恶意 Shell 脚本;
    • 对比上述 i.sh 下载链接与刚获取到的最新配置数据中执行的 i.sh 下载链接是否相同,不同则对最新配置数据中指定的 i.sh 脚本也做下载&解析操作。
  6. 如果成功获取到 i.sh 脚本,则解析其中的样本 Download URL,下载样本,同本地已下载到的其他样本 MD5 和下载 URL 作对比,MD5 和 下载 URL 其中之一是新的,就保留样本,否则删除刚下载到的样本。对于新样本,通过 Slack 的 Message 接口 Push 相关消息到自己的 Slack Channel 中;
  7. 最后,将非重复的最新活跃的 C&C Server 列表保存到本地文件中。

3.2 加入 P2P 网络

前文提到,调用 memberlist.Join() 来加入 ddg 的 P2P 网络,需要一个 init_peers 的 IP List。这个 IP List 最初来自 ddg 主样本中硬编码的 HUB IP List,而以后追踪程序每次执行,都要先从数据库中获取这个 IP List。这里先给出一个可用的数据表结构,用来存储 Peer 信息:

+---------+----------------------+------+-----+---------+----------------+
| Field   | Type                 | Null | Key | Default | Extra          |
+---------+----------------------+------+-----+---------+----------------+
| id      | int(10) unsigned     | NO   | PRI | <null>  | auto_increment |
| ip      | char(16)             | NO   |     | <null>  |                |
| port    | smallint(5) unsigned | NO   |     | <null>  |                |
| version | smallint(5) unsigned | NO   |     | <null>  |                |
| hash    | char(32)             | YES  |     | <null>  |                |
| tdate   | datetime             | NO   |     | <null>  |                |
+---------+----------------------+------+-----+---------+----------------+

最新的 Peers 信息在我们加入 ddg 的 P2P 网络后可以调用 memberlist.Members() 来获取。在 Memberlist 框架的源码中,这个函数返回的是一个 Node 信息指针列表 (Go 语言变量 []*Node)。Memberlist 框架中的 Node 结构体的定义如下:

// Node represents a node in the cluster.
type Node struct {
    Name string
    Addr net.IP
    Port uint16
    Meta []byte // Metadata from the delegate for this node.
    PMin uint8  // Minimum protocol version this understands
    PMax uint8  // Maximum protocol version this understands
    PCur uint8  // Current version node is speaking
    DMin uint8  // Min protocol version for the delegate to understand
    DMax uint8  // Max protocol version for the delegate to understand
    DCur uint8  // Current version delegate is speaking
}

其中第一项 Name 是形如 VerNumber.HashValue 的一个字符串,如:3020.b1634b9e0c747a6ae728e07c40883e2d 。这里的 Hash 值在 Memberlist 框架中被定义为 UID ,每一个 Peer 都不同,其值是通过对当前 Peer 主机的网络配置用 MD5 算法计算得出。

Memberlist 的开源项目主页上,有一个简单的 Usage Demo,演示加入一个集群(本文就指 ddg 的 P2P 网络了)并获取节点信息的最简方法:

/* Create the initial memberlist from a safe configuration.
   Please reference the godoc for other default config types.
   http://godoc.org/github.com/hashicorp/memberlist#Config
*/
list, err := memberlist.Create(memberlist.DefaultLocalConfig())
if err != nil {
    panic("Failed to create memberlist: " + err.Error())
}

// Join an existing cluster by specifying at least one known member.
n, err := list.Join([]string{"1.2.3.4"})
if err != nil {
    panic("Failed to join cluster: " + err.Error())
}

// Ask for members of the cluster
for _, member := range list.Members() {
    fmt.Printf("Member: %s %sn", member.Name, member.Addr)
}

// Continue doing whatever you need, memberlist will maintain membership
// information in the background. Delegates can be used for receiving
// events when members join or leave.

可以看到在执行 Join() 函数加入集群之前,还要调用 memberlist.Create() 函数生成一个 Peer 对象(代表当前 Peer),然后用当前对象执行 Join 以及后续操作。这里有一个关键点是当前 Peer 的配置。这份配置的底层结构体定义,在 Memberlist 的 Godoc 文档中有详细说明,此处不赘述。这份配置结构中的两个关键配置项(网络配置和密钥),关乎到追踪程序能否成功加入到 ddg 的 P2P 网络中,以及加入之后能否正常与其他 Peers 通信,这两个关键点要逆向 ddg 主样本和熟知 Memberlist 的原理和实现才能搞定,这里也不赘述。想要自行实现这么一套追踪程序,需要自行完成这两个工作。

需要一提的是,配置项中有一个关于日志输出的配置项:

// Logger is a custom logger which you provide. If Logger is set, it will use
// this for the internal logger. If Logger is not set, it will fall back to the
// behavior for using LogOutput. You cannot specify both LogOutput and Logger
// at the same time.
Logger *log.Logger

我们要用到日志功能,把全局的日志句柄配置在这里,这样 Memberlist 整个框架的运行日志都会打到我们指定的日志文件中。

3.3 获取并解析最新的 Peers List

前文提到,获取最新的 Peers List,只需在加入 ddg 的 P2P 网络后调用 memberlist.Members() 即可。

其实只说了一半,因为这里还有个偶然发现的小 Trick:这个函数获取到的 Peers List 数量并不大,反倒是从 Memberlist 框架的运行日志中可以抽取更多 Peer 信息。

根据 Memberlist 的框架特性,当前节点加入 P2P 网络之后,会随机与其他 Peers 以 Gossip 的形式通信,这种通信具有节点探测的功能。通信的结果会记录在日志中,尤其是通信失败的日志,记录的比较详细。一条失败的 Gossip 通信日志如下:

2019/01/23 08:44:53 [ERR] memberlist: Failed to send gossip to 58.144.150.24:7946: write udp 127.0.0.1:7946->58.144.150.24:7946: sendto: invalid argument

打出这段日志的代码,在 memberlist/stat.go 中实现:

不过,这段错误信息还不足以提供我们想要的 Peer Info 5 元组。那就动手 Patch 一下这段代码,让它打出我们想要的信息。Patch 后的代码如下:

然后,打出来的日志内容就会是如下形式:

2019/02/24 18:01:06 [ERR] memberlist: Failed to send gossip to (114.118.18.70:7946:3020:91f7f67194e0d31d9b58d9e6bef4f711)

这样,既缩减了日志文件的体积,也能精准捕获到我们需要的信息。然后,就可以把 memberlist.Members() 函数获取到的 Peers 信息和日志文件中打出来的 Peers 信息汇总起来,保存到一个变量中,以待后用。

3.4 保存 Peers 信息

将上述步骤获取到的 Peers 信息保存到数据库中,最新的 20 条 Peers 信息示例如下:

3.5 探测最新活跃的 C&C,拉取最新的配置数据

对上面获取到的 Peers Info 中的每一个 Peer IP,拼接成 URL 串 http://<peer_ip>:8000/slave ,向该 URL 发 Post 请求。能获取符合格式的配置数据的,即为当前存活的 C&C IP。把存活的 C&C Host 信息保存到一个非重复的、并发安全的 List 结构的变量中,最后把这份 C&C Host 列表保存到本地文件中。本地 C&C Host 文件列表部分内容如下:

➜ tail cc_server.list
20190503060101  132.148.241.138:8000
20190503060101  109.237.25.145:8000
20190503060101  104.128.230.16:8000
20190503060101  117.141.5.87:8000
20190506060101  132.148.241.138:8000
20190506060101  104.128.230.16:8000
20190506060101  117.141.5.87:8000
20190506120102  104.128.230.16:8000
20190506120102  132.148.241.138:8000
20190506120102  117.141.5.87:8000

前面提到过 ddg 配置数据是经过 msgPack 编码的,前文也列出了解码后的配置数据示例。受限于 Memberlist 的框架实现,我们的追踪程序也只能用 Go 语言来实现。要解码这份配置数据,直接调用 msgPack 的 Go 语言 API 是不够的,还需要逆向分析出配置数据的正确结构,并用 Go 语言的语法来定义这个配置数据的结构。下面是掉了两把头发才逆向出来的配置数据结构,以 Go 语言来定义的结构体:

import msgpack
/*
    Salve conf struct
*/

type Conf struct {
    Data      []byte
    Signature []byte
}

type ConfData struct {
    CfgVer int
    Config MainConf
    Miner  []MinerConf
    Cmd    CmdConf
}

type MainConf struct {
    Interval string
}

type MinerConf struct {
    Exe string
    Md5 string
    Url string
}

type CmdConf struct {
    AAredis CmdConfDetail
    AAssh   CmdConfDetail
    Sh      []ShConf
    Killer  []ProcConf
    LKProc  []ProcConf
}

type CmdConfDetail struct {
    Id         int
    Version    int
    ShellUrl   string
    Duration   string
    NThreads   int
    IPDuration string
    GenLan     bool
    GenAAA     bool
    Timeout    string
    Ports      []int
}

type ShConf struct {
    Id      int
    Version int
    Line    string
    Timeout string
}

type ProcConf struct {
    _msgpack struct{} `msgpack:",omitempty"`
    Id       int
    Version  int
    Expr     string
    Timeout  string
}

将解码成功的配置数据打到日志文件中,只把未解码的 RAW 配置数据保存到本地。最新获取到的配置数据如下:

➜ ll -t slave_conf | head
total 4.6M
2.0K May  6 12:34 117_141_5_87__20190506123410.raw    
2.0K May  6 12:34 132_148_241_138__20190506123410.raw 
2.0K May  6 12:33 104_128_230_16__20190506123309.raw  
2.0K May  6 06:33 104_128_230_16__20190506063312.raw  
2.0K May  6 06:31 117_141_5_87__20190506063142.raw    
2.0K May  6 06:30 132_148_241_138__20190506063042.raw 
2.0K May  6 00:39 104_128_230_16__20190506003932.raw  
2.0K May  6 00:37 117_141_5_87__20190506003723.raw    
2.0K May  6 00:34 132_148_241_138__20190506003442.raw

3.6 下载最新样本

对于上面步骤中,每一个可以获取合格配置数据的 C&C IP,拼接 URL 串 http://<cc_ip>:8000/i.sh ,这是 ddg 目前用到的最新恶意 Shell 脚本的下载链接。通过 HTTP GET 请求下载这个 i.sh 文件,跟本地已有的、相同 URL 下载到的 i.sh 文件对比 MD5 值,如果 MD5 跟旧的 i.sh 相同,则丢弃刚下载 i.sh 文件。

如果最新的 i.sh 文件跟旧 i.sh 文件 MD5 不同,则进行以下两步操作:

  1. 对比上述 i.sh 下载链接与刚获取到的最新配置数据中执行的 i.sh 下载链接是否相同,不同则对最新配置数据中指定的 i.sh 脚本也做下载&解析操作;
  2. 成功获取到 i.sh 脚本,则解析其中的样本 Download URL,下载样本,同本地相同 URL 下载到的样本对比 MD5 和 FileName(其实是 Download URL),如果 MD5 或者 FileName 不同,则保留样本,否则删除刚下载到的样本。样本 MD5 和 FileName 的对比结果,有三种情况:
    • 仅仅 MD5 不同而 FileName 相同,说明同一个 URL 中下到了不同 MD5 的样本,即样本有更新;
    • 仅仅 FileName 不同而 MD5 相同,则不同的 URL 想到了相同 MD5 的样本,通常意味着 C&C 有变动;
    • 两者都不同则说明 C&C 有变动并且样本有更新。

截至目前,我通过 ddg 追踪程序监控到的一部分 ddg 样本如下:

➜ ll sample
total 115M
1.7K 104_236_156_211__8000__i_sh+8801aff2ec7c44bed9750f0659e4c533
8.8M 104_236_156_211__8000__static__3019__fmt_i686+8c2e1719192caa4025ed978b132988d6
 11M 104_236_156_211__8000__static__3019__fmt_x86_64+d6187a44abacfb8f167584668e02c918
1.8K 104_248_181_42__8000__i_sh+dc477d4810a8d3620d42a6c9f2e40b40
3.6M 104_248_181_42__8000__static__3020__ddgs_i686+3ebe43220041fe7da8be63d7c758e1a8
3.9M 104_248_181_42__8000__static__3020__ddgs_x86_64+d894bb2504943399f57657472e46c07d
1.9K 104_248_251_227__8000__i_sh+55ea97d94c6d74ceefea2ab9e1de4d9f
3.6M 104_248_251_227__8000__static__3020__ddgs_i686+3ebe43220041fe7da8be63d7c758e1a8
3.9M 104_248_251_227__8000__static__3020__ddgs_x86_64+d894bb2504943399f57657472e46c07d
1.1K 117_141_5_87__8000__i_sh+100d1048ee202ff6d5f3300e3e3c77cc
1.7K 117_141_5_87__8000__i_sh+5760d5571fb745e7d9361870bc44f7a3
8.8M 117_141_5_87__8000__static__3019__fmt_i686+8c2e1719192caa4025ed978b132988d6
 11M 117_141_5_87__8000__static__3019__fmt_x86_64+d6187a44abacfb8f167584668e02c918
3.6M 117_141_5_87__8000__static__3020__ddgs_i686+3ebe43220041fe7da8be63d7c758e1a8
3.9M 117_141_5_87__8000__static__3020__ddgs_x86_64+d894bb2504943399f57657472e46c07d
1.3K 119_9_106_27__8000__i_sh+09a3a0f662738279e344b2a38dc93ecb
1.2K 119_9_106_27__8000__i_sh+9dc32a4a87d2b579d03b6adb27e3f604
1.6K 119_9_106_27__8000__i_sh+b8a64e8bfe4a69c36760505cc757c38d
3.6M 119_9_106_27__8000__static__3020__ddgs_i686+3ebe43220041fe7da8be63d7c758e1a8
3.9M 119_9_106_27__8000__static__3020__ddgs_x86_64+d894bb2504943399f57657472e46c07d
9.4M 119_9_106_27__8000__static__3022__ddgs_i686+c32bd921a71d82696517c22021173480
 11M 119_9_106_27__8000__static__3022__ddgs_x86_64+79d762d1ff16142ea3bdae560558e718
1.7K 132_148_241_138__8000__i_sh+44feb3cd31b957e24b18f97c46b57431
1.1K 132_148_241_138__8000__i_sh+fcc003280d8e9060e00fb7273d8edee7
8.8M 132_148_241_138__8000__static__3019__fmt_i686+8c2e1719192caa4025ed978b132988d6
 11M 132_148_241_138__8000__static__3019__fmt_x86_64+d6187a44abacfb8f167584668e02c918
3.6M 132_148_241_138__8000__static__3020__ddgs_i686+3ebe43220041fe7da8be63d7c758e1a8
3.9M 132_148_241_138__8000__static__3020__ddgs_x86_64+d894bb2504943399f57657472e46c07d
➜
➜ md5sum sample/*
8801aff2ec7c44bed9750f0659e4c533  104_236_156_211__8000__i_sh+8801aff2ec7c44bed9750f0659e4c533
8c2e1719192caa4025ed978b132988d6  104_236_156_211__8000__static__3019__fmt_i686+8c2e1719192caa4025ed978b132988d6
d6187a44abacfb8f167584668e02c918  104_236_156_211__8000__static__3019__fmt_x86_64+d6187a44abacfb8f167584668e02c918
dc477d4810a8d3620d42a6c9f2e40b40  104_248_181_42__8000__i_sh+dc477d4810a8d3620d42a6c9f2e40b40
3ebe43220041fe7da8be63d7c758e1a8  104_248_181_42__8000__static__3020__ddgs_i686+3ebe43220041fe7da8be63d7c758e1a8
d894bb2504943399f57657472e46c07d  104_248_181_42__8000__static__3020__ddgs_x86_64+d894bb2504943399f57657472e46c07d
55ea97d94c6d74ceefea2ab9e1de4d9f  104_248_251_227__8000__i_sh+55ea97d94c6d74ceefea2ab9e1de4d9f
3ebe43220041fe7da8be63d7c758e1a8  104_248_251_227__8000__static__3020__ddgs_i686+3ebe43220041fe7da8be63d7c758e1a8
d894bb2504943399f57657472e46c07d  104_248_251_227__8000__static__3020__ddgs_x86_64+d894bb2504943399f57657472e46c07d
100d1048ee202ff6d5f3300e3e3c77cc  117_141_5_87__8000__i_sh+100d1048ee202ff6d5f3300e3e3c77cc
5760d5571fb745e7d9361870bc44f7a3  117_141_5_87__8000__i_sh+5760d5571fb745e7d9361870bc44f7a3
8c2e1719192caa4025ed978b132988d6  117_141_5_87__8000__static__3019__fmt_i686+8c2e1719192caa4025ed978b132988d6
d6187a44abacfb8f167584668e02c918  117_141_5_87__8000__static__3019__fmt_x86_64+d6187a44abacfb8f167584668e02c918
3ebe43220041fe7da8be63d7c758e1a8  117_141_5_87__8000__static__3020__ddgs_i686+3ebe43220041fe7da8be63d7c758e1a8
d894bb2504943399f57657472e46c07d  117_141_5_87__8000__static__3020__ddgs_x86_64+d894bb2504943399f57657472e46c07d
09a3a0f662738279e344b2a38dc93ecb  119_9_106_27__8000__i_sh+09a3a0f662738279e344b2a38dc93ecb
9dc32a4a87d2b579d03b6adb27e3f604  119_9_106_27__8000__i_sh+9dc32a4a87d2b579d03b6adb27e3f604
b8a64e8bfe4a69c36760505cc757c38d  119_9_106_27__8000__i_sh+b8a64e8bfe4a69c36760505cc757c38d
3ebe43220041fe7da8be63d7c758e1a8  119_9_106_27__8000__static__3020__ddgs_i686+3ebe43220041fe7da8be63d7c758e1a8
d894bb2504943399f57657472e46c07d  119_9_106_27__8000__static__3020__ddgs_x86_64+d894bb2504943399f57657472e46c07d
c32bd921a71d82696517c22021173480  119_9_106_27__8000__static__3022__ddgs_i686+c32bd921a71d82696517c22021173480
79d762d1ff16142ea3bdae560558e718  119_9_106_27__8000__static__3022__ddgs_x86_64+79d762d1ff16142ea3bdae560558e718
44feb3cd31b957e24b18f97c46b57431  132_148_241_138__8000__i_sh+44feb3cd31b957e24b18f97c46b57431
fcc003280d8e9060e00fb7273d8edee7  132_148_241_138__8000__i_sh+fcc003280d8e9060e00fb7273d8edee7
8c2e1719192caa4025ed978b132988d6  132_148_241_138__8000__static__3019__fmt_i686+8c2e1719192caa4025ed978b132988d6
d6187a44abacfb8f167584668e02c918  132_148_241_138__8000__static__3019__fmt_x86_64+d6187a44abacfb8f167584668e02c918
3ebe43220041fe7da8be63d7c758e1a8  132_148_241_138__8000__static__3020__ddgs_i686+3ebe43220041fe7da8be63d7c758e1a8
d894bb2504943399f57657472e46c07d  132_148_241_138__8000__static__3020__ddgs_x86_64+d894bb2504943399f57657472e46c07d

综合以上描述,本地文件目录及文件示例如下:

- ddg_tracker/
|-- cc_server.list
|-- log/
|   |-- 20190123164450.log
|   |-- 20190123180232.log
|   |-- 20190123211837.log
|   |-- 20190124000101.log
|   |-- 20190124060101.log
|   `-- ......
|-- sample/
|   |-- 104_236_156_211__8000__i_sh+8801aff2ec7c44bed9750f0659e4c533
|   |-- 104_236_156_211__8000__static__3019__fmt_i686+8c2e1719192caa4025ed978b132988d6
|   |-- 104_236_156_211__8000__static__3019__fmt_x86_64+d6187a44abacfb8f167584668e02c918
|   |-- 104_248_181_42__8000__i_sh+dc477d4810a8d3620d42a6c9f2e40b40
|   |-- 104_248_181_42__8000__static__3020__ddgs_i686+3ebe43220041fe7da8be63d7c758e1a8
|   |-- 104_248_181_42__8000__static__3020__ddgs_x86_64+d894bb2504943399f57657472e46c07d
|   |-- 104_248_251_227__8000__i_sh+55ea97d94c6d74ceefea2ab9e1de4d9f
|   |-- 104_248_251_227__8000__static__3020__ddgs_i686+3ebe43220041fe7da8be63d7c758e1a8
|   |-- 104_248_251_227__8000__static__3020__ddgs_x86_64+d894bb2504943399f57657472e46c07d
|   |-- 117_141_5_87__8000__i_sh+100d1048ee202ff6d5f3300e3e3c77cc
|   |-- 117_141_5_87__8000__i_sh+5760d5571fb745e7d9361870bc44f7a3
|   |-- 117_141_5_87__8000__static__3019__fmt_i686+8c2e1719192caa4025ed978b132988d6
|   `-- ......
`-- slave_conf/
    |-- 104_236_156_211__20190123165004.raw
    |-- 104_236_156_211__20190123185208.raw
    |-- 104_236_156_211__20190123223044.raw
    |-- 104_236_156_211__20190124012600.raw
    |-- 132_148_241_138__20190224191449.raw
    `-- ......

至此,我们就完成了 ddg 追踪程序的设计,为这个程序设置一个计划任务,定时运行一次即可。我个人的源码暂时不会放出来,有兴趣的朋友可以自己动手实现一下。目前的追踪成果(uniq peer ip):

一次探测到的活跃节点数:

部分 DDG 更新的 Slack 消息推送:

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多