Signature Syntax
CILFI's main design principle is that it should be easy to go from a CIL method body to a CILFI method signature. This is why the grammar that CILFI uses is very similar to normal CIL, but with some extra keywords and constructions specifically designed for pattern matching.
Signature Block
A signature file can define one or multiple signature blocks, denoted by the .signature keyword.
.signature MyFirstSignature
{
}
Optionally, a signature block can be decorated with various metadata directives, such as author and description.
.signature MyFirstSignature
{
.author "John Doe"
.description "My first CILFI signature"
}
Method Syntax
Each signature block can define exactly one method definition pattern that describes the general expected shape of the method.
.signature MyFirstSignature
{
/* ... */
.method void Foo() cil managed
{
// Define your pattern here.
}
}
Code Blocks
Method blocks can contain code patterns.
The general rule of thumb is that any valid disassembly of a managed method (e.g., directly copied from your favorite disassembler/decompiler) should more or less also be a valid CILFI code block with only minor changes.
The following is a valid CILFI signature that matches code in the ADD_DWORD instruction of the KoiVM obfuscator:
.signature KoiVM_OpCodes_ADD_DWORD_Run
{
.method public final hidebysig newslot virtual instance void Run (
class KoiVM.Runtime.Execution.VMContext ctx,
[out] valuetype KoiVM.Runtime.Execution.ExecutionState& state
) cil managed
{
{
.block $block1
{
ldloca.s V_3
ldloca.s V_2
call instance uint32 KoiVM.Runtime.Execution.VMSlot::get_U4()
ldloca.s V_1
call instance uint32 KoiVM.Runtime.Execution.VMSlot::get_U4()
add
call instance void KoiVM.Runtime.Execution.VMSlot::set_U4(uint32)
}
}
}
It is possible to mask out specific parts with wildcards, regular expressions or similar to generalize certain constructions.
For example, any identifier (including sub-identifiers within types) can be replaced with a wildcard (??) symbol.
.signature KoiVM_OpCodes_ADD_DWORD_Run
{
.method final hidebysig newslot virtual instance void ?? (
class ??,
valuetype ??&
) cil managed
{
.block $block1 ignorenops
{
ldloca ??
ldloca ??
call instance uint32 ??::??()
ldloca ??
call instance uint32 ??::??()
add
call instance void ??::??(uint32)
}
}
}
Block matching can be further refined by specifying modifiers to make the signature more robust.
Current supported block modifiers:
| Modifier | Meaning |
|---|---|
ignorenops |
Indicates NOP instructions in the input should be ignored. |
.signature KoiVM_OpCodes_ADD_DWORD_Run
{
.method public final hidebysig newslot virtual instance void ?? (
class ??,
[out] valuetype ??&
) cil managed
{
.block $block1 ignorenops // <-- ignore all NOP instructions that may be inserted.
{
ldloca ??
ldloca ??
call instance uint32 ??::??()
ldloca ??
call instance uint32 ??::??()
add
call instance void ??::??(uint32)
}
}
}
You can define multiple blocks in the same body pattern, and customize the matching logic with a .condition clause.
.signature KoiVM_OpCodes_ADD_DWORD_Run
{
.method public final hidebysig newslot virtual instance void ?? (
class ??,
[out] valuetype ??&
) cil managed
{
.condition ($block1 | $block2) & $block3
.block $block1 ignorenops
{
ldloca.s ??
ldloca.s ??
call instance uint32 ??::??()
ldloca.s ??
call instance uint32 ??::??()
add
call instance void ??::??(uint32)
}
.block $block2 ignorenops
{
ldloca.s ??
ldloca.s ??
call instance uint64 ??::??()
ldloca.s ??
call instance uint64 ??::??()
add
call instance void ??::??(uint64)
}
.block $block3 ignorenops
{
ldarg ??
ldfld valuetype ??[] ??::??
ldsfld uint8 ??::??
ldelema ??
ldloc.s ??
call instance void ??::??(uint8)
}
}
}
By default, when no .condition block is defined, CILFI assumes all blocks should be present in the method body.
Advanced Pattern Constructions
The easiest form of pattern matching is using the wildcard syntax (??).
However, when more precision is required, other types of constructions can be used as well.
For example, string operands can be replaced with regular expressions.
ldstr r"[0-9a-fA-F]+" // Matches on hexadecimal strings.
If concrete alternatives operands are known, they can be combined into a single pattern using the | operator:
// ldc.i4 instruction with either 1337 or 1338 as operand.
ldc.i4 (1337 | 1338)
// Call to a method returning either int32 or int64
call (int32 | int64) ??::??()
Reserved Keywords
CILFI also defines some extra keywords that make it easier to match on common types.
The $corlib keyword can be used as a special resolution scope for a type reference to indicate that any corlib assembly (e.g., mscorlib, netstandard or System.Runtime) is a valid match:
call void [$corlib] System.IO.MemoryStream::.ctor(byte[])
Additionally, $module can be used to reference the global module type (typically called <Module>) when present:
call void $module::SomeGlobalMethod()