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..