| /* |
| * Copyright (C) 2014 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 dexfuzz.rawdex; |
| |
| import dexfuzz.Log; |
| |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.RandomAccessFile; |
| |
| /** |
| * An extension to RandomAccessFile that allows reading/writing |
| * DEX files in little-endian form, the variable-length LEB format |
| * and also provides word-alignment functions. |
| */ |
| public class DexRandomAccessFile extends RandomAccessFile { |
| private OffsetTracker offsetTracker; |
| |
| public OffsetTracker getOffsetTracker() { |
| return offsetTracker; |
| } |
| |
| public void setOffsetTracker(OffsetTracker offsetTracker) { |
| this.offsetTracker = offsetTracker; |
| } |
| |
| /** |
| * Constructor, passes straight on to RandomAccessFile currently. |
| * @param filename The file to open. |
| * @param mode Strings "r" or "rw" work best. |
| */ |
| public DexRandomAccessFile(String filename, String mode) |
| throws FileNotFoundException { |
| super(filename, mode); |
| } |
| |
| /** |
| * @return A 16-bit number, read from the file as little-endian. |
| */ |
| public short readUShort() throws IOException { |
| int b1 = readUnsignedByte(); |
| int b2 = readUnsignedByte(); |
| return (short) ((b2 << 8) | b1); |
| } |
| |
| /** |
| * @param value A 16-bit number to be written to the file in little-endian. |
| */ |
| public void writeUShort(short value) throws IOException { |
| int b1 = value & 0xff; |
| int b2 = (value & 0xff00) >> 8; |
| writeByte(b1); |
| writeByte(b2); |
| } |
| |
| /** |
| * @return A 32-bit number, read from the file as little-endian. |
| */ |
| public int readUInt() throws IOException { |
| int b1 = readUnsignedByte(); |
| int b2 = readUnsignedByte(); |
| int b3 = readUnsignedByte(); |
| int b4 = readUnsignedByte(); |
| return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1; |
| } |
| |
| /** |
| * @param value A 32-bit number to be written to the file in little-endian. |
| */ |
| public void writeUInt(int value) throws IOException { |
| int b1 = value & 0xff; |
| writeByte(b1); |
| int b2 = (value & 0xff00) >> 8; |
| writeByte(b2); |
| int b3 = (value & 0xff0000) >> 16; |
| writeByte(b3); |
| int b4 = (value & 0xff000000) >> 24; |
| writeByte(b4); |
| } |
| |
| /** |
| * @return An up to 32-bit number, read from the file in ULEB128 form. |
| */ |
| public int readUleb128() throws IOException { |
| int shift = 0; |
| int value = 0; |
| int rawByte = readUnsignedByte(); |
| boolean done = false; |
| while (!done) { |
| // Get the lower seven bits. |
| // 0x7f = 0111 1111 |
| value |= ((rawByte & 0x7f) << shift); |
| shift += 7; |
| // Check the 8th bit - if it's 0, we're done. |
| // 0x80 = 1000 0000 |
| if ((rawByte & 0x80) == 0) { |
| done = true; |
| } else { |
| rawByte = readUnsignedByte(); |
| } |
| } |
| return value; |
| } |
| |
| /** |
| * @param value A 32-bit number to be written to the file in ULEB128 form. |
| */ |
| public void writeUleb128(int value) throws IOException { |
| if (value == 0) { |
| writeByte(0); |
| return; |
| } |
| |
| while (value != 0) { |
| int marker = 1; |
| // If we're down to the last 7 bits, the marker will be 0. |
| if ((value & 0xffffff80) == 0) { |
| marker = 0; |
| } |
| // Get the lowest 7 bits, add on the marker in the high bit. |
| int nextByte = value & 0x7f | (marker << 7); |
| writeByte(nextByte); |
| value >>>= 7; |
| } |
| } |
| |
| /** |
| * Write out ULEB128 value always using 5 bytes. |
| * A version of ULEB128 that will always write out 5 bytes, because this |
| * value will be patched later, and if we used a smaller encoding, the new value |
| * may overflow the previously selected encoding size. |
| * The largest encoding for 0 in ULEB128 would be: |
| * 0x80 0x80 0x80 0x80 0x00 |
| * and for 1 would be: |
| * 0x81 0x80 0x80 0x80 0x00 |
| */ |
| public void writeLargestUleb128(int value) throws IOException { |
| Log.debug("Writing " + value + " using the largest possible ULEB128 encoding."); |
| if (value == 0) { |
| writeByte(0x80); |
| writeByte(0x80); |
| writeByte(0x80); |
| writeByte(0x80); |
| writeByte(0x0); |
| return; |
| } |
| |
| for (int i = 0; i < 5; i++) { |
| int marker = 1; |
| // If we're writing the 5th byte, the marker is 0. |
| if (i == 4) { |
| marker = 0; |
| } |
| // Get the lowest 7 bits, add on the marker in the high bit. |
| int nextByte = value & 0x7f | (marker << 7); |
| writeByte(nextByte); |
| value >>>= 7; |
| } |
| } |
| |
| /** |
| * @return An up to 32-bit number, read from the file in SLEB128 form. |
| */ |
| public int readSleb128() throws IOException { |
| int shift = 0; |
| int value = 0; |
| int rawByte = readUnsignedByte(); |
| boolean done = false; |
| boolean mustSignExtend = false; |
| while (!done) { |
| // Get the lower seven bits. |
| // 0x7f = 0111 1111 |
| value |= ((rawByte & 0x7f) << shift); |
| shift += 7; |
| // Check the 8th bit - if it's 0, we're done. |
| // 0x80 = 1000 0000 |
| if ((rawByte & 0x80) == 0) { |
| // Check the 7th bit - if it's a 1, we need to sign extend. |
| if ((rawByte & 0x60) != 0) { |
| mustSignExtend = true; |
| } |
| done = true; |
| } else { |
| rawByte = readUnsignedByte(); |
| } |
| } |
| if (mustSignExtend) { |
| // Example: |
| // say we shifted 7 bits, we need |
| // to make all the upper 25 bits 1s. |
| // load a 1... |
| // 00000000 00000000 00000000 00000001 |
| // << 7 |
| // 00000000 00000000 00000000 10000000 |
| // - 1 |
| // 00000000 00000000 00000000 01111111 |
| // ~ |
| // 11111111 11111111 11111111 10000000 |
| int upperOnes = ~((1 << shift) - 1); |
| value |= (upperOnes); |
| } |
| return value; |
| } |
| |
| /** |
| * @param value A 32-bit number to be written to the file in SLEB128 form. |
| */ |
| public void writeSleb128(int value) throws IOException { |
| if (value == 0) { |
| writeByte(0); |
| return; |
| } |
| if (value > 0) { |
| writeUleb128(value); |
| return; |
| } |
| if (value == -1) { |
| writeByte(0x7f); |
| } |
| |
| // When it's all 1s (0xffffffff), we're done! |
| while (value != 0xffffffff) { |
| int marker = 1; |
| // If we're down to the last 7 bits (i.e., shifting a further 7 is all 1s), |
| // the marker will be 0. |
| if ((value >> 7) == 0xffffffff) { |
| marker = 0; |
| } |
| // Get the lowest 7 bits, add on the marker in the high bit. |
| int nextByte = value & 0x7f | (marker << 7); |
| writeByte(nextByte); |
| value >>= 7; |
| } |
| } |
| |
| /** |
| * In DEX format, strings are in MUTF-8 format, the first ULEB128 value is the decoded size |
| * (i.e., string.length), and then follows a null-terminated series of characters. |
| * @param decodedSize The ULEB128 value that should have been read just before this. |
| * @return The raw bytes of the string, not including the null character. |
| */ |
| public byte[] readDexUtf(int decodedSize) throws IOException { |
| // In the dex MUTF-8, the encoded size can never be larger than 3 times |
| // the actual string's length (which is the ULEB128 value just before this |
| // string, the "decoded size") |
| |
| // Therefore, allocate as much space as we might need. |
| byte[] str = new byte[decodedSize * 3]; |
| |
| // Get our first byte. |
| int encodedSize = 0; |
| byte rawByte = readByte(); |
| |
| // Keep reading until we find the end marker. |
| while (rawByte != 0) { |
| str[encodedSize++] = rawByte; |
| rawByte = readByte(); |
| } |
| |
| // Copy everything we read into str into the correctly-sized actual string. |
| byte[] actualString = new byte[encodedSize]; |
| for (int i = 0; i < encodedSize; i++) { |
| actualString[i] = str[i]; |
| } |
| |
| return actualString; |
| } |
| |
| /** |
| * Writes out raw bytes that would have been read by readDexUTF(). |
| * Will automatically write out the null-byte at the end. |
| * @param data Bytes to be written out. |
| */ |
| public void writeDexUtf(byte[] data) throws IOException { |
| write(data); |
| // Remember to add the end marker. |
| writeByte(0); |
| } |
| |
| /** |
| * Align the file handle's seek pointer to the next N bytes. |
| * @param alignment N to align to. |
| */ |
| public void alignForwards(int alignment) throws IOException { |
| long offset = getFilePointer(); |
| long mask = alignment - 1; |
| if ((offset & mask) != 0) { |
| int extra = alignment - (int) (offset & mask); |
| seek(offset + extra); |
| } |
| } |
| |
| /** |
| * Align the file handle's seek pointer backwards to the previous N bytes. |
| * @param alignment N to align to. |
| */ |
| public void alignBackwards(int alignment) throws IOException { |
| long offset = getFilePointer(); |
| long mask = alignment - 1; |
| if ((offset & mask) != 0) { |
| offset &= (~mask); |
| seek(offset); |
| } |
| } |
| } |