Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.4</version>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
Expand All @@ -80,12 +80,24 @@
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.21.0-GA</version>
<version>3.30.2-GA</version>
</dependency>
<dependency>
<groupId>com.sun.activation</groupId>
<artifactId>javax.activation</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
37 changes: 15 additions & 22 deletions src/main/java/io/zhile/crack/atlassian/agent/KeyTransformer.java
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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<String, String> 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");
Expand Down Expand Up @@ -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");
Expand All @@ -144,4 +137,4 @@ private byte[] handleLicenseDecoder() throws IllegalClassFormatException {
}
}

}
}
104 changes: 104 additions & 0 deletions src/test/java/io/zhile/crack/atlassian/agent/KeyTransformerTest.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
}