探索IOTA ICT,代码的逆向工程-第一部分

我们将在本文中探讨如何利用开放的基于Web的Java反编译工具来反编译Java源代码,然后使用标准的Java编译器来对它进行重新编译,修改代码以确认收到UDP数据包,并期望以此来更好的理解ICT的代码。?

只通过查看源代码来理解IOTA controlled agent(ICT)背后隐含的目的对我个人来说有点复杂,主要原因如下:

  • 我不是专业的程序员
  • 我没有群集逻辑(swarm logics)的经验
  • 我第一次接触Java还是在上学的时候,但那是在90年代早期。
  • ...

无论如何 - 让我们开始尝试一下吧。

我仍在我的谷歌云实例(f1-micro)上对此进行测试。我使用的文本编辑器是vim - 如果你想自己设置这个环境,请查看我的其他文章。

探索IOTA#1 - 在谷歌云上获取Linux终端并发送交易

探索IOTA #ICT-1,在云计算机上测试ICT

本文无意对ICT的测试阶段造成任何破坏或者干扰,只是记录一下我通过查看代码库来了解ICT的基本机制的过程。我已经在discord的#ICT频道上请求Come-from-Beyond发布这个小步骤。

此外,我的发现也有可能是完全错误的。?

反编译类文件

如果你有幸从IOTA官方discord频道中发布的下载链接下载到了ICT的首个pre-alpha版本(即ICT 0.1.1,下载链接现已失效),您就可以继续跟进后续所有步骤。如果没有下载到,那么请等待下一个版本放出的时候才有机会下载并亲自体验。Come-from-Beyond在discord中提到他很快就会在重构之后发布新版本。

探索IOTA ICT,代码的逆向工程-第一部分

*.class文件(编译后的*.java代码)在下载到的ict-0.1.1.zip压缩包中。从压缩包中提取这些类文件后,就可以使用在线反编译网站来反编译。我用的是下面的网站,它提供了数个不同类型的反编译器:

http://www.javadecompilers.com/

上传类文件后,再选择一个合适的反编译器即可开始反编译。我首先尝试使用CFR,但没有成功 - 反编译后的代码不是100%可读的,更重要的是CFR反编译后的文件不能重新编译,在改用Procycon后取得了不错的效果。

探索IOTA ICT,代码的逆向工程-第一部分

使用Procycon对zip文件夹中的所有类文件进行反编译,并存储生成的java文件。

我们可以使用任何文本编辑器来浏览代码。

有五个文件需要查看:

  • Ict.java  - 主要的类文件,包含完整的任务调度和主函数。
  • Transaction.java  - 分析接收到的IOTA交易并在无效时返回ERROR的类文件。
  • Converter.java  - 将接收到的交易从bytes转换为trits和trytes(这些转换传递函数是基于字符串的)
  • Curl.java  - “臭名昭著"的哈希函数,重新计算此交易的哈希并确认所谓的nonce的有效性。
  • Neighbor.java  - 只是将传入/传出交易记录到集合中(all, new, invalid, required, shared)

重新编译java文件

在对类文件进行反编译之后,结果发现我对这些代码一无所知。但是我知道我得搞清楚如何再次编译这些java文件,以便可以再次运行ICT应用程序。

首先,需要使用命令来编译类文件。这就可能需要从Java Runtime Environment(JRE)切换到Java Development Kit(JDK):

~$ sudo apt-get update

~$ sudo apt-get install default-jdk

接下来,我们来编译所有的java文件,并将它们移动到java文件中指定的专用类路径中(cfb/ict/)。假设我们已经创建了一个子文件夹 - IOTA:

~/IOTA$ javac *.java
~/IOTA$ mkdir cfb && cd cfb
~/IOTA/cfb$ mkdir ict
~/IOTA$ mv *.class cfb/ict/

类文件需要放在你要运行应用程序的相对路径中,或者自己在环境的PATH变量中添加它们的位置。

查看我们反编译后的Ict.java文件的第一行,显示了java期望在cfb.ict中找到的包和类。

//
// Decompiled by Procyon v0.5.30
//

package cfb.ict;

import cfb.ict.Converter;
import cfb.ict.Neighbor;
import cfb.ict.Transaction;

import java.util.HashMap;
import java.net.SocketAddress;

HashMap和SocketAddress等其它的类都是从java中的已知库导入的。

有了这个,我们就可以运行重新编译的java程序。我们只需要在IOTA文件夹中放置一个ict.properties文件(自行创建和添加该文件,其内容只有一行:port=11111)。此时,我们可以使用后文的命令运行程序。

请不要在主网上使用重新编译后的ICT,我之所以没有使用通用的端口而是设置了不同的端口(11111)就是期望大家不要让真正的邻居来添加重新编译后的ICT(ICT不能主动设置邻居,只能让其它邻居添加ICT),以防止它连入主网。我们不能确定重新编译后的程序是否能够正常工作,所以它连入主网后可能会干扰正常ICT的测试。此外,邻居也肯定是希望能够添加正常的ICT,如果知道你正在利用他们的设备来测试不稳定的ICT,他们肯定会不高兴的。?

~/IOTA$ java cfb.ict.Ict ict.properties

我们已经指定了要在cfb.ict中调用的主类ICT和ict.properties文件。此时的输出结果并不值得重点关注,因为还没有传入交易:

