java运行库一键修复
- 不能被规避
- 难以排除/绕过/更换
- 只需不提供错误修正
在这种情况下,解决问题仍然是一项艰巨的任务。
作为这种情况的诱因,请考虑对“哈希索引”数据结构的攻击,例如java.util.Hashtable和java.util.HashMap (对于不熟悉此类攻击的人,我建议查看以下内容28C3: 对Web应用程序进行拒绝服务攻击变得容易( )。
长话短说,核心问题是使用非加密哈希函数(在其中查找冲突很容易)。 根本原因隐藏在java.lang.String.hashCode()函数中。 显而易见的方法是修补java.lang.String类,这很困难,原因有两个:
- 它包含本机代码
- 它属于Java核心类,它们随Java安装一起提供,因此不受我们的控制
第一点将迫使我们修补体系结构和特定于OS的库,我们应该在可能的情况下规避这些库。 第二点是正确的,但它会更灵活一些,我们将在下面看到。
好的,让我们重新考虑:修补本机很脏,我们不急于采用这种方式–我们必须为不愿意修复其代码的其他人(在本例中为SDK SDK库)做一些工作。
尝试:
哈希问题涉及类java.util.Hashtable和java.util.HashMap ,它们不使用任何本机代码。 修补这些类要容易得多,因为为所有体系结构和OS提供一个已编译的类就足够了。
我们可以使用提供的错误解决方案之一,并用固定版本调整(或替换)原始类。 困难在于在不接触核心库的情况下修补VM –我想如果用户必须更改其JVM安装的一部分,或者更糟糕的是,我们的应用程序在安装过程中自动执行此操作,用户将非常失望。 在某些情况下,进一步引入新的自定义类加载器可能会很困难。
我们需要的是一种动态修补单个应用程序的解决方案–替换有问题的类,不要碰其他任何东西。 如果我们透明地执行此操作,则其他软件部分甚至都不会识别任何更改(最好),并保持与类的接口,而无需进行任何修改。
可以通过滥用Java Instrumentation API轻松地完成此操作。 引用JavaDoc :
这正是我们所需要的!
概念证明
首先,我们需要一个示例应用程序来演示该概念:
public class StringChanger {public static void main(String[] args) {System.out.println(A.shout());}}public class A {public static String shout() {return "A";}
}
运行此类时,它仅输出:A
应用我们的“补丁”之后,我们将获得以下输出:已补丁
“修补的代码如下所示:
public class A {public static String shout() {return "Apatched";}
}
进一步,我们需要一个“代理”来管理所使用的类并修补正确的类:
final public class PatchingAgent implements ClassFileTransformer {private static byte[] PATCHED_BYTES;private static final String PATH_TO_FILE = "Apatched.class";private static final String CLASS_TO_PATCH = "stringchanger/A";public PatchingAgent() throws FileNotFoundException, IOException {if (PATCHED_BYTES == null) {PATCHED_BYTES = readPatchedCode(PATH_TO_FILE);}}public static void premain(String agentArgument,final Instrumentation instrumentation) { System.out.println("Initializing hot patcher...");PatchingAgent agent = null;try {agent = new PatchingAgent();} catch(Exception e) {System.out.println("terrible things happened....");}instrumentation.addTransformer(agent);}@Overridepublic byte[] transform(final ClassLoader loader, String className,final Class classBeingRedefined, final ProtectionDomain protectionDomain,final byte[] classfileBuffer) throws IllegalClassFormatException {byte[] result = null;if (className.equals(CLASS_TO_PATCH)) {System.out.println("Patching... " + className);result = PATCHED_BYTES;}return result;}private byte[] readPatchedCode(final String path)throws FileNotFoundException, IOException {...}
}
不用担心–我不会打扰您实现细节,因为这只是PoC代码,远非美观,巧妙,快速而简洁。 除了我因为此时太懒而捕获 Exception以外,我没有过滤输入,构建深拷贝(防御性编程作为流行语),这实际上不应该被视为生产代码。
公共PatchingAgent()
初始化代理(在这种情况下,将获取修补的A.class文件的字节。修补的类已编译并存储在我们可以访问它的位置。
公共静态无效premain(…)
在JVM初始化并准备代理后,将调用此方法。
公共字节[]变换(…)
每当定义了一个类时(例如,通过ClassLoader.defineClass (…)),该函数都会被调用,并且可以转换已处理的类字节 []( classfileBuffer )。 可以看出,我们将为stringchanger包中的A类执行此操作。 您不受限制如何转换类(只要它仍然是有效的Java 类 )–例如,您可以利用字节码修改框架…–为使事情变得简单,我们假设我们将旧字节 []替换为修补类之一(通过将完整的修补A.class文件简单地缓存到字节 []中)。
这就是修补程序编码部分的全部内容……最后,我们必须使用一个特殊的manifest.mf文件构建一个jar容器,该文件告诉JVM如何调用该代理。
清单版本:1.0
X-COMMENT:Main-Class将通过构建自动添加
Premain-Class:stringchanger.PatchingAgent
构建完这个jar之后,我们可以尝试PoC应用程序。 首先,我们将在没有调用代理的必要JVM参数的情况下调用它:
跑:
一个
建立成功(总时间:0秒)
它的行为符合预期,并输出未修补类定义的输出。
现在,我们将使用神奇的JVM参数来尝试调用代理-javaagent:StringChanger.jar:
跑:
初始化热修补程序…
读取修补文件。 修补…换弦器/ A 已分配 建立成功(总时间:0秒)
Voilà,该代码已成功进行了实时修补!
如我们所见,有可能动态地对JVM进行热补丁而不用触摸交付的代码。 必须做的是开发修补程序和修补的类。 目前,我还不了解性能评估数据。 因此,我不确定该解决方案对生产系统的实用性以及对应用程序性能的影响程度。
明确地说,这不是一个优雅的解决方案–至少它很脏! 最好的方法是修补根本原因,但只要没有供应商修复程序,开发人员就可以通过热修补来防止其软件运行,而无需重写使用易受攻击类的每一行。
最后,我希望提出意见,改进或只是更好的解决方案。 非常感谢Juraj Somorovsky与我在这个问题上共同努力。
参考: JCG合作伙伴 在运行时修补Java Christopher Meyer讨论了Java安全性和相关主题 。
翻译自: https://www.javacodegeeks.com/2012/02/patching-java-at-runtime.html
java运行库一键修复