libifps: allow reading invalid operands

this should allow disassembling some malware samples (etc)
This commit is contained in:
zc 2023-03-31 12:27:42 +01:00
parent 9a8df96f7c
commit 58e78597e7
5 changed files with 78 additions and 27 deletions

View File

@ -24,6 +24,10 @@ namespace IFPSLib.Emit
/// <summary>
/// Operand refers to an element of an indexed variable, where the index is specified as a local or global variable, or an argument.
/// </summary>
IndexedVariable
IndexedVariable,
/// <summary>
/// Operand is invalid. Stops execution.
/// </summary>
Invalid = 0xff
}
}

View File

@ -5,6 +5,8 @@ using System.IO;
using System.Text;
using System.Linq;
using System.Collections;
using System.Net;
using System.Data;
namespace IFPSLib.Emit
{
@ -380,6 +382,31 @@ namespace IFPSLib.Emit
return FixBranchOffset(br, br.Read<uint>());
}
private static T TryReadIndex<T>(BinaryReader br, IList<T> list)
{
var index = (int)br.Read<uint>();
if (index < list.Count) return list[index];
return default;
}
private static Operand TryReadFunction(BinaryReader br, Script script)
{
var func = TryReadIndex(br, script.Functions);
if (func == null) func = new ScriptFunction()
{
Name = "INVALID",
Arguments = new List<FunctionArgument>()
};
return Operand.Create(func);
}
private static Operand TryReadType(BinaryReader br, Script script)
{
var type = TryReadIndex(br, script.Types);
if (type == null) type = UnknownType.Instance;
return Operand.Create(type);
}
internal static Instruction Load(BinaryReader br, Script script, ScriptFunction function)
{
var ret = new Instruction();
@ -437,10 +464,10 @@ namespace IFPSLib.Emit
ret.m_Operands = new List<Operand>(2) { new Operand(new TypedData(InstructionType.Instance, FixBranchOffset(br, brOffset))), valOp };
break;
case OperandType.InlineFunction:
ret.m_Operands = new List<Operand>(1) { Operand.Create(script.Functions[(int)br.Read<uint>()]) };
ret.m_Operands = new List<Operand>(1) { TryReadFunction(br, script) };
break;
case OperandType.InlineType:
ret.m_Operands = new List<Operand>(1) { Operand.Create(script.Types[(int)br.Read<uint>()]) };
ret.m_Operands = new List<Operand>(1) { TryReadType(br, script) };
break;
case OperandType.InlineCmpValue:
ret.m_Operands = new List<Operand>(3) { Operand.LoadValue(br, script, function), Operand.LoadValue(br, script, function), Operand.LoadValue(br, script, function) };
@ -449,7 +476,7 @@ namespace IFPSLib.Emit
ret.m_Operands = new List<Operand>(3) {
Operand.LoadValue(br, script, function),
Operand.LoadValue(br, script, function),
Operand.Create(script.Types[(int)br.Read<uint>()])
TryReadType(br, script)
};
break;
case OperandType.InlineEH:
@ -466,7 +493,7 @@ namespace IFPSLib.Emit
break;
case OperandType.InlineTypeVariable:
ret.m_Operands = new List<Operand>(2) {
Operand.Create(script.Types[(int)br.Read<uint>()]),
TryReadType(br, script),
new Operand(VariableBase.Load(br, script, function))
};
return ret;

View File

@ -105,6 +105,12 @@ namespace IFPSLib.Emit
m_Value = op.m_Value;
}
private Operand(Exception ex = null)
{
m_Type = BytecodeOperandType.Invalid;
if (ex != null) m_Value.Immediate = new TypedData(Types.UnknownType.Instance, ex);
}
public static Operand Create<TType>(Types.PrimitiveType type, TType value)
{
return new Operand(TypedData.Create(type, value));
@ -156,31 +162,39 @@ namespace IFPSLib.Emit
return new Operand(arr, varIdx);
}
public static Operand CreateInvalid(Exception ex = null)
{
return new Operand(ex);
}
internal static Operand LoadValue(BinaryReader br, Script script, ScriptFunction function)
{
var type = (BytecodeOperandType)br.ReadByte();
switch (type)
try
{
case BytecodeOperandType.Variable:
return new Operand(VariableBase.Load(br, script, function));
case BytecodeOperandType.Immediate:
return new Operand(TypedData.Load(br, script));
case BytecodeOperandType.IndexedImmediate:
{
var variable = VariableBase.Load(br, script, function);
var idx = br.Read<uint>();
return Create(variable, idx);
}
case BytecodeOperandType.IndexedVariable:
{
var variable = VariableBase.Load(br, script, function);
var idx = VariableBase.Load(br, script, function);
return new Operand(variable, idx);
}
default:
throw new ArgumentOutOfRangeException(string.Format("Invalid operand type {0}", (byte)type));
}
switch (type)
{
case BytecodeOperandType.Variable:
return new Operand(VariableBase.Load(br, script, function));
case BytecodeOperandType.Immediate:
return new Operand(TypedData.Load(br, script));
case BytecodeOperandType.IndexedImmediate:
{
var variable = VariableBase.Load(br, script, function);
var idx = br.Read<uint>();
return Create(variable, idx);
}
case BytecodeOperandType.IndexedVariable:
{
var variable = VariableBase.Load(br, script, function);
var idx = VariableBase.Load(br, script, function);
return new Operand(variable, idx);
}
default:
throw new ArgumentOutOfRangeException(string.Format("Invalid operand type {0}", (byte)type));
}
} catch (Exception ex) { return new Operand(ex); }
}
public override string ToString()
@ -195,6 +209,8 @@ namespace IFPSLib.Emit
return String.Format("{0}[{1}]", IndexedVariable.Name, IndexImmediate);
case BytecodeOperandType.IndexedVariable:
return String.Format("{0}[{1}]", IndexedVariable.Name, IndexVariable.Name);
case BytecodeOperandType.Invalid:
return "$INVALID";
default:
return "";
}

View File

@ -313,7 +313,11 @@ namespace IFPSLib
switch (Type.BaseType)
{
case PascalTypeCode.Type:
return ValueAs<IType>().Name;
{
var type = ValueAs<IType>();
if (type is UnknownType) return "$UNKNOWN";
return type.Name;
}
case PascalTypeCode.Instruction:
if (Value == null) return "null";
return string.Format("loc_{0}", ValueAs<Emit.Instruction>().Offset.ToString("x"));

View File

@ -19,7 +19,7 @@ Library implementing an assembler for IFPS scripts.
Depends on IFPSLib.
Assembling the output of IFPSLib's disassembler is expected to output an identical binary, not doing so is considered a bug.
Assembling the output of IFPSLib's disassembler (for sane bytecode with no invalid instructions/operands) is expected to output an identical binary, not doing so is considered a bug.
## ifpsdasm