| /* |
| * 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); |
| } |
| } |
| |
| } |