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> /// <summary>
/// Operand refers to an element of an indexed variable, where the index is specified as a local or global variable, or an argument. /// Operand refers to an element of an indexed variable, where the index is specified as a local or global variable, or an argument.
/// </summary> /// </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.Text;
using System.Linq; using System.Linq;
using System.Collections; using System.Collections;
using System.Net;
using System.Data;
namespace IFPSLib.Emit namespace IFPSLib.Emit
{ {
@ -380,6 +382,31 @@ namespace IFPSLib.Emit
return FixBranchOffset(br, br.Read<uint>()); 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) internal static Instruction Load(BinaryReader br, Script script, ScriptFunction function)
{ {
var ret = new Instruction(); 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 }; ret.m_Operands = new List<Operand>(2) { new Operand(new TypedData(InstructionType.Instance, FixBranchOffset(br, brOffset))), valOp };
break; break;
case OperandType.InlineFunction: 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; break;
case OperandType.InlineType: 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; break;
case OperandType.InlineCmpValue: 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) }; 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) { ret.m_Operands = new List<Operand>(3) {
Operand.LoadValue(br, script, function), Operand.LoadValue(br, script, function),
Operand.LoadValue(br, script, function), Operand.LoadValue(br, script, function),
Operand.Create(script.Types[(int)br.Read<uint>()]) TryReadType(br, script)
}; };
break; break;
case OperandType.InlineEH: case OperandType.InlineEH:
@ -466,7 +493,7 @@ namespace IFPSLib.Emit
break; break;
case OperandType.InlineTypeVariable: case OperandType.InlineTypeVariable:
ret.m_Operands = new List<Operand>(2) { 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)) new Operand(VariableBase.Load(br, script, function))
}; };
return ret; return ret;

View File

@ -105,6 +105,12 @@ namespace IFPSLib.Emit
m_Value = op.m_Value; 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) public static Operand Create<TType>(Types.PrimitiveType type, TType value)
{ {
return new Operand(TypedData.Create(type, value)); return new Operand(TypedData.Create(type, value));
@ -156,10 +162,17 @@ namespace IFPSLib.Emit
return new Operand(arr, varIdx); 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) internal static Operand LoadValue(BinaryReader br, Script script, ScriptFunction function)
{ {
var type = (BytecodeOperandType)br.ReadByte(); var type = (BytecodeOperandType)br.ReadByte();
try
{
switch (type) switch (type)
{ {
case BytecodeOperandType.Variable: case BytecodeOperandType.Variable:
@ -181,6 +194,7 @@ namespace IFPSLib.Emit
default: default:
throw new ArgumentOutOfRangeException(string.Format("Invalid operand type {0}", (byte)type)); throw new ArgumentOutOfRangeException(string.Format("Invalid operand type {0}", (byte)type));
} }
} catch (Exception ex) { return new Operand(ex); }
} }
public override string ToString() public override string ToString()
@ -195,6 +209,8 @@ namespace IFPSLib.Emit
return String.Format("{0}[{1}]", IndexedVariable.Name, IndexImmediate); return String.Format("{0}[{1}]", IndexedVariable.Name, IndexImmediate);
case BytecodeOperandType.IndexedVariable: case BytecodeOperandType.IndexedVariable:
return String.Format("{0}[{1}]", IndexedVariable.Name, IndexVariable.Name); return String.Format("{0}[{1}]", IndexedVariable.Name, IndexVariable.Name);
case BytecodeOperandType.Invalid:
return "$INVALID";
default: default:
return ""; return "";
} }

View File

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