diff --git a/pom.xml b/pom.xml
index b3b1536..e3e64d2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -70,7 +70,7 @@
commons-cli
commons-cli
- 1.4
+ 1.6.0
javax.xml.bind
@@ -80,12 +80,24 @@
org.javassist
javassist
- 3.21.0-GA
+ 3.30.2-GA
com.sun.activation
javax.activation
1.2.0
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ commons-codec
+ commons-codec
+ 1.15
+ test
+
\ No newline at end of file
diff --git a/src/main/java/io/zhile/crack/atlassian/agent/KeyTransformer.java b/src/main/java/io/zhile/crack/atlassian/agent/KeyTransformer.java
index 1626542..e0d4fcc 100644
--- a/src/main/java/io/zhile/crack/atlassian/agent/KeyTransformer.java
+++ b/src/main/java/io/zhile/crack/atlassian/agent/KeyTransformer.java
@@ -1,13 +1,14 @@
package io.zhile.crack.atlassian.agent;
+
import javassist.*;
-import java.io.File;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Arrays;
-import java.util.Map;
-import java.util.Objects;
+import java.io.ByteArrayInputStream; // Import for the inserted code to resolve
+import java.io.DataInputStream; // Import for the inserted code to resolve
+import java.nio.charset.StandardCharsets; // Import for the inserted code to resolve
/**
* @author pengzhile
@@ -29,7 +30,7 @@ public byte[] transform(ClassLoader loader, String className, Class> classBein
if (className.equals(CN_KEY_SPEC)) {
return handleKeySpec();
} else if (className.equals(LICENSE_DECODER_PATH)) {
- return handleLicenseDecoder();
+ return handleLicenseDecoder(loader);
}
return classfileBuffer;
@@ -74,24 +75,16 @@ private byte[] handleKeySpec() throws IllegalClassFormatException {
* @return 修改过的类的字节码
* @throws IllegalClassFormatException 当某些地方出问题了就会抛出这个异常
*/
- private byte[] handleLicenseDecoder() throws IllegalClassFormatException {
+ private byte[] handleLicenseDecoder(ClassLoader loader) throws IllegalClassFormatException {
try {
- // 我不知道怎么从 com.atlassian.bitbucket.internal.launcher.BitbucketServerLauncher 读取这个路径,所以我直接 HARD CODE
- // Forgive me pls...
-
- Map osEnv = System.getenv();
- String binDir = osEnv.get("BIN_DIR");
- String path = binDir.replace("bin", "app/WEB-INF/lib");
- File libs = new File(path);
- ClassPool cp = ClassPool.getDefault();
+ // Use a ClassPool that defaults to the system classpath (includes JDK classes)
+ ClassPool cp = new ClassPool(true);
- Arrays.stream(Objects.requireNonNull(libs.listFiles())).map(File::getAbsolutePath).forEach((it) -> {
- try {
- cp.insertClassPath(it);
- } catch (Exception e) {
- e.printStackTrace();
- }
- });
+ // Append the classpath of the loader that loaded the target class.
+ // This ensures we can resolve classes available to the target class (e.g. libraries in WEB-INF/lib).
+ if (loader != null) {
+ cp.appendClassPath(new LoaderClassPath(loader));
+ }
cp.importPackage("com.atlassian.extras.common.LicenseException");
cp.importPackage("com.atlassian.extras.common.org.springframework.util.DefaultPropertiesPersister");
@@ -120,7 +113,7 @@ private byte[] handleLicenseDecoder() throws IllegalClassFormatException {
cp.importPackage("java.util.zip.InflaterInputStream");
cp.importPackage("org.apache.commons.codec.binary.Base64");
- CtClass target = cp.getCtClass(LICENSE_DECODER_CLASS);
+ CtClass target = cp.get(LICENSE_DECODER_CLASS);
CtMethod verifyLicenseHash = target.getDeclaredMethod("verifyLicenseHash");
verifyLicenseHash.setBody("{System.out.println(\"atlassian-agent: skip license hash check\");}");
CtMethod checkAndGetLicenseText = target.getDeclaredMethod("checkAndGetLicenseText");
@@ -144,4 +137,4 @@ private byte[] handleLicenseDecoder() throws IllegalClassFormatException {
}
}
-}
\ No newline at end of file
+}
diff --git a/src/test/java/io/zhile/crack/atlassian/agent/KeyTransformerTest.java b/src/test/java/io/zhile/crack/atlassian/agent/KeyTransformerTest.java
new file mode 100644
index 0000000..1ba9ec8
--- /dev/null
+++ b/src/test/java/io/zhile/crack/atlassian/agent/KeyTransformerTest.java
@@ -0,0 +1,104 @@
+package io.zhile.crack.atlassian.agent;
+
+import org.junit.Test;
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Comparator;
+import static org.junit.Assert.*;
+
+public class KeyTransformerTest {
+
+ @Test
+ public void testHandleLicenseDecoder() throws Exception {
+ // 1. Setup temporary directory for dummy classes
+ Path tempPath = Files.createTempDirectory("agent_test_classes");
+ File tempDir = tempPath.toFile();
+ tempDir.deleteOnExit();
+
+ try {
+ // 2. Create Version2LicenseDecoder dummy class
+ File packageDir = new File(tempDir, "com/atlassian/extras/decoder/v2");
+ assertTrue(packageDir.mkdirs());
+
+ File sourceFile = new File(packageDir, "Version2LicenseDecoder.java");
+ Files.write(sourceFile.toPath(),
+ ("package com.atlassian.extras.decoder.v2;\n" +
+ "public class Version2LicenseDecoder {\n" +
+ " public void verifyLicenseHash() {}\n" +
+ " public String checkAndGetLicenseText(String s) { return \"\"; }\n" +
+ "}\n").getBytes());
+
+ runJavac(sourceFile);
+
+ // 3. Create LicenseException dummy class
+ File commonDir = new File(tempDir, "com/atlassian/extras/common");
+ assertTrue(commonDir.mkdirs());
+
+ File leSourceFile = new File(commonDir, "LicenseException.java");
+ Files.write(leSourceFile.toPath(),
+ ("package com.atlassian.extras.common;\n" +
+ "public class LicenseException extends RuntimeException {\n" +
+ " public LicenseException(Throwable t) { super(t); }\n" +
+ "}\n").getBytes());
+
+ runJavac(leSourceFile);
+
+ // 4. Create custom ClassLoader
+ URL[] urls = new URL[] { tempDir.toURI().toURL() };
+ // Use the current test classloader as parent so it can find commons-codec (which is in test dependencies)
+ ClassLoader parentLoader = getClass().getClassLoader();
+ URLClassLoader appLoader = new URLClassLoader(urls, parentLoader);
+
+ // 5. Instantiate Transformer
+ KeyTransformer transformer = new KeyTransformer();
+ String className = "com/atlassian/extras/decoder/v2/Version2LicenseDecoder";
+
+ // 6. Transform
+ byte[] dummyBytes = new byte[0];
+ byte[] result = transformer.transform(appLoader, className, null, null, dummyBytes);
+
+ assertNotNull("Transformation result should not be null", result);
+ assertTrue("Transformation result should not be empty", result.length > 0);
+
+ } finally {
+ // Cleanup
+ Files.walk(tempPath)
+ .sorted(Comparator.reverseOrder())
+ .map(Path::toFile)
+ .forEach(File::delete);
+ }
+ }
+
+ @Test
+ public void testHandleKeySpec() throws Exception {
+ // This test verifies that handleKeySpec returns transformed bytecode.
+ // It relies on java.security.spec.EncodedKeySpec being available in the system (it is).
+ // And javassist finding it (via ClassPool.getDefault()).
+
+ KeyTransformer transformer = new KeyTransformer();
+ String className = "java/security/spec/EncodedKeySpec";
+
+ // We pass empty byte array. The transformer uses ClassPool to read the class.
+ byte[] dummyBytes = new byte[0];
+
+ // Note: transform takes "className" with slashes
+ byte[] result = transformer.transform(ClassLoader.getSystemClassLoader(), className, null, null, dummyBytes);
+
+ assertNotNull("Transformation result should not be null for EncodedKeySpec", result);
+ assertTrue("Transformation result should not be empty for EncodedKeySpec", result.length > 0);
+ }
+
+ private void runJavac(File sourceFile) throws Exception {
+ Process p = Runtime.getRuntime().exec("javac " + sourceFile.getAbsolutePath());
+ p.waitFor();
+ if (p.exitValue() != 0) {
+ // Read stderr
+ java.util.Scanner s = new java.util.Scanner(p.getErrorStream()).useDelimiter("\\A");
+ String err = s.hasNext() ? s.next() : "";
+ throw new RuntimeException("Failed to compile " + sourceFile.getName() + ": " + err);
+ }
+ }
+}
diff --git a/src/test/java/io/zhile/crack/atlassian/agent/ManualAgentVerify.java b/src/test/java/io/zhile/crack/atlassian/agent/ManualAgentVerify.java
new file mode 100644
index 0000000..093c074
--- /dev/null
+++ b/src/test/java/io/zhile/crack/atlassian/agent/ManualAgentVerify.java
@@ -0,0 +1,29 @@
+package io.zhile.crack.atlassian.agent;
+
+import java.security.spec.EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+
+/**
+ * Manual verification script.
+ * Usage:
+ * java -javaagent:target/atlassian-agent.jar -cp target/test-classes io.zhile.crack.atlassian.agent.ManualAgentVerify
+ */
+public class ManualAgentVerify {
+ public static void main(String[] args) {
+ System.out.println("Starting ManualAgentVerify...");
+
+ // This key matches the one hardcoded in KeyTransformer to trigger the "agent working" message
+ String keyBase64 = "MIIBuDCCASwGByqGSM44BAEwggEfAoGBAP1/U4EddRIpUt9KnC7s5Of2EbdSPO9EAMMeP4C2USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVClpJ+f6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith1yrv8iIDGZ3RSAHHAhUAl2BQjxUjC8yykrmCouuEC/BYHPUCgYEA9+GghdabPd7LvKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/hWuWfBpKLZl6Ae1UlZAFMO/7PSSoDgYUAAoGBAIvfweZvmGo5otwawI3no7Udanxal3hX2haw962KL/nHQrnC4FG2PvUFf34OecSK1KtHDPQoSQ+DHrfdf6vKUJphw0Kn3gXm4LS8VK/LrY7on/wh2iUobS2XlhuIqEc5mLAUu9Hd+1qxsQkQ50d0lzKrnDqPsM0WA9htkdJJw2nS";
+ byte[] key = Base64.getDecoder().decode(keyBase64);
+
+ try {
+ // X509EncodedKeySpec extends EncodedKeySpec.
+ // Creating this instance should trigger the agent transformation if attached.
+ EncodedKeySpec spec = new X509EncodedKeySpec(key);
+ System.out.println("Created EncodedKeySpec instance.");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}