我正在为大学写一篇学期论文。任务是编写两个程序,其中一个程序收集一些信息,用数字密钥对其进行签名,并将加密数据和密钥保存到单独的文件中。下一个程序收集相同的信息,读取加密的数据和密钥,并验证数字签名。
第二步是编写一个使用 java.lang.instrument.Instrumentation 的代理;还可能使用 javaassist 等第三方库进行字节码替换,以便程序始终给出成功的验证结果
我尝试在教程的帮助下实现这一点,但我的程序卡在了更改阶段
这里是需要改的字节码的分析
static void checkSign(java.lang.String, java.lang.String, java.lang.String) throws java.lang.Exception;
Code:
0: new #206 // class java/io/BufferedReader
3: dup
4: new #258 // class java/io/FileReader
7: dup
8: aload_0
9: invokespecial #260 // Method java/io/FileReader."<init>":(Ljava/lang/String;)V
12: invokespecial #219 // Method java/io/BufferedReader."<init>":(Ljava/io/Reader;)V
15: astore_3
16: aload_3
17: invokevirtual #225 // Method java/io/BufferedReader.readLine:()Ljava/lang/String;
20: astore 4
22: invokestatic #262 // Method java/util/Base64.getDecoder:()Ljava/util/Base64$Decoder;
25: aload 4
27: invokevirtual #268 // Method java/util/Base64$Decoder.decode:(Ljava/lang/String;)[B
30: astore 5
32: aload_3
33: invokevirtual #245 // Method java/io/BufferedReader.close:()V
36: new #206 // class java/io/BufferedReader
39: dup
40: new #258 // class java/io/FileReader
43: dup
44: aload_1
45: invokespecial #260 // Method java/io/FileReader."<init>":(Ljava/lang/String;)V
48: invokespecial #219 // Method java/io/BufferedReader."<init>":(Ljava/io/Reader;)V
51: astore 6
53: aload 6
55: invokevirtual #225 // Method java/io/BufferedReader.readLine:()Ljava/lang/String;
58: astore 7
60: aload 6
62: invokevirtual #245 // Method java/io/BufferedReader.close:()V
65: invokestatic #262 // Method java/util/Base64.getDecoder:()Ljava/util/Base64$Decoder;
68: aload 7
70: invokevirtual #268 // Method java/util/Base64$Decoder.decode:(Ljava/lang/String;)[B
73: astore 8
75: ldc_w #274 // String RSA
78: invokestatic #276 // Method java/security/KeyFactory.getInstance:(Ljava/lang/String;)Ljava/security/KeyFactory;
81: astore 9
83: new #281 // class java/security/spec/X509EncodedKeySpec
86: dup
87: aload 8
89: invokespecial #283 // Method java/security/spec/X509EncodedKeySpec."<init>":([B)V
92: astore 10
94: aload 9
96: aload 10
98: invokevirtual #286 // Method java/security/KeyFactory.generatePublic:(Ljava/security/spec/KeySpec;)Ljava/security/PublicKey;
101: checkcast #290 // class java/security/interfaces/RSAPublicKey
104: astore 11
106: ldc_w #274 // String RSA
109: invokestatic #292 // Method javax/crypto/Cipher.getInstance:(Ljava/lang/String;)Ljavax/crypto/Cipher;
112: astore 12
114: aload 12
116: iconst_2
117: aload 11
119: invokevirtual #297 // Method javax/crypto/Cipher.init:(ILjava/security/Key;)V
122: aload 12
124: aload 5
126: invokevirtual #301 // Method javax/crypto/Cipher.doFinal:([B)[B
129: astore 13
131: new #17 // class java/lang/String
134: dup
135: aload 13
137: invokespecial #304 // Method java/lang/String."<init>":([B)V
140: astore 14
142: aload 14
144: aload_2
145: invokevirtual #237 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
148: ifeq 163
151: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
154: ldc_w #305 // String Verification successfully!
157: invokevirtual #26 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
160: goto 171
163: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
166: ldc #204 // String Verification failed!
168: invokevirtual #26 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
171: return
这是我的经纪人
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class AgentMain {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("Ha, ha! This program was hacked ;-)");
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
if (className.equals("Defender")) {
try {
System.out.println("I'm here");
ClassPool cp = ClassPool.getDefault();
System.out.println("Class pool created");
CtClass cc = cp.get("Defender");
CtMethod method = cc.getDeclaredMethod("checkSign");
String newBody = "{" +
" System.out.println(\"Verification successfully!\");" +
"}";
method.setBody(newBody);
return cc.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
}
return classfileBuffer;
}
});
}
}
我特意安排了“我在这里”消息的输出,以了解错误在哪里。
将所有类编译成jar后,我用命令运行它
java -javaagent:HackAgent.jar -jar Defender.jar
但结果我明白了
我做错了什么?
这个问题显然是那个问题的后续问题,该问题已关闭,因为您只提供了原始代码,但没有解释您试图解决问题的内容。在这里,情况恰恰相反。不管怎样,把这两个问题结合起来,我看到你自己尝试了一些东西,而不仅仅是指望完全为你工作,这是值得赞扬的。因此,尽管它不是 Javassist 而是 AspectJ 答案,但我想向您展示如何解决问题。我尝试使用你的原始代码。
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class DefenderAspect {
@Around("cflow(execution(void checkSign(String, String, String)) && args(*, *, hash)) && call(String.new(byte[]))")
public String replaceDecryptedSignString(String hash) {
return hash;
}
}
切入点告诉 AspectJ 选择
String(byte[])
构造函数调用(如果它们位于 checkSign
方法的控制流中)。它还将第三个参数 hash
绑定到通知方法参数,并使用它来简单地返回构造函数调用应该返回的内容,以满足 dectyptedSignString.equals(hash)
语句中的 if
条件。
该解决方案适用于编译后和加载时编织。
当然,就像我在评论中所说的那样,您可以使用 ASM、Byte Buddy、BCEL、Javassist 或类似框架设计类似的解决方案。 AspectJ 只是我选择的工具,因为我喜欢它优雅的语法。