Results 1 to 4 of 4

Thread: Unpacked an encrypted jar

  1. #1
    Join Date
    Feb 2011
    Location
    The Future.
    Posts
    5,600
    Mentioned
    396 Post(s)
    Quoted
    1598 Post(s)

    Default Unpacking an encrypted jar

    So there's usually some sort of legal trouble when unpacking encrypted jars.

    Without looking at said jar at all, you can write code to grab the decrypted files when the application itself decrypts it..

    By that I mean, you can hook the system JarOutputStream, JarInputStream, ByteArrayOutputStream, ByteArrayInputStream and most of the JDK's Stream classes via XBootClassPath. This way, you never open the jar, you never touch it and you never decrypt it.

    ONE or more of the above classes has to be used to unpack an encrypted jar. The ones that's used is JarOutputStream. All of them are used but this one's a nice one because you can write the unpacked jar directly to the disk upon close.


    That all being said, how do we do it?

    1. Create a package called java.util.jar. Yes it's a forbidden package because it begins with "java" but who cares.. Next we create a class called JarOutputStream that extends ZipOutputStream.

    2. In this class we add a static string which will be used to determine where to unpack the jar.

    3. Add a method that calls "super.close()".

    4. Override "close" to unpack the jar to the disk and call our "super close function from step 3" to close the stream.


    Example:

    Java Code:
    package java.util.jar;

    import java.io.*;
    import java.util.zip.ZipEntry;
    import java.util.zip.ZipOutputStream;

    public class JarOutputStream extends ZipOutputStream {
        private static String copyPath = null; //OUR STRING.. This will be where we save the decrypted jar.
        private static final int JAR_MAGIC = 0xCAFE;
        private boolean firstEntry = true;

        //original Java method copied from OpenJDK.
        public JarOutputStream(OutputStream out, Manifest man) throws IOException {
            super(out);
            if (man == null) {
                throw new NullPointerException("man");
            }
            ZipEntry e = new ZipEntry(JarFile.MANIFEST_NAME);
            putNextEntry(e);
            man.write(new BufferedOutputStream(this));
            closeEntry();
        }

        //original Java method copied from OpenJDK.
        public JarOutputStream(OutputStream out) throws IOException {
            super(out);
        }

        //original Java method copied from OpenJDK.
        public void putNextEntry(ZipEntry ze) throws IOException {
            if (firstEntry) {
                byte[] edata = ze.getExtra();
                if (edata == null || !hasMagic(edata)) {
                    if (edata == null) {
                        edata = new byte[4];
                    } else {
                        byte[] tmp = new byte[edata.length + 4];
                        System.arraycopy(edata, 0, tmp, 4, edata.length);
                        edata = tmp;
                    }
                    set16(edata, 0, JAR_MAGIC);
                    set16(edata, 2, 0);
                    ze.setExtra(edata);
                }
                firstEntry = false;
            }
            super.putNextEntry(ze);
        }

        //original Java method copied from OpenJDK.
        private static boolean hasMagic(byte[] edata) {
            try {
                int i = 0;
                while (i < edata.length) {
                    if (get16(edata, i) == JAR_MAGIC) {
                        return true;
                    }
                    i += get16(edata, i + 2) + 4;
                }
            } catch (ArrayIndexOutOfBoundsException e) {
            }
            return false;
        }

        //original Java method copied from OpenJDK.
        private static int get16(byte[] b, int off) {
            return Byte.toUnsignedInt(b[off]) | ( Byte.toUnsignedInt(b[off+1]) << 8);
        }

        //original Java method copied from OpenJDK.
        private static void set16(byte[] b, int off, int value) {
            b[off+0] = (byte)value;
            b[off+1] = (byte)(value >> 8);
        }


        //OUR custom method that calls "super.close".
        private void superClose() throws IOException {
            super.close();
        }

        //OUR custom close method that saves the jar without touching or decrypting the original..
        @Override
        public void close() throws IOException {
            if (copyPath != null) { //Check if our string is not null.. If it isn't, we want to unpack the jar.. otherwise forget it.

                //Below, we create an input stream to the super's byte array. This array contains all the classes. "super.out".
                //Then we create a jar output stream to the path where we want to save these bytes/classes.
                //Basically, we redirected the jar's output stream to our input stream and then redirected our input stream to the disk/file.
                //Next, we for every class we read, we write. Voila..

                ByteArrayOutputStream out = (ByteArrayOutputStream) super.out;
                try (JarInputStream jis = new JarInputStream(new ByteArrayInputStream(out.toByteArray()))) {
                    JarOutputStream jos = new JarOutputStream(new FileOutputStream(new File(copyPath)));

                    int bytes_read;
                    byte[] buffer = new byte[1024];
                    ZipEntry entry = jis.getNextEntry();

                    while (entry != null) {
                        jos.putNextEntry(entry);
                        while ((bytes_read = jis.read(buffer, 0, buffer.length)) != -1) {
                            jos.write(buffer, 0, bytes_read);
                        }
                        jos.flush();
                        jos.closeEntry();
                        entry = jis.getNextEntry();
                    }

                    jos.superClose(); //call our custom close method for the jar output stream since we are overriding it.
                }
            }
            super.close(); //Call super close method because we aren't overriding anything.. Maybe not needed, but it's safer this way.
        }
    }


    Once we XBootClassPath with the above, we can use "reflection" to activate the hook.

    How? Well as usual, we need to obtain the original jar first.

    To do this I use the following:

    Java Code:
    //At this point we have the game all ready to run.. just like normal.. Here's where we activate our hook.
           
    Class cls = loader.loadClass("java.util.jar.JarOutputStream"); //We load the "java.util.jar.JarOutputStream". This will instead load our XBoot class.
    Field f = cls.getDeclaredField("copyPath"); //We use reflection to get a handle to our String.
    f.setAccessible(true); //We make the string accessible so we can change it. It is our string so this is legal.
    f.set(null, path); //Our string is static so the instance is null. We set our string to the "Path" where to save the decrypted jar.
           
    //We load the game just like we normally do..
    //We exit the game immediately.


    We can now run code on the obtained jar file and analyse it, hook fields, etc..

    Happy trails..
    Last edited by Brandon; 02-18-2015 at 06:32 PM.
    I am Ggzz..
    Hackintosher

  2. #2
    Join Date
    Feb 2011
    Location
    The Future.
    Posts
    5,600
    Mentioned
    396 Post(s)
    Quoted
    1598 Post(s)

    Default

    @3Garrett3; There. Now we don't have any more legal arguments or anything specifically targeting a specific application. We can skip the law debate and get down to the purpose of the tutorial (learning). We all know what it will be used for (since this is a cheating community and programming community in one) but as stated here: https://www.eff.org/issues/coders/re...ring-faq#faq13

    I am only displaying my findings in the form of a tutorial with no copy-righted code or specific intentions. No more grey area except for those that use said code, not product.


    What "can" it be used for? Updaters, hooking (via reflection [not injection but can also be applicable]), dumping classes, analysing, etc.. It's not for the average user to answer your previous question. It's for programmers and reverse engineering.

    The above code is ONE of many ways to unpack an encrypted jar without decrypting said jar or using application specific code or divulging application specific information. How? Because sooner or later, an application has to load the decrypted classes and it's usually done via a Stream as shown in the OP.
    Last edited by Brandon; 02-18-2015 at 06:35 PM.
    I am Ggzz..
    Hackintosher

  3. #3
    Join Date
    Dec 2007
    Posts
    289
    Mentioned
    4 Post(s)
    Quoted
    86 Post(s)

    Default

    Interesting educational piece.

    The law is also an interesting area. We are so not prepared (legally) for the technology we have available to us.
    Experiencing this to a lesser degree at a number of client sites for my work - so many outdated policies that have absolutely no way to deal with modern technology.

  4. #4
    Join Date
    Dec 2010
    Posts
    483
    Mentioned
    30 Post(s)
    Quoted
    328 Post(s)

    Default

    Great solution, same idea as overriding the canvas class to double buffer for a Java bot.

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •