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

问题描述 投票:12回答:5

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

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

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

java constants bytecode-manipulation .class-file
5个回答
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

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


4
投票

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

有两点需要注意:

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

如果你计划这么做,最好得到一个类文件编辑器工具,但是十六进制编辑器对于快速更改很有用。


2
投票

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

但是,如果您尝试替换静态最终成员,则可能无法获得所需的效果,因为编译器会将此常量内联到任何位置。

Kode使用Goursystem

//constant holder.Java

public class ConstantHolder {

 public static final String HELLO="hello";

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

//modify constant.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”。

希望能帮助到你。


2
投票

我最近编写了自己的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源代码的启发


0
投票

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

这是一个截图

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

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

© www.soinside.com 2019 - 2024. All rights reserved.