-
Notifications
You must be signed in to change notification settings - Fork 59
Match API
The Match API is a powerful tool for matching bytecode instruction patterns. It provides an easy-to-use API to match and transform complex bytecode patterns. No need to write complex visitor patterns or manually iterate over instructions. The API is designed to be simple and expressive, allowing you to focus on the logic you want to implement.
The most basic example of using Match API looks like this:
Match match = OpcodeMatch.of(ALOAD);
boolean matches = match.matches(insnContext); // Returns true if instruction is ALOAD
It matches bytecode like:
aload 0 # Matches
aload 1 # Matches
aload 2 # Matches
astore 0 # Does not match - different opcode
The API provides several built-in matches:
Matches instruction by its opcode:
Match match = OpcodeMatch.of(ALOAD);
Matches bytecode like:
aload 0 # Matches
aload 1 # Matches
aload 2 # Matches
astore 0 # Does not match - different opcode
Matches numeric instructions (ICONST, BIPUSH, SIPUSH, LDC):
Match match = NumberMatch.numInteger(); // Matches any integer
Match match = NumberMatch.num(5); // Matches number 5
Matches bytecode like:
iconst_5 # Matches both
bipush 5 # Matches both
sipush 5 # Matches both
ldc 5 # Matches both
iconst_1 # Matches only numInteger()
ldc 3.14 # Does not match - not an integer
Matches method invocations:
Match match = MethodMatch.invokeVirtual() // Any method invocation
.owner("java/lang/String") // Match owner class
.name("length") // Match method name
.desc("()I"); // Match method descriptor
Matches bytecode like:
invokevirtual java/lang/String.length ()I # Matches
invokevirtual java/lang/String.isEmpty ()Z # Matches only invoke() - different method
invokestatic java/lang/Math.abs (I)I # Does not match - different class
Matches field accesses:
Match match = FieldMatch.getStatic() // Static field access
.owner("java/lang/System") // Match owner class
.name("out") // Match field name
.desc("Ljava/io/PrintStream;"); // Match field descriptor
Matches bytecode like:
getstatic java/lang/System.out Ljava/io/PrintStream; # Matches
putstatic java/lang/System.out Ljava/io/PrintStream; # Does not match - put instead of get
getstatic java/lang/System.err Ljava/io/PrintStream; # Does not match - different field name
Warning
SequenceMatch
should not be used for complex pattern matching. Instead, it is recommended to use FrameMatch
as it is more flexible and resilient to slight changes in obfuscation.
Matches sequence of instructions:
Match match = SequenceMatch.of(
OpcodeMatch.of(ALOAD),
MethodMatch.invoke(),
OpcodeMatch.of(POP)
);
Matches bytecode like:
aload 0 # First instruction matches
invokevirtual java/lang/Object.toString ()V # Second instruction matches
pop # Third instruction matches
# Above sequence matches
aload 0 # First instruction matches
pop # Does not match - missing invoke in between
Matches can be chained using logical operators:
Match match = OpcodeMatch.of(ALOAD)
.and(MethodMatch.invoke().owner("java/lang/String"))
.or(FieldMatch.getStatic().owner("java/lang/System"));
The Match API supports matching values on the stack! For example, to get System.out.println
message:
Match match = MethodMatch.invokeVirtual()
.owner("java/io/PrintStream")
.name("println")
.desc("(Ljava/lang/String;)V")
.and(FrameMatch.stack(0, OpcodeMatch.of(LDC).capture("println-msg")));
MatchContext result = match.matchResult(insnContext);
LdcInsnNode ldcInsnNode = (LdcInsnNode) result.captures().get("println-msg").insn();
System.out.println("println message: " + ldcInsnNode.cst);
Matches bytecode like:
getstatic java/lang/System.out Ljava/io/PrintStream;
ldc "Hello World"
invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V # Matches
As well as this:
getstatic java/lang/System.out Ljava/io/PrintStream;
ldc "Hello World"
dup
dup
pop
pop
invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V # Matches
You can capture specific instructions for later use:
Match match = MethodMatch.invoke().capture("method-call");
MatchContext result = match.matchResult(insnContext);
// We can now access the captured instruction
MethodInsnNode methodCall = (MethodInsnNode)result.captures().get("method-call").insn();
Match match = MethodMatch.invoke()
.owner("java/io/PrintStream")
.name("println");
Matches bytecode like:
getstatic java/lang/System.out Ljava/io/PrintStream;
ldc "Hello World"
invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V # Matches
Match match = SequenceMatch.of(
NumberMatch.num(0),
OpcodeMatch.of(IRETURN)
);
Matches bytecode like:
iconst_0 # First instruction matches
ireturn # Second instruction matches
bipush 0 # First instruction matches
ireturn # Second instruction matches
iconst_1 # Does not match - different number
ireturn
You can see other examples inside transformers. Here are some examples: