Merge remote-tracking branch

'origin/GP-414_jmlagor_Process_and_display_the_CustomAttrib_blob_type'

Conflicts:
	Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/streams/CliStreamBlob.java
This commit is contained in:
ghidra1
2021-02-22 11:20:43 -05:00
3 changed files with 521 additions and 10 deletions
@@ -0,0 +1,475 @@
/* ###
* IP: GHIDRA
*
* 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 ghidra.app.util.bin.format.pe.cli.blobs;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.pe.cli.blobs.CliAbstractSig.CliElementType;
import ghidra.app.util.bin.format.pe.cli.blobs.CliAbstractSig.CliParam;
import ghidra.app.util.bin.format.pe.cli.streams.CliStreamMetadata;
import ghidra.app.util.bin.format.pe.cli.tables.CliAbstractTableRow;
import ghidra.app.util.bin.format.pe.cli.tables.CliTableCustomAttribute.CliCustomAttributeRow;
import ghidra.app.util.bin.format.pe.cli.tables.CliTableMemberRef.CliMemberRefRow;
import ghidra.app.util.bin.format.pe.cli.tables.CliTableMethodDef.CliMethodDefRow;
import ghidra.app.util.bin.format.pe.cli.tables.CliTypeTable;
import ghidra.app.util.bin.format.pe.cli.tables.indexes.CliIndexCustomAttributeType;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
import ghidra.util.exception.InvalidInputException;
public class CliBlobCustomAttrib extends CliBlob {
private CliFixedArg[] fixedArgs;
private CliNamedArg[] namedArgs;
private short numNamed;
// Fixed constants for validating the structure
private static final short CLIBLOBCUSTOMATTRIB_PROLOG = 0x0001;
private static final byte CLIBLOBCUSTOMATTRIB_TYPE_FIELD = 0x53;
private static final byte CLIBLOBCUSTOMATTRIB_TYPE_PROPERTY = 0x54;
private static final int CLIBLOBCUSTOMATTRIB_STRING_BOUNDARY_128 = 0x80;
private static final int CLIBLOBCUSTOMATTRIB_STRING_BOUNDARY_192 = 0xC0;
private static final int CLIBLOBCUSTOMATTRIB_STRING_SIZE_ONE = 0x01;
private static final int CLIBLOBCUSTOMATTRIB_STRING_SIZE_TWO = 0x02;
private static final int CLIBLOBCUSTOMATTRIB_STRING_SIZE_FOUR = 0x03;
private static final int CLIBLOBCUSTOMATTRIB_STRING_SIZE_BITMASK = 0x3F;
private static final int CLIBLOBCUSTOMATTRIB_STRING_INDICATOR_SHIFT = 0x06;
private static final int CLIBLOBCUSTOMATTRIB_STRING_INDICATOR_BITMASK = 0x03;
// UTF-8 boundaries that help detect the end of a string where
// lengths aren't specified in FixedArg
private static final int CLIBLOBCUSTOMATTRIB_UTF8_LOW = 0x1F;
private static final int CLIBLOBCUSTOMATTRIB_UTF8_HIGH = 0x7F;
private class CliFixedArg {
private CliElementType elem;
private Object value;
public CliFixedArg(CliElementType elem, Object value) {
this.elem = elem;
this.value = value;
}
public CliElementType getElem() {
return elem;
}
public Object getValue() {
return value;
}
}
private class CliNamedArg {
private int fieldOrProp;
private CliElementType fieldOrPropType;
private String fieldOrPropName;
public CliNamedArg(int fieldOrProp, CliElementType fieldOrPropType,
String fieldOrPropName) {
this.fieldOrProp = fieldOrProp;
this.fieldOrPropType = fieldOrPropType;
this.fieldOrPropName = fieldOrPropName;
}
public int getFieldOrProp() {
return fieldOrProp;
}
public CliElementType getFieldOrPropType() {
return fieldOrPropType;
}
public String getFieldOrPropName() {
return fieldOrPropName;
}
}
public CliBlobCustomAttrib(CliBlob blob, CliCustomAttributeRow row,
CliStreamMetadata metadataStream) throws IOException {
super(blob);
BinaryReader reader = blob.getContentsReader();
// Validate the blob prolog
short prolog = reader.readNextShort();
if (prolog != CLIBLOBCUSTOMATTRIB_PROLOG) {
Msg.warn(this,
getName() + " had unexpected prolog (0x" + Integer.toHexString(prolog) + ")");
return;
}
// The location in the blob table for the actual CustomAttrib blob
int valueIndex = row.valueIndex;
// The entry in the MethodRef or MethodDef table that corresponds to the method
// This is a CustomAttributeType coded index
int typeIndex = row.typeIndex;
// The entry of the parent table index, also a CustomAttributeType coded index
int parentIndex = row.parentIndex;
// The FixedArg parameters in the CustomAttrib blob are stored concatenated
// against each other without delimeters or type indicators, so you have to look
// back to the originating method signature to figure out what's what.
CliParam[] params = null;
try {
// Get the table type and row for the attribute and depending on the type
// get the parameters
CliTypeTable tableType = CliIndexCustomAttributeType.getTableName(typeIndex);
int tableRowIndex = CliIndexCustomAttributeType.getRowIndex(typeIndex);
CliAbstractTableRow tableRow = metadataStream.getTable(tableType).getRow(tableRowIndex);
if (tableType == CliTypeTable.MemberRef) {
CliMemberRefRow memberRefRow = (CliMemberRefRow) tableRow;
CliBlob memberRefBlob =
metadataStream.getBlobStream().getBlob(memberRefRow.signatureIndex);
CliSigMethodRef methodRefSig = new CliSigMethodRef(memberRefBlob);
params = methodRefSig.getParams();
}
else if (tableType == CliTypeTable.MethodDef) {
CliMethodDefRow methodDefRow = (CliMethodDefRow) tableRow;
CliBlob methodDefBlob =
metadataStream.getBlobStream().getBlob(methodDefRow.sigIndex);
CliSigMethodDef methodDefSig = new CliSigMethodDef(methodDefBlob);
params = methodDefSig.getParamTypes();
}
}
catch (InvalidInputException e) {
Msg.warn(this, "Unable to process the parameters in " + getName());
return;
}
// Process zero to multiple FixedArgs
fixedArgs = processFixedArgs(reader, params).toArray(CliFixedArg[]::new);
// Process zero to multiple NamedArgs here
namedArgs = processNamedArgs(reader).toArray(CliNamedArg[]::new);
}
@Override
public DataType getContentsDataType() {
StructureDataType struct = new StructureDataType(new CategoryPath(PATH), getName(), 0);
struct.add(WORD, "PROLOG", "Magic (0x0001)");
// Display the FixedArgs
if (fixedArgs != null) {
for (int i = 0; i < fixedArgs.length; i++) {
CliElementType elem = fixedArgs[i].elem;
switch (elem) {
case ELEMENT_TYPE_CHAR:
struct.add(UTF16, "FixedArg_" + i, "Elem (" + fixedArgs[i].getElem() + ")");
break;
case ELEMENT_TYPE_I1:
case ELEMENT_TYPE_U1:
case ELEMENT_TYPE_BOOLEAN:
struct.add(BYTE, "FixedArg_" + i, "Elem (" + fixedArgs[i].getElem() + ")");
break;
case ELEMENT_TYPE_I2:
case ELEMENT_TYPE_U2:
struct.add(WORD, "FixedArg_" + i, "Elem (" + fixedArgs[i].getElem() + ")");
break;
case ELEMENT_TYPE_I4:
case ELEMENT_TYPE_U4:
case ELEMENT_TYPE_R4:
case ELEMENT_TYPE_VALUETYPE:
struct.add(DWORD, "FixedArg_" + i, "Elem (" + fixedArgs[i].getElem() + ")");
break;
case ELEMENT_TYPE_I8:
case ELEMENT_TYPE_U8:
case ELEMENT_TYPE_R8:
struct.add(QWORD, "FixedArg_" + i, "Elem (" + fixedArgs[i].getElem() + ")");
case ELEMENT_TYPE_STRING:
String s = (String) fixedArgs[i].value;
int l = s.length();
if (l < CLIBLOBCUSTOMATTRIB_STRING_BOUNDARY_128) {
struct.add(BYTE, "PackedLen", "");
}
else if (l < CLIBLOBCUSTOMATTRIB_STRING_BOUNDARY_192) {
struct.add(WORD, "PackedLen", "");
}
else {
struct.add(DWORD, "PackedLen", "");
}
struct.add(UTF8, ((String) fixedArgs[i].value).length(), "FixedArg_" + i,
"");
break;
case ELEMENT_TYPE_I:
struct.add(BYTE, "ELEMENT_TYPE_I", "");
struct.add(UTF8, ((String) fixedArgs[i].value).length(), "FixedArg_" + i,
"");
break;
default:
Msg.warn(this, "Unprocessed FixedArg element type in CustomAttr #" +
(i + 1) + ": " + fixedArgs[i].getElem().name());
break;
}
}
}
struct.add(WORD, "NumNamed", "Number of NamedArgs to follow");
// Display the NamedArgs
if (namedArgs != null) {
for (CliNamedArg cliNamedArg : namedArgs) {
int fieldOrProp = cliNamedArg.getFieldOrProp();
if (fieldOrProp == CLIBLOBCUSTOMATTRIB_TYPE_FIELD) {
struct.add(BYTE, "FieldOrProp", "FIELD");
}
else if (fieldOrProp == CLIBLOBCUSTOMATTRIB_TYPE_PROPERTY) {
struct.add(BYTE, "FieldOrProp", "PROPERTY");
}
else {
struct.add(BYTE, "FieldOrProp", "Unknown value");
}
struct.add(BYTE, "FieldOrPropType", cliNamedArg.getFieldOrPropType().name());
int nameLen = cliNamedArg.getFieldOrPropName().length();
if (nameLen < CLIBLOBCUSTOMATTRIB_STRING_BOUNDARY_128) {
struct.add(BYTE, "PackedLen", "");
}
else if (nameLen < CLIBLOBCUSTOMATTRIB_STRING_BOUNDARY_192) {
struct.add(WORD, "PackedLen", "");
}
else {
struct.add(DWORD, "PackedLen", "");
}
struct.add(UTF8, nameLen, "FieldOrPropName", "");
}
}
return struct;
}
@Override
public String getContentsName() {
return "CustomAttrib";
}
@Override
public String getContentsComment() {
return "A CustomAttrib blob stores values of fixed or named parameters supplied when " +
"instantiating a custom attribute";
}
@Override
public String getRepresentation() {
return "Blob (" + getContentsDataType().getDisplayName() + ")";
}
// SerStrings ("serialized strings") have a length field that varies in size
// based on the length of the string. This measures and decodes the Byte, Word,
// or DWord length field and returns it.
private int readSerStringLength(BinaryReader reader) throws IOException {
byte[] lengthBytes;
int length = 0;
ByteBuffer buf;
// The first byte is either the size or an indicator that we have more
// size bytes ahead. Values contained in more than one bytes are stored
// big-endian.
byte firstByte = reader.readNextByte();
// Shift the highest two bits to the bottom and mask off to detect the
// size of the field holding the size of the string (1, 2, or 4 bytes),
// then cut the indicator bits off the first byte of the length.
byte stringSizeIndicator = (byte) (firstByte >> CLIBLOBCUSTOMATTRIB_STRING_INDICATOR_SHIFT &
CLIBLOBCUSTOMATTRIB_STRING_INDICATOR_BITMASK);
firstByte = (byte) (firstByte & CLIBLOBCUSTOMATTRIB_STRING_SIZE_BITMASK);
if (stringSizeIndicator <= CLIBLOBCUSTOMATTRIB_STRING_SIZE_ONE) {
length = firstByte;
}
else if (stringSizeIndicator == CLIBLOBCUSTOMATTRIB_STRING_SIZE_TWO) {
lengthBytes = new byte[] { firstByte, reader.readNextByte() };
// Convert from big-endian
buf = ByteBuffer.wrap(lengthBytes);
buf.order(ByteOrder.BIG_ENDIAN);
length = buf.getShort();
}
else if (stringSizeIndicator == CLIBLOBCUSTOMATTRIB_STRING_SIZE_FOUR) {
lengthBytes = new byte[] { firstByte, reader.readNextByte(), reader.readNextByte(),
reader.readNextByte() };
// Convert from big-endian
buf = ByteBuffer.wrap(lengthBytes);
buf.order(ByteOrder.BIG_ENDIAN);
length = buf.getInt();
}
return length;
}
private ArrayList<CliFixedArg> processFixedArgs(BinaryReader reader, CliParam[] params)
throws IOException {
ArrayList<CliFixedArg> processFixedArgs = new ArrayList<>();
if (params == null) {
return processFixedArgs;
}
for (CliParam param : params) {
byte elemByte = reader.peekNextByte();
if (elemByte == CliElementType.ELEMENT_TYPE_I.id()) {
reader.readNextByte();
// IntPtr followed by a string of the name of the element, the
// length of which is not specified and must be read until a
// non-printable UTF-8 character is encountered to signal the
// end of the name
StringBuilder sb = new StringBuilder();
while (((reader.peekNextByte() &
CLIBLOBCUSTOMATTRIB_UTF8_HIGH) > CLIBLOBCUSTOMATTRIB_UTF8_LOW) &&
((reader.peekNextByte() &
CLIBLOBCUSTOMATTRIB_UTF8_HIGH) < CLIBLOBCUSTOMATTRIB_UTF8_HIGH)) {
sb.append((char) reader.readNextByte());
}
processFixedArgs.add(new CliFixedArg(CliElementType.ELEMENT_TYPE_I, sb.toString()));
}
else {
// Process Elem types
CliElementType baseTypeCode = param.getType().baseTypeCode;
switch (baseTypeCode) {
case ELEMENT_TYPE_BOOLEAN:
addFixedArg(processFixedArgs, baseTypeCode, reader.readNextByte());
break;
case ELEMENT_TYPE_CHAR:
addFixedArg(processFixedArgs, baseTypeCode, reader.readNextShort());
break;
case ELEMENT_TYPE_I1:
addFixedArg(processFixedArgs, baseTypeCode, reader.readNextByte());
break;
case ELEMENT_TYPE_U1:
addFixedArg(processFixedArgs, baseTypeCode, reader.readNextUnsignedByte());
break;
case ELEMENT_TYPE_I2:
addFixedArg(processFixedArgs, baseTypeCode, reader.readNextShort());
break;
case ELEMENT_TYPE_U2:
addFixedArg(processFixedArgs, baseTypeCode, reader.readNextUnsignedShort());
break;
case ELEMENT_TYPE_I4:
addFixedArg(processFixedArgs, baseTypeCode, reader.readNextInt());
break;
case ELEMENT_TYPE_U4:
addFixedArg(processFixedArgs, baseTypeCode, reader.readNextUnsignedInt());
break;
case ELEMENT_TYPE_I8:
addFixedArg(processFixedArgs, baseTypeCode, reader.readNextByte());
processFixedArgs.add(
new CliFixedArg(param.getType().baseTypeCode, reader.readNextLong()));
break;
case ELEMENT_TYPE_U8:
addFixedArg(processFixedArgs, baseTypeCode,
reader.readNextByteArray(LongLongDataType.dataType.getLength()));
break;
case ELEMENT_TYPE_R4:
addFixedArg(processFixedArgs, baseTypeCode,
reader.readNextByteArray(Float4DataType.dataType.getLength()));
break;
case ELEMENT_TYPE_R8:
addFixedArg(processFixedArgs, baseTypeCode,
reader.readNextByteArray(Float8DataType.dataType.getLength()));
break;
case ELEMENT_TYPE_STRING:
int length = readSerStringLength(reader);
if (length > 0) {
addFixedArg(processFixedArgs, baseTypeCode, new String(
reader.readNextByteArray(length), StandardCharsets.UTF_8));
}
break;
case ELEMENT_TYPE_VALUETYPE:
addFixedArg(processFixedArgs, baseTypeCode, reader.readNextInt());
break;
default:
Msg.info(this,
"A CustomAttrib with an unprocessed element type was deteceted: " +
param.getRepresentation());
}
}
}
return processFixedArgs;
}
private void addFixedArg(ArrayList<CliFixedArg> fixedArgs, CliElementType baseTypeCode,
Object value) {
fixedArgs.add(new CliFixedArg(baseTypeCode, value));
}
private ArrayList<CliNamedArg> processNamedArgs(BinaryReader reader) throws IOException {
numNamed = reader.readNextShort();
// Process zero to multiple NamedArgs here
ArrayList<CliNamedArg> processNamedArgs = new ArrayList<>();
for (int i = 0; i < numNamed; i++) {
byte fieldOrProp = reader.readNextByte();
if ((fieldOrProp != CLIBLOBCUSTOMATTRIB_TYPE_FIELD) &&
fieldOrProp != CLIBLOBCUSTOMATTRIB_TYPE_PROPERTY) {
Msg.warn(this, "Invalid FieldOrProp value in NamedArg #" + (i + 1) + ": 0x" +
Integer.toHexString(fieldOrProp));
continue;
}
CliElementType fieldOrPropType = CliElementType.fromInt(reader.readNextByte());
// +1 to account for the null terminator
int nameLen = readSerStringLength(reader) + 1;
String fieldOrPropName =
new String(reader.readNextByteArray(nameLen), StandardCharsets.UTF_8);
processNamedArgs.add(new CliNamedArg(fieldOrProp, fieldOrPropType, fieldOrPropName));
}
return processNamedArgs;
}
}
@@ -435,7 +435,7 @@ public class CliStreamMetadata extends CliAbstractStream {
table.markup(program, isBinary, monitor, log, ntHeader);
}
catch (Exception e) {
Msg.error(this, "Failed to markup " + table);
Msg.error(this, "Failed to markup " + table + ": " + e.toString());
}
}
}
@@ -18,22 +18,30 @@ package ghidra.app.util.bin.format.pe.cli.tables;
import java.io.IOException;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.pe.cli.streams.CliStreamMetadata;
import ghidra.app.util.bin.format.pe.NTHeader;
import ghidra.app.util.bin.format.pe.cli.blobs.CliBlobCustomAttrib;
import ghidra.app.util.bin.format.pe.cli.streams.*;
import ghidra.app.util.bin.format.pe.cli.tables.indexes.CliIndexCustomAttributeType;
import ghidra.app.util.bin.format.pe.cli.tables.indexes.CliIndexHasCustomAttribute;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.data.StructureDataType;
import ghidra.program.model.listing.Program;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
/**
* Describes the CustomAttribute table.
* Describes the CustomAttribute table.
*/
public class CliTableCustomAttribute extends CliAbstractTable {
public class CliCustomAttributeRow extends CliAbstractTableRow {
public int parentIndex;
public int typeIndex;
public int valueIndex;
public CliCustomAttributeRow(int parentIndex, int typeIndex, int valueIndex) {
super();
this.parentIndex = parentIndex;
@@ -45,13 +53,17 @@ public class CliTableCustomAttribute extends CliAbstractTable {
public String getRepresentation() {
String parentRep, typeRep;
try {
parentRep = getRowRepresentationSafe(CliIndexHasCustomAttribute.getTableName(parentIndex), CliIndexHasCustomAttribute.getRowIndex(parentIndex));
parentRep =
getRowRepresentationSafe(CliIndexHasCustomAttribute.getTableName(parentIndex),
CliIndexHasCustomAttribute.getRowIndex(parentIndex));
}
catch (InvalidInputException e) {
parentRep = Integer.toHexString(parentIndex);
}
try {
typeRep = getRowRepresentationSafe(CliIndexCustomAttributeType.getTableName(parentIndex), CliIndexCustomAttributeType.getRowIndex(parentIndex));
typeRep =
getRowRepresentationSafe(CliIndexCustomAttributeType.getTableName(parentIndex),
CliIndexCustomAttributeType.getRowIndex(parentIndex));
}
catch (InvalidInputException e) {
typeRep = Integer.toHexString(typeIndex);
@@ -59,23 +71,47 @@ public class CliTableCustomAttribute extends CliAbstractTable {
return String.format("Parent %s Type %s Value %x", parentRep, typeRep, valueIndex);
}
}
public CliTableCustomAttribute(BinaryReader reader, CliStreamMetadata stream, CliTypeTable tableId) throws IOException {
public CliTableCustomAttribute(BinaryReader reader, CliStreamMetadata stream,
CliTypeTable tableId) throws IOException {
super(reader, stream, tableId);
for (int i = 0; i < this.numRows; i++) {
CliCustomAttributeRow row = new CliCustomAttributeRow(CliIndexHasCustomAttribute.readCodedIndex(reader, stream),
CliCustomAttributeRow row = new CliCustomAttributeRow(
CliIndexHasCustomAttribute.readCodedIndex(reader, stream),
CliIndexCustomAttributeType.readCodedIndex(reader, stream), readBlobIndex(reader));
rows.add(row);
blobs.add(row.valueIndex);
}
reader.setPointerIndex(this.readerOffset);
}
@Override
public StructureDataType getRowDataType() {
StructureDataType rowDt = new StructureDataType(new CategoryPath(PATH), "CustomAttribute Row", 0);
StructureDataType rowDt =
new StructureDataType(new CategoryPath(PATH), "CustomAttribute Row", 0);
rowDt.add(CliIndexHasCustomAttribute.toDataType(metadataStream), "Parent", null);
rowDt.add(CliIndexCustomAttributeType.toDataType(metadataStream), "Type", null);
rowDt.add(metadataStream.getBlobIndexDataType(), "Value", null);
return rowDt;
}
@Override
public void markup(Program program, boolean isBinary, TaskMonitor monitor, MessageLog log,
NTHeader ntHeader)
throws DuplicateNameException, CodeUnitInsertionException, IOException {
CliStreamBlob blobStream = metadataStream.getBlobStream();
for (CliAbstractTableRow row : rows) {
CliCustomAttributeRow customRow = (CliCustomAttributeRow) row;
int valueIndex = customRow.valueIndex;
Address addr = CliAbstractStream.getStreamMarkupAddress(program, isBinary, monitor, log,
ntHeader, blobStream, valueIndex);
// Create CustomAttrib Blob object
CliBlobCustomAttrib blob =
new CliBlobCustomAttrib(blobStream.getBlob(valueIndex), customRow, metadataStream);
blobStream.updateBlob(blob, addr, program);
}
}
}