1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dxconvext;
18 
19 import dxconvext.util.FileUtils;
20 
21 import java.io.BufferedOutputStream;
22 import java.io.BufferedReader;
23 import java.io.ByteArrayInputStream;
24 import java.io.ByteArrayOutputStream;
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.FileNotFoundException;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.InputStreamReader;
31 import java.io.OutputStream;
32 import java.io.Reader;
33 import java.io.UnsupportedEncodingException;
34 import java.security.DigestException;
35 import java.security.MessageDigest;
36 import java.security.NoSuchAlgorithmException;
37 import java.util.zip.Adler32;
38 
39 public class ClassFileAssembler {
40 
41     /**
42      * @param args
43      */
main(String[] args)44     public static void main(String[] args) {
45         ClassFileAssembler cfa = new ClassFileAssembler();
46         cfa.run(args);
47     }
48 
run(String[] args)49     private void run(String[] args) {
50         // this class can be used to generate .class files that are somehow
51         // damaged in order to test the dalvik vm verifier.
52         // The input is a .cfh (class file hex) file.
53         // The output is a java vm .class file.
54         // The .cfh files can be generated as follows:
55         // 1. create the initial .cfh file from an existing .class files by using
56         //    the ClassFileParser
57         // 2. modify some bytes to damage the structure of the .class file in a
58         //    way that would not be possible with e.g. jasmin (otherwise you are
59         //    better off using jasmin).
60         //    Uncomment the original bytes, and write "MOD:" meaning a modified
61         // entry (with the original commented out)
62         //
63         // Use the ClassFileAssembler to generate the .class file.
64         // this class here simply takes all non-comment lines from the .cfh
65         // file, parses them as hex values and writes the bytes to the class file
66         File cfhF = new File(args[0]);
67         if (!cfhF.getName().endsWith(".cfh") &&
68             !cfhF.getName().endsWith(".dfh")) {
69             System.out.println("file must be a .cfh or .dfh file, and its filename end with .cfh or .dfh");
70             return;
71         }
72 
73         String outBase = args[1];
74 
75         boolean isDex = cfhF.getName().endsWith(".dfh");
76 
77         byte[] cfhbytes = FileUtils.readFile(cfhF);
78         ByteArrayInputStream bais = new ByteArrayInputStream(cfhbytes);
79         // encoding should not matter, since we are skipping comment lines and parsing
80         try {
81             // get the package name
82             BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(cfhF)));
83             String firstLine = br.readLine();
84             br.close();
85             String classHdr = "//@class:";
86             String dexHdr = "// Processing '";
87             String hdr;
88             if(isDex)
89                 hdr = dexHdr;
90             else
91                 hdr = classHdr;
92 
93             if (!firstLine.startsWith(hdr)) throw new RuntimeException("wrong format:"+firstLine +" isDex=" + isDex);
94             String tFile;
95             if(isDex) {
96                 tFile = outBase + "/classes.dex";
97             } else {
98                 String classO = firstLine.substring(hdr.length()).trim();
99                 tFile = outBase +"/"+classO+".class";
100             }
101             File outFile = new File(tFile);
102             System.out.println("outfile:" + outFile);
103             String mkdir = tFile.substring(0, tFile.lastIndexOf("/"));
104             new File(mkdir).mkdirs();
105 
106             Reader r = new InputStreamReader(bais,"utf-8");
107             OutputStream os = new FileOutputStream(outFile);
108             BufferedOutputStream bos = new BufferedOutputStream(os);
109             writeClassFile(r, bos, isDex);
110             bos.close();
111         } catch (UnsupportedEncodingException e) {
112             throw new RuntimeException("problem while parsing .dfh or .cfh file: "+cfhF.getAbsolutePath(), e);
113         } catch (FileNotFoundException e) {
114             throw new RuntimeException("problem while parsing .dfh or .cfh file: "+cfhF.getAbsolutePath(), e);
115         } catch (IOException e) {
116             throw new RuntimeException("problem while parsing .dfh or .cfh file: "+cfhF.getAbsolutePath(), e);
117         }
118     }
119 
120     /**
121      * Calculates the signature for the <code>.dex</code> file in the
122      * given array, and modify the array to contain it.
123      *
124      * Originally from com.android.dx.dex.file.DexFile.
125      *
126      * @param bytes non-null; the bytes of the file
127      */
calcSignature(byte[] bytes)128     private void calcSignature(byte[] bytes) {
129         MessageDigest md;
130 
131         try {
132             md = MessageDigest.getInstance("SHA-1");
133         } catch (NoSuchAlgorithmException ex) {
134             throw new RuntimeException(ex);
135         }
136 
137         md.update(bytes, 32, bytes.length - 32);
138 
139         try {
140             int amt = md.digest(bytes, 12, 20);
141             if (amt != 20) {
142                 throw new RuntimeException("unexpected digest write: " + amt +
143                                            " bytes");
144             }
145         } catch (DigestException ex) {
146             throw new RuntimeException(ex);
147         }
148     }
149 
150     /**
151      * Calculates the checksum for the <code>.dex</code> file in the
152      * given array, and modify the array to contain it.
153      *
154      * Originally from com.android.dx.dex.file.DexFile.
155      *
156      * @param bytes non-null; the bytes of the file
157      */
calcChecksum(byte[] bytes)158     private void calcChecksum(byte[] bytes) {
159         Adler32 a32 = new Adler32();
160 
161         a32.update(bytes, 12, bytes.length - 12);
162 
163         int sum = (int) a32.getValue();
164 
165         bytes[8]  = (byte) sum;
166         bytes[9]  = (byte) (sum >> 8);
167         bytes[10] = (byte) (sum >> 16);
168         bytes[11] = (byte) (sum >> 24);
169     }
170 
writeClassFile(Reader r, OutputStream rOs, boolean isDex)171     public void writeClassFile(Reader r, OutputStream rOs, boolean isDex) {
172         ByteArrayOutputStream baos = new ByteArrayOutputStream(8192);
173         BufferedReader br = new BufferedReader(r);
174         String line;
175         String secondLine = null;
176         int lineCnt = 0;
177         try {
178             while ((line = br.readLine()) != null) {
179                 if (isDex && lineCnt++ == 1) {
180                     secondLine = line;
181                 }
182                 // skip it if it is a comment
183                 if (!line.trim().startsWith("//")) {
184                     // we have a row like "    ae 08 21 ff" etc.
185                     String[] parts = line.split("\\s+");
186                     for (int i = 0; i < parts.length; i++) {
187                         String part = parts[i].trim();
188                         if (!part.equals("")) {
189                             int res = Integer.parseInt(part, 16);
190                             baos.write(res);
191                         }
192                     }
193                 }
194             }
195 
196             // now for dex, update the checksum and the signature.
197             // special case:
198             // for two tests (currently T_f1_9.dfh and T_f1_10.dfh), we need
199             // to keep the checksum or the signature, respectively.
200             byte[] outBytes = baos.toByteArray();
201             if (isDex) {
202                 boolean leaveChecksum = secondLine.contains("//@leaveChecksum");
203                 boolean leaveSignature= secondLine.contains("//@leaveSignature");
204                 // update checksum and signature for dex file
205                 if(!leaveSignature)
206                     calcSignature(outBytes);
207                 if(!leaveChecksum)
208                     calcChecksum(outBytes);
209             }
210             rOs.write(outBytes);
211             rOs.close();
212         } catch (IOException e) {
213             throw new RuntimeException("problem while writing file",e);
214         }
215     }
216 
217 }
218