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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10682042/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 02:05:28  来源:igfitidea点击:

Change string constant in a compiled class

javaconstantsbytecode-manipulation.class-file

提问by Bart van Heukelom

I need to change a string constant in a deployed Java program, i.e. the value inside the compiled .class-files. It can be restarted, but not easily recompiled (though it's an inconvenient option if this question yields no answers). Is this possible?

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

Update: I just looked at the file with a hex editor and it looks like I can easily change the string there. Would that work, i.e. won't that invalidate some kind of signature of the file? The old and new string are both alphanumeric, and can be the same length if needed.

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

Update 2: I fixed it. Because the specific class I needed to change is very small and didn't change in the new version of the project, I could just compile that and take the new class from there. Still interested in an answer that doesn't involve compilation though, for educational purposes.

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

回答by Aaron Digulla

If you have the sources for this class, then my approach is:

如果你有这门课的资源,那么我的方法是:

  • Get the JAR file
  • Get the source for the single class
  • Compile the source with the JAR on the classpath (that way, you don't have to compile anything else; it doesn't hurt that the JAR already contains the binary). You can use the latest Java version for this; just downgrade the compiler using -sourceand -target.
  • Replace the class file in the JAR with the new one using jar uor an Ant task
  • 获取 JAR 文件
  • 获取单个类的源
  • 使用类路径上的 JAR 编译源代码(这样,您不必编译任何其他内容;JAR 已经包含二进制文件并没有什么坏处)。您可以为此使用最新的 Java 版本;只需使用-sourceand降级编译器-target
  • 将 JAR 中的类文件替换为新的 usingjar u或 Ant 任务

Example for an Ant task:

Ant 任务的示例:

        <jar destfile="${jar}"
            compress="true" update="true" duplicate="preserve" index="true"
            manifest="tmp/META-INF/MANIFEST.MF"
        >
            <fileset dir="build/classes">
                <filter />
            </fileset>
            <zipfileset src="${origJar}">
                <exclude name="META-INF/*"/>
            </zipfileset>
        </jar>

Here I also update the manifest. Put the new classes first and then add all the files from the original JAR. duplicate="preserve"will make sure that the new code will not be overwritten.

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

If the code isn't signed, you can also try to replace the bytes if the new string has the exact same length as the old one. Java does some checks on the code but there is no checksum in the .class files.

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

You must preserve the length; otherwise the class loader will get confused.

您必须保留长度;否则类加载器会感到困惑。

回答by Antimony

The only extra data required when modifying a string (technically a Utf8 item) in the constant pool is the length field (2 bytes big endian preceding the data). There are no additional checksums or offsets that require modification.

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

There are two caveats:

有两个警告:

  • The string may be used in other places. For example "Code" is used for a method code attribute, so changing it would break the file.
  • The string is stored in Modified Utf8 format. So null bytes and unicode characters outside the basic plane are encoded differently. The length field is the number of bytes, not characters, and is limited to 65535.
  • 字符串可以用在其他地方。例如,“代码”用于方法代码属性,因此更改它会破坏文件。
  • 该字符串以修改后的 Utf8 格式存储。所以基本平面之外的空字节和 unicode 字符的编码方式不同。长度字段是字节数,而不是字符数,限制为 65535。

If you plan to do this a lot, it's better to get a class file editor tool, but the hex editor is useful for quick changes.

如果您打算经常这样做,最好使用类文件编辑器工具,但十六进制编辑器对于快速更改很有用。

回答by krishnakumarp

You can modify .class using many bytecode engineering libraries. For e.g., using javaassist.

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

However, if you're trying to replace a static final member, it may not give you the desired effect, because the compiler would inline this constant wherever it is used.

但是,如果您尝试替换静态 final 成员,它可能不会给您想要的效果,因为编译器会在任何使用它的地方内联这个常量。

Sample code using javaassist.jar

使用 javaassist.jar 的示例代码

//ConstantHolder.java

//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

//修改常量.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);
  }
 }  
}

In this case, the program successfully modifies the value of HELLO from "Hello" to "Hell". However, when you run ConstantHolder class, it would still print "Value:Hello" because of inlining by the compiler.

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

Hope it helps.

希望能帮助到你。

回答by Daniel Worthington-Bodart

I recently wrote my own ConstantPool mapper because ASM and JarJar had the following issues:

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

  • To slow
  • Didn't support rewriting without all class dependencies
  • Didn't support streaming
  • Didn't support Remapper in Tree API mode
  • Had to expand and collapse StackMaps
  • 放慢
  • 不支持没有所有类依赖的重写
  • 不支持流媒体
  • 不支持 Tree API 模式下的 Remapper
  • 必须展开和折叠 StackMaps

I ended up with the following:

我最终得到了以下结果:

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);
}

And then

接着

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);
}

Just thought I'd show alternatives without libraries as it's quite a nice place to start hacking from. My code is was inspired by javapsource code

只是想我会展示没有库的替代方案,因为它是开始黑客攻击的好地方。我的代码灵感来自javap源代码

回答by hjbello

I had a similar issue in the past. My solution was to use one of the mentioned bytecode engineering libraries. I could not find javaassist, however there is a great tool called dirtyJOEthat allows you (among many things) to edit constants in your .class file.

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

Here is a screenshot

这是屏幕截图

You just import the .class file and click on the constant

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

You just import the .class file and click on the constant

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