From 58e78597e7b56e5232bc60e85daa042f157d10b8 Mon Sep 17 00:00:00 2001
From: zc <2650838+Wack0@users.noreply.github.com>
Date: Fri, 31 Mar 2023 12:27:42 +0100
Subject: [PATCH] libifps: allow reading invalid operands this should allow
disassembling some malware samples (etc)
---
IFPSLib/Emit/BytecodeOperandType.cs | 6 +++-
IFPSLib/Emit/Instruction.cs | 35 +++++++++++++++---
IFPSLib/Emit/Operand.cs | 56 ++++++++++++++++++-----------
IFPSLib/TypedData.cs | 6 +++-
readme.md | 2 +-
5 files changed, 78 insertions(+), 27 deletions(-)
diff --git a/IFPSLib/Emit/BytecodeOperandType.cs b/IFPSLib/Emit/BytecodeOperandType.cs
index d336e95..72e40ce 100644
--- a/IFPSLib/Emit/BytecodeOperandType.cs
+++ b/IFPSLib/Emit/BytecodeOperandType.cs
@@ -24,6 +24,10 @@ namespace IFPSLib.Emit
///
/// Operand refers to an element of an indexed variable, where the index is specified as a local or global variable, or an argument.
///
- IndexedVariable
+ IndexedVariable,
+ ///
+ /// Operand is invalid. Stops execution.
+ ///
+ Invalid = 0xff
}
}
diff --git a/IFPSLib/Emit/Instruction.cs b/IFPSLib/Emit/Instruction.cs
index cb11f82..161968e 100644
--- a/IFPSLib/Emit/Instruction.cs
+++ b/IFPSLib/Emit/Instruction.cs
@@ -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());
}
+ private static T TryReadIndex(BinaryReader br, IList list)
+ {
+ var index = (int)br.Read();
+ 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()
+ };
+ 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(2) { new Operand(new TypedData(InstructionType.Instance, FixBranchOffset(br, brOffset))), valOp };
break;
case OperandType.InlineFunction:
- ret.m_Operands = new List(1) { Operand.Create(script.Functions[(int)br.Read()]) };
+ ret.m_Operands = new List(1) { TryReadFunction(br, script) };
break;
case OperandType.InlineType:
- ret.m_Operands = new List(1) { Operand.Create(script.Types[(int)br.Read()]) };
+ ret.m_Operands = new List(1) { TryReadType(br, script) };
break;
case OperandType.InlineCmpValue:
ret.m_Operands = new List(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(3) {
Operand.LoadValue(br, script, function),
Operand.LoadValue(br, script, function),
- Operand.Create(script.Types[(int)br.Read()])
+ TryReadType(br, script)
};
break;
case OperandType.InlineEH:
@@ -466,7 +493,7 @@ namespace IFPSLib.Emit
break;
case OperandType.InlineTypeVariable:
ret.m_Operands = new List(2) {
- Operand.Create(script.Types[(int)br.Read()]),
+ TryReadType(br, script),
new Operand(VariableBase.Load(br, script, function))
};
return ret;
diff --git a/IFPSLib/Emit/Operand.cs b/IFPSLib/Emit/Operand.cs
index bb29da1..769afd8 100644
--- a/IFPSLib/Emit/Operand.cs
+++ b/IFPSLib/Emit/Operand.cs
@@ -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(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();
- 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();
+ 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 "";
}
diff --git a/IFPSLib/TypedData.cs b/IFPSLib/TypedData.cs
index 2c3dcf3..25d7d6c 100644
--- a/IFPSLib/TypedData.cs
+++ b/IFPSLib/TypedData.cs
@@ -313,7 +313,11 @@ namespace IFPSLib
switch (Type.BaseType)
{
case PascalTypeCode.Type:
- return ValueAs().Name;
+ {
+ var type = ValueAs();
+ if (type is UnknownType) return "$UNKNOWN";
+ return type.Name;
+ }
case PascalTypeCode.Instruction:
if (Value == null) return "null";
return string.Format("loc_{0}", ValueAs().Offset.ToString("x"));
diff --git a/readme.md b/readme.md
index 324e34b..adcd8aa 100644
--- a/readme.md
+++ b/readme.md
@@ -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