├── .gitignore
├── MixBukkit.png
├── TestMixin
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── dragoncommissions
│ │ └── testmixin
│ │ ├── TestMixin.java
│ │ └── examples
│ │ └── EDragonArrowShieldRemover.java
│ └── resources
│ └── plugin.yml
├── dependency-reduced-pom.xml
├── docs
└── Getting Started.md
├── pom.xml
├── readme.md
└── src
└── main
├── java
└── com
│ └── dragoncommissions
│ └── mixbukkit
│ ├── MixBukkit.java
│ ├── addons
│ └── AutoMapper.java
│ ├── agent
│ ├── AgentMain.java
│ ├── ClassesManager.java
│ └── JVMAttacher.java
│ ├── api
│ ├── MixinPlugin.java
│ ├── ObfMap.java
│ ├── action
│ │ ├── MixinAction.java
│ │ └── impl
│ │ │ ├── MActionCallSuper.java
│ │ │ ├── MActionInsertShellCode.java
│ │ │ ├── MActionMethodCallSpoofer.java
│ │ │ ├── MActionMethodReplacer.java
│ │ │ └── MActionPipeline.java
│ ├── locator
│ │ ├── HookLocator.java
│ │ └── impl
│ │ │ ├── HLocatorFieldAccess.java
│ │ │ ├── HLocatorFieldRead.java
│ │ │ ├── HLocatorFieldWrite.java
│ │ │ ├── HLocatorHead.java
│ │ │ ├── HLocatorMethodInvoke.java
│ │ │ └── HLocatorReturn.java
│ └── shellcode
│ │ ├── IShellCode.java
│ │ ├── LocalVarManager.java
│ │ ├── ShellCode.java
│ │ ├── ShellCodeInfo.java
│ │ └── impl
│ │ ├── api
│ │ ├── CallbackInfo.java
│ │ ├── ShellCodeComment.java
│ │ ├── ShellCodePrintMessage.java
│ │ ├── ShellCodePrintTopStackType.java
│ │ └── ShellCodeReflectionMixinPluginMethodCall.java
│ │ └── inner
│ │ ├── IShellCodeLoadClassFromPCL.java
│ │ ├── IShellCodeMethodInvoke.java
│ │ ├── IShellCodeNewArrayAndAddContent.java
│ │ ├── IShellCodePushInt.java
│ │ └── IShellCodeReflectionMethodInvoke.java
│ └── utils
│ ├── ASMUtils.java
│ ├── CustomPrinter.java
│ ├── CustomTextifier.java
│ ├── PostPreState.java
│ └── io
│ └── BukkitErrorOutputStream.java
└── resources
├── META-INF
└── MANIFEST.MF
├── config.yml
└── plugin.yml
/.gitignore:
--------------------------------------------------------------------------------
1 | **/*.iml
2 | **/.idea
3 | **/target
4 | **/out/
--------------------------------------------------------------------------------
/MixBukkit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DragonCommissions/MixBukkit/2f3a678e557704fd90796a43754dceb899db8252/MixBukkit.png
--------------------------------------------------------------------------------
/TestMixin/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.dragoncommissions
8 | TestMixin
9 | jar
10 | 1.0-SNAPSHOT
11 |
12 |
13 | 8
14 | 8
15 |
16 |
17 |
18 |
19 |
20 | net.md-5
21 | specialsource-maven-plugin
22 | 1.2.2
23 |
24 |
25 | package
26 |
27 | remap
28 |
29 | remap-obf
30 |
31 | org.spigotmc:minecraft-server:1.18.1-R0.1-SNAPSHOT:txt:maps-mojang
32 | true
33 | org.spigotmc:spigot:1.18.1-R0.1-SNAPSHOT:jar:remapped-mojang
34 | true
35 | remapped-obf
36 |
37 |
38 |
39 | package
40 |
41 | remap
42 |
43 | remap-spigot
44 |
45 | ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar
46 | org.spigotmc:minecraft-server:1.18.1-R0.1-SNAPSHOT:csrg:maps-spigot
47 | org.spigotmc:spigot:1.18.1-R0.1-SNAPSHOT:jar:remapped-obf
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | com.dragoncommissions
58 | MixBukkit
59 | 1.0-SNAPSHOT
60 | provided
61 |
62 |
63 | org.projectlombok
64 | lombok
65 | 1.18.22
66 | provided
67 |
68 |
69 | org.spigotmc
70 | spigot
71 | 1.18.1-R0.1-SNAPSHOT
72 | remapped-mojang
73 | provided
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/TestMixin/src/main/java/com/dragoncommissions/testmixin/TestMixin.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.testmixin;
2 |
3 | import com.dragoncommissions.mixbukkit.MixBukkit;
4 | import com.dragoncommissions.mixbukkit.addons.AutoMapper;
5 | import com.dragoncommissions.mixbukkit.api.MixinPlugin;
6 | import lombok.SneakyThrows;
7 | import org.bukkit.event.Listener;
8 | import org.bukkit.plugin.java.JavaPlugin;
9 |
10 | public class TestMixin extends JavaPlugin implements Listener {
11 |
12 |
13 | @Override
14 | @SneakyThrows
15 | public void onEnable() {
16 | MixinPlugin plugin = MixBukkit.registerMixinPlugin(this, AutoMapper.getMappingAsStream());
17 |
18 | // EDragonArrowShieldRemover.register(plugin);
19 |
20 | }
21 |
22 |
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/TestMixin/src/main/java/com/dragoncommissions/testmixin/examples/EDragonArrowShieldRemover.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.testmixin.examples;
2 |
3 | import com.dragoncommissions.mixbukkit.api.MixinPlugin;
4 | import com.dragoncommissions.mixbukkit.api.action.impl.MActionInsertShellCode;
5 | import com.dragoncommissions.mixbukkit.api.locator.impl.HLocatorHead;
6 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.api.CallbackInfo;
7 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.api.ShellCodeReflectionMixinPluginMethodCall;
8 | import lombok.SneakyThrows;
9 | import net.minecraft.world.damagesource.DamageSource;
10 | import net.minecraft.world.entity.boss.enderdragon.phases.AbstractDragonSittingPhase;
11 | import net.minecraft.world.entity.projectile.AbstractArrow;
12 | import org.bukkit.Bukkit;
13 |
14 | public class EDragonArrowShieldRemover {
15 |
16 | @SneakyThrows
17 | public static void register(MixinPlugin plugin) {
18 | plugin.registerMixin("dragon_arrow_blocker_remove",
19 | new MActionInsertShellCode(
20 | new ShellCodeReflectionMixinPluginMethodCall(EDragonArrowShieldRemover.class.getDeclaredMethod("onHurt", AbstractDragonSittingPhase.class, DamageSource.class, float.class, CallbackInfo.class)),
21 | new HLocatorHead()
22 | ), AbstractDragonSittingPhase.class, "onHurt", float.class, DamageSource.class, float.class);
23 | }
24 |
25 | /*
26 | Original Sourcecode of AbstractDragonSittingPhase#onHurt(DamageSource, float):
27 | public float onHurt(DamageSource var0, float var1) {
28 | if (var0.getDirectEntity() instanceof AbstractArrow) { // A
29 | var0.getDirectEntity().setSecondsOnFire(1); // B
30 | return 0.0F; // C
31 | } else {
32 | return super.onHurt(var0, var1);
33 | }
34 | }
35 |
36 |
37 | this code prevents players from attacking dragons with arrows while dragon is in sitting phase.
38 | There's no way to make it possible with vanilla bukkit API other than tracing down EnderDragon#hurt(EnderDragonPart, DamageSource, float)
39 | Well, that's not a good idea.
40 |
41 | In this case, Mixin is the best solution (if you are fine making it only works with 1 version)
42 |
43 | In this mixin, it modifies the original onHurt method, makes a call to EDragonArrowShieldRemover#onHurt(AbstractDragonSittingPhase, DamageSource, float, CallbackInfo)
44 | It checks of the damage source is arrow, if it is then it will return the original damage instead of 0 (0 is returned in vanilla if source is arrow)
45 | Since the "A" will never be called, the arrow will never bounce off again.
46 | */
47 |
48 | public static void onHurt(AbstractDragonSittingPhase phase, DamageSource source, float damage, CallbackInfo info) {
49 | if (source.getDirectEntity() instanceof AbstractArrow) {
50 | info.setReturned(true);
51 | info.setReturnValue(damage);
52 | Bukkit.broadcastMessage("Successfully hit ender dragon with arrow");
53 | }
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/TestMixin/src/main/resources/plugin.yml:
--------------------------------------------------------------------------------
1 | main: com.dragoncommissions.testmixin.TestMixin
2 | name: TestMixin
3 | version: 1.0
4 |
--------------------------------------------------------------------------------
/dependency-reduced-pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.dragoncommissions
5 | MixBukkit
6 | 1.0-SNAPSHOT
7 |
8 |
9 |
10 | maven-shade-plugin
11 | 3.2.4
12 |
13 |
14 | package
15 |
16 | shade
17 |
18 |
19 |
20 |
21 | *:*
22 |
23 | META-INF/*.SF
24 | META-INF/*.DSA
25 | META-INF/*.RSA
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | spigot-repo
38 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/
39 |
40 |
41 |
42 |
43 | org.spigotmc
44 | spigot-api
45 | 1.8.8-R0.1-SNAPSHOT
46 | provided
47 |
48 |
49 | org.projectlombok
50 | lombok
51 | 1.18.22
52 | provided
53 |
54 |
55 |
56 | 8
57 | 8
58 |
59 |
60 |
--------------------------------------------------------------------------------
/docs/Getting Started.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 | In this tutorial, I'll be teaching you how to start coding your first mixin plugin!
3 | ## Out of date
4 | This document is outdated. Require an update.
5 |
6 | ## Requirements
7 | 1. Some basic Java knowledge
8 | 2. Some basic Reflection knowledge
9 | 3. Understand that doing hacky things like this won't make your plugin cross version support, unless you do things other than things I mentioned in this tutorial.
10 |
11 | ## Add Dependency
12 | We haven't published an artifact to any repository yet, but you can install it to your
13 | local maven repository or add it as a system scoped library.
14 | ### Method 1: Build & Install it to your maven repository
15 | 1. Clone this repository:
16 | ```shell
17 | $ git clone https://github.com/DragonCommissions/MixBukkit
18 | ```
19 | 2. Build MixBukkit project:
20 | ```shell
21 | $ mvn clean package install
22 | ```
23 | > Note: Install maven `mvn` command doesn't work
24 | 3. Add MixBukkit to your project dependencies:
25 | ```xml
26 |
27 | com.dragoncommissions
28 | MixBukkit
29 | 1.0-SNAPSHOT
30 | provided
31 |
32 | ```
33 |
34 | ### Method 2: Install it from command line
35 | ```shell
36 | $ mvn install:install-file -Dfile= -DgroupId=com.dragoncommissions -DartifactId=MixBukkit -Dversion=1.0-SNAPSHOT -Dpackaging=jar
37 | ```
38 | ```xml
39 |
40 | com.dragoncommissions
41 | MixBukkit
42 | 1.0-SNAPSHOT
43 | provided
44 |
45 | ```
46 |
47 | ### Method 3: Add the plugin jar as library
48 | ```xml
49 |
50 | com.dragoncommissions
51 | MixBukkit
52 | 1.0-SNAPSHOT
53 | system
54 | ${basedir}/"relative path to MixBukkit.jar"
55 |
56 | ```
57 |
58 | > Note: You don't need to shade MixBukkit, shading it won't work.
59 |
60 | ## Create your first mixin
61 | You don't need any extra file (as resources) for MixBukkit, which means
62 | you can finish all of them in Java.
63 | Also, If you want a full example, you can check out `TestMixin/`, that's the project
64 | where we test features of MixBukkit. And I'll be using TestMixin as example here.
65 |
66 | There are a few major steps:
67 | 1. Get the `MixinPlugin` instance
68 | 1. Obtain Members Mappings (Optional)
69 | 2. Add your own mixin
70 |
71 | ### Get the `MixinPlugin` instance
72 | To do this, you need to obtain a members mappings first.
73 | Luckily, we have a full auto mapper loader that detects version and remap status.
74 | This also means if you are running a remapped version of Spigot, it won't break,
75 | but I won't be talking about this in this tutorial.
76 |
77 | Mapping in MixBukkit is used to improve your coding experience. Since methods of inner minecraft
78 | (also known as NMS, but those classes have their package name now, so I'll call it
79 | inner minecraft) are obfuscated, it's not easy to figure out what are those, so
80 | people usually use remapped spigot as dependency instead of unmapped. And
81 | when you are using remapped version of it (Check [Spigot's Official Post](https://www.spigotmc.org/threads/spigot-bungeecord-1-17-1-17-1.510208/#post-4184317)), you'll see deobfuscated names, but they
82 | are not their real name, which means if you use those name to find methods with
83 | reflection, it won't find it successfully. With the auto mapper we are having, it will
84 | map the field name string input (let's say: `tickBlockEntities`) into obfuscated name
85 | (`tickBlockEntities` was `R`, so it will be looking for `R` instead of a not existing method:
86 | `tickBlockEntities`)
87 |
88 | Here's how you do it with auto mapping downloader:
89 | ```java
90 | MixinPlugin plugin = MixBukkit.registerMixinPlugin(this, AutoMapper.getMappingAsStream());
91 | ```
92 | Here's how you do it with your own mapping:
93 | ```java
94 | MixinPlugin plugin = MixBukkit.registerMixinPlugin(this, TestMixin.class.getClassLoader().getResourceAsStream("mappings.csrg"));
95 | ```
96 | > Note: Don't forget to replace TestMixin to your main class name, and mappings.csrg to the location of the mapping
97 |
98 | ### Add your own Mixin
99 | #### Keywords you need to know first
100 | ##### ShellCode
101 | Also be known as Bytecode Generator. For example: `ShellCodeReflectionPluginMethodCall` uses reflection to call plugin methods. Every shellcode should get an annotation: `ShellCodeInfo`, which contains information about the shellcode. While implementing your own the shellcode, you should always annotate it with @ShellCodeInfo with required information.
102 | To get a list of ShellCodes, simply type `ShellCode` in your IDE and let it auto completes:
103 | 
104 |
105 | ##### MixinAction (MAction)
106 | MixinAction has ability to modify entire method,
107 | which is the lowest level of mixin. If you want to do something special
108 | other than inserting shellcode (for example: replace it with a super call, trash the method),
109 | you can use this. Same as shellcode, you can do get a list of MixinAction with `MAction` and let the IDE list them for you.
110 | Here's an example MixinAction (MActionMethodTrasher), which trashes entire method and replace them with nothing. Note that it doesn't work with variables requires a return value:
111 | 
112 |
113 | ##### HookLocator (HLocator)
114 | HookLocator will return a list of instruction index.
115 | Let's say you want to inject a shellcode into the top of the method,
116 | you would want to use `new MActionInsertShellCode(shellCode, new HLocatorTop())`
117 |
118 |
119 |
120 | #### Example
121 | After knowing some keywords, you can start adding your own mixins.
122 | Let's use `ShellCodeReflectionMixinPluginMethodCall` as example here, since it will
123 | be the most used shellcode.
124 | Here's an example:
125 | ```java
126 | plugin.registerMixin(
127 | "Test Mixin", // Namespace of the mixin, used to identify them/avoid imjecting same mixin multiple times, so any char is allowed
128 | new MActionInsertShellCode(
129 | new ShellCodeReflectionMixinPluginMethodCall(TestMixin.class.getDeclaredMethod("hurt", EnderMan.class, DamageSource.class, float.class, CallbackInfo.class), false),
130 | // If you want a document of ShellCodeReflectionMixinPluginMethodCall, check the docs for that (obviously not Getting Started.md)
131 | new HLocatorTop()
132 | // Inject to top of the method
133 | ),
134 | EnderMan.class, // Target class
135 | "hurt", // Deobfuscated Method Name
136 | boolean.class, // Return Type
137 | DamageSource.class, float.class // Parameter Types
138 | );
139 | ```
140 |
141 | As you can see, it'll be looking for `TestMixin.hurt()` (Line 4), here's the hurt() example:
142 | ```java
143 | public static void hurt(EnderMan test, DamageSource damageSource, float damage, CallbackInfo callBackInfo) {
144 | Bukkit.broadcastMessage(test.getDisplayName().getString() + " gets hurt from " + damageSource.getMsgId() + " (Damage amount: " + damage + ")");
145 | callBackInfo.setReturned(true); // If it's true, it will return something
146 | callBackInfo.setReturnValue(false); // Return value of it. hurt() in vanilla returns a boolean, so I returned boolean
147 | }
148 | ```
149 |
150 | ## Limitation
151 | Since we don't want to make the framework/library/api very heavy, we decided to
152 | self-attach & redefine classes (like hotswap) instead of transform every class.
153 | If you have ever used HotSwap, you know the limitation. No new method, no new field
154 | , no structure change, and no new class define. I think all you need to beware of is
155 | no new method for overriding. Here's an example:
156 |
157 | ```java
158 | public class LivingEntity {
159 | public void hurt(DamageSource source, float damage) {
160 | // Blablabla
161 | }
162 | }
163 |
164 | public class Skeleton extends LivingEntity {
165 |
166 | }
167 | ```
168 | In this case, `hurt` is not defined in Skeleton class, but LivingEntity class, you are not allowed
169 | to create mixin in `Skeleton.hurt`, so technically you can't hook into `Skeleton.hurt()`.
170 | This is a pretty bad thing, but if we think outside the box:
171 | ```java
172 | public static void hurt(LivingEntity entity, DamageSource damageSource, float damage, CallbackInfo callBackInfo) {
173 | if (entity instanceof Skeleton) {
174 | Bukkit.broadcastMessage(entity.getDisplayName().getString() + " gets hurt from " + damageSource.getMsgId() + " (Damage amount: " + damage + ")");
175 | callBackInfo.setReturned(true);
176 | callBackInfo.setReturnValue(false);
177 | }
178 | }
179 | ```
180 | Yes, you can just check the class and see if it's an instance of Skeleton.
181 | It's same as hooking into `Skeleton.hurt()`
182 |
183 | ## Mapping API
184 | While using `HLocatorMethodInvoke`, it requires a java reflection api `Method` object,
185 | which can be obtained with `getDeclaredMethod`, but obviously it won't work
186 | since the method name on the server is obfuscated. We provided an API for this,
187 | you can simply call `plugin.getMethod()` to solve this problem, it takes
188 | basically same parameter as getDeclaredMethod, but it will find the obfuscated version
189 | according to the members map loaded.
190 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.dragoncommissions
8 | MixBukkit
9 | jar
10 | 1.0-SNAPSHOT
11 |
12 |
13 | 8
14 | 8
15 |
16 |
17 |
18 |
19 | spigot-repo
20 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/
21 |
22 |
23 |
24 |
25 |
26 | org.spigotmc
27 | spigot-api
28 | 1.8.8-R0.1-SNAPSHOT
29 | provided
30 |
31 |
32 | org.projectlombok
33 | lombok
34 | 1.18.22
35 | provided
36 |
37 |
38 | org.ow2.asm
39 | asm
40 | 9.2
41 | compile
42 |
43 |
44 | org.ow2.asm
45 | asm-util
46 | 9.2
47 | compile
48 |
49 |
50 | com.github.olivergondza
51 | maven-jdk-tools-wrapper
52 | 0.1
53 | compile
54 |
55 |
56 | org.javassist
57 | javassist
58 | 3.28.0-GA
59 | compile
60 |
61 |
62 | net.bytebuddy
63 | byte-buddy-agent
64 | 1.12.8
65 | compile
66 |
67 |
68 | io.github.kasukusakura
69 | jvm-self-attach
70 | 0.0.1
71 | compile
72 |
73 |
74 | io.github.karlatemp
75 | unsafe-accessor
76 | 1.6.1
77 | compile
78 |
79 |
80 | org.eclipse.jgit
81 | org.eclipse.jgit
82 | 5.13.0.202109080827-r
83 |
84 |
85 |
86 |
87 |
88 |
89 | org.apache.maven.plugins
90 | maven-shade-plugin
91 | 3.2.4
92 |
93 |
94 | package
95 | shade
96 |
97 |
98 |
99 | *:*
100 |
101 | META-INF/*.SF
102 | META-INF/*.DSA
103 | META-INF/*.RSA
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | 
2 | MixBukkit is a mixin framework inspired by SpongePowered's Mixin framework using ASM.
3 |
4 | ## When will you need it
5 | Let's say you want to hook something in `Skeleton.class`, but obviously you can't just edit spigot source code. Having a custom build just for a plugin is ridiculous. MixBukkit provides an easy to use API that allows you to hook anything in NMS/CraftBukkit/Spigot API/Plugins and even libraries.
6 |
7 | ## Environment
8 | In theory, it should work from Java 8 ~ Java latest, Linux & Windows & MacOS, but I only tested it on Linux with Java 17.
9 | Minecraft version is not limited, but it will result in mapping different
10 |
11 | ## Basic Usage
12 | (Please check `docs/Getting Started.md` for more information)
13 | ### Mapping
14 | #### Method 1. Use Spigot's Members Mapping
15 | If you want to make things easy/fast/good/great/simple, you can use this method.
16 | Simply skip to next step, you don't need to worry about mapping
17 | #### Method 2. Use custom mapping
18 | Since we get an AutoMapper, you can use it instead, but you can also do this if
19 | you have a custom mapping to load. Doing the following thing will get you a
20 | working vanilla mapping. You can also grab it from `%server_root%/mappings.csrg`.
21 | 1. Run buildtool with this following command: `java -jar BuildTool.jar --rev --remapped`
22 | 2. After that, go to your local maven repository (usually `{user.home}/.m2/repository`), and copy `minecraft-server--maps-spigot-members.csrg`. For example, it's in `/home/fan87/.m2/repository/org/spigotmc/minecraft-server/1.18.1-R0.1-SNAPSHOT/` on my computer
23 | 3. Paste that file into the same directory as `plugin.yml`, and name it to anything you want. For example: `mapping.csrg`
24 | #### Bad way of loading a custom members mapping
25 | Do not replace `%server_root%/mappings.csrg` to load a custom mapping. It
26 | will probably kill all plugins that is using Mixin.
27 | Unless you want to make the mapping globally (let's say you are the user, you can do this).
28 |
29 | ### Register `MixinPlugin`
30 | If you wish to use Spigot's members mapping:
31 | ```java
32 | // onEnable()
33 | MixinPlugin mixinPlugin = MixBukkit.registerMixinPlugin(this, AutoMapper.getMappingAsStream());
34 | ```
35 | If you wish to use a custom members mapping:
36 | ```java
37 | // onEnable()
38 | MixinPlugin mixinPlugin = MixBukkit.registerMixinPlugin(this, TestMixin.class.getClassLoader().getResourceAsStream("mapping.csrg" /* Type the mapping location here */));
39 | ```
40 | After registering MixinPlugin, you can start registering mixins.
41 | Please check `TestMixin/` module for examples!
42 |
43 | ### Project Keywords/Tips and tricks
44 | #### ShellCode
45 | Also be known as Bytecode Generator. For example: `ShellCodeReflectionPluginMethodCall` uses reflection to call plugin methods. Every shellcode should get an annotation: `ShellCodeInfo`, which contains information about the shellcode. While implementing your own the shellcode, you should always annotate it with @ShellCodeInfo with required information.
46 | To get a list of ShellCodes, simply type `ShellCode` in your IDE and let it auto completes:
47 | 
48 |
49 | #### MixinAction (MAction)
50 | MixinAction has ability to modify entire method,
51 | which is the lowest level of mixin. If you want to do something special
52 | other than inserting shellcode (for example: replace it with a super call, trash the method),
53 | you can use this. Same as shellcode, you can do get a list of MixinAction with `MAction` and let the IDE list them for you.
54 | Here's an example MixinAction (MActionMethodTrasher), which trashes entire method and replace them with nothing. Note that it doesn't work with variables requires a return value:
55 | 
56 |
57 | #### HookLocator (HLocator)
58 | HookLocator will return a list of instruction index.
59 | Let's say you want to inject a shellcode into the top of the method,
60 | you would want to use `new MActionInsertShellCode(shellCode, new HLocatorTop())`
61 |
62 | ## Advanced Usage
63 | Coming Soon! (JavaDoc also coming soon : D )
64 |
65 | ## Gallery
66 | 
67 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/MixBukkit.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit;
2 |
3 | import com.dragoncommissions.mixbukkit.addons.AutoMapper;
4 | import com.dragoncommissions.mixbukkit.agent.ClassesManager;
5 | import com.dragoncommissions.mixbukkit.agent.JVMAttacher;
6 | import com.dragoncommissions.mixbukkit.api.ObfMap;
7 | import com.dragoncommissions.mixbukkit.api.MixinPlugin;
8 | import com.dragoncommissions.mixbukkit.utils.io.BukkitErrorOutputStream;
9 | import lombok.AllArgsConstructor;
10 | import lombok.Getter;
11 | import lombok.SneakyThrows;
12 | import org.bukkit.Bukkit;
13 | import org.bukkit.ChatColor;
14 | import org.bukkit.configuration.file.YamlConfiguration;
15 | import org.bukkit.plugin.Plugin;
16 | import org.bukkit.plugin.java.JavaPlugin;
17 |
18 | import java.io.File;
19 | import java.io.InputStream;
20 | import java.lang.instrument.Instrumentation;
21 | import java.lang.reflect.Method;
22 | import java.net.URLClassLoader;
23 | import java.util.HashMap;
24 | import java.util.Map;
25 | import java.util.jar.JarFile;
26 |
27 | public class MixBukkit extends JavaPlugin {
28 |
29 | public final static String VERSION = "0.1";
30 | public final static BuildType BUILD_TYPE = BuildType.SNAPSHOT;
31 | public static boolean DEBUG = BUILD_TYPE.isDevBuild();
32 | public static boolean WRITE_TRANSFORMED_CLASS = false;
33 | public static boolean SAFE_MODE = true;
34 | public static Instrumentation INSTRUMENTATION = null;
35 | public static boolean PREPARED = false;
36 | public static BukkitErrorOutputStream ERROR_OUTPUT_STREAM = new BukkitErrorOutputStream();
37 |
38 | @Getter
39 | private File pluginFile;
40 |
41 | @Getter
42 | private static JVMAttacher jvmAttacher;
43 |
44 | public static ClassesManager classesManager;
45 |
46 |
47 | @Getter
48 | private static final Map plugins = new HashMap<>();
49 |
50 | @Override
51 | public void onDisable() {
52 |
53 | }
54 |
55 | @Override
56 | @SneakyThrows
57 | public void onEnable() {
58 | URLClassLoader parent = ((URLClassLoader) getClassLoader().getParent());
59 | pluginFile = getFile();
60 |
61 | loadConfig();
62 |
63 | getServer().getConsoleSender().sendMessage(ChatColor.GREEN + "=-=-=-=-= MixBukkit Loader =-=-=-=-=");
64 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + "Version: " + VERSION);
65 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + "Build Type: " + BUILD_TYPE);
66 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + "MC Version: " + AutoMapper.getMCVersion());
67 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + "Server Remapped: " + !AutoMapper.isObfuscatedBuild());
68 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + "");
69 | if (!SAFE_MODE) {
70 | getServer().getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Warning: Safe mode is disabled! It might load invalid class and crash the Server/JVM");
71 | }
72 | if (!DEBUG) {
73 | getServer().getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// If you wish to see debug messages, please enable \"debug-mode\" in your config file");
74 | } else {
75 | if (!WRITE_TRANSFORMED_CLASS) {
76 | getServer().getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// If you wish to see transformed version of class (for testing purposes), you can enable \"write-transformed-class\" in config!");
77 | }
78 | }
79 | if (WRITE_TRANSFORMED_CLASS) {
80 | getServer().getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Write output class enabled! Transformed classes will be renamed and go into your temp folder.");
81 | }
82 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + "");
83 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + "~~ Started loading ~~");
84 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + " - Attaching to JVM...");
85 | jvmAttacher = new JVMAttacher(this);
86 | jvmAttacher.attach();
87 | if (INSTRUMENTATION == null) {
88 | setEnabled(false);
89 | getServer().getConsoleSender().sendMessage(ChatColor.RED + "- Failed grabbing instrumentation! If you believe this is an issue, please open a ticket");
90 | getServer().getConsoleSender().sendMessage(ChatColor.RED + "");
91 | getServer().getConsoleSender().sendMessage(ChatColor.RED + "======= FAILED GETTING INSTRUMENTATION ======");
92 | getServer().getConsoleSender().sendMessage(ChatColor.RED + "Please check those things before opening an issue:");
93 | getServer().getConsoleSender().sendMessage(ChatColor.RED + "1. Do you have -XX:+DisableAttachMechanism? If yes, remove it from server start command.");
94 | getServer().getConsoleSender().sendMessage(ChatColor.RED + "2. Does the server have permission to spawn a process? If no, give it. Normally yes unless you are using server panel that limits the privilege");
95 | getServer().getConsoleSender().sendMessage(ChatColor.RED + "");
96 | throw new NullPointerException("Instrumentation is null");
97 | }
98 | getServer().getConsoleSender().sendMessage(ChatColor.GREEN + "- Finished Attaching!");
99 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + "- Preparing class transformers...");
100 | ClassesManager.init();
101 | getServer().getConsoleSender().sendMessage(ChatColor.GREEN + "- Finished preparing class transformers!");
102 | getServer().getConsoleSender().sendMessage(ChatColor.GREEN + "");
103 | getServer().getConsoleSender().sendMessage(ChatColor.GREEN + "");
104 | getServer().getConsoleSender().sendMessage(ChatColor.GREEN + "[!] Finished loading MixBukkit!");
105 | getServer().getConsoleSender().sendMessage(ChatColor.GREEN + "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-");
106 | PREPARED = true;
107 | }
108 |
109 | private void loadConfig() {
110 | try {
111 |
112 | YamlConfiguration config = new YamlConfiguration();
113 | File configFile = new File(getDataFolder(), "config.yml");
114 | if (!getDataFolder().exists()) getDataFolder().mkdirs();
115 | if (!configFile.exists()) configFile.createNewFile();
116 |
117 | config.load(configFile);
118 | if (!config.contains("safe-mode")) config.set("safe-mode", SAFE_MODE);
119 | if (!config.contains("debug-mode")) config.set("debug-mode", DEBUG);
120 | if (!config.contains("write-transformed-class")) config.set("write-transformed-class", WRITE_TRANSFORMED_CLASS);
121 | SAFE_MODE = config.getBoolean("safe-mode");
122 | DEBUG = config.getBoolean("debug-mode");
123 | WRITE_TRANSFORMED_CLASS = config.getBoolean("write-transformed-class");
124 | config.save(configFile);
125 | } catch (Exception e) {
126 | e.printStackTrace();
127 | }
128 | }
129 |
130 | public static MixinPlugin registerMixinPlugin(Plugin plugin, InputStream membersMapStream) {
131 | MixinPlugin mixinPlugin = plugins.get(plugin.getName());
132 | if (mixinPlugin != null) {
133 | return mixinPlugin;
134 | }
135 | mixinPlugin = new MixinPlugin(plugin, new ObfMap(membersMapStream));
136 | plugins.put(plugin.getName(), mixinPlugin);
137 | try {
138 | Method getFile = JavaPlugin.class.getDeclaredMethod("getFile");
139 | getFile.setAccessible(true);
140 | File pluginFile = ((File) getFile.invoke(plugin));
141 | pluginFile = pluginFile.getAbsoluteFile();
142 | } catch (Exception e) {
143 | e.printStackTrace();
144 | }
145 |
146 |
147 | return mixinPlugin;
148 | }
149 |
150 | @SneakyThrows
151 | public static void addLibrary(File file) {
152 | INSTRUMENTATION.appendToSystemClassLoaderSearch(new JarFile(file));
153 |
154 | if (DEBUG) {
155 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Loading " + file.getAbsolutePath());
156 | }
157 | }
158 |
159 | @AllArgsConstructor
160 | @Getter
161 | public enum BuildType {
162 | SNAPSHOT(true),
163 | BETA(false),
164 | RELEASE(false);
165 |
166 | boolean devBuild;
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/addons/AutoMapper.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.addons;
2 |
3 | import com.dragoncommissions.mixbukkit.MixBukkit;
4 | import com.google.common.collect.BiMap;
5 | import com.google.common.collect.HashBiMap;
6 | import com.google.gson.Gson;
7 | import com.google.gson.JsonObject;
8 | import lombok.AllArgsConstructor;
9 | import lombok.Data;
10 | import lombok.SneakyThrows;
11 | import org.bukkit.Bukkit;
12 | import org.bukkit.ChatColor;
13 | import org.eclipse.jgit.api.Git;
14 |
15 | import java.io.*;
16 | import java.net.URL;
17 | import java.net.URLConnection;
18 | import java.nio.file.Files;
19 | import java.util.ArrayList;
20 | import java.util.Collections;
21 | import java.util.List;
22 | import java.util.Map;
23 | import java.util.regex.Matcher;
24 | import java.util.regex.Pattern;
25 |
26 | public class AutoMapper {
27 |
28 | private static boolean prepared = false;
29 | private static File mappingFile;
30 |
31 | @SneakyThrows
32 | public static InputStream getMappingAsStream() {
33 | if (!prepared) {
34 | try {
35 | prepareMapping();
36 | } catch (Exception e) {
37 | if (MixBukkit.DEBUG) {
38 | e.printStackTrace();
39 | }
40 | Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[!] Error loading mapping! Have you connected to the internet?");
41 | if (MixBukkit.SAFE_MODE) {
42 | Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[!] Server shutdown because safe mode is on, not loading mapping correctly may cause critical bugs/saves corruption.");
43 | Bukkit.getServer().shutdown();
44 | throw e;
45 | }
46 | }
47 | prepared = true;
48 | }
49 | if (mappingFile == null) return null; // Don't load any mapping
50 | return new FileInputStream(mappingFile);
51 | }
52 | @SneakyThrows
53 | private static void prepareMapping() {
54 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Detecting mapping info...");
55 | if (!shouldLoadMapping()) {
56 | Bukkit.getConsoleSender().sendMessage(ChatColor.GREEN + "[!] You don't need any mapping for this build!");
57 | return;
58 | }
59 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Mappings required! Preparing mapping...");
60 | mappingFile = new File("mappings.csrg");
61 | if (mappingFile.exists()) {
62 | if (!mappingFile.isDirectory()) {
63 | Bukkit.getConsoleSender().sendMessage(ChatColor.GREEN + "[!] Pre-downloaded mapping detected! Using it. If anything went wrong, please try deleting " + ChatColor.DARK_GRAY + mappingFile.getAbsolutePath() + ChatColor.GREEN + " and try again");
64 | return;
65 | }
66 | mappingFile.delete();
67 | }
68 | File buildDataDir = new File(System.getProperty("user.home"), "BuildData");
69 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Fetching BuildData version from Spigot API...");
70 | Gson gson = new Gson();
71 | URLConnection connection = new URL("https://hub.spigotmc.org/versions/" + getMCVersion() + ".json").openConnection();
72 | JsonObject object = gson.fromJson(new InputStreamReader(connection.getInputStream()), JsonObject.class);
73 | String buildDataVersion = object.get("refs").getAsJsonObject().get("BuildData").getAsString();
74 | Git buildData = null;
75 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Fetched BuildData Version: " + buildDataVersion + "!");
76 | if (buildDataDir.exists()) {
77 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Found Spigot's BuildData cache at " + buildDataDir.getAbsolutePath() + "! Doing some simple verification...");
78 | try {
79 | buildData = Git.open(buildDataDir);
80 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Verified! Updating BuildData...");
81 | buildData.pull().call();
82 | } catch (Exception e) {
83 | buildDataDir.delete();
84 | }
85 | }
86 | if (!buildDataDir.exists()) {
87 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Cloning Spigot's BuildData repository to " + buildDataDir.getAbsolutePath() + " . It should take a while (Usually around 35 MB), but it's a one time process (across every server)");
88 | buildData = Git.cloneRepository().setURI("https://hub.spigotmc.org/stash/scm/spigot/builddata.git").setDirectory(buildDataDir).call();
89 | }
90 |
91 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Successfully fetched BuildData! Switching to " + buildDataVersion);
92 | buildData.checkout().setName(buildDataVersion).call();
93 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Checking version info...");
94 | VersionInfo versionInfo = gson.fromJson(new FileReader(new File(buildDataDir, "info.json")), VersionInfo.class);
95 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Scanning for members mapping...");
96 | File classMappings = new File(buildDataDir, "mappings/" + versionInfo.classMappings);
97 | if (versionInfo.memberMappings == null) {
98 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Didn't find a members mapping! Building one...");
99 | MapUtil mapUtil = new MapUtil();
100 | mapUtil.loadBuk(classMappings);
101 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Downloading Minecraft's Mappings & Building Members Mappings...");
102 | InputStream inputStream = new URL(versionInfo.mappingsUrl).openConnection().getInputStream();
103 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
104 | while (true) {
105 | int read = inputStream.read();
106 | if (read == -1) break;
107 | outputStream.write(read);
108 | }
109 | mapUtil.makeFieldMaps(new String(outputStream.toByteArray()), mappingFile, true);
110 | } else {
111 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Found a pre-built members mapping! Extracting...");
112 | mappingFile.createNewFile();
113 | Files.copy(new File(buildDataDir, "mappings/" + versionInfo.memberMappings).toPath(), mappingFile.toPath());
114 | }
115 | Bukkit.getConsoleSender().sendMessage(ChatColor.GREEN + "[!] Finished loading mappings!");
116 |
117 | }
118 |
119 | public static String getMCVersion() {
120 | return Bukkit.getBukkitVersion().split("-")[0];
121 | }
122 |
123 | private static boolean shouldLoadMapping() {
124 | return Integer.parseInt(getMCVersion().split("\\.")[1]) >= 17 && isObfuscatedBuild();
125 | // Remapped option is only available after 1.17
126 | }
127 |
128 | public static boolean isObfuscatedBuild() {
129 | try {
130 | Class> aClass = Class.forName("net.minecraft.world.entity.EntityLiving");
131 | return true;
132 | } catch (Throwable ignored) {}
133 | return false;
134 | }
135 |
136 | /**
137 | * Source: Spigot BuildTools
138 | */
139 | @Data
140 | @AllArgsConstructor
141 | private static class VersionInfo
142 | {
143 |
144 | private String minecraftVersion;
145 | private String accessTransforms;
146 | private String classMappings;
147 | private String memberMappings;
148 | private String packageMappings;
149 | private String minecraftHash;
150 | private String classMapCommand;
151 | private String memberMapCommand;
152 | private String finalMapCommand;
153 | private String decompileCommand;
154 | private String serverUrl;
155 | private String mappingsUrl;
156 | private String spigotVersion;
157 | private int toolsVersion = -1;
158 |
159 | public VersionInfo(String minecraftVersion, String accessTransforms, String classMappings, String memberMappings, String packageMappings, String minecraftHash)
160 | {
161 | this.minecraftVersion = minecraftVersion;
162 | this.accessTransforms = accessTransforms;
163 | this.classMappings = classMappings;
164 | this.memberMappings = memberMappings;
165 | this.packageMappings = packageMappings;
166 | this.minecraftHash = minecraftHash;
167 | }
168 |
169 | public VersionInfo(String minecraftVersion, String accessTransforms, String classMappings, String memberMappings, String packageMappings, String minecraftHash, String decompileCommand)
170 | {
171 | this.minecraftVersion = minecraftVersion;
172 | this.accessTransforms = accessTransforms;
173 | this.classMappings = classMappings;
174 | this.memberMappings = memberMappings;
175 | this.packageMappings = packageMappings;
176 | this.minecraftHash = minecraftHash;
177 | this.decompileCommand = decompileCommand;
178 | }
179 |
180 | public String getShaServerHash()
181 | {
182 | return hashFromUrl( serverUrl );
183 | }
184 |
185 | public String getShaMappingsHash()
186 | {
187 | return hashFromUrl( mappingsUrl );
188 | }
189 | private static final Pattern URL_PATTERN = Pattern.compile( "https://launcher.mojang.com/v1/objects/([0-9a-f]{40})/.*" );
190 |
191 | public static String hashFromUrl(String url)
192 | {
193 | if ( url == null )
194 | {
195 | return null;
196 | }
197 |
198 | Matcher match = URL_PATTERN.matcher( url );
199 | return ( match.find() ) ? match.group( 1 ) : null;
200 | }
201 | }
202 | private static class MapUtil
203 | {
204 |
205 | private static final Pattern MEMBER_PATTERN = Pattern.compile( "(?:\\d+:\\d+:)?(.*?) (.*?) \\-> (.*)" );
206 | //
207 | private List header = new ArrayList<>();
208 | private final BiMap obf2Buk = HashBiMap.create();
209 | private final BiMap moj2Obf = HashBiMap.create();
210 |
211 | public void loadBuk(File bukClasses) throws IOException
212 | {
213 | for ( String line : Files.readAllLines( bukClasses.toPath() ) )
214 | {
215 | if ( line.startsWith( "#" ) )
216 | {
217 | header.add( line );
218 | continue;
219 | }
220 |
221 | String[] split = line.split( " " );
222 | if ( split.length == 2 )
223 | {
224 | obf2Buk.put( split[0], split[1] );
225 | }
226 | }
227 | }
228 |
229 | public void makeFieldMaps(String mojIn, File fields, boolean includeMethods) throws IOException
230 | {
231 | List lines = new ArrayList<>();
232 | if ( includeMethods )
233 | {
234 | for (String line : mojIn.split("\n")) {
235 | lines.add(line);
236 | if ( line.startsWith( "#" ) )
237 | {
238 | continue;
239 | }
240 |
241 | if ( line.endsWith( ":" ) )
242 | {
243 | String[] parts = line.split( " -> " );
244 | String orig = parts[0].replace( '.', '/' );
245 | String obf = parts[1].substring( 0, parts[1].length() - 1 ).replace( '.', '/' );
246 |
247 | moj2Obf.put( orig, obf );
248 | }
249 | }
250 | }
251 |
252 | List outFields = new ArrayList<>( header );
253 |
254 | String currentClass = null;
255 | outer:
256 | for ( String line : mojIn.split("\n") )
257 | {
258 | if ( line.startsWith( "#" ) )
259 | {
260 | continue;
261 | }
262 | line = line.trim();
263 |
264 | if ( line.endsWith( ":" ) )
265 | {
266 | currentClass = null;
267 |
268 | String[] parts = line.split( " -> " );
269 | String orig = parts[0].replace( '.', '/' );
270 | String obf = parts[1].substring( 0, parts[1].length() - 1 ).replace( '.', '/' );
271 |
272 | String buk = deobfClass( obf, obf2Buk );
273 | if ( buk == null )
274 | {
275 | continue;
276 | }
277 |
278 | currentClass = buk;
279 | } else if ( currentClass != null )
280 | {
281 | Matcher matcher = MEMBER_PATTERN.matcher( line );
282 | matcher.find();
283 |
284 | String obf = matcher.group( 3 );
285 | String nameDesc = matcher.group( 2 );
286 | if ( !nameDesc.contains( "(" ) )
287 | {
288 | if ( nameDesc.equals( obf ) || nameDesc.contains( "$" ) )
289 | {
290 | continue;
291 | }
292 | if ( !includeMethods && ( obf.equals( "if" ) || obf.equals( "do" ) ) )
293 | {
294 | obf += "_";
295 | }
296 |
297 | outFields.add( currentClass + " " + obf + " " + nameDesc );
298 | } else if ( includeMethods )
299 | {
300 | String sig = csrgDesc( moj2Obf, obf2Buk, nameDesc.substring( nameDesc.indexOf( '(' ) ), matcher.group( 1 ) );
301 | String mojName = nameDesc.substring( 0, nameDesc.indexOf( '(' ) );
302 |
303 | if ( obf.equals( mojName ) || mojName.contains( "$" ) || obf.equals( "" ) || obf.equals( "" ) )
304 | {
305 | continue;
306 | }
307 | outFields.add( currentClass + " " + obf + " " + sig + " " + mojName );
308 | }
309 | }
310 | }
311 |
312 | Collections.sort( outFields );
313 | fields.createNewFile();
314 | Files.write( fields.toPath(), outFields );
315 | }
316 |
317 | public void makeCombinedMaps(File out, File... members) throws IOException
318 | {
319 | List combined = new ArrayList<>( header );
320 |
321 | for ( Map.Entry map : obf2Buk.entrySet() )
322 | {
323 | combined.add( map.getKey() + " " + map.getValue() );
324 | }
325 |
326 | for ( File member : members )
327 | {
328 | for ( String line : Files.readAllLines( member.toPath() ) )
329 | {
330 | if ( line.startsWith( "#" ) )
331 | {
332 | continue;
333 | }
334 | line = line.trim();
335 |
336 | String[] split = line.split( " " );
337 | if ( split.length == 3 )
338 | {
339 | String clazz = split[0];
340 | String orig = split[1];
341 | String targ = split[2];
342 |
343 | combined.add( deobfClass( clazz, obf2Buk.inverse() ) + " " + orig + " " + targ );
344 | } else if ( split.length == 4 )
345 | {
346 | String clazz = split[0];
347 | String orig = split[1];
348 | String desc = split[2];
349 | String targ = split[3];
350 |
351 | combined.add( deobfClass( clazz, obf2Buk.inverse() ) + " " + orig + " " + toObf( desc, obf2Buk.inverse() ) + " " + targ );
352 | }
353 | }
354 | }
355 |
356 | Files.write( out.toPath(), combined );
357 | }
358 |
359 | public static String deobfClass(String obf, Map classMaps)
360 | {
361 | String buk = classMaps.get( obf );
362 | if ( buk == null )
363 | {
364 | StringBuilder inner = new StringBuilder();
365 |
366 | while ( buk == null )
367 | {
368 | int idx = obf.lastIndexOf( '$' );
369 | if ( idx == -1 )
370 | {
371 | return null;
372 | }
373 | inner.insert( 0, obf.substring( idx ) );
374 | obf = obf.substring( 0, idx );
375 |
376 | buk = classMaps.get( obf );
377 | }
378 |
379 | buk += inner;
380 | }
381 | return buk;
382 | }
383 |
384 | public static String toObf(String desc, Map map)
385 | {
386 | desc = desc.substring( 1 );
387 | StringBuilder out = new StringBuilder( "(" );
388 | if ( desc.charAt( 0 ) == ')' )
389 | {
390 | desc = desc.substring( 1 );
391 | out.append( ')' );
392 | }
393 | while ( desc.length() > 0 )
394 | {
395 | desc = obfType( desc, map, out );
396 | if ( desc.length() > 0 && desc.charAt( 0 ) == ')' )
397 | {
398 | desc = desc.substring( 1 );
399 | out.append( ')' );
400 | }
401 | }
402 | return out.toString();
403 | }
404 |
405 | public static String obfType(String desc, Map map, StringBuilder out)
406 | {
407 | int size = 1;
408 | switch ( desc.charAt( 0 ) )
409 | {
410 | case 'B':
411 | case 'C':
412 | case 'D':
413 | case 'F':
414 | case 'I':
415 | case 'J':
416 | case 'S':
417 | case 'Z':
418 | case 'V':
419 | out.append( desc.charAt( 0 ) );
420 | break;
421 | case '[':
422 | out.append( "[" );
423 | return obfType( desc.substring( 1 ), map, out );
424 | case 'L':
425 | String type = desc.substring( 1, desc.indexOf( ";" ) );
426 | size += type.length() + 1;
427 | out.append( "L" ).append( map.containsKey( type ) ? map.get( type ) : type ).append( ";" );
428 | }
429 | return desc.substring( size );
430 | }
431 |
432 | private static String csrgDesc(Map first, Map second, String args, String ret)
433 | {
434 | String[] parts = args.substring( 1, args.length() - 1 ).split( "," );
435 | StringBuilder desc = new StringBuilder( "(" );
436 | for ( String part : parts )
437 | {
438 | if ( part.isEmpty() )
439 | {
440 | continue;
441 | }
442 | desc.append( toJVMType( first, second, part ) );
443 | }
444 | desc.append( ")" );
445 | desc.append( toJVMType( first, second, ret ) );
446 | return desc.toString();
447 | }
448 |
449 | private static String toJVMType(Map first, Map second, String type)
450 | {
451 | switch ( type )
452 | {
453 | case "byte":
454 | return "B";
455 | case "char":
456 | return "C";
457 | case "double":
458 | return "D";
459 | case "float":
460 | return "F";
461 | case "int":
462 | return "I";
463 | case "long":
464 | return "J";
465 | case "short":
466 | return "S";
467 | case "boolean":
468 | return "Z";
469 | case "void":
470 | return "V";
471 | default:
472 | if ( type.endsWith( "[]" ) )
473 | {
474 | return "[" + toJVMType( first, second, type.substring( 0, type.length() - 2 ) );
475 | }
476 | String clazzType = type.replace( '.', '/' );
477 | String obf = deobfClass( clazzType, first );
478 | String mappedType = deobfClass( ( obf != null ) ? obf : clazzType, second );
479 |
480 | return "L" + ( ( mappedType != null ) ? mappedType : clazzType ) + ";";
481 | }
482 | }
483 | }
484 |
485 |
486 | }
487 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/agent/AgentMain.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.agent;
2 |
3 | import java.lang.instrument.Instrumentation;
4 |
5 | public class AgentMain {
6 |
7 | public static void agentmain(Instrumentation instrumentation, String args) {
8 |
9 | }
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/agent/ClassesManager.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.agent;
2 |
3 | import com.dragoncommissions.mixbukkit.MixBukkit;
4 | import com.dragoncommissions.mixbukkit.utils.ASMUtils;
5 | import javassist.ClassPool;
6 | import javassist.CtClass;
7 | import org.objectweb.asm.tree.ClassNode;
8 |
9 | import java.io.ByteArrayInputStream;
10 | import java.lang.instrument.ClassFileTransformer;
11 | import java.lang.instrument.IllegalClassFormatException;
12 | import java.lang.instrument.UnmodifiableClassException;
13 | import java.security.ProtectionDomain;
14 | import java.util.HashMap;
15 | import java.util.Map;
16 |
17 | public class ClassesManager {
18 |
19 |
20 | public static Map classes = new HashMap<>();
21 | public static Map classNodes = new HashMap<>();
22 | private static ClassPool cp = ClassPool.getDefault();
23 |
24 | public static void init() {
25 | MixBukkit.INSTRUMENTATION.addTransformer(new ClassFileTransformer() {
26 | @Override
27 | public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
28 | classes.put(className.replace("/", "."), classfileBuffer);
29 | return classfileBuffer;
30 | }
31 | }, true);
32 | }
33 |
34 | public static ClassNode getClassNode(String name) {
35 | ClassNode classNode1 = classNodes.get(name);
36 | if (classNode1 == null) {
37 | byte[] classBytecode = getClassBytecode(name);
38 | if (classBytecode == null) return null;
39 | classNode1 = ASMUtils.toClassNode(classBytecode);
40 | return classNode1;
41 | }
42 | return classNode1;
43 | }
44 |
45 | public synchronized static byte[] getClassBytecode(String name) {
46 | name = name.replace("/", ".");
47 | byte[] bytes = classes.get(name);
48 | if (bytes == null) {
49 | Class[] allLoadedClasses = MixBukkit.INSTRUMENTATION.getAllLoadedClasses();
50 | for (Class allLoadedClass : allLoadedClasses) {
51 | if (allLoadedClass.getName().equals(name)) {
52 | try {
53 | MixBukkit.INSTRUMENTATION.retransformClasses(allLoadedClass);
54 | } catch (Exception e) {
55 | e.printStackTrace();
56 | }
57 | }
58 | }
59 | }
60 | return classes.get(name);
61 | }
62 |
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/agent/JVMAttacher.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.agent;
2 |
3 | import com.dragoncommissions.mixbukkit.MixBukkit;
4 | import io.github.karlatemp.unsafeaccessor.UnsafeAccess;
5 | import io.github.kasukusakura.jsa.JvmSelfAttach;
6 | import lombok.SneakyThrows;
7 |
8 | import java.io.File;
9 | import java.lang.management.ManagementFactory;
10 |
11 | public class JVMAttacher {
12 |
13 | private MixBukkit mixBukkit;
14 |
15 | public JVMAttacher(MixBukkit mixBukkit) {
16 | this.mixBukkit = mixBukkit;
17 | }
18 |
19 | @SneakyThrows
20 | public void attach() {
21 | JvmSelfAttach.init(new File(System.getProperty("java.io.tmpdir")));
22 | MixBukkit.INSTRUMENTATION = JvmSelfAttach.getInstrumentation();
23 |
24 | }
25 |
26 | public int getCurrentPID() {
27 | return Integer.parseInt(ManagementFactory.getRuntimeMXBean().getName().split("@")[0]);
28 | }
29 |
30 | static final UnsafeAccess UA = UnsafeAccess.getInstance();
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/MixinPlugin.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api;
2 |
3 | import com.dragoncommissions.mixbukkit.MixBukkit;
4 | import com.dragoncommissions.mixbukkit.agent.ClassesManager;
5 | import com.dragoncommissions.mixbukkit.api.action.MixinAction;
6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils;
7 | import com.dragoncommissions.mixbukkit.utils.CustomTextifier;
8 | import lombok.Getter;
9 | import lombok.SneakyThrows;
10 | import org.bukkit.Bukkit;
11 | import org.bukkit.ChatColor;
12 | import org.bukkit.plugin.Plugin;
13 | import org.objectweb.asm.*;
14 | import org.objectweb.asm.tree.ClassNode;
15 | import org.objectweb.asm.tree.MethodNode;
16 | import org.objectweb.asm.tree.analysis.Analyzer;
17 | import org.objectweb.asm.tree.analysis.AnalyzerException;
18 | import org.objectweb.asm.tree.analysis.BasicValue;
19 | import org.objectweb.asm.tree.analysis.SimpleVerifier;
20 | import org.objectweb.asm.util.CheckClassAdapter;
21 |
22 | import java.io.IOException;
23 | import java.io.OutputStream;
24 | import java.io.PrintWriter;
25 | import java.lang.instrument.ClassDefinition;
26 | import java.lang.reflect.Method;
27 | import java.util.ArrayList;
28 | import java.util.Iterator;
29 | import java.util.List;
30 |
31 | public class MixinPlugin {
32 |
33 | @Getter
34 | private ObfMap obfMap;
35 | @Getter
36 | private Plugin plugin;
37 |
38 | private List registeredMixins = new ArrayList<>();
39 |
40 | public MixinPlugin(Plugin plugin, ObfMap obfMap) {
41 | this.plugin = plugin;
42 | this.obfMap = obfMap;
43 | }
44 |
45 | @SneakyThrows
46 | public Method getMethod(Class> clazz, String methodName, Class> returnType, Class>... arguments) {
47 | String original = obfMap.resolveMapping(new ObfMap.MethodMapping(clazz.getName().replace(".", "/"), ASMUtils.getDescriptor(returnType, arguments), methodName));
48 | return clazz.getDeclaredMethod(original, arguments);
49 | }
50 |
51 | @SneakyThrows
52 | public boolean registerMixin(String namespace, MixinAction mixinAction, Class> owner, String deObfMethodName, Class> returnType, Class>... arguments) {
53 | if (registeredMixins.contains(namespace)) {
54 | if (MixBukkit.DEBUG) {
55 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Mixin with namespace: " + namespace + " is already registered! Skipping...");
56 | }
57 | return false;
58 | }
59 | if (MixBukkit.DEBUG) {
60 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Registering Mixin: " + plugin.getName() + ":" + namespace);
61 | }
62 | String descriptor = ASMUtils.getDescriptor(returnType, arguments);
63 | String obfMethodName = obfMap.resolveMapping(new ObfMap.MethodMapping(owner.getName().replace(".", "/"), descriptor, deObfMethodName));
64 | if (MixBukkit.DEBUG) {
65 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Obfuscated method name: " + owner.getName().replace(".", "/") + "." + obfMethodName + descriptor);
66 | }
67 |
68 | ClassNode classNode = ClassesManager.getClassNode(owner.getName());
69 | if (classNode == null) {
70 | Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[!] Failed to load mixin: " + plugin.getName() + ":" + namespace + ", Reason: Could not find the target class: " + owner.getName());
71 | return false;
72 | }
73 | PrintWriter printWriter = new PrintWriter(MixBukkit.ERROR_OUTPUT_STREAM, true);
74 | for (MethodNode method : classNode.methods) {
75 | // System.out.println(method.name + method.desc + " / " + obfMethodName + descriptor);
76 | if (method.name.equals(obfMethodName) && method.desc.equals(descriptor)) {
77 | if (MixBukkit.DEBUG) {
78 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Found Method to hook!");
79 | }
80 |
81 | if (MixBukkit.DEBUG) {
82 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Processing...");
83 | }
84 | mixinAction.action(owner, method);
85 |
86 |
87 |
88 | if (MixBukkit.DEBUG) {
89 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Assembling...");
90 | }
91 | byte[] data = ASMUtils.fromClassNode(classNode);
92 |
93 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Verifying...");
94 | ClassReader classReader = new ClassReader(data);
95 | boolean[] illegal = new boolean[] {false};
96 | CheckClassAdapter.verify(classReader, getClass().getClassLoader().getParent(), false, new PrintWriter(new OutputStream() {
97 | @Override
98 | public void write(int b) throws IOException {
99 | illegal[0] = true;
100 | }
101 | }));
102 |
103 | if (illegal[0]) {
104 | if (MixBukkit.DEBUG) {
105 | printWriter.println("Mixin Method:");
106 | CustomTextifier methodVisitor = new CustomTextifier();
107 | method.accept(methodVisitor);
108 | for (Object o : methodVisitor.text) {
109 | printWriter.println(o);
110 | }
111 | printWriter.println("");
112 | printWriter.println("");
113 | CheckClassAdapter.verify(classReader, getClass().getClassLoader().getParent(), false, printWriter);
114 | }
115 | if (MixBukkit.SAFE_MODE) {
116 | Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[!] Failed to load mixin: " + plugin.getName() + ":" + namespace + ", Reason: Invalid Bytecode, and safe-mode is on");
117 | return false;
118 | } else {
119 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[?] Mixin: " + plugin.getName() + ":" + namespace + " has failed the verification, and it might crash your server! Be careful.");
120 | }
121 | }
122 |
123 | try {
124 | if (MixBukkit.DEBUG) {
125 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Redefining class...");
126 | }
127 |
128 | MixBukkit.INSTRUMENTATION.redefineClasses(new ClassDefinition(owner, data));
129 | ClassesManager.classNodes.put(owner.getName(), classNode);
130 | ClassesManager.classes.put(owner.getName(), data);
131 | } catch (Exception e) {
132 | e.printStackTrace();
133 | Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[!] Failed to load mixin: " + plugin.getName() + ":" + namespace + ", Reason: Could not redefine class: " + owner.getSimpleName());
134 | }
135 | if (MixBukkit.DEBUG) {
136 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Successfully hooked " + namespace);
137 | }
138 | registeredMixins.add(namespace);
139 | return true;
140 | }
141 | }
142 | Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[!] Failed to load mixin: " + plugin.getName() + ":" + namespace + ", Reason: Could not find the target method");
143 | return false;
144 | }
145 |
146 | public static void verify(ClassReader classReader, ClassLoader loader, PrintWriter printWriter) {
147 | ClassNode classNode = new ClassNode();
148 | classReader.accept(new CheckClassAdapter(Opcodes.ASM9, classNode, true) {
149 | }, 2);
150 | Type syperType = classNode.superName == null ? null : Type.getObjectType(classNode.superName);
151 | List methods = classNode.methods;
152 | List interfaces = new ArrayList();
153 | Iterator var8 = classNode.interfaces.iterator();
154 |
155 | while(var8.hasNext()) {
156 | String interfaceName = (String)var8.next();
157 | interfaces.add(Type.getObjectType(interfaceName));
158 | }
159 |
160 | var8 = methods.iterator();
161 |
162 | while(var8.hasNext()) {
163 | MethodNode method = (MethodNode)var8.next();
164 | SimpleVerifier verifier = new SimpleVerifier(Type.getObjectType(classNode.name), syperType, interfaces, (classNode.access & 512) != 0);
165 | Analyzer analyzer = new Analyzer(verifier);
166 | if (loader != null) {
167 | verifier.setClassLoader(loader);
168 | }
169 |
170 | try {
171 | analyzer.analyze(classNode.name, method);
172 | } catch (AnalyzerException var13) {
173 | var13.printStackTrace(printWriter);
174 | }
175 |
176 | }
177 |
178 | printWriter.flush();
179 | }
180 |
181 | }
182 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/ObfMap.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api;
2 |
3 | import com.dragoncommissions.mixbukkit.MixBukkit;
4 | import lombok.EqualsAndHashCode;
5 | import lombok.Getter;
6 | import lombok.ToString;
7 | import org.bukkit.Bukkit;
8 | import org.bukkit.ChatColor;
9 |
10 | import java.io.InputStream;
11 | import java.util.HashMap;
12 | import java.util.Map;
13 | import java.util.Scanner;
14 |
15 | public class ObfMap {
16 |
17 | // Deobf -> obf
18 | private Map fieldMappings = new HashMap<>();
19 | private Map methodMappings = new HashMap<>();
20 |
21 | public ObfMap(InputStream memberMap) {
22 | if (memberMap == null) {
23 | if (MixBukkit.DEBUG) {
24 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Mapping is null");
25 | }
26 | return;
27 | }
28 | Scanner scanner = new Scanner(memberMap);
29 | while (scanner.hasNextLine()) {
30 | String line = scanner.nextLine();
31 | if (line.startsWith("#")) continue;
32 | String[] s = line.split(" ");
33 | if (s.length == 4) { // Method mapping,
34 | methodMappings.put(new MethodMapping(s[0], s[2], s[3]), s[1]);
35 | } else if (s.length == 3) { // Field mapping,
36 | fieldMappings.put(new FieldMapping(s[0], s[2]), s[1]);
37 | } else {
38 | System.out.println("Illegal Mapping: " + line + " (Length: " + s.length + ")");
39 | }
40 | }
41 | }
42 |
43 | public String resolveMapping(FieldMapping fieldMapping) {
44 | String s = fieldMappings.get(fieldMapping);
45 | if (s == null) return fieldMapping.getFieldName();
46 | return s;
47 | }
48 |
49 | public String resolveMapping(MethodMapping methodMapping) {
50 | String s = methodMappings.get(methodMapping);
51 | if (s == null) return methodMapping.getMethodName();
52 | return s;
53 | }
54 |
55 | @Getter
56 | @EqualsAndHashCode
57 | @ToString
58 | public static class FieldMapping {
59 | private final String ownerName; // Replaced / with .
60 | private final String fieldName;
61 |
62 | public FieldMapping(String ownerName, String fieldName) {
63 | this.ownerName = ownerName.replace(".", "/");
64 | this.fieldName = fieldName;
65 | }
66 | }
67 |
68 | @Getter
69 | @EqualsAndHashCode
70 | @ToString
71 | public static class MethodMapping {
72 | private final String ownerName; // Replaced / with .
73 | private final String descriptor;
74 | private final String methodName;
75 |
76 | public MethodMapping(String ownerName, String descriptor, String methodName) {
77 | this.ownerName = ownerName.replace(".", "/");
78 | this.descriptor = descriptor;
79 | this.methodName = methodName;
80 | }
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/action/MixinAction.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.action;
2 |
3 | import org.objectweb.asm.tree.InsnList;
4 | import org.objectweb.asm.tree.MethodNode;
5 |
6 | public interface MixinAction {
7 |
8 | void action(Class> owner, MethodNode method);
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/action/impl/MActionCallSuper.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.action.impl;
2 |
3 | import com.dragoncommissions.mixbukkit.api.MixinPlugin;
4 | import com.dragoncommissions.mixbukkit.api.action.MixinAction;
5 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeMethodInvoke;
6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils;
7 | import javassist.bytecode.Opcode;
8 | import org.objectweb.asm.tree.MethodNode;
9 | import org.objectweb.asm.tree.VarInsnNode;
10 |
11 | import java.lang.reflect.Method;
12 | import java.lang.reflect.Modifier;
13 |
14 | public class MActionCallSuper implements MixinAction {
15 |
16 | private MixinPlugin plugin;
17 |
18 | public MActionCallSuper(MixinPlugin plugin) {
19 | this.plugin = plugin;
20 | }
21 |
22 | @Override
23 | public void action(Class> owner, MethodNode method) {
24 | Method m = null;
25 | Method superMethod = null;
26 | for (Method declaredMethod : owner.getDeclaredMethods()) {
27 | if (declaredMethod.getName().equals(method.name)) {
28 | if (ASMUtils.getDescriptor(declaredMethod.getReturnType(), declaredMethod.getParameterTypes()).equals(method.desc)) {
29 | m = declaredMethod;
30 | }
31 | }
32 | }
33 | if (m != null) {
34 | Class> superclass = owner.getSuperclass();
35 | while (superclass != null) {
36 | for (Method declaredMethod : superclass.getDeclaredMethods()) {
37 | if (declaredMethod.getName().equals(method.name)) {
38 | if (ASMUtils.getDescriptor(declaredMethod.getReturnType(), declaredMethod.getParameterTypes()).equals(method.desc)) {
39 | superMethod = declaredMethod;
40 | }
41 | }
42 | }
43 | superclass = superclass.getSuperclass();
44 | }
45 | }
46 | if (superMethod == null) {
47 | throw new IllegalArgumentException("Could not find super method in " + owner.getSimpleName());
48 | }
49 | method.instructions.clear();
50 | int varNum = 0;
51 | if (Modifier.isStatic(superMethod.getModifiers())) {
52 | method.instructions.add(new VarInsnNode(Opcode.ALOAD, varNum++));
53 | }
54 | Class>[] parameterTypes = superMethod.getParameterTypes();
55 | for (int i = 0; i < parameterTypes.length; i++) {
56 | method.instructions.add(ASMUtils.loadVar(parameterTypes[i], varNum++));
57 | }
58 | method.instructions.add(new IShellCodeMethodInvoke(superMethod).generate());
59 | method.instructions.add(ASMUtils.genReturnNode(superMethod.getReturnType()));
60 |
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/action/impl/MActionInsertShellCode.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.action.impl;
2 |
3 | import com.dragoncommissions.mixbukkit.api.action.MixinAction;
4 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator;
5 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager;
6 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode;
7 | import lombok.AllArgsConstructor;
8 | import lombok.Getter;
9 | import org.bukkit.Bukkit;
10 | import org.bukkit.ChatColor;
11 | import org.objectweb.asm.tree.InsnList;
12 | import org.objectweb.asm.tree.MethodNode;
13 |
14 | import java.util.List;
15 |
16 | @AllArgsConstructor
17 | @Getter
18 | public class MActionInsertShellCode implements MixinAction {
19 |
20 | private ShellCode shellCode;
21 | private HookLocator hookLocator;
22 |
23 |
24 | @Override
25 | public void action(Class> owner, MethodNode method) {
26 | // Copy hookLocator.getLineNumber(method) to listHooks
27 | List hooks = hookLocator.getLineNumber(method.instructions);
28 |
29 | LocalVarManager localVarManager = new LocalVarManager(method);
30 |
31 | // Hook!
32 | InsnList newInstructions = new InsnList();
33 | for (int i = 0; i < method.instructions.size(); i++) {
34 | if (hooks.contains(i)) {
35 | if (shellCode.getShellCodeInfo().calledDirectly()) {
36 | try {
37 | InsnList instructions = shellCode.generate(method, localVarManager);
38 | newInstructions.add(instructions);
39 | newInstructions.add(shellCode.popExtraStack());
40 | } catch (Exception e) {
41 | e.printStackTrace();
42 | Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[!] Shell Code \"" + ChatColor.YELLOW + shellCode.getShellCodeInfo().name() + ChatColor.RED + "\" has failed generating instructions: Exception Thrown");
43 | }
44 | } else {
45 | Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[!] Shell Code \"" + ChatColor.YELLOW + shellCode.getShellCodeInfo().name() + ChatColor.RED + "\" shouldn't be called directly (calledDirectly = false)");
46 | }
47 |
48 | }
49 | newInstructions.add(method.instructions.get(i));
50 | }
51 | method.instructions = newInstructions;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/action/impl/MActionMethodCallSpoofer.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.action.impl;
2 |
3 | import com.dragoncommissions.mixbukkit.api.action.MixinAction;
4 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeReflectionMethodInvoke;
5 | import com.dragoncommissions.mixbukkit.utils.ASMUtils;
6 | import javassist.bytecode.Opcode;
7 | import lombok.SneakyThrows;
8 | import org.objectweb.asm.tree.*;
9 |
10 | import java.lang.reflect.Method;
11 | import java.util.HashMap;
12 | import java.util.Map;
13 | import java.util.UUID;
14 | import java.util.function.Predicate;
15 |
16 | @SuppressWarnings("Untested")
17 | public class MActionMethodCallSpoofer implements MixinAction {
18 |
19 | private Method method;
20 | private String key;
21 | private Predicate filter;
22 |
23 | private static Map getters = new HashMap<>();
24 |
25 | public static Object get(String value) {
26 | return getters.get(value).getReturnValue();
27 | }
28 |
29 | private MActionMethodCallSpoofer(Method method, ReturnValueGetter returnValueGetter, Predicate filter) {
30 | this.method = method;
31 | if (method.getReturnType() == void.class) throw new IllegalArgumentException("Method: " + method.getName() + " is not returning anything. Nothing to spoof");
32 | UUID uuid = UUID.randomUUID();
33 | while (getters.containsKey(uuid.toString())) {
34 | uuid = UUID.randomUUID();
35 | System.out.println("Come on! You are so f**king lucky! You literally got same UUID, how?");
36 | System.out.println("If you see this message without cheating, today is literally your most luckiest day");
37 | System.out.println("I would say the chance of it is lower than every thing you can think of");
38 | System.out.println("UUID: " + uuid);
39 | }
40 | getters.put(uuid.toString(), returnValueGetter);
41 | this.key = uuid.toString();
42 | }
43 |
44 | @Override
45 | @SneakyThrows
46 | public void action(Class> owner, MethodNode methodNode) {
47 | InsnList out = new InsnList();
48 | int amount = 0;
49 | for (AbstractInsnNode instruction : methodNode.instructions) {
50 | out.add(instruction);
51 | if (instruction instanceof MethodInsnNode) {
52 | MethodInsnNode insn = (MethodInsnNode) instruction;
53 | if (insn.name.equals(method.getName()) && insn.owner.equals(method.getDeclaringClass().getName().replace(".", "/"))
54 | && insn.desc.equals(ASMUtils.getDescriptor(method.getReturnType(), method.getParameterTypes())) && filter.test(amount++)) {
55 | out.add(new InsnNode(Opcode.POP)); // Pop the return value first
56 | out.add(new LdcInsnNode(key));
57 | out.add(new IShellCodeReflectionMethodInvoke(MActionMethodCallSpoofer.class.getDeclaredMethod("get", String.class)).generate());
58 | out.add(ASMUtils.cast(method.getReturnType()));
59 | }
60 | }
61 | }
62 | methodNode.instructions = out;
63 | }
64 |
65 | public interface ReturnValueGetter {
66 | Object getReturnValue();
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/action/impl/MActionMethodReplacer.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.action.impl;
2 |
3 | import com.dragoncommissions.mixbukkit.api.action.MixinAction;
4 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager;
5 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeReflectionMethodInvoke;
6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils;
7 | import javassist.bytecode.Opcode;
8 | import lombok.SneakyThrows;
9 | import org.objectweb.asm.tree.InsnList;
10 | import org.objectweb.asm.tree.InsnNode;
11 | import org.objectweb.asm.tree.MethodNode;
12 |
13 | import java.lang.reflect.Method;
14 |
15 | public class MActionMethodReplacer implements MixinAction {
16 |
17 | private Method handler;
18 |
19 | public MActionMethodReplacer(Method handler) {
20 | this.handler = handler;
21 | }
22 |
23 | @Override
24 | @SneakyThrows
25 | public void action(Class> owner, MethodNode methodNode) {
26 | methodNode.tryCatchBlocks.clear();
27 | methodNode.localVariables.clear();
28 | methodNode.instructions.clear();
29 | Class> returnType = ASMUtils.getReturnType(methodNode.desc);
30 | if (returnType != handler.getReturnType()) throw new IllegalArgumentException("Handler: " + handler.getName() + " is not returning same type as target method (" + handler.getReturnType().getName() + "(return type of handler) != " + returnType.getName() + "(return type of target) )");
31 | LocalVarManager varManager = new LocalVarManager(methodNode);
32 | InsnList out = new InsnList();
33 | Class>[] parameterTypes = handler.getParameterTypes();
34 | for (int i = 0; i < parameterTypes.length; i++) {
35 | Class> parameterType = parameterTypes[i];
36 | out.add(ASMUtils.castToObject(i, parameterType));
37 | }
38 | IShellCodeReflectionMethodInvoke shellCodeReflectionMethodInvoke = new IShellCodeReflectionMethodInvoke(handler);
39 | out.add(shellCodeReflectionMethodInvoke.generate(methodNode, varManager));
40 | if (!methodNode.desc.endsWith("V")) {
41 | out.add(ASMUtils.cast(returnType));
42 | out.add(ASMUtils.genReturnNode(returnType));
43 | } else {
44 | out.add(new InsnNode(Opcode.RETURN));
45 | }
46 | methodNode.instructions = out;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/action/impl/MActionPipeline.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.action.impl;
2 |
3 | import com.dragoncommissions.mixbukkit.api.action.MixinAction;
4 | import org.objectweb.asm.tree.MethodNode;
5 |
6 | public class MActionPipeline implements MixinAction {
7 |
8 | private final MixinAction[] actions;
9 |
10 | public MActionPipeline(MixinAction... actions) {
11 | this.actions = actions;
12 | }
13 |
14 | @Override
15 | public void action(Class> owner, MethodNode method) {
16 | for (MixinAction action : actions) {
17 | action.action(owner, method);
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/locator/HookLocator.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.locator;
2 |
3 | import org.objectweb.asm.tree.InsnList;
4 |
5 | import java.util.List;
6 |
7 | public interface HookLocator {
8 |
9 | /**
10 | * Process a method, and return the line number to hook at. Return empty array if it fails to find target hook location
11 | * @param insnList MethodNode input
12 | * @return Array of line numbers, it will be inserted, means 0 will make first instruction into hooking instruction
13 | */
14 | List getLineNumber(InsnList insnList);
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorFieldAccess.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.locator.impl;
2 |
3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator;
4 | import com.dragoncommissions.mixbukkit.utils.ASMUtils;
5 | import com.dragoncommissions.mixbukkit.utils.PostPreState;
6 | import lombok.AllArgsConstructor;
7 | import org.objectweb.asm.tree.FieldInsnNode;
8 | import org.objectweb.asm.tree.InsnList;
9 |
10 | import java.lang.reflect.Field;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 | import java.util.function.Predicate;
14 |
15 | @AllArgsConstructor
16 | public class HLocatorFieldAccess implements HookLocator {
17 |
18 | private Field field;
19 | private PostPreState state;
20 | private Predicate filter;
21 |
22 | @Override
23 | public List getLineNumber(InsnList insnList) {
24 | List out = new ArrayList<>();
25 | int amount = 0;
26 | for (int i = 0; i < insnList.size(); i++) {
27 | if (insnList.get(i) instanceof FieldInsnNode) {
28 | FieldInsnNode fieldInsnNode = (FieldInsnNode) insnList.get(i);
29 | String owner = field.getDeclaringClass().getName().replace(".", "/");
30 | String name = field.getName();
31 | String desc = ASMUtils.toDescriptorTypeName(field.getType().getName());
32 | if (fieldInsnNode.owner.equals(owner) && fieldInsnNode.name.equals(name) && fieldInsnNode.desc.equals(desc) && filter.test(amount++)) {
33 | out.add(i + (state == PostPreState.POST?1:0));
34 | }
35 | }
36 | }
37 | return out;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorFieldRead.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.locator.impl;
2 |
3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator;
4 | import com.dragoncommissions.mixbukkit.utils.ASMUtils;
5 | import com.dragoncommissions.mixbukkit.utils.PostPreState;
6 | import javassist.bytecode.Opcode;
7 | import lombok.AllArgsConstructor;
8 | import org.objectweb.asm.tree.FieldInsnNode;
9 | import org.objectweb.asm.tree.InsnList;
10 |
11 | import java.lang.reflect.Field;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 | import java.util.function.Predicate;
15 |
16 | @AllArgsConstructor
17 | public class HLocatorFieldRead implements HookLocator {
18 |
19 | private Field field;
20 | private PostPreState state;
21 | private Predicate filter;
22 |
23 | @Override
24 | public List getLineNumber(InsnList insnList) {
25 | List out = new ArrayList<>();
26 | int amount = 0;
27 | for (int i = 0; i < insnList.size(); i++) {
28 | if (insnList.get(i) instanceof FieldInsnNode) {
29 | FieldInsnNode fieldInsnNode = (FieldInsnNode) insnList.get(i);
30 | String owner = field.getDeclaringClass().getName().replace(".", "/");
31 | String name = field.getName();
32 | String desc = ASMUtils.toDescriptorTypeName(field.getType().getName());
33 | if (fieldInsnNode.owner.equals(owner) && fieldInsnNode.name.equals(name) && fieldInsnNode.desc.equals(desc) && filter.test(amount++)) {
34 | if (fieldInsnNode.getOpcode() == Opcode.GETFIELD || fieldInsnNode.getOpcode() == Opcode.GETSTATIC)
35 | out.add(i + (state == PostPreState.POST?1:0));
36 | }
37 | }
38 | }
39 | return out;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorFieldWrite.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.locator.impl;
2 |
3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator;
4 | import com.dragoncommissions.mixbukkit.utils.ASMUtils;
5 | import com.dragoncommissions.mixbukkit.utils.PostPreState;
6 | import javassist.bytecode.Opcode;
7 | import lombok.AllArgsConstructor;
8 | import org.objectweb.asm.tree.FieldInsnNode;
9 | import org.objectweb.asm.tree.InsnList;
10 |
11 | import java.lang.reflect.Field;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 | import java.util.function.Predicate;
15 |
16 | @AllArgsConstructor
17 | public class HLocatorFieldWrite implements HookLocator {
18 |
19 | private Field field;
20 | private PostPreState state;
21 | private Predicate filter;
22 |
23 | @Override
24 | public List getLineNumber(InsnList insnList) {
25 | List out = new ArrayList<>();
26 | int amount = 0;
27 | for (int i = 0; i < insnList.size(); i++) {
28 | if (insnList.get(i) instanceof FieldInsnNode) {
29 | FieldInsnNode fieldInsnNode = (FieldInsnNode) insnList.get(i);
30 | String owner = field.getDeclaringClass().getName().replace(".", "/");
31 | String name = field.getName();
32 | String desc = ASMUtils.toDescriptorTypeName(field.getType().getName());
33 | if (fieldInsnNode.owner.equals(owner) && fieldInsnNode.name.equals(name) && fieldInsnNode.desc.equals(desc) && filter.test(amount++)) {
34 | if (fieldInsnNode.getOpcode() == Opcode.PUTFIELD || fieldInsnNode.getOpcode() == Opcode.PUTSTATIC)
35 | out.add(i + (state == PostPreState.POST?1:0));
36 | }
37 | }
38 | }
39 | return out;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorHead.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.locator.impl;
2 |
3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator;
4 | import org.objectweb.asm.tree.InsnList;
5 |
6 | import java.util.Arrays;
7 | import java.util.List;
8 |
9 | public class HLocatorHead implements HookLocator {
10 | @Override
11 | public List getLineNumber(InsnList insnNodes) {
12 | return Arrays.asList(0);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorMethodInvoke.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.locator.impl;
2 |
3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator;
4 | import com.dragoncommissions.mixbukkit.utils.ASMUtils;
5 | import com.dragoncommissions.mixbukkit.utils.PostPreState;
6 | import org.objectweb.asm.tree.InsnList;
7 | import org.objectweb.asm.tree.MethodInsnNode;
8 |
9 | import java.lang.reflect.Method;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 | import java.util.function.Predicate;
13 |
14 | public class HLocatorMethodInvoke implements HookLocator {
15 |
16 | private String owner;
17 | private String desc;
18 | private String name;
19 | private PostPreState state;
20 | private Predicate filter;
21 |
22 | public HLocatorMethodInvoke(Method method, PostPreState state, Predicate filter) {
23 | owner = method.getDeclaringClass().getName().replace(".", "/");
24 | desc = ASMUtils.getDescriptor(method.getReturnType(), method.getParameterTypes());
25 | name = method.getName();
26 | this.filter = filter;
27 | this.state = state;
28 | }
29 |
30 | public HLocatorMethodInvoke(Class> owner, Method method, PostPreState state, Predicate filter) {
31 | this.owner = owner.getName().replace(".", "/");
32 | desc = ASMUtils.getDescriptor(method.getReturnType(), method.getParameterTypes());
33 | name = method.getName();
34 | this.filter = filter;
35 | this.state = state;
36 | }
37 |
38 | @Override
39 | public List getLineNumber(InsnList insnList) {
40 | int amount = 0;
41 | List out = new ArrayList<>();
42 | for (int i = 0; i < insnList.size(); i++) {
43 | if (insnList.get(i) instanceof MethodInsnNode) {
44 | MethodInsnNode insnNode = (MethodInsnNode) insnList.get(i);
45 | if (insnNode.owner.equals(owner) && insnNode.name.equals(name) && insnNode.desc.equals(desc) && filter.test(amount++)) {
46 | out.add(i + (state == PostPreState.POST?1:0));
47 | }
48 | }
49 | }
50 | return out;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorReturn.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.locator.impl;
2 |
3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator;
4 | import org.objectweb.asm.tree.AbstractInsnNode;
5 | import org.objectweb.asm.tree.InsnList;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | public class HLocatorReturn implements HookLocator {
11 | @Override
12 | public List getLineNumber(InsnList insnList) {
13 | List list = new ArrayList<>();
14 | for (int i = 0; i < insnList.size(); i++) {
15 | AbstractInsnNode insnNode = insnList.get(i);
16 | if (insnNode.getOpcode() >= 172 && insnNode.getOpcode() <= 177) {
17 | list.add(i);
18 | }
19 | }
20 | return list;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/IShellCode.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.shellcode;
2 |
3 | import org.objectweb.asm.tree.InsnList;
4 | import org.objectweb.asm.tree.MethodNode;
5 |
6 | public interface IShellCode {
7 |
8 | InsnList generate(MethodNode methodNode, LocalVarManager varManager);
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/LocalVarManager.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.shellcode;
2 |
3 | import com.dragoncommissions.mixbukkit.utils.ASMUtils;
4 | import lombok.Getter;
5 | import org.objectweb.asm.tree.MethodNode;
6 |
7 | public class LocalVarManager {
8 |
9 | @Getter
10 | private MethodNode methodNode;
11 |
12 | private int latestVarNumber;
13 |
14 | public LocalVarManager(MethodNode methodNode) {
15 | this.methodNode = methodNode;
16 | latestVarNumber = ASMUtils.getLatestVarNumber(methodNode.instructions) + 1;
17 | }
18 |
19 | public int allocateVarNumber() {
20 | return latestVarNumber++;
21 | }
22 |
23 | public int getLatestUnusedVarNumber() {
24 | return latestVarNumber;
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/ShellCode.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.shellcode;
2 |
3 | import org.objectweb.asm.Opcodes;
4 | import org.objectweb.asm.tree.InsnList;
5 | import org.objectweb.asm.tree.InsnNode;
6 |
7 | public abstract class ShellCode implements IShellCode {
8 |
9 | public ShellCode() {
10 | if (getShellCodeInfo() == null) throw new NullPointerException("Shellcode: " + this.getClass().getName() + " is invalid! @ShellCodeInfo annotation is not presented.");
11 | }
12 |
13 | public InsnList generate() {
14 | return generate(null, null);
15 | }
16 |
17 | public InsnList popExtraStack() {
18 | InsnList list = new InsnList();
19 | for (int i = 0; i < getShellCodeInfo().stacksContent().length; i++) {
20 | list.add(new InsnNode(Opcodes.POP));
21 | }
22 | return list;
23 | }
24 |
25 | public ShellCodeInfo getShellCodeInfo() {
26 | try {
27 | return getClass().getAnnotationsByType(ShellCodeInfo.class)[0];
28 | } catch (Exception ignored) {
29 | return null;
30 | }
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/ShellCodeInfo.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.shellcode;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Target(ElementType.TYPE)
9 | @Retention(RetentionPolicy.RUNTIME)
10 | public @interface ShellCodeInfo {
11 |
12 | /**
13 | * @return Official name of the shellcode
14 | */
15 | String name();
16 |
17 | /**
18 | * @return Description of the shellcode, like what it will do etc.
19 | */
20 | String description();
21 |
22 | /**
23 | * @return Returns true if it's going to modify the MethodNode. For example: Add a try & catch block
24 | */
25 | boolean requireMethodNodeModification() default false;
26 |
27 | /**
28 | * @return Returns true if it will need to allocate new variables
29 | */
30 | boolean requireVarManager() default false;
31 |
32 | /**
33 | * @return Content of stack, first one pushed into stack should be in the bottom of return value
34 | */
35 | String[] stacksContent() default {};
36 |
37 | /**
38 | * @return What's required on the stack
39 | */
40 | String[] requiredStacksContent() default {};
41 |
42 | /**
43 | * @return Is it safe to call it directly inside a method
44 | */
45 | boolean calledDirectly() default false;
46 |
47 | /**
48 | * @return Is it required to use -noverify in order to load the shellcode
49 | */
50 | boolean failsClassVerification() default true;
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/api/CallbackInfo.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.api;
2 |
3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager;
4 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeLoadClassFromPCL;
5 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeMethodInvoke;
6 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeReflectionMethodInvoke;
7 | import com.dragoncommissions.mixbukkit.utils.ASMUtils;
8 | import javassist.bytecode.Opcode;
9 | import lombok.Getter;
10 | import lombok.Setter;
11 | import lombok.SneakyThrows;
12 | import org.objectweb.asm.tree.*;
13 |
14 | public class CallbackInfo {
15 |
16 | @Getter
17 | @Setter
18 | private Object returnValue = null;
19 | @Getter
20 | @Setter
21 | private boolean returned;
22 |
23 | @SneakyThrows
24 | public static InsnList generateCallBackInfo() {
25 | InsnList out = new InsnList();
26 | out.add(new IShellCodeLoadClassFromPCL(CallbackInfo.class).generate());
27 | out.add(new IShellCodeMethodInvoke(Class.class.getDeclaredMethod("newInstance")).generate());
28 | return out;
29 | }
30 |
31 | @SneakyThrows
32 | public static InsnList processCallBackInfo(MethodNode hookedMethod, LocalVarManager varManager, int varLocation) {
33 | InsnList out = new InsnList();
34 | LabelNode returnBranch = new LabelNode();
35 | LabelNode defaultBranch = new LabelNode();
36 |
37 | out.add(new VarInsnNode(Opcode.ALOAD, varLocation));
38 | out.add(new IShellCodeReflectionMethodInvoke(CallbackInfo.class.getDeclaredMethod("isReturned")).generate(null, varManager));
39 | out.add(new TypeInsnNode(Opcode.CHECKCAST, Boolean.class.getName().replace(".", "/")));
40 | out.add(new IShellCodeMethodInvoke(Boolean.class.getDeclaredMethod("booleanValue")).generate());
41 | out.add(new JumpInsnNode(Opcode.IFEQ, defaultBranch));
42 | out.add(returnBranch);
43 | out.add(new VarInsnNode(Opcode.ALOAD, varLocation));
44 | out.add(new IShellCodeReflectionMethodInvoke(CallbackInfo.class.getDeclaredMethod("getReturnValue")).generate(null, varManager));
45 | if (!hookedMethod.desc.endsWith("V")) {
46 | Class> returnType = ASMUtils.getReturnType(hookedMethod.desc);
47 | out.add(ASMUtils.cast(returnType));
48 | out.add(ASMUtils.genReturnNode(returnType));
49 | } else {
50 | out.add(new InsnNode(Opcode.RETURN));
51 | }
52 |
53 | out.add(defaultBranch);
54 | return out;
55 | }
56 |
57 | }
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/api/ShellCodeComment.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.api;
2 |
3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager;
4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode;
5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo;
6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils;
7 | import javassist.bytecode.Opcode;
8 | import lombok.NonNull;
9 | import lombok.RequiredArgsConstructor;
10 | import org.objectweb.asm.tree.InsnList;
11 | import org.objectweb.asm.tree.InsnNode;
12 | import org.objectweb.asm.tree.LdcInsnNode;
13 | import org.objectweb.asm.tree.MethodNode;
14 |
15 | @ShellCodeInfo(
16 | name = "Comment",
17 | description = "Leave a comment, and it won't do anything other than wasting performance",
18 | calledDirectly = true
19 | )
20 | @RequiredArgsConstructor
21 | public class ShellCodeComment extends ShellCode {
22 |
23 | @NonNull
24 | private String comment;
25 |
26 | @Override
27 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) {
28 | return ASMUtils.asInsnList(
29 | new LdcInsnNode(comment),
30 | new InsnNode(Opcode.POP)
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/api/ShellCodePrintMessage.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.api;
2 |
3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager;
4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode;
5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo;
6 | import lombok.AllArgsConstructor;
7 | import lombok.Getter;
8 | import org.objectweb.asm.tree.*;
9 |
10 | import static javassist.bytecode.Opcode.GETSTATIC;
11 | import static javassist.bytecode.Opcode.INVOKEVIRTUAL;
12 |
13 | @AllArgsConstructor
14 | @Getter
15 | @ShellCodeInfo(
16 | name = "stdout Print Message",
17 | description = "Call a System.out.println",
18 | requireVarManager = false,
19 | stacksContent = {},
20 | requiredStacksContent = {},
21 | calledDirectly = true
22 | )
23 | public class ShellCodePrintMessage extends ShellCode {
24 |
25 | private String message;
26 |
27 |
28 | @Override
29 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) {
30 | InsnList list = new InsnList();
31 | list.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
32 | list.add(new LdcInsnNode(message));
33 | list.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V"));
34 | return list;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/api/ShellCodePrintTopStackType.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.api;
2 |
3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager;
4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode;
5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo;
6 | import lombok.AllArgsConstructor;
7 | import lombok.Getter;
8 | import org.objectweb.asm.tree.*;
9 |
10 | import static javassist.bytecode.Opcode.GETSTATIC;
11 | import static javassist.bytecode.Opcode.INVOKEVIRTUAL;
12 |
13 | @AllArgsConstructor
14 | @Getter
15 | @ShellCodeInfo(
16 | name = "Print Type",
17 | description = "System.out the full class name of the content that's on the top of the stack",
18 | requireVarManager = false,
19 | stacksContent = {},
20 | requiredStacksContent = {},
21 | calledDirectly = true
22 | )
23 | public class ShellCodePrintTopStackType extends ShellCode {
24 |
25 | private String message;
26 |
27 |
28 | @Override
29 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) {
30 | InsnList list = new InsnList();
31 | list.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
32 | list.add(new LdcInsnNode(message));
33 | list.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V"));
34 | return list;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/api/ShellCodeReflectionMixinPluginMethodCall.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.api;
2 |
3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager;
4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode;
5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo;
6 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeReflectionMethodInvoke;
7 | import com.dragoncommissions.mixbukkit.utils.ASMUtils;
8 | import lombok.AllArgsConstructor;
9 | import lombok.SneakyThrows;
10 | import org.objectweb.asm.tree.InsnList;
11 | import org.objectweb.asm.tree.MethodNode;
12 |
13 | import java.lang.reflect.Method;
14 |
15 | @ShellCodeInfo(
16 | name = "Reflection Mixin Plugin Method Call",
17 | description = "It will use a URLClassLoader of a random plugin to load the target plugin class and invoke the method. " +
18 | "A regular method invoke doesn't work since it uses PluginClassLoader.",
19 | requireVarManager = true,
20 | requireMethodNodeModification = true,
21 | stacksContent = {"Return value of invoked method (as Object)"},
22 | requiredStacksContent = {},
23 | calledDirectly = true
24 | )
25 | @AllArgsConstructor
26 | public class ShellCodeReflectionMixinPluginMethodCall extends ShellCode {
27 |
28 | private Method handler;
29 |
30 | @Override
31 | @SneakyThrows
32 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) {
33 | InsnList out = new InsnList();
34 | Class>[] parameterTypes = handler.getParameterTypes();
35 | boolean hasCallBackInfo = false;
36 | for (int i = 0; i < parameterTypes.length; i++) {
37 | Class> parameterType = parameterTypes[i];
38 | if (CallbackInfo.class.isAssignableFrom(parameterType) && i == parameterTypes.length - 1) {
39 | out.add(CallbackInfo.generateCallBackInfo());
40 | hasCallBackInfo = true;
41 | } else {
42 | out.add(ASMUtils.castToObject(i, parameterType));
43 | }
44 | }
45 | IShellCodeReflectionMethodInvoke shellCodeReflectionMethodInvoke = new IShellCodeReflectionMethodInvoke(handler);
46 | out.add(shellCodeReflectionMethodInvoke.generate(methodNode, varManager));
47 | if (hasCallBackInfo) {
48 | Integer varLocation = shellCodeReflectionMethodInvoke.getArgumentVarIndex().get(parameterTypes.length - 1);
49 | out.add(CallbackInfo.processCallBackInfo(methodNode, varManager, varLocation));
50 | }
51 | return out;
52 | }
53 |
54 |
55 |
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/inner/IShellCodeLoadClassFromPCL.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.inner;
2 |
3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager;
4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode;
5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo;
6 | import javassist.bytecode.Opcode;
7 | import lombok.SneakyThrows;
8 | import org.bukkit.Bukkit;
9 | import org.bukkit.plugin.PluginManager;
10 | import org.objectweb.asm.tree.InsnList;
11 | import org.objectweb.asm.tree.InsnNode;
12 | import org.objectweb.asm.tree.LdcInsnNode;
13 | import org.objectweb.asm.tree.MethodNode;
14 |
15 | @ShellCodeInfo(
16 | name = "Load Class From Plugin Class Loader",
17 | description = "Load a class from the plugin class loader, and get its \"class\" instance",
18 | stacksContent = {"Class"},
19 | calledDirectly = true
20 | )
21 | public class IShellCodeLoadClassFromPCL extends ShellCode {
22 |
23 | private String name;
24 |
25 | public IShellCodeLoadClassFromPCL(String className) {
26 | this.name = className.replace("/", ".");
27 | }
28 |
29 | public IShellCodeLoadClassFromPCL(Class> clazz) {
30 | this.name = clazz.getName();
31 | }
32 |
33 | @Override
34 | @SneakyThrows
35 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) {
36 | InsnList out = new InsnList();
37 | out.add(new LdcInsnNode(name));
38 | out.add(new IShellCodePushInt(1).generate()); // true
39 | out.add(new IShellCodeMethodInvoke(Bukkit.class.getDeclaredMethod("getPluginManager")).generate());
40 | out.add(new IShellCodeMethodInvoke(PluginManager.class.getDeclaredMethod("getPlugins")).generate());
41 | out.add(new IShellCodePushInt(0).generate());
42 | out.add(new InsnNode(Opcode.AALOAD));
43 | out.add(new IShellCodeMethodInvoke(Object.class.getDeclaredMethod("getClass")).generate());
44 | out.add(new IShellCodeMethodInvoke(Class.class.getDeclaredMethod("getClassLoader")).generate());
45 | out.add(new IShellCodeMethodInvoke(Class.class.getDeclaredMethod("forName", String.class, boolean.class, ClassLoader.class)).generate());
46 | return out;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/inner/IShellCodeMethodInvoke.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.inner;
2 |
3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager;
4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode;
5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo;
6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils;
7 | import javassist.bytecode.Opcode;
8 | import lombok.AllArgsConstructor;
9 | import lombok.Getter;
10 | import org.objectweb.asm.tree.InsnList;
11 | import org.objectweb.asm.tree.MethodInsnNode;
12 | import org.objectweb.asm.tree.MethodNode;
13 |
14 | import java.lang.reflect.Method;
15 | import java.lang.reflect.Modifier;
16 |
17 | @ShellCodeInfo(
18 | name = "Method Invoke",
19 | description = "Call methods programmatically",
20 | stacksContent = {"Return value of invoked method"},
21 | requiredStacksContent = {"Object that calls the method", "Arguments (in order)"}
22 | )
23 | @AllArgsConstructor
24 | @Getter
25 | public class IShellCodeMethodInvoke extends ShellCode {
26 |
27 | private Method method;
28 |
29 | @Override
30 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) {
31 | InsnList list = new InsnList();
32 | list.add(new MethodInsnNode(
33 | Modifier.isStatic(method.getModifiers()) ? Opcode.INVOKESTATIC:(method.getDeclaringClass().isInterface()?Opcode.INVOKEINTERFACE:Opcode.INVOKEVIRTUAL),
34 | method.getDeclaringClass().getName().replace(".", "/"),
35 | method.getName(),
36 | ASMUtils.getDescriptor(method.getReturnType(), method.getParameterTypes()),
37 | method.getDeclaringClass().isInterface() && !Modifier.isStatic(method.getModifiers())
38 | ));
39 | return list;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/inner/IShellCodeNewArrayAndAddContent.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.inner;
2 |
3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager;
4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode;
5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo;
6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils;
7 | import javassist.bytecode.Opcode;
8 | import lombok.AllArgsConstructor;
9 | import lombok.Getter;
10 | import org.objectweb.asm.tree.InsnList;
11 | import org.objectweb.asm.tree.InsnNode;
12 | import org.objectweb.asm.tree.MethodNode;
13 | import org.objectweb.asm.tree.TypeInsnNode;
14 |
15 | @ShellCodeInfo(
16 | name = "New Array and Add Content",
17 | description = "Create a new array, set the content of it (by for looping), and pushes the array to stack",
18 | requireVarManager = false,
19 | stacksContent = {"The created array"},
20 | requiredStacksContent = {},
21 | calledDirectly = false
22 | )
23 | @AllArgsConstructor
24 | @Getter
25 | public class IShellCodeNewArrayAndAddContent extends ShellCode {
26 |
27 | private int arraySize;
28 | private Class> type;
29 | private ForLoopInstructionGenerator gen;
30 |
31 | @Override
32 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) {
33 | InsnList out = new InsnList();
34 | out.add(ASMUtils.pushInt(arraySize));
35 | out.add(new TypeInsnNode(Opcode.ANEWARRAY, type.getName().replace(".", "/")));
36 | for (int i = 0; i < arraySize; i++) {
37 | out.add(new InsnNode(Opcode.DUP));
38 | out.add(ASMUtils.pushInt(i));
39 | out.add(gen.generate(i));
40 | out.add(new InsnNode(Opcode.AASTORE));
41 | }
42 | return out;
43 | }
44 |
45 | public interface ForLoopInstructionGenerator {
46 | InsnList generate(int index);
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/inner/IShellCodePushInt.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.inner;
2 |
3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager;
4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode;
5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo;
6 | import javassist.bytecode.Opcode;
7 | import lombok.AllArgsConstructor;
8 | import lombok.Getter;
9 | import org.objectweb.asm.tree.InsnList;
10 | import org.objectweb.asm.tree.InsnNode;
11 | import org.objectweb.asm.tree.IntInsnNode;
12 | import org.objectweb.asm.tree.MethodNode;
13 |
14 | @ShellCodeInfo(
15 | name = "Push Int",
16 | description = "Push an int into stack"
17 | )
18 | @AllArgsConstructor
19 | @Getter
20 | public class IShellCodePushInt extends ShellCode {
21 |
22 | private int value;
23 |
24 | @Override
25 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) {
26 | InsnList out = new InsnList();
27 | if (value <= 5 && value >= -1) {
28 | out.add(new InsnNode(value + 3));
29 | return out;
30 | }
31 | if (value < 255 & value > 0) {
32 | out.add(new IntInsnNode(Opcode.BIPUSH, value));
33 | return out;
34 | }
35 | out.add(new IntInsnNode(Opcode.SIPUSH, value));
36 | return out;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/inner/IShellCodeReflectionMethodInvoke.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.inner;
2 |
3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager;
4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode;
5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo;
6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils;
7 | import javassist.bytecode.Opcode;
8 | import lombok.Getter;
9 | import lombok.SneakyThrows;
10 | import org.bukkit.Bukkit;
11 | import org.objectweb.asm.tree.*;
12 |
13 | import java.lang.reflect.Method;
14 | import java.lang.reflect.Modifier;
15 | import java.util.ArrayList;
16 | import java.util.Collections;
17 | import java.util.List;
18 |
19 | @ShellCodeInfo(
20 | name = "Reflection Method Invoke",
21 | description = "Invoke a method with similar usage as ShellCodeMethodInvoke",
22 | stacksContent = {"Return value of invoked method"},
23 | requiredStacksContent = {"Object that calls the method", "Arguments (in order)"}
24 | )
25 | @Getter
26 | public class IShellCodeReflectionMethodInvoke extends ShellCode {
27 |
28 | private Method method;
29 | private List argumentVarIndex = new ArrayList<>();
30 |
31 | public IShellCodeReflectionMethodInvoke(Method method) {
32 | this.method = method;
33 | }
34 |
35 | @SneakyThrows
36 | private static void action() {
37 | Class.forName("CLASS_NAME_HERE", true, Bukkit.getPluginManager().getPlugins()[0].getClass().getClassLoader())
38 | .getDeclaredMethod("methodNameHere", int.class, float.class, double.class, char.class, boolean.class, byte.class, short.class, String.class, String[].class, int[].class)
39 | .invoke(null);
40 | }
41 |
42 | @Override
43 | @SneakyThrows
44 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) {
45 | InsnList out = new InsnList();
46 | argumentVarIndex = new ArrayList<>();
47 | int obj = -1;
48 | if (!Modifier.isStatic(method.getModifiers())) {
49 | obj = varManager.allocateVarNumber();
50 | out.add(new VarInsnNode(Opcode.ASTORE, obj));
51 | }
52 | int length = method.getParameterTypes().length;
53 | for (int i = 0; i < length; i++) {
54 | int arg = varManager.allocateVarNumber();
55 | argumentVarIndex.add(arg);
56 | out.add(new VarInsnNode(Opcode.ASTORE, arg));
57 | }
58 | Collections.reverse(argumentVarIndex);
59 |
60 | out.add(new IShellCodeLoadClassFromPCL(method.getDeclaringClass()).generate());
61 | out.add(new LdcInsnNode(method.getName()));
62 | out.add(new IShellCodeNewArrayAndAddContent(method.getParameterTypes().length, Class.class, index -> {
63 | InsnList list = new InsnList();
64 | list.add(ASMUtils.generateGetClassNode(method.getParameterTypes()[index]));
65 | return list;
66 | }).generate());
67 | out.add(new IShellCodeMethodInvoke(Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class)).generate());
68 | if (obj == -1) {
69 | out.add(new InsnNode(Opcode.ACONST_NULL));
70 | } else {
71 | out.add(new VarInsnNode(Opcode.ALOAD, obj));
72 | }
73 | out.add(new IShellCodeNewArrayAndAddContent(argumentVarIndex.size(), Object.class, index -> {
74 | InsnList list = new InsnList();
75 | try {
76 | list.add(new VarInsnNode(Opcode.ALOAD, argumentVarIndex.get(index)));
77 | } catch (Exception ignored) {}
78 | return list;
79 | }).generate());
80 | out.add(new IShellCodeMethodInvoke(Method.class.getDeclaredMethod("invoke", Object.class, Object[].class)).generate());
81 |
82 | return out;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/utils/ASMUtils.java:
--------------------------------------------------------------------------------
1 | package com.dragoncommissions.mixbukkit.utils;
2 |
3 | import com.dragoncommissions.mixbukkit.MixBukkit;
4 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeLoadClassFromPCL;
5 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeMethodInvoke;
6 | import javassist.CtClass;
7 | import javassist.bytecode.Opcode;
8 | import lombok.SneakyThrows;
9 | import org.bukkit.Bukkit;
10 | import org.bukkit.ChatColor;
11 | import org.objectweb.asm.ClassReader;
12 | import org.objectweb.asm.ClassWriter;
13 | import org.objectweb.asm.tree.*;
14 |
15 | import java.io.File;
16 | import java.io.FileOutputStream;
17 | import java.util.UUID;
18 |
19 | public class ASMUtils {
20 |
21 |
22 |
23 | public static String toDescriptorTypeName(String type) {
24 | switch ( type )
25 | {
26 | case "byte":
27 | return "B";
28 | case "char":
29 | return "C";
30 | case "double":
31 | return "D";
32 | case "float":
33 | return "F";
34 | case "int":
35 | return "I";
36 | case "long":
37 | return "J";
38 | case "short":
39 | return "S";
40 | case "boolean":
41 | return "Z";
42 | case "void":
43 | return "V";
44 | default:
45 | if ( type.endsWith( "[]" ) )
46 | {
47 | String s = toDescriptorTypeName(type.substring(0, type.length() - 2));
48 | return "[" + s.substring(0, s.length());
49 | }
50 | String clazzType = type.replace( '.', '/' );
51 | if ( type.startsWith("[") && type.endsWith(";") )
52 | {
53 | return clazzType;
54 | }
55 | return "L" + clazzType + ";";
56 | }
57 | }
58 |
59 | public static String getDescriptor(Class> returnType, Class>... arguments) {
60 | StringBuilder out = new StringBuilder("(");
61 | for (Class> argument : arguments) {
62 | out.append(toDescriptorTypeName(argument.getName()));
63 | }
64 | out.append(")").append(toDescriptorTypeName(returnType.getName()));
65 | return out.toString();
66 | }
67 |
68 |
69 | @SneakyThrows
70 | public static AbstractInsnNode loadVar(Class> type, int varNumber) {
71 | String result = "LOAD";
72 | if (type == byte.class) result = "I" + result;
73 | else if (type == char.class) result = "I" + result;
74 | else if (type == double.class) result = "D" + result;
75 | else if (type == float.class) result = "F" + result;
76 | else if (type == int.class) result = "I" + result;
77 | else if (type == long.class) result = "L" + result;
78 | else if (type == short.class) result = "I" + result;
79 | else if (type == boolean.class) result = "I" + result;
80 | else result = "A" + result;
81 | return new VarInsnNode(((int) Opcode.class.getField(result).get(null)), varNumber);
82 | }
83 |
84 | @SneakyThrows
85 | public static AbstractInsnNode genReturnNode(Class> type) {
86 | String result = "RETURN";
87 | if (type == byte.class) result = "I" + result;
88 | else if (type == char.class) result = "I" + result;
89 | else if (type == double.class) result = "D" + result;
90 | else if (type == float.class) result = "F" + result;
91 | else if (type == int.class) result = "I" + result;
92 | else if (type == long.class) result = "L" + result;
93 | else if (type == short.class) result = "I" + result;
94 | else if (type == boolean.class) result = "I" + result;
95 | else result = "A" + result;
96 | return new InsnNode(((int) Opcode.class.getField(result).get(null)));
97 | }
98 |
99 | @SneakyThrows
100 | public static ClassNode toClassNode(CtClass ctClass) {
101 | ClassNode node = new ClassNode();
102 | ClassReader reader = new ClassReader(ctClass.toBytecode());
103 | reader.accept(node, 0);
104 | return node;
105 | }
106 |
107 | @SneakyThrows
108 | public static ClassNode toClassNode(byte[] bytecode) {
109 | ClassNode node = new ClassNode();
110 | ClassReader reader = new ClassReader(bytecode);
111 | reader.accept(node, 0);
112 | return node;
113 | }
114 |
115 | @SneakyThrows
116 | public static byte[] fromClassNode(ClassNode node) {
117 | ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
118 | try {
119 | node.accept(writer);
120 | } catch (Exception e) {
121 | e.printStackTrace();
122 | throw e;
123 | }
124 |
125 | if (MixBukkit.WRITE_TRANSFORMED_CLASS) {
126 | try {
127 | File outFile = new File(System.getProperty("java.io.tmpdir"), UUID.randomUUID() + ".class");
128 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Wrote output class to " + outFile);
129 | FileOutputStream outputStream = new FileOutputStream(outFile);
130 | outputStream.write(writer.toByteArray());
131 | outputStream.close();
132 | } catch (Exception e) {
133 | e.printStackTrace();
134 | }
135 | }
136 | return writer.toByteArray();
137 | }
138 |
139 |
140 | @SneakyThrows
141 | public static AbstractInsnNode pushInt(int value) {
142 | if (value == -1) {
143 | return new InsnNode(Opcode.ICONST_M1);
144 | }
145 | if (value <= 5 && value >= 0) {
146 | return new InsnNode(value + 3);
147 | }
148 | if (value < 255) {
149 | return new IntInsnNode(Opcode.BIPUSH, value);
150 | }
151 | return new IntInsnNode(Opcode.SIPUSH, value);
152 | }
153 |
154 | public static Class> getObjectedType(Class> type) {
155 | if (type == byte.class) return Byte.class;
156 | else if (type == char.class) return Character.class;
157 | else if (type == double.class) return Double.class;
158 | else if (type == float.class) return Float.class;
159 | else if (type == int.class) return Integer.class;
160 | else if (type == long.class) return Long.class;
161 | else if (type == short.class) return Short.class;
162 | else if (type == boolean.class) return Boolean.class;
163 | return type;
164 | }
165 |
166 | public static String getObjectedTypeName(Class> type) {
167 | if (type == byte.class) return Byte.class.getName().replace(".", "/");
168 | else if (type == char.class) return Character.class.getName().replace(".", "/");
169 | else if (type == double.class) return Double.class.getName().replace(".", "/");
170 | else if (type == float.class) return Float.class.getName().replace(".", "/");
171 | else if (type == int.class) return Integer.class.getName().replace(".", "/");
172 | else if (type == long.class) return Long.class.getName().replace(".", "/");
173 | else if (type == short.class) return Short.class.getName().replace(".", "/");
174 | else if (type == boolean.class) return Boolean.class.getName().replace(".", "/");
175 | else return "";
176 | }
177 |
178 | public static InsnList generateGetClassNode(Class> type) {
179 | if (type == byte.class) return ASMUtils.asInsnList(new FieldInsnNode(Opcode.GETSTATIC, Byte.class.getName().replace(".", "/"), "TYPE", "Ljava/lang/Class;"));
180 | else if (type == char.class) return ASMUtils.asInsnList(new FieldInsnNode(Opcode.GETSTATIC, Character.class.getName().replace(".", "/"), "TYPE", "Ljava/lang/Class;"));
181 | else if (type == double.class) return ASMUtils.asInsnList(new FieldInsnNode(Opcode.GETSTATIC, Double.class.getName().replace(".", "/"), "TYPE", "Ljava/lang/Class;"));
182 | else if (type == float.class) return ASMUtils.asInsnList(new FieldInsnNode(Opcode.GETSTATIC, Float.class.getName().replace(".", "/"), "TYPE", "Ljava/lang/Class;"));
183 | else if (type == int.class) return ASMUtils.asInsnList(new FieldInsnNode(Opcode.GETSTATIC, Integer.class.getName().replace(".", "/"), "TYPE", "Ljava/lang/Class;"));
184 | else if (type == long.class) return ASMUtils.asInsnList(new FieldInsnNode(Opcode.GETSTATIC, Long.class.getName().replace(".", "/"), "TYPE", "Ljava/lang/Class;"));
185 | else if (type == short.class) return ASMUtils.asInsnList(new FieldInsnNode(Opcode.GETSTATIC, Short.class.getName().replace(".", "/"), "TYPE", "Ljava/lang/Class;"));
186 | else if (type == boolean.class) return ASMUtils.asInsnList(new FieldInsnNode(Opcode.GETSTATIC, Boolean.class.getName().replace(".", "/"), "TYPE", "Ljava/lang/Class;"));
187 | return new IShellCodeLoadClassFromPCL(type).generate();
188 | }
189 |
190 | public static InsnList asInsnList(AbstractInsnNode... nodes) {
191 | InsnList list = new InsnList();
192 | for (AbstractInsnNode node : nodes) {
193 | list.add(node);
194 | }
195 | return list;
196 | }
197 |
198 | public static InsnList castToObject(int varLocation, Class> type) {
199 | InsnList list = new InsnList();
200 | AbstractInsnNode insnNode = loadVar(type, varLocation);
201 | list.add(insnNode);
202 | String objectedType = getObjectedTypeName(type);
203 | if (objectedType.equals("")) return list;
204 | list.add(new MethodInsnNode(Opcode.INVOKESTATIC, objectedType, "valueOf", getDescriptor(getObjectedType(type), type)));
205 | return list;
206 | }
207 |
208 | @SneakyThrows
209 | public static Class> descriptorToClass(String s) {
210 | Class> type = null;
211 | switch (s) {
212 | case "Z" :
213 | type = boolean.class;
214 | break;
215 | case "C" :
216 | type = char.class;
217 | break;
218 | case "B" :
219 | type = byte.class;
220 | break;
221 | case "S" :
222 | type = short.class;
223 | break;
224 | case "I" :
225 | type = int.class;
226 | break;
227 | case "J" :
228 | type = long.class;
229 | break;
230 | case "F" :
231 | type = float.class;
232 | break;
233 | case "D" :
234 | type = double.class;
235 | break;
236 | case "V" :
237 | type = void.class;
238 | break;
239 | }
240 | if (s.startsWith("L")) {
241 | type = Class.forName(s.substring(1, s.length()-1));
242 | }
243 | return type;
244 | }
245 |
246 | public static Class> getReturnType(String descriptor) {
247 | String s = descriptor.split("\\)")[1];
248 | return descriptorToClass(s);
249 | }
250 |
251 | @SneakyThrows
252 | public static InsnList cast(Class> type) {
253 | InsnList out = new InsnList();
254 | Class> objectedType = ASMUtils.getObjectedType(type);
255 | if (objectedType != type) {
256 | out.add(new TypeInsnNode(Opcode.CHECKCAST, objectedType.getName().replace(".", "/")));
257 | out.add(new IShellCodeMethodInvoke(objectedType.getDeclaredMethod(type.getName() + "Value")).generate());
258 | } else {
259 | out.add(new TypeInsnNode(Opcode.CHECKCAST, objectedType.getName().replace(".", "/")));
260 | }
261 | return out;
262 | }
263 |
264 | public static int getLatestVarNumber(InsnList list) {
265 | int out = 5;
266 | for (AbstractInsnNode insnNode : list) {
267 | if (insnNode instanceof VarInsnNode) {
268 | int var = ((VarInsnNode) insnNode).var;
269 | out = Math.max(var, out);
270 | }
271 | }
272 | return out;
273 | }
274 |
275 | }
276 |
--------------------------------------------------------------------------------
/src/main/java/com/dragoncommissions/mixbukkit/utils/CustomPrinter.java:
--------------------------------------------------------------------------------
1 | //
2 | // Source code recreated from a .class file by IntelliJ IDEA
3 | // (powered by FernFlower decompiler)
4 | //
5 |
6 | package com.dragoncommissions.mixbukkit.utils;
7 |
8 | import java.io.FileInputStream;
9 | import java.io.IOException;
10 | import java.io.PrintWriter;
11 | import java.util.ArrayList;
12 | import java.util.Iterator;
13 | import java.util.List;
14 | import org.objectweb.asm.Attribute;
15 | import org.objectweb.asm.ClassReader;
16 | import org.objectweb.asm.ClassVisitor;
17 | import org.objectweb.asm.Handle;
18 | import org.objectweb.asm.Label;
19 | import org.objectweb.asm.TypePath;
20 | import org.objectweb.asm.util.Printer;
21 | import org.objectweb.asm.util.TraceClassVisitor;
22 |
23 | public abstract class CustomPrinter extends Printer {
24 | public static final String[] OPCODES = new String[]{"NOP", "ACONST_NULL", "ICONST_M1", "ICONST_0", "ICONST_1", "ICONST_2", "ICONST_3", "ICONST_4", "ICONST_5", "LCONST_0", "LCONST_1", "FCONST_0", "FCONST_1", "FCONST_2", "DCONST_0", "DCONST_1", "BIPUSH", "SIPUSH", "LDC", "LDC_W", "LDC2_W", "ILOAD", "LLOAD", "FLOAD", "DLOAD", "ALOAD", "ILOAD_0", "ILOAD_1", "ILOAD_2", "ILOAD_3", "LLOAD_0", "LLOAD_1", "LLOAD_2", "LLOAD_3", "FLOAD_0", "FLOAD_1", "FLOAD_2", "FLOAD_3", "DLOAD_0", "DLOAD_1", "DLOAD_2", "DLOAD_3", "ALOAD_0", "ALOAD_1", "ALOAD_2", "ALOAD_3", "IALOAD", "LALOAD", "FALOAD", "DALOAD", "AALOAD", "BALOAD", "CALOAD", "SALOAD", "ISTORE", "LSTORE", "FSTORE", "DSTORE", "ASTORE", "ISTORE_0", "ISTORE_1", "ISTORE_2", "ISTORE_3", "LSTORE_0", "LSTORE_1", "LSTORE_2", "LSTORE_3", "FSTORE_0", "FSTORE_1", "FSTORE_2", "FSTORE_3", "DSTORE_0", "DSTORE_1", "DSTORE_2", "DSTORE_3", "ASTORE_0", "ASTORE_1", "ASTORE_2", "ASTORE_3", "IASTORE", "LASTORE", "FASTORE", "DASTORE", "AASTORE", "BASTORE", "CASTORE", "SASTORE", "POP", "POP2", "DUP", "DUP_X1", "DUP_X2", "DUP2", "DUP2_X1", "DUP2_X2", "SWAP", "IADD", "LADD", "FADD", "DADD", "ISUB", "LSUB", "FSUB", "DSUB", "IMUL", "LMUL", "FMUL", "DMUL", "IDIV", "LDIV", "FDIV", "DDIV", "IREM", "LREM", "FREM", "DREM", "INEG", "LNEG", "FNEG", "DNEG", "ISHL", "LSHL", "ISHR", "LSHR", "IUSHR", "LUSHR", "IAND", "LAND", "IOR", "LOR", "IXOR", "LXOR", "IINC", "I2L", "I2F", "I2D", "L2I", "L2F", "L2D", "F2I", "F2L", "F2D", "D2I", "D2L", "D2F", "I2B", "I2C", "I2S", "LCMP", "FCMPL", "FCMPG", "DCMPL", "DCMPG", "IFEQ", "IFNE", "IFLT", "IFGE", "IFGT", "IFLE", "IF_ICMPEQ", "IF_ICMPNE", "IF_ICMPLT", "IF_ICMPGE", "IF_ICMPGT", "IF_ICMPLE", "IF_ACMPEQ", "IF_ACMPNE", "GOTO", "JSR", "RET", "TABLESWITCH", "LOOKUPSWITCH", "IRETURN", "LRETURN", "FRETURN", "DRETURN", "ARETURN", "RETURN", "GETSTATIC", "PUTSTATIC", "GETFIELD", "PUTFIELD", "INVOKEVIRTUAL", "INVOKESPECIAL", "INVOKESTATIC", "INVOKEINTERFACE", "INVOKEDYNAMIC", "NEW", "NEWARRAY", "ANEWARRAY", "ARRAYLENGTH", "ATHROW", "CHECKCAST", "INSTANCEOF", "MONITORENTER", "MONITOREXIT", "WIDE", "MULTIANEWARRAY", "IFNULL", "IFNONNULL"};
25 | public static final String[] TYPES = new String[]{"", "", "", "", "T_BOOLEAN", "T_CHAR", "T_FLOAT", "T_DOUBLE", "T_BYTE", "T_SHORT", "T_INT", "T_LONG"};
26 | public static final String[] HANDLE_TAG = new String[]{"", "H_GETFIELD", "H_GETSTATIC", "H_PUTFIELD", "H_PUTSTATIC", "H_INVOKEVIRTUAL", "H_INVOKESTATIC", "H_INVOKESPECIAL", "H_NEWINVOKESPECIAL", "H_INVOKEINTERFACE"};
27 | private static final String UNSUPPORTED_OPERATION = "Must be overridden";
28 | protected final int api;
29 | protected final StringBuilder stringBuilder;
30 | public final List