Giter Club home page Giter Club logo

hypo's Introduction

Hypo

Maven Central Version 2.3.0 Test

Hypo is a model for Java bytecode inspection. The main idea behind Hypo is to separate the process of determining facts about Java bytecode, and the analysis of those facts.

The logic for determining things from bytecode can sometimes be a little tricky, but once that's worked out, by storing that data in the model and making it easy to access later, it's much simpler to analyze by just asking the model for that data which has already been pre-computed, rather than combining the logic to compute the data with your analysis logic.

Note about the connection with Lorenz obfuscation mapping

Hypo is not tied to Java obfuscation mapping analysis, but the primary purpose for Hypo is in the hypo-mappings module which uses Hypo and Lorenz for Java obfuscation mapping analysis using Hypo's bytecode analytical model. This is the only module (except for hypo-test) which uses Lorenz and the rest of Hypo can be used independently of that.

Getting Hypo

Releases of Hypo are deployed to Maven Central.

Using SNAPSHOT versions

You can also use the latest SNAPSHOT commit to main with Sonatype's snapshot repo:

repositories {
    maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
}

The easiest way to use Hypo is to use hypo-platform to keep the multiple versions of the artifacts in sync for you.

If you're using Gradle 7.0+ you can also use hypo-catalog if you like.

Gradle Kotlin DSL

Click to show build.gradle.kts
repositories {
    mavenCentral()
}

dependencies {
    implementation(platform("dev.denwav.hypo:hypo-platform:2.3.0"))
    // Whichever modules you need:
    implementation("dev.denwav.hypo:hypo-model")
    implementation("dev.denwav.hypo:hypo-core")
    implementation("dev.denwav.hypo:hypo-hydrate")
    implementation("dev.denwav.hypo:hypo-asm")
    implementation("dev.denwav.hypo:hypo-asm-hydrate")
    implementation("dev.denwav.hypo:hypo-mappings")
}

Gradle Groovy DSL

Click to show build.gradle
repositories {
    mavenCentral()
}

dependencies {
    implementation platform('dev.denwav.hypo:hypo-platform:2.3.0')
    // Whichever modules you need:
    implementation 'dev.denwav.hypo:hypo-model'
    implementation 'dev.denwav.hypo:hypo-core'
    implementation 'dev.denwav.hypo:hypo-hydrate'
    implementation 'dev.denwav.hypo:hypo-asm'
    implementation 'dev.denwav.hypo:hypo-asm-hydrate'
    implementation 'dev.denwav.hypo:hypo-mappings'
}

Maven

Click to show pom.xml
<project>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>dev.denwav.hypo</groupId>
                <artifactId>hypo-platform</artifactId>
                <version>2.3.0</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <!-- Whichever modules you need -->
    <dependencies>
        <dependency>
            <groupId>dev.denwav.hypo</groupId>
            <artifactId>hypo-model</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.denwav.hypo</groupId>
            <artifactId>hypo-core</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.denwav.hypo</groupId>
            <artifactId>hypo-hydrate</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.denwav.hypo</groupId>
            <artifactId>hypo-asm</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.denwav.hypo</groupId>
            <artifactId>hypo-asm-hydrate</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.denwav.hypo</groupId>
            <artifactId>hypo-mappings</artifactId>
        </dependency>
    </dependencies>
</project>

Using Hypo

The Hypo Model

hypo-model is Hypo's foundational module, all other modules depend on it. It contains the Java class object model interfaces. The default implementation of hypo-model is hypo-asm, which uses the ASM library for parsing Java class files. Theoretically a model implementation could even be built around Java source files, as long as the model API is implemented, but currently there is no such implementation.

How to load data into the model

Define roots you want to use. The default implementation has roots for directories, jars, and a system root for JDK classes, which come from the currently running JVM.

Click to show Java snippet
import dev.denwav.hypo.model.ClassProviderRoot;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Example {
    public static void main(String[] args) {
        Path dirPath = Paths.get("someDir");
        Path jarPath = Paths.get("someJar");

        try (
            ClassProviderRoot dirRoot = ClassProviderRoot.fromDir(dirPath);
            ClassProviderRoot jarRoot = ClassProviderRoot.fromJar(jarPath);
            ClassProviderRoot jdkRoot = ClassProviderRoot.ofJdk()
        ) {
            ...
        }
    }
}

