package burp.j2ee.passive.strutstoken;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;

/**
 * Recover seed from Random instance base on a single Struts token.
 * @author Philippe Arteau
 */
public class StrutsTokenCracker {

    //Constants used to reproduce Java LCG PRNG
    private static final long multiplier = 0x5DEECE66DL;
    private static final long addend = 0xBL;
    private static final long mask = (1L << 48) - 1;

    /**
     * The guessing game can start ...
     * @param token
     */
    public static boolean testToken(String token) {
        //System.out.println("== bytes representation (reconstructed byte array)");
        int[] tokenInts = bytesToInt(bigIntToByte(token));
        /*for(int i=0;i<tokenInts.length;i++) {
            PrintHex.printInt(tokenInts[i]);
        }*/

        long seed = findSeed(reverseByteOrder(tokenInts[1]), reverseByteOrder(tokenInts[2]));
        ReplayRandom random = new ReplayRandom(seed);

        //System.out.println("== following int .. (should match the initial token last part) ");
        int[] nextInts = new int[4];
        for(int i=0;i<nextInts.length;i++) {
            nextInts[i] = reverseByteOrder(random.nextInt());
        }

        boolean match1 = tokenInts[2] == nextInts[0];
        boolean match2 = tokenInts[3] == nextInts[1];
        boolean match3 = tokenInts[4] == nextInts[2];

        return match1 && match2 && match3;
    }

    //Utility methods to restructure the data..

    private static byte[] bigIntToByte(String hexValue) {
        return new BigInteger(hexValue, 36).toByteArray();
    }

    private static int[] bytesToInt(byte[] bytes) {
        //I don't feel like to doing binary operations today..
        IntBuffer intBuf = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).asIntBuffer();
        int[] array = new int[intBuf.remaining()];
        intBuf.get(array);
        return array;
    }

    /**
     * Need because of special bytes array construction in <code>java.util.Random#nextBytes(byte[])</code>
     */
    public static int reverseByteOrder(long value) {
        int reverseValue = 0x00000000;
        //Many
        reverseValue |= value << 24 & 0xFF000000;
        reverseValue |= value <<  8 & 0xFF0000;
        reverseValue |= value >>  8 & 0xFF00;
        reverseValue |= value >> 24 & 0xFF;

        return reverseValue;
    }

    //PRNG Brute Force

    /**
     * Guessing the unknown 16 bits of the seed.. (base on two int)
     * Taken from : https://jazzy.id.au/2010/09/20/cracking_random_number_generators_part_1.html
     */
    private static long findSeed(long v1, long v2) {
        //Important to remove the four 0xFF in case the initial int were negative
        v1 = v1 & 0xFFFFFFFFL;
        v2 = v2 & 0xFFFFFFFFL;

        //Brute for the 16 bit that is unknown (48 bits seed - 32 bits return value = 16 bits)
        for (int i = 0; i < 0x10000; i++) {
            long seed = (v1 << 16) + i;
            if ((((seed * multiplier + addend) & mask) >>> 16) == v2) {
                //System.out.println("Seed found: " + seed);
                return seed;
            }
        }
        //throw new RuntimeException("Not Found");
        //System.err.println("Seed Not Found. :(");
        return -1;
    }

}