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.FileNotFoundException;
22 import java.io.IOException;
23 import java.io.RandomAccessFile;
24 
25 /**
26  * An extension to RandomAccessFile that allows reading/writing
27  * DEX files in little-endian form, the variable-length LEB format
28  * and also provides word-alignment functions.
29  */
30 public class DexRandomAccessFile extends RandomAccessFile {
31   private OffsetTracker offsetTracker;
32 
getOffsetTracker()33   public OffsetTracker getOffsetTracker() {
34     return offsetTracker;
35   }
36 
setOffsetTracker(OffsetTracker offsetTracker)37   public void setOffsetTracker(OffsetTracker offsetTracker) {
38     this.offsetTracker = offsetTracker;
39   }
40 
41   /**
42    * Constructor, passes straight on to RandomAccessFile currently.
43    * @param filename The file to open.
44    * @param mode Strings "r" or "rw" work best.
45    */
DexRandomAccessFile(String filename, String mode)46   public DexRandomAccessFile(String filename, String mode)
47       throws FileNotFoundException {
48     super(filename, mode);
49   }
50 
51   /**
52    * @return A 16-bit number, read from the file as little-endian.
53    */
readUShort()54   public short readUShort() throws IOException {
55     int b1 = readUnsignedByte();
56     int b2 = readUnsignedByte();
57     return (short) ((b2 << 8) | b1);
58   }
59 
60   /**
61    * @param value A 16-bit number to be written to the file in little-endian.
62    */
writeUShort(short value)63   public void writeUShort(short value) throws IOException {
64     int b1 = value & 0xff;
65     int b2 = (value & 0xff00) >> 8;
66     writeByte(b1);
67     writeByte(b2);
68   }
69 
70   /**
71    * @return A 32-bit number, read from the file as little-endian.
72    */
readUInt()73   public int readUInt() throws IOException {
74     int b1 = readUnsignedByte();
75     int b2 = readUnsignedByte();
76     int b3 = readUnsignedByte();
77     int b4 = readUnsignedByte();
78     return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1;
79   }
80 
81   /**
82    * @param value A 32-bit number to be written to the file in little-endian.
83    */
writeUInt(int value)84   public void writeUInt(int value) throws IOException {
85     int b1 = value & 0xff;
86     writeByte(b1);
87     int b2 = (value & 0xff00) >> 8;
88     writeByte(b2);
89     int b3 = (value & 0xff0000) >> 16;
90     writeByte(b3);
91     int b4 = (value & 0xff000000) >> 24;
92     writeByte(b4);
93   }
94 
95   /**
96    * @return An up to 32-bit number, read from the file in ULEB128 form.
97    */
readUleb128()98   public int readUleb128() throws IOException {
99     int shift = 0;
100     int value = 0;
101     int rawByte = readUnsignedByte();
102     boolean done = false;
103     while (!done) {
104       // Get the lower seven bits.
105       // 0x7f = 0111 1111
106       value |= ((rawByte & 0x7f) << shift);
107       shift += 7;
108       // Check the 8th bit - if it's 0, we're done.
109       // 0x80 = 1000 0000
110       if ((rawByte & 0x80) == 0) {
111         done = true;
112       } else {
113         rawByte = readUnsignedByte();
114       }
115     }
116     return value;
117   }
118 
119   /**
120    * @param value A 32-bit number to be written to the file in ULEB128 form.
121    */
writeUleb128(int value)122   public void writeUleb128(int value) throws IOException {
123     if (value == 0) {
124       writeByte(0);
125       return;
126     }
127 
128     while (value != 0) {
129       int marker = 1;
130       // If we're down to the last 7 bits, the marker will be 0.
131       if ((value & 0xffffff80) == 0) {
132         marker = 0;
133       }
134       // Get the lowest 7 bits, add on the marker in the high bit.
135       int nextByte = value & 0x7f | (marker << 7);
136       writeByte(nextByte);
137       value >>>= 7;
138     }
139   }
140 
141   /**
142    * Write out ULEB128 value always using 5 bytes.
143    * A version of ULEB128 that will always write out 5 bytes, because this
144    * value will be patched later, and if we used a smaller encoding, the new value
145    * may overflow the previously selected encoding size.
146    * The largest encoding for 0 in ULEB128 would be:
147    *   0x80 0x80 0x80 0x80 0x00
148    * and for 1 would be:
149    *   0x81 0x80 0x80 0x80 0x00
150    */
writeLargestUleb128(int value)151   public void writeLargestUleb128(int value) throws IOException {
152     Log.debug("Writing " + value + " using the largest possible ULEB128 encoding.");
153     if (value == 0) {
154       writeByte(0x80);
155       writeByte(0x80);
156       writeByte(0x80);
157       writeByte(0x80);
158       writeByte(0x0);
159       return;
160     }
161 
162     for (int i = 0; i < 5; i++) {
163       int marker = 1;
164       // If we're writing the 5th byte, the marker is 0.
165       if (i == 4) {
166         marker = 0;
167       }
168       // Get the lowest 7 bits, add on the marker in the high bit.
169       int nextByte = value & 0x7f | (marker << 7);
170       writeByte(nextByte);
171       value >>>= 7;
172     }
173   }
174 
175   /**
176    * @return An up to 32-bit number, read from the file in SLEB128 form.
177    */
readSleb128()178   public int readSleb128() throws IOException {
179     int shift = 0;
180     int value = 0;
181     int rawByte = readUnsignedByte();
182     boolean done = false;
183     boolean mustSignExtend = false;
184     while (!done) {
185       // Get the lower seven bits.
186       // 0x7f = 0111 1111
187       value |= ((rawByte & 0x7f) << shift);
188       shift += 7;
189       // Check the 8th bit - if it's 0, we're done.
190       // 0x80 = 1000 0000
191       if ((rawByte & 0x80) == 0) {
192         // Check the 7th bit - if it's a 1, we need to sign extend.
193         if ((rawByte & 0x60) != 0) {
194           mustSignExtend = true;
195         }
196         done = true;
197       } else {
198         rawByte = readUnsignedByte();
199       }
200     }
201     if (mustSignExtend) {
202       // Example:
203       // say we shifted 7 bits, we need
204       // to make all the upper 25 bits 1s.
205       // load a 1...
206       // 00000000 00000000 00000000 00000001
207       // << 7
208       // 00000000 00000000 00000000 10000000
209       // - 1
210       // 00000000 00000000 00000000 01111111
211       // ~
212       // 11111111 11111111 11111111 10000000
213       int upperOnes = ~((1 << shift) - 1);
214       value |= (upperOnes);
215     }
216     return value;
217   }
218 
219   /**
220    * @param value A 32-bit number to be written to the file in SLEB128 form.
221    */
writeSleb128(int value)222   public void writeSleb128(int value) throws IOException {
223     if (value == 0) {
224       writeByte(0);
225       return;
226     }
227     if (value > 0) {
228       writeUleb128(value);
229       return;
230     }
231     if (value == -1) {
232       writeByte(0x7f);
233     }
234 
235     // When it's all 1s (0xffffffff), we're done!
236     while (value != 0xffffffff) {
237       int marker = 1;
238       // If we're down to the last 7 bits (i.e., shifting a further 7 is all 1s),
239       // the marker will be 0.
240       if ((value >> 7) == 0xffffffff) {
241         marker = 0;
242       }
243       // Get the lowest 7 bits, add on the marker in the high bit.
244       int nextByte = value & 0x7f | (marker << 7);
245       writeByte(nextByte);
246       value >>= 7;
247     }
248   }
249 
250   /**
251    * In DEX format, strings are in MUTF-8 format, the first ULEB128 value is the decoded size
252    * (i.e., string.length), and then follows a null-terminated series of characters.
253    * @param decodedSize The ULEB128 value that should have been read just before this.
254    * @return The raw bytes of the string, not including the null character.
255    */
readDexUtf(int decodedSize)256   public byte[] readDexUtf(int decodedSize) throws IOException {
257     // In the dex MUTF-8, the encoded size can never be larger than 3 times
258     // the actual string's length (which is the ULEB128 value just before this
259     // string, the "decoded size")
260 
261     // Therefore, allocate as much space as we might need.
262     byte[] str = new byte[decodedSize * 3];
263 
264     // Get our first byte.
265     int encodedSize = 0;
266     byte rawByte = readByte();
267 
268     // Keep reading until we find the end marker.
269     while (rawByte != 0) {
270       str[encodedSize++] = rawByte;
271       rawByte = readByte();
272     }
273 
274     // Copy everything we read into str into the correctly-sized actual string.
275     byte[] actualString = new byte[encodedSize];
276     for (int i = 0; i < encodedSize; i++) {
277       actualString[i] = str[i];
278     }
279 
280     return actualString;
281   }
282 
283   /**
284    * Writes out raw bytes that would have been read by readDexUTF().
285    * Will automatically write out the null-byte at the end.
286    * @param data Bytes to be written out.
287    */
writeDexUtf(byte[] data)288   public void writeDexUtf(byte[] data) throws IOException {
289     write(data);
290     // Remember to add the end marker.
291     writeByte(0);
292   }
293 
294   /**
295    * Align the file handle's seek pointer to the next N bytes.
296    * @param alignment N to align to.
297    */
alignForwards(int alignment)298   public void alignForwards(int alignment) throws IOException {
299     long offset = getFilePointer();
300     long mask = alignment - 1;
301     if ((offset & mask) != 0) {
302       int extra = alignment - (int) (offset & mask);
303       seek(offset + extra);
304     }
305   }
306 
307   /**
308    * Align the file handle's seek pointer backwards to the previous N bytes.
309    * @param alignment N to align to.
310    */
alignBackwards(int alignment)311   public void alignBackwards(int alignment) throws IOException {
312     long offset = getFilePointer();
313     long mask = alignment - 1;
314     if ((offset & mask) != 0) {
315       offset &= (~mask);
316       seek(offset);
317     }
318   }
319 }
320