Pass these roots to a ClassDataProvider, the default implementation is in hypo-asm called AsmClassDataProvider.

Click to show Java snippet
import dev.denwav.hypo.asm.AsmClassDataProvider;
import dev.denwav.hypo.model.ClassDataProvider;
import dev.denwav.hypo.model.ClassProviderRoot;

public class Example {
    public static void main(String[] args) {
        try (ClassDataProvider provider = AsmClassDataProvider.of(ClassProviderRoot.ofJdk())) {
            ...
        }
    }
}

hypo-core defines a HypoContext. A context is an immutable config object which is passed around to most Hypo users, which defines the "world" of Java class data which will be read.

Create a HypoContext using the providers you created:

Click to show Java snippet
import dev.denwav.hypo.asm.AsmClassDataProvider;
import dev.denwav.hypo.core.HypoContext;
import dev.denwav.hypo.model.ClassProviderRoot;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Example {
    public static void main(String[] args) {
        Path jarPath = Paths.get("someDir");
        try (
            HypoContext context = HypoContext.builder()
                .withProvider(AsmClassDataProvider.of(ClassProviderRoot.fromJar(jarPath)))
                .withContextProvider(AsmClassDataProvider.of(ClassProviderRoot.ofJdk()))
                .build()
        ) {
            ...
        }
    }
}

Standard providers are the providers which make up the collection of classes you want to analyze. Context providers fill out any additional class data on the classpath needed to complete the model.

For example, if you have a class called CustomList in your standard provider which implements java.util.List then data about CustomList's super class won't be available unless ClassProviderRoot.ofJdk() is given as a context provider.

Only class data in the standard provider will be iterated over when filling out the model during hydration, running hydration over your entire classpath could take much longer and use much more memory, so it's a good idea to separate your standard and context providers.

Now that the providers are set up in a context, you can find ClassData objects for specific classes or loop over all classes available. This data model is not fully complete yet however, as it has not been hydrated yet.

Click to show Java snippet
import dev.denwav.hypo.asm.AsmClassDataProvider;
import dev.denwav.hypo.core.HypoContext;
import dev.denwav.hypo.model.ClassProviderRoot;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Example {
    public static void main(String[] args) {
        Path jarPath = Paths.get("someDir");
        try (
            HypoContext context = HypoContext.builder()
                    .withProvider(AsmClassDataProvider.of(ClassProviderRoot.fromJar(jarPath)))
                    .withContextProvider(AsmClassDataProvider.of(ClassProviderRoot.ofJdk()))
                    .build()
        ) {
            ClassData exampleClassData = context.getProvider().findClass("com.example.ExampleClass");
            for (ClassData classData : this.context.getProvider().allClasses()) {
                System.out.println(classData);
            }
        }
    }
}

Hydration

With class data in a HypoContext, that context can be passed to a HydrationManager to fill out the model with additional information.

Some data is easy to get directly from Java class files, such as the class's super class, but other data is much more difficult, or impossible to retrieve directly, such as every class which extend a given class. The only way to get that data is to read every class on the classpath and check that class's super class value, and build the class hierarchy directly. That is the core of what hydration does.

Create the default implementation of HydrationManager and hydrate your HypoContext.

Click to show Java snippet
import dev.denwav.hypo.core.HypoContext;
import dev.denwav.hypo.hydrate.HydrationManager;

public class Example {
    public static void main(String[] args) {
        // HypoContext building omitted for brevity
        try (HypoContext context = HypoContext.buidler().build()) {
            HydrationManager.createDefault().hydrate(context);
        }
    }
}

The default hydrator fills in extra data in the ClassData and MethodData classes of the model allowing additional hydration only methods to be called.

Additional arbitrary data may be included in the model through hydration through custom HydrationProvider classes. These classes read HypoData objects (e.g. ClassData, MethodData, and FieldData) and add more information to these objects using HypoKeys.

Click to show Java snippet
import dev.denwav.hypo.asm.hydrate.BridgeMethodHydrator;
import dev.denwav.hypo.core.HypoContext;
import dev.denwav.hypo.hydrate.generic.HypoHydration;
import dev.denwav.hypo.hydrate.HydrationManager;

