1 /*
2  * Copyright (C) 2014 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 dexfuzz.rawdex;
18 
19 import dexfuzz.Log;
20 
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.List;
24 
25 public class RawDexFile implements RawDexObject {
26   private OffsetTracker offsetTracker;
27 
28   public HeaderItem header;
29 
30   public MapList mapList;
31 
32   // Can be allocated after reading the header.
33   public List<StringIdItem> stringIds;
34   public List<TypeIdItem> typeIds;
35   public List<ProtoIdItem> protoIds;
36   public List<FieldIdItem> fieldIds;
37   public List<MethodIdItem> methodIds;
38   public List<ClassDefItem> classDefs;
39 
40   // Need to be allocated later (will be allocated in MapList.java)
41   public List<StringDataItem> stringDatas;
42   public List<ClassDataItem> classDatas;
43   public List<TypeList> typeLists;
44   public List<CodeItem> codeItems;
45   public DebugInfoItem debugInfoItem;
46   public List<AnnotationsDirectoryItem> annotationsDirectoryItems;
47   public List<AnnotationSetRefList> annotationSetRefLists;
48   public List<AnnotationSetItem> annotationSetItems;
49   public List<AnnotationItem> annotationItems;
50   public List<EncodedArrayItem> encodedArrayItems;
51 
52   @Override
read(DexRandomAccessFile file)53   public void read(DexRandomAccessFile file) throws IOException {
54     // Get a reference to the OffsetTracker, so that IdCreator can use it.
55     offsetTracker = file.getOffsetTracker();
56 
57     file.seek(0);
58 
59     // Read header.
60     (header = new HeaderItem()).read(file);
61 
62     // We can allocate all of these now.
63     stringIds = new ArrayList<StringIdItem>(header.stringIdsSize);
64     typeIds = new ArrayList<TypeIdItem>(header.typeIdsSize);
65     protoIds = new ArrayList<ProtoIdItem>(header.protoIdsSize);
66     fieldIds = new ArrayList<FieldIdItem>(header.fieldIdsSize);
67     methodIds = new ArrayList<MethodIdItem>(header.methodIdsSize);
68     classDefs = new ArrayList<ClassDefItem>(header.classDefsSize);
69 
70     mapList = new MapList(this);
71     mapList.read(file);
72 
73     file.getOffsetTracker().associateOffsets();
74   }
75 
76   @Override
write(DexRandomAccessFile file)77   public void write(DexRandomAccessFile file) throws IOException {
78     file.seek(0);
79 
80     // We read the header first, and then the map list, and then everything
81     // else. Therefore, when we get to the end of the header, tell OffsetTracker
82     // to skip past the map list offsets, and then when we get to the map list,
83     // tell OffsetTracker to skip back there, and then return to where it was previously.
84 
85     // Update the map items' sizes first
86     // - but only update the items that we expect to have changed size.
87     // ALSO update the header's table sizes!
88     for (MapItem mapItem : mapList.mapItems) {
89       switch (mapItem.type) {
90         case MapItem.TYPE_STRING_ID_ITEM:
91           if (mapItem.size != stringIds.size()) {
92             Log.debug("Updating StringIDs List size: " + stringIds.size());
93             mapItem.size = stringIds.size();
94             header.stringIdsSize = stringIds.size();
95           }
96           break;
97         case MapItem.TYPE_STRING_DATA_ITEM:
98           if (mapItem.size != stringDatas.size()) {
99             Log.debug("Updating StringDatas List size: " + stringDatas.size());
100             mapItem.size = stringDatas.size();
101           }
102           break;
103         case MapItem.TYPE_METHOD_ID_ITEM:
104           if (mapItem.size != methodIds.size()) {
105             Log.debug("Updating MethodIDs List size: " + methodIds.size());
106             mapItem.size = methodIds.size();
107             header.methodIdsSize = methodIds.size();
108           }
109           break;
110         case MapItem.TYPE_FIELD_ID_ITEM:
111           if (mapItem.size != fieldIds.size()) {
112             Log.debug("Updating FieldIDs List size: " + fieldIds.size());
113             mapItem.size = fieldIds.size();
114             header.fieldIdsSize = fieldIds.size();
115           }
116           break;
117         case MapItem.TYPE_PROTO_ID_ITEM:
118           if (mapItem.size != protoIds.size()) {
119             Log.debug("Updating ProtoIDs List size: " + protoIds.size());
120             mapItem.size = protoIds.size();
121             header.protoIdsSize = protoIds.size();
122           }
123           break;
124         case MapItem.TYPE_TYPE_ID_ITEM:
125           if (mapItem.size != typeIds.size()) {
126             Log.debug("Updating TypeIDs List size: " + typeIds.size());
127             mapItem.size = typeIds.size();
128             header.typeIdsSize = typeIds.size();
129           }
130           break;
131         case MapItem.TYPE_TYPE_LIST:
132           if (mapItem.size != typeLists.size()) {
133             Log.debug("Updating TypeLists List size: " + typeLists.size());
134             mapItem.size = typeLists.size();
135           }
136           break;
137         default:
138       }
139     }
140 
141     // Use the map list to write the file.
142     for (MapItem mapItem : mapList.mapItems) {
143       switch (mapItem.type) {
144         case MapItem.TYPE_HEADER_ITEM:
145           header.write(file);
146           file.getOffsetTracker().skipToAfterMapList();
147           break;
148         case MapItem.TYPE_STRING_ID_ITEM:
149           if (mapItem.size != stringIds.size()) {
150             Log.errorAndQuit("MapItem's size " + mapItem.size
151                 + " no longer matches StringIDs table size " + stringIds.size());
152           }
153           for (StringIdItem stringId : stringIds) {
154             stringId.write(file);
155           }
156           break;
157         case MapItem.TYPE_TYPE_ID_ITEM:
158           if (mapItem.size != typeIds.size()) {
159             Log.errorAndQuit("MapItem's size " + mapItem.size
160                 + " no longer matches TypeIDs table size " + typeIds.size());
161           }
162           for (TypeIdItem typeId : typeIds) {
163             typeId.write(file);
164           }
165           break;
166         case MapItem.TYPE_PROTO_ID_ITEM:
167           if (mapItem.size != protoIds.size()) {
168             Log.errorAndQuit("MapItem's size " + mapItem.size
169                 + " no longer matches ProtoIDs table size " + protoIds.size());
170           }
171           for (ProtoIdItem protoId : protoIds) {
172             protoId.write(file);
173           }
174           break;
175         case MapItem.TYPE_FIELD_ID_ITEM:
176           if (mapItem.size != fieldIds.size()) {
177             Log.errorAndQuit("MapItem's size " + mapItem.size
178                 + " no longer matches FieldIDs table size " + fieldIds.size());
179           }
180           for (FieldIdItem fieldId : fieldIds) {
181             fieldId.write(file);
182           }
183           break;
184         case MapItem.TYPE_METHOD_ID_ITEM:
185           if (mapItem.size != methodIds.size()) {
186             Log.errorAndQuit("MapItem's size " + mapItem.size
187                 + " no longer matches MethodIDs table size " + methodIds.size());
188           }
189           for (MethodIdItem methodId : methodIds) {
190             methodId.write(file);
191           }
192           break;
193         case MapItem.TYPE_CLASS_DEF_ITEM:
194           if (mapItem.size != classDefs.size()) {
195             Log.errorAndQuit("MapItem's size " + mapItem.size
196                 + " no longer matches ClassDefs table size " + classDefs.size());
197           }
198           for (ClassDefItem classDef : classDefs) {
199             classDef.write(file);
200           }
201           break;
202         case MapItem.TYPE_MAP_LIST:
203           file.getOffsetTracker().goBackToMapList();
204           mapList.write(file);
205           file.getOffsetTracker().goBackToPreviousPoint();
206           break;
207         case MapItem.TYPE_TYPE_LIST:
208           if (mapItem.size != typeLists.size()) {
209             Log.errorAndQuit("MapItem's size " + mapItem.size
210                 + " no longer matches TypeLists table size " + typeLists.size());
211           }
212           for (TypeList typeList : typeLists) {
213             typeList.write(file);
214           }
215           break;
216         case MapItem.TYPE_ANNOTATION_SET_REF_LIST:
217           if (mapItem.size != annotationSetRefLists.size()) {
218             Log.errorAndQuit("MapItem's size " + mapItem.size
219                 + " no longer matches AnnotationSetRefLists table size "
220                 + annotationSetRefLists.size());
221           }
222           for (AnnotationSetRefList annotationSetRefList : annotationSetRefLists) {
223             annotationSetRefList.write(file);
224           }
225           break;
226         case MapItem.TYPE_ANNOTATION_SET_ITEM:
227           if (mapItem.size != annotationSetItems.size()) {
228             Log.errorAndQuit("MapItem's size " + mapItem.size
229                 + " no longer matches AnnotationSetItems table size "
230                 + annotationSetItems.size());
231           }
232           for (AnnotationSetItem annotationSetItem : annotationSetItems) {
233             annotationSetItem.write(file);
234           }
235           break;
236         case MapItem.TYPE_CLASS_DATA_ITEM:
237           if (mapItem.size != classDatas.size()) {
238             Log.errorAndQuit("MapItem's size " + mapItem.size
239                 + " no longer matches ClassDataItems table size " + classDatas.size());
240           }
241           for (ClassDataItem classData : classDatas) {
242             classData.write(file);
243           }
244           break;
245         case MapItem.TYPE_CODE_ITEM:
246           if (mapItem.size != codeItems.size()) {
247             Log.errorAndQuit("MapItem's size " + mapItem.size
248                 + " no longer matches CodeItems table size " + codeItems.size());
249           }
250           for (CodeItem codeItem : codeItems) {
251             codeItem.write(file);
252           }
253           break;
254         case MapItem.TYPE_STRING_DATA_ITEM:
255           if (mapItem.size != stringDatas.size()) {
256             Log.errorAndQuit("MapItem's size " + mapItem.size
257                 + " no longer matches StringDataItems table size "
258                 + stringDatas.size());
259           }
260           for (StringDataItem stringDataItem : stringDatas) {
261             stringDataItem.write(file);
262           }
263           break;
264         case MapItem.TYPE_DEBUG_INFO_ITEM:
265           debugInfoItem.write(file);
266           break;
267         case MapItem.TYPE_ANNOTATION_ITEM:
268           if (mapItem.size != annotationItems.size()) {
269             Log.errorAndQuit("MapItem's size " + mapItem.size
270                 + " no longer matches AnnotationItems table size "
271                 + annotationItems.size());
272           }
273           for (AnnotationItem annotationItem : annotationItems) {
274             annotationItem.write(file);
275           }
276           break;
277         case MapItem.TYPE_ENCODED_ARRAY_ITEM:
278           if (mapItem.size != encodedArrayItems.size()) {
279             Log.errorAndQuit("MapItem's size " + mapItem.size
280                 + " no longer matches EncodedArrayItems table size "
281                 + encodedArrayItems.size());
282           }
283           for (EncodedArrayItem encodedArrayItem : encodedArrayItems) {
284             encodedArrayItem.write(file);
285           }
286           break;
287         case MapItem.TYPE_ANNOTATIONS_DIRECTORY_ITEM:
288           if (mapItem.size != annotationsDirectoryItems.size()) {
289             Log.errorAndQuit("MapItem's size " + mapItem.size
290                 + " no longer matches AnnotationDirectoryItems table size "
291                 + annotationsDirectoryItems.size());
292           }
293           for (AnnotationsDirectoryItem annotationsDirectory : annotationsDirectoryItems) {
294             annotationsDirectory.write(file);
295           }
296           break;
297         default:
298           Log.errorAndQuit("Encountered unknown map item in map item list.");
299       }
300     }
301 
302     file.getOffsetTracker().updateOffsets(file);
303   }
304 
305   /**
306    * Given a DexRandomAccessFile, calculate the correct adler32 checksum for it.
307    */
calculateAdler32Checksum(DexRandomAccessFile file)308   private int calculateAdler32Checksum(DexRandomAccessFile file) throws IOException {
309     // Skip magic + checksum.
310     file.seek(12);
311     int a = 1;
312     int b = 0;
313     while (file.getFilePointer() < file.length()) {
314       a = (a + file.readUnsignedByte()) % 65521;
315       b = (b + a) % 65521;
316     }
317     return (b << 16) | a;
318   }
319 
320   /**
321    * Given a DexRandomAccessFile, update the file size, data size, and checksum.
322    */
updateHeader(DexRandomAccessFile file)323   public void updateHeader(DexRandomAccessFile file) throws IOException {
324     // File size must be updated before checksum.
325     int newFileSize = (int) file.length();
326     file.seek(32);
327     file.writeUInt(newFileSize);
328 
329     // Data size must be updated before checksum.
330     int newDataSize = newFileSize - header.dataOff.getNewPositionOfItem();
331     file.seek(104);
332     file.writeUInt(newDataSize);
333 
334     // Now update the checksum.
335     int newChecksum = calculateAdler32Checksum(file);
336     file.seek(8);
337     file.writeUInt(newChecksum);
338 
339     header.fileSize = newFileSize;
340     header.dataSize = newDataSize;
341     header.checksum = newChecksum;
342   }
343 
344   /**
345    * This should only be called from NewItemCreator.
346    */
getOffsetTracker()347   public OffsetTracker getOffsetTracker() {
348     return offsetTracker;
349   }
350 
351   @Override
incrementIndex(IndexUpdateKind kind, int insertedIdx)352   public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
353     for (TypeIdItem typeId : typeIds) {
354       typeId.incrementIndex(kind, insertedIdx);
355     }
356     for (ProtoIdItem protoId : protoIds) {
357       protoId.incrementIndex(kind, insertedIdx);
358     }
359     for (FieldIdItem fieldId : fieldIds) {
360       fieldId.incrementIndex(kind, insertedIdx);
361     }
362     for (MethodIdItem methodId : methodIds) {
363       methodId.incrementIndex(kind, insertedIdx);
364     }
365     for (ClassDefItem classDef : classDefs) {
366       classDef.incrementIndex(kind, insertedIdx);
367     }
368     for (ClassDataItem classData : classDatas) {
369       classData.incrementIndex(kind, insertedIdx);
370     }
371     if (typeLists != null) {
372       for (TypeList typeList : typeLists) {
373         typeList.incrementIndex(kind, insertedIdx);
374       }
375     }
376     for (CodeItem codeItem : codeItems) {
377       codeItem.incrementIndex(kind, insertedIdx);
378     }
379     if (annotationsDirectoryItems != null) {
380       for (AnnotationsDirectoryItem annotationsDirectoryItem : annotationsDirectoryItems) {
381         annotationsDirectoryItem.incrementIndex(kind, insertedIdx);
382       }
383     }
384     if (annotationItems != null) {
385       for (AnnotationItem annotationItem : annotationItems) {
386         annotationItem.incrementIndex(kind, insertedIdx);
387       }
388     }
389   }
390 }
391