在已编译的类中更改字符串常量

我需要在已部署的Java程序中更改字符串常量,即编译的.class -files中的值。 它可以重新启动,但不容易重新编译(虽然如果这个问题没有答案,这是一个不方便的选择)。 这可能吗?

更新:我只是用hex编辑器查看了文件,看起来我可以轻松地在那里更改字符串。 这会起作用,即不会使文件的某种签名无效吗? 旧字符串和新字符串都是字母数字,如果需要,可以是相同的长度。

更新2:我修复了它。 因为我需要更改的特定类非常小并且在新版本的项目中没有更改,所以我可以编译它并从那里获取新类。 出于教育目的,仍然对不涉及编译的答案感兴趣。

如果你有这个类的来源,那么我的方法是:

  • 获取JAR文件
  • 获取单个类的源代码
  • 在类路径上使用JAR编译源代码(这样,您不必编译任何其他内容; JAR已经包含二进制文件并不会造成损害)。 您可以使用最新的Java版本; 只需使用-source-target降级编译器。
  • 使用jar u或Ant任务将JAR中的类文件替换为新文件

Ant任务的示例:

          

在这里我也更新了清单。 首先放置新类,然后添加原始JAR中的所有文件。 duplicate="preserve"将确保不会覆盖新代码。

如果代码没有签名,如果新字符串的长度与旧字符串完全相同,您也可以尝试替换字节。 Java对代码进行了一些检查,但.class文件中没有校验和 。

你必须保持长度; 否则类加载器会混淆。

修改常量池中的字符串(技术上是Utf8项)时所需的唯一额外数据是长度字段(数据前2字节大端)。 没有需要修改的额外校验和或偏移。

有两点需要注意:

  • 该字符串可以在其他地方使用。 例如,“代码”用于方法代码属性,因此更改它会破坏文件。
  • 该字符串以Modified Utf8格式存储。 因此,基本平面之外的空字节和unicode字符的编码方式不同。 length字段是字节数,而不是字符,限制为65535。

如果你打算做很多事情,最好得到一个类文件编辑器工具,但是hex编辑器对于快速更改很有用。

您可以使用许多字节码工程库修改.class。 例如,使用javaassist 。

但是,如果您尝试替换静态最终成员,它可能无法提供所需的效果,因为编译器会在使用它的任何地方内联此常量。

使用javaassist.jar的示例代码

//ConstantHolder.java

 public class ConstantHolder { public static final String HELLO="hello"; public static void main(String[] args) { System.out.println("Value:" + ConstantHolder.HELLO); } } 

//ModifyConstant.java

 import java.io.IOException; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.CtField; import javassist.NotFoundException; //ModifyConstant.java public class ModifyConstant { public static void main(String[] args) { modifyConstant(); } private static void modifyConstant() { ClassPool pool = ClassPool.getDefault(); try { CtClass pt = pool.get("ConstantHolder"); CtField field = pt.getField("HELLO"); pt.removeField(field); CtField newField = CtField.make("public static final String HELLO=\"hell\";", pt); pt.addField(newField); pt.writeFile(); } catch (NotFoundException e) { e.printStackTrace();System.exit(-1); } catch (CannotCompileException e) { e.printStackTrace();System.exit(-1); } catch (IOException e) { e.printStackTrace();System.exit(-1); } } } 

在这种情况下,程序成功地将HELLO的值从“Hello”修改为“Hell”。 但是,当您运行ConstantHolder类时,由于编译器的内联,它仍然会打印“Value:Hello”。

希望能帮助到你。

我最近编写了自己的ConstantPool映射器,因为ASM和JarJar有以下问题:

  • 慢下来
  • 没有所有类依赖项,不支持重写
  • 不支持流式传输
  • 在Tree API模式下不支持Remapper
  • 不得不扩展和崩溃StackMaps

我最终得到以下内容:

 public void process(DataInputStream in, DataOutputStream out, Function mapper) throws IOException { int magic = in.readInt(); if (magic != 0xcafebabe) throw new ClassFormatError("wrong magic: " + magic); out.writeInt(magic); copy(in, out, 4); // minor and major int size = in.readUnsignedShort(); out.writeShort(size); for (int i = 1; i < size; i++) { int tag = in.readUnsignedByte(); out.writeByte(tag); Constant constant = Constant.constant(tag); switch (constant) { case Utf8: out.writeUTF(mapper.apply(in.readUTF())); break; case Double: case Long: i++; // "In retrospect, making 8-byte constants take two constant pool entries was a poor choice." // See http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4.5 default: copy(in, out, constant.size); break; } } Streams.copyAndClose(in, out); } private final byte[] buffer = new byte[8]; private void copy(DataInputStream in, DataOutputStream out, int amount) throws IOException { in.readFully(buffer, 0, amount); out.write(buffer, 0, amount); } 

接着

 public enum Constant { Utf8(1, -1), Integer(3, 4), Float(4, 4), Long(5, 8), Double(6,8), Class(7, 2), String(8, 2), Field(9, 4), Method(10, 4), InterfaceMethod(11, 4), NameAndType(12, 4), MethodHandle(15, 3), MethodType(16, 2), InvokeDynamic(18, 4); public final int tag, size; Constant(int tag, int size) { this.tag = tag; this.size = size; } private static final Constant[] constants; static{ constants = new Constant[19]; for (Constant c : Constant.values()) constants[c.tag] = c; } public static Constant constant(int tag) { try { Constant constant = constants[tag]; if(constant != null) return constant; } catch (IndexOutOfBoundsException ignored) { } throw new ClassFormatError("Unknown tag: " + tag); } 

只是想我会展示没有库的替代品,因为它是一个非常好的开始黑客攻击的地方。 我的代码受javap源代码的启发

我过去也遇到过类似的问题。 我的解决方案是使用上面提到的字节码工程库之一。 我找不到javaassist,但是有一个很棒的工具叫做dirtyJOE ,允许你(在很多事情中)编辑你的.class文件中的常量。

这是一个截图

您只需导入.class文件并单击常量

您只需导入.class文件并单击常量