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 */ 44 public static void main(String[] args) { 45 ClassFileAssembler cfa = new ClassFileAssembler(); 46 cfa.run(args); 47 } 48 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 */ 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 */ 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 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