我们将在本文中探讨如何利用开放的基于Web的Java反编译工具来反编译Java源代码,然后使用标准的Java编译器来对它进行重新编译,修改代码以确认收到UDP数据包,并期望以此来更好的理解ICT的代码。?
只通过查看源代码来理解IOTA controlled agent(ICT)背后隐含的目的对我个人来说有点复杂,主要原因如下:
- 我不是专业的程序员
- 我没有群集逻辑(swarm logics)的经验
- 我第一次接触Java还是在上学的时候,但那是在90年代早期。
- ...
无论如何 - 让我们开始尝试一下吧。
我仍在我的谷歌云实例(f1-micro)上对此进行测试。我使用的文本编辑器是vim - 如果你想自己设置这个环境,请查看我的其他文章。
探索IOTA#1 - 在谷歌云上获取Linux终端并发送交易
本文无意对ICT的测试阶段造成任何破坏或者干扰,只是记录一下我通过查看代码库来了解ICT的基本机制的过程。我已经在discord的#ICT频道上请求Come-from-Beyond发布这个小步骤。
此外,我的发现也有可能是完全错误的。?
反编译类文件
如果你有幸从IOTA官方discord频道中发布的下载链接下载到了ICT的首个pre-alpha版本(即ICT 0.1.1,下载链接现已失效),您就可以继续跟进后续所有步骤。如果没有下载到,那么请等待下一个版本放出的时候才有机会下载并亲自体验。Come-from-Beyond在discord中提到他很快就会在重构之后发布新版本。
*.class文件(编译后的*.java代码)在下载到的ict-0.1.1.zip压缩包中。从压缩包中提取这些类文件后,就可以使用在线反编译网站来反编译。我用的是下面的网站,它提供了数个不同类型的反编译器:
http://www.javadecompilers.com/
上传类文件后,再选择一个合适的反编译器即可开始反编译。我首先尝试使用CFR,但没有成功 - 反编译后的代码不是100%可读的,更重要的是CFR反编译后的文件不能重新编译,在改用Procycon后取得了不错的效果。
使用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 = 02018-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声明,即这些声明的其中之一:
- 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