public class Example {
    public static void main(String[] args) {
        // HypoContext building omitted for brevity
        try (HypoContext context = HypoContext.buidler().build()) {
            HydrationManager.createDefault()
                    .register(BridgeMethodHydrator.create())
                    .hydrate(context);

            // Get additional data out of the model provided by the BridgeMethodHydrator
            MethodData syntheticTargetMethod = context.getProvider()
                    .findClass("com.example.ExampleClass")
                    .methods("someMethod")
                    .get(0)
                    .get(HypoHydration.SYNTHETIC_TARGET);
        }
    }
}

Custom hydration providers are usually going to be specific to a particular model implementation. This is because custom hydration providers may need to access additional data outside the standard model to build their custom data. For example, the providers in hypo-asm-hydrate use the ASM data nodes to learn what they need to know from the bytecode.

You can run multiple ClassData implementations and multiple HydrationProvider implementations in the same model at the same time - the HydrationManager will only pass implementations which match what the provider targets to the model.

License

Hypo is licensed under the LGPL version 3.0 only (no later versions).

hypo's People

Contributors

denwav avatar jpenilla avatar machine-maker avatar maestro-denery avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

hypo's Issues

LocalClassHydrator assumes incorrect order for fields

In determining captured values in the LocalClassHydrator, it assumes an incorrect order of fields, breaking out a loop to collect fields before all captured values have been collected.

class Demo {

	Demo(String arg1, String arg2) {
		
		class Other {
			private final String innerValue = "test";

			public String toString() {
				return arg2 + this.innerValue;
			}
		}

		new Other().toString();
	}
}

The above example produces a list of fields that begins with the "innerValue" field, then the 1 val$arg2 field, then the this$0 field.

CopyMappingsDown applies mappings to implicit params

The CopyMappingsDown mappings change contributor doesn't check to make sure the parameter for which it is applying mappings would actually have a mapping.

class ParentClass {
	static class InnerStaticClass {
		InnerStaticClass(ParentClass parent, double value) {
		}
	}
}

class ChildClass extends ParentClass {
	class InnerClass extends ParentClass.InnerStaticClass {
		InnerClass(double value) {
			super(ChildClass.this, value);
		}
	}
}

The above example, if provided with mappings only for ParentClass$InnerStaticClass#<init> would apply 2 parameter mappings to ChildClass$InnerClass#<init>, the double param's mappings as well as the implicit ParentClass parameter.

I assume this is also an issue for any captured variables that would be in the constructor of a method-local class.

Tests I wrote to demo the issue: https://pastes.dev/BMugODf0zE

BridgeMethodHydrator does not take into account synthetics on sub classes

Synthetic methods can be generated on subclasses, but then point to a method on the superclass. The invoke instruction used is the INVOKESPECIAL which currently the BridgeMethodHydrator treats as invalid, and so the method isn't hydrated properly.

There is also a check to make sure the class names are the same which isn't required.

And this also will probably requiring changing the HypoHydration.SYNTHETIC_SOURCE to be able to hold multiple methods, as there can be more than 1 synthetic bridge pointing to the same method.

LambdaCallHydrator incorrect param lvt indicies when param comes from field

The LambdaCallHydrator doesn't properly capture param lvt indicies if one of the parameters comes from a field node. Now usually this isn't the case, but it seems to be in the following situation.

abstract class AbstractClass {
	abstract Supplier<String> get(String arg);
}

class OuterClass {
	
	AbstractClass create(final String input) {
		final String value = input + "other";
		return new AbstractClass() {
			@Override
			Supplier<String> get(String arg) {
				return () -> value + arg;
			}
		}
	}
}

In this example, the lambda at OuterClass$1$lambda$create$0 (I think that's the correct name it'd have) or the one on being returned from the overriden method won't have the "arg" param marked as captured.

The issue comes from the logic collecting the param indicies. If it encounters a non var insn it breaks out of the loop, not completing the collection so an empty array is set in the LambdaCall object.

HydrationProvider target type checks are reversed

When checking if a hydration provider applies to an impl of ClassData, FieldData, or MethodData, the check is reversed where it currently checks if the target class supplied by the HydrationProvider extends the method's actual class.

It should instead of the method's actual class extends the target class from the HydrationProvider.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.