lin
2025-08-21 57113df3a0e2be01232281fad9a5f2c060567981
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/*
 * 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);
        }
    }
 
}