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

时间:2012-05-21 08:45:30

标签: java constants bytecode-manipulation .class-file

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

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

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

5 个答案:

答案 0 :(得分:8)

如果您有此课程的来源,那么我的方法是:

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

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>

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

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

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

答案 1 :(得分:4)

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

有两点需要注意:

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

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

答案 2 :(得分:2)

您可以使用许多字节码工程库修改.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”。

希望它有所帮助。

答案 3 :(得分:2)

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

  • 放慢速度
  • 在没有所有类依赖性的情况下,不支持重写
  • 不支持流式传输
  • 在树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源代码

的启发

答案 4 :(得分:0)

我过去也有类似的问题。我的解决方案是使用提到的字节码工程库之一。 我找不到javaassist,但是有一个名为dirtyJOE的出色工具,可以让您(在很多方面)编辑.class文件中的常量。

这是屏幕截图

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

您只需导入.class文件,然后单击常量