| /* | |
| * Copyright (C) 2008 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| package dxconvext; | |
| import dxconvext.util.FileUtils; | |
| import java.io.BufferedOutputStream; | |
| import java.io.BufferedReader; | |
| import java.io.ByteArrayInputStream; | |
| import java.io.ByteArrayOutputStream; | |
| import java.io.File; | |
| import java.io.FileInputStream; | |
| import java.io.FileNotFoundException; | |
| import java.io.FileOutputStream; | |
| import java.io.IOException; | |
| import java.io.InputStreamReader; | |
| import java.io.OutputStream; | |
| import java.io.Reader; | |
| import java.io.UnsupportedEncodingException; | |
| import java.security.DigestException; | |
| import java.security.MessageDigest; | |
| import java.security.NoSuchAlgorithmException; | |
| import java.util.zip.Adler32; | |
| public class ClassFileAssembler { | |
| /** | |
| * @param args | |
| */ | |
| public static void main(String[] args) { | |
| ClassFileAssembler cfa = new ClassFileAssembler(); | |
| cfa.run(args); | |
| } | |
| private void run(String[] args) { | |
| // this class can be used to generate .class files that are somehow | |
| // damaged in order to test the dalvik vm verifier. | |
| // The input is a .cfh (class file hex) file. | |
| // The output is a java vm .class file. | |
| // The .cfh files can be generated as follows: | |
| // 1. create the initial .cfh file from an existing .class files by using | |
| // the ClassFileParser | |
| // 2. modify some bytes to damage the structure of the .class file in a | |
| // way that would not be possible with e.g. jasmin (otherwise you are | |
| // better off using jasmin). | |
| // Uncomment the original bytes, and write "MOD:" meaning a modified | |
| // entry (with the original commented out) | |
| // | |
| // Use the ClassFileAssembler to generate the .class file. | |
| // this class here simply takes all non-comment lines from the .cfh | |
| // file, parses them as hex values and writes the bytes to the class file | |
| File cfhF = new File(args[0]); | |
| if (!cfhF.getName().endsWith(".cfh") && | |
| !cfhF.getName().endsWith(".dfh")) { | |
| System.out.println("file must be a .cfh or .dfh file, and its filename end with .cfh or .dfh"); | |
| return; | |
| } | |
| String outBase = args[1]; | |
| boolean isDex = cfhF.getName().endsWith(".dfh"); | |
| byte[] cfhbytes = FileUtils.readFile(cfhF); | |
| ByteArrayInputStream bais = new ByteArrayInputStream(cfhbytes); | |
| // encoding should not matter, since we are skipping comment lines and parsing | |
| try { | |
| // get the package name | |
| BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(cfhF))); | |
| String firstLine = br.readLine(); | |
| br.close(); | |
| String classHdr = "//@class:"; | |
| String dexHdr = "// Processing '"; | |
| String hdr; | |
| if(isDex) | |
| hdr = dexHdr; | |
| else | |
| hdr = classHdr; | |
| if (!firstLine.startsWith(hdr)) throw new RuntimeException("wrong format:"+firstLine +" isDex=" + isDex); | |
| String tFile; | |
| if(isDex) { | |
| tFile = outBase + "/classes.dex"; | |
| } else { | |
| String classO = firstLine.substring(hdr.length()).trim(); | |
| tFile = outBase +"/"+classO+".class"; | |
| } | |
| File outFile = new File(tFile); | |
| System.out.println("outfile:" + outFile); | |
| String mkdir = tFile.substring(0, tFile.lastIndexOf("/")); | |
| new File(mkdir).mkdirs(); | |
| Reader r = new InputStreamReader(bais,"utf-8"); | |
| OutputStream os = new FileOutputStream(outFile); | |
| BufferedOutputStream bos = new BufferedOutputStream(os); | |
| writeClassFile(r, bos, isDex); | |
| bos.close(); | |
| } catch (UnsupportedEncodingException e) { | |
| throw new RuntimeException("problem while parsing .dfh or .cfh file: "+cfhF.getAbsolutePath(), e); | |
| } catch (FileNotFoundException e) { | |
| throw new RuntimeException("problem while parsing .dfh or .cfh file: "+cfhF.getAbsolutePath(), e); | |
| } catch (IOException e) { | |
| throw new RuntimeException("problem while parsing .dfh or .cfh file: "+cfhF.getAbsolutePath(), e); | |
| } | |
| } | |
| /** | |
| * Calculates the signature for the <code>.dex</code> file in the | |
| * given array, and modify the array to contain it. | |
| * | |
| * Originally from com.android.dx.dex.file.DexFile. | |
| * | |
| * @param bytes non-null; the bytes of the file | |
| */ | |
| private void calcSignature(byte[] bytes) { | |
| MessageDigest md; | |
| try { | |
| md = MessageDigest.getInstance("SHA-1"); | |
| } catch (NoSuchAlgorithmException ex) { | |
| throw new RuntimeException(ex); | |
| } | |
| md.update(bytes, 32, bytes.length - 32); | |
| try { | |
| int amt = md.digest(bytes, 12, 20); | |
| if (amt != 20) { | |
| throw new RuntimeException("unexpected digest write: " + amt + | |
| " bytes"); | |
| } | |
| } catch (DigestException ex) { | |
| throw new RuntimeException(ex); | |
| } | |
| } | |
| /** | |
| * Calculates the checksum for the <code>.dex</code> file in the | |
| * given array, and modify the array to contain it. | |
| * | |
| * Originally from com.android.dx.dex.file.DexFile. | |
| * | |
| * @param bytes non-null; the bytes of the file | |
| */ | |
| private void calcChecksum(byte[] bytes) { | |
| Adler32 a32 = new Adler32(); | |
| a32.update(bytes, 12, bytes.length - 12); | |
| int sum = (int) a32.getValue(); | |
| bytes[8] = (byte) sum; | |
| bytes[9] = (byte) (sum >> 8); | |
| bytes[10] = (byte) (sum >> 16); | |
| bytes[11] = (byte) (sum >> 24); | |
| } | |
| public void writeClassFile(Reader r, OutputStream rOs, boolean isDex) { | |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(8192); | |
| BufferedReader br = new BufferedReader(r); | |
| String line; | |
| String secondLine = null; | |
| int lineCnt = 0; | |
| try { | |
| while ((line = br.readLine()) != null) { | |
| if (isDex && lineCnt++ == 1) { | |
| secondLine = line; | |
| } | |
| // skip it if it is a comment | |
| if (!line.trim().startsWith("//")) { | |
| // we have a row like " ae 08 21 ff" etc. | |
| String[] parts = line.split("\\s+"); | |
| for (int i = 0; i < parts.length; i++) { | |
| String part = parts[i].trim(); | |
| if (!part.equals("")) { | |
| int res = Integer.parseInt(part, 16); | |
| baos.write(res); | |
| } | |
| } | |
| } | |
| } | |
| // now for dex, update the checksum and the signature. | |
| // special case: | |
| // for two tests (currently T_f1_9.dfh and T_f1_10.dfh), we need | |
| // to keep the checksum or the signature, respectively. | |
| byte[] outBytes = baos.toByteArray(); | |
| if (isDex) { | |
| boolean leaveChecksum = secondLine.contains("//@leaveChecksum"); | |
| boolean leaveSignature= secondLine.contains("//@leaveSignature"); | |
| // update checksum and signature for dex file | |
| if(!leaveSignature) | |
| calcSignature(outBytes); | |
| if(!leaveChecksum) | |
| calcChecksum(outBytes); | |
| } | |
| rOs.write(outBytes); | |
| rOs.close(); | |
| } catch (IOException e) { | |
| throw new RuntimeException("problem while writing file",e); | |
| } | |
| } | |
| } |