/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.nifi.processors.evtx.parser; import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedLong; import org.apache.commons.io.Charsets; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; import java.util.Base64; import java.util.Date; /** * Class to simplify reading binary values from the evtx file */ public class BinaryReader { public static final long EPOCH_OFFSET = 11644473600000L; public static final int[][] INDEX_ARRAYS = new int[][]{{3, 2, 1, 0}, {5, 4}, {7, 6}, {8, 9}, {10, 11, 12, 13, 14, 15}}; private final byte[] bytes; private int position; /** * Constructs a binary reader with the given one's byte array and an arbitrary position * * @param binaryReader the source BinaryReader * @param position the new position */ public BinaryReader(BinaryReader binaryReader, int position) { this.bytes = binaryReader.bytes; this.position = position; } /** * Reads size bytes from the inputStream and creates a BinaryReader for them * * @param inputStream the input stream * @param size the number of bytes * @throws IOException if there is an error reading from the input stream */ public BinaryReader(InputStream inputStream, int size) throws IOException { byte[] bytes = new byte[size]; int read = 0; while (read < size) { read += inputStream.read(bytes, read, size - read); } this.bytes = bytes; this.position = 0; } /** * Creates a BinaryReader for the given bytes * * @param bytes the bytes */ public BinaryReader(byte[] bytes) { this.bytes = bytes; this.position = 0; } /** * Reads a single byte * * @return the byte's int value */ public int read() { return bytes[position++]; } /** * Returns the byte that would be read without changing the position * * @return the byte that would be read without changing the position */ public int peek() { return bytes[position]; } /** * Returns the next length bytes without changing the position * * @param length the number of bytes * @return the bytes */ public byte[] peekBytes(int length) { return Arrays.copyOfRange(bytes, position, position + length); } /** * Reads the next length bytes * * @param length the number of bytes * @return the bytes */ public byte[] readBytes(int length) { byte[] result = peekBytes(length); position += length; return result; } /** * Reads the next length bytes into the buf buffer at a given offset * * @param buf the buffer * @param offset the offset * @param length the number of bytes */ public void readBytes(byte[] buf, int offset, int length) { System.arraycopy(bytes, position, buf, offset, length); position += length; } /** * Reads an Evtx formatted guid (16 bytes arranged into the grouping described by INDEX_ARRAYS) * * @return the guid */ public String readGuid() { StringBuilder result = new StringBuilder(); int maxIndex = 0; for (int[] indexArray : INDEX_ARRAYS) { for (int index : indexArray) { maxIndex = Math.max(maxIndex, index); result.append(String.format("%02X", bytes[position + index]).toLowerCase()); } result.append("-"); } result.setLength(result.length() - 1); position += (maxIndex + 1); return result.toString(); } /** * Reads a string made up of single byte characters * * @param length the length * @return the string * @throws IOException if the String wasn't null terminated */ public String readString(int length) throws IOException { StringBuilder result = new StringBuilder(length - 1); boolean foundNull = false; int exclusiveLastIndex = position + length; for (int i = position; i < exclusiveLastIndex; i++) { byte b = bytes[i]; if (b == 0) { foundNull = true; break; } result.append((char) b); } if (!foundNull) { throw new IOException("Expected null terminated string"); } position += length; return result.toString(); } /** * Reads a string encoded with UTF_16LE of a given length * * @param length the number of characters * @return the string */ public String readWString(int length) { int numBytes = length * 2; String result = Charsets.UTF_16LE.decode(ByteBuffer.wrap(bytes, position, numBytes)).toString(); position += numBytes; return result; } /** * Reads 8 bytes in litte endian order and returns the UnsignedLong value * * @return the value */ public UnsignedLong readQWord() { ByteBuffer byteBuffer = ByteBuffer.wrap(bytes, position, 8); position += 8; return UnsignedLong.fromLongBits(byteBuffer.order(ByteOrder.LITTLE_ENDIAN).getLong()); } /** * Reads 4 bytes in little endian order and returns the UnsignedInteger value * * @return the value */ public UnsignedInteger readDWord() { ByteBuffer byteBuffer = ByteBuffer.wrap(bytes, position, 4); position += 4; return UnsignedInteger.fromIntBits(byteBuffer.order(ByteOrder.LITTLE_ENDIAN).getInt()); } /** * Reads 4 bytes in big endian order and returns the UnsignedInteger value * * @return the value */ public UnsignedInteger readDWordBE() { ByteBuffer byteBuffer = ByteBuffer.wrap(bytes, position, 4); position += 4; return UnsignedInteger.fromIntBits(byteBuffer.order(ByteOrder.BIG_ENDIAN).getInt()); } /** * Reads 2 bytes in little endian order and returns the int value * * @return the value */ public int readWord() { byte[] bytes = new byte[4]; readBytes(bytes, 0, 2); return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt(); } /** * Reads 2 bytes in big endian order and returns the int value * * @return the value */ public int readWordBE() { byte[] bytes = new byte[4]; readBytes(bytes, 2, 2); return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt(); } /** * Reads a timestamp that is the number of hundreds of nanoseconds since Jan 1 1601 * (see http://integriography.wordpress.com/2010/01/16/using-phython-to-parse-and-present-windows-64-bit-timestamps/) * * @return the date corresponding to the timestamp */ public Date readFileTime() { UnsignedLong hundredsOfNanosecondsSinceJan11601 = readQWord(); long millisecondsSinceJan11601 = hundredsOfNanosecondsSinceJan11601.dividedBy(UnsignedLong.valueOf(10000)).longValue(); long millisecondsSinceEpoch = millisecondsSinceJan11601 - EPOCH_OFFSET; return new Date(millisecondsSinceEpoch); } /** * Reads length bytes and Bas64 encodes them * * @param length the number of bytes * @return a Base64 encoded string */ public String readAndBase64EncodeBinary(int length) { return Base64.getEncoder().encodeToString(readBytes(length)); } /** * Skips bytes in the BinaryReader * * @param bytes the number of bytes to skip */ public void skip(int bytes) { position += bytes; } /** * Returns the current position of the BinaryReader * * @return the current position */ public int getPosition() { return position; } /** * Returns the backing byte array * * @return the byte array */ public byte[] getBytes() { return bytes; } }