[2018-07-22T11:28:30.883] Ict 0.1.1
2018-07-22T11:28:31.049
Number of transactions = 0
Number of missing transactions = 0

接下来,我们将在另一个f1-micro实例中通过UDP向ICT发送一条消息。这意味着你需要创建另一个f1-micro实例,或者也可以使用任何连接到互联网并且没有被ICT客户端屏蔽的其他linux发行版。

我所发现的向ICT客户端发送UDP数据包的最简单的方式:

$echo "Hello ICT" > /dev/udp/YOUR_ICT_IP/11111

您的ICT客户端(约一分钟后)会产生“无聊的”响应:

[2018-07-22T11:28:30.883] Ict 0.1.1
2018-07-22T11:28:31.049
Number of transactions = 0
Number of missing transactions = 0

2018-07-22T11:30:11.848
Number of transactions = 0
Number of missing transactions = 0

为了确保我们能够收到数据包,我们可以重写发送的UDP消息的输出代码。在byte[] data = datagramPacket.getData();之后,我们添加以下内容来输出传入的数据包:

String string = new String(data, 0, datagramPacket.getLength());
System.out.println(string);
System.out.println("----");

此外,我们希望看到发送方的IP,在final SocketAddress socketAddress = datagramPacket.getSocketAddress();之后添加如下代码来输出地址:

System.out.println("SocketAddress =" + socketAddress);

在编译ICT.java并将*.class文件移动到cfb/ict/后,我们就可以运行修改和重编译后的ICT,当再次发送四次相同的消息后,得到如下的结果(需要运行一段时间直到完全显示最后一行):

[2018-07-22T11:46:39.484] Ict 0.1.1
2018-07-22T11:46:39.585
Number of transactions = 0
Number of missing transactions = 0
Hello ICT
----
Hello ICT
----
Hello ICT
----
Hello ICT
----
2018-07-22T11:47:44.403
Number of transactions = 0
Number of missing transactions = 0

我们收到了消息,但里面没有关于IP地址的信息。由于未通过该声明,所以重新检验一下代码:

if (datagramPacket.getLength() != 1650) {
datagramPacket.setLength(1650);
} else {

用Linux构造一个带有1650个字符的消息应该很容易,我们甚至可以使用SEED生成函数并将其扩展到1650,以便得到大写字母 A...Z9:

cat /dev/urandom |tr -dc A-Z9|head -c${1:-1650} > test.tx

我们不能在此使用echo命令,因为它将1650长度的消息分解为更小的消息。但是我们可以在非ict实例上执行以下操作:

cat test.tx > /dev/udp/10.142.0.4/11111

结果实际上不能打印完整的消息,但我们得到更多的内容:

{deleted: content of test.tx 1650 chars long}
----
SocketAddress =/IP:39025
2018-07-22T11:59:59.169
/IP:39025: 2 / 0 / 1 / 0 / 0
Number of transactions = 0
Number of missing transactions = 0

上述ICT报告中加粗标记的那一行是新的内容,需要加以理解 - 可以查看Discord里的官方开发者Alex所做的详细总结。我们刚好证明了我们自己的invalidTxs声明,即这些声明的其中之一:

探索IOTA ICT,代码的逆向工程-第一部分

  • allTxs =1分钟内在指定端口(默认 = 14265)接收到的UDP包数量。
  • newTxs =1分钟内未收到的有效交易数。
  • invalidTxs = 不能构成正确交易的UDP包的数量(无效值,无效地址,无效时间戳,即在Mon Oct 23 12:00:00 UTC 2017之前,无效交易哈希)。
  • requestedTxs = ICT可以回答的来自该邻居的特定交易请求的次数。
  • sharedTxs =1分钟内转发给该邻居的新交易的数量。
  • Number of transactions= 所有邻居节点1分钟内所有newTxs的总和。
  • Number of missing transactions =ICT在1分钟内无法应答的所有请求的数量。

我们可以通过观察来扩展这一声明。invalidTxs的数量不会超过1个。如上文所示,我发送了2次test.tx无效交易,并且计数器停在了1。这很可能是因为,一旦发现无效的交易,就不会在该epoch(1分钟期间)再次输入用于验证交易的代码。

总结一下:我们使用在线反编译工具(网站)查看ICT的java代码,然后使用标准java编译器重新编译,最后再次运行重编译后的ICT。我们将UDP数据包发送到重编译后的ICT,然后分析代码的前几行。

在下一章中,我正在计划(尝试)更深入的了解ICT的逻辑,并向您展示如何识别无效交易并理解为什么它们目前通过了IRI和ICT。

我希望看到你的问题,怀疑或者对文章给予纠正。

本文涉及技术层面,可能存在部分内容翻译不精确的情况,如果遇到不好理解的地方,建议参照原文加深理解。

 

原文链接:https://medium.com/coinmonks/exploring-iota-ict-2-reverse-engineering-the-code-1-ecda56d4908

胖子李

专栏作者:胖子李

个人简介:我共发表了 190 篇文章,总计被阅读了653,988 次,共获得了 1,884 个赞。

作者邮箱 作者主页 Ta的文章

发表评论

邮箱地址不会被公开。 必填项已用*标注