Java中的操作系统名称(不是"os.name")

4

我想知道如何获取jvm运行的操作系统类型。同时,这个方法必须是“安全”的,所以System.getProperty("os.name")并不是一个好的选择,因为它可以轻松地通过-D指令规避。

所谓“安全”,是指难以被规避。这是为桌面应用程序而设计的。用户总是可以对代码进行反混淆、反编译、修改和重新编译,但这比传递-D给jvm要困难得多。我们希望让调整变得更加困难,而不是不可能(因为这是不可能的)。


7
定义“secure”。你想要保护自己免受谁的侵害?如果是某个能够启动应用程序并控制运行该程序计算机的人,那么你可能没有办法。如果是在AS/400分区中运行的Linux虚拟机中运行的Windows上的JVM呢?那应该报告什么? - Joachim Sauer
该应用程序需要执行许多Java标准库不支持的操作。这意味着代码包含许多exec()调用。这些命令是依赖于操作系统的。 - Wouter Lievens
2
为什么不将特定于操作系统的命令抽象成一个接口,并使用特定于操作系统的实现?即,接口Native{},类WindowsImpl实现Native{},类LinuxImpl实现Native{},然后在构建时确定要使用哪个,而不是在运行时确定。 - Kevin
有一个JAR文件必须为每个平台提供服务。 - Wouter Lievens
或者至少目前是这样设计的,但我可以改变它。我会考虑一下。 - Wouter Lievens
显示剩余2条评论
12个回答

15

首先,无法防止运行时环境任意操纵代码。但为了让检查更难被欺骗,最好的方法可能是基于文件系统的操作系统指纹识别

File.listRoots() 是你的起点;在类Unix系统上,它将返回一个包含特征目录(如/etc、/usr等)的单个根目录。在Windows上,它将返回多个结果,但据我所知,操作系统安装驱动器不一定是C:,而且特征目录在Windows版本和区域设置之间有所不同-请注意不要假设每个人都运行英文版的Vista。

你可以投入大量工作来识别不同版本的Windows和Linux,以及BSD或MacOS-但一旦编译代码发布出去后,从中删除检查可能需要更少的工作。


8

你为什么担心这个问题?如果终端用户够蠢,去乱动os.*属性,何不让应用程序崩溃呢?

话虽如此,这些属性可能对你的目的足够好用。

//can be defeated by adding com.apple.eawt.Application to the classpath
public boolean isMac() {
    try {
        Class.forName("com.apple.eawt.Application");
        return true;
    } catch(Exception e) {
        return false;
    }
}

//can be defeated by creating a cmd.exe in PATH
public boolean isWin() {
    try{
        Runtime.getRuntime().exec( new String[]{"cmd.exe","/C","dir"} ).waitFor();
        return true;
    } catch (Exception e) {
        return false;
    }
}

public boolean isLinux() {
    if(isMac()) return false;
    try{
        Runtime.getRuntime().exec(  new String[]{"sh","-c","ls"} ).waitFor();
        return true;
    } catch (Exception e) {
        return false;
    }
}

8
系统属性是我所知道的获取操作系统信息的唯一方法。即使OperatingSystemMXBean也是从os.X系统属性中获取其值。
如果有人可以调整您的应用程序的启动方式,那么您面临的问题比os.name是否正确更大。
但是,如果您担心恶意代码在应用程序运行时设置该属性,则可以使用Java Security Manager确保属性受到安全保护。

2
您可以通过Runtime类(exec方法)运行外部命令,例如“ver”(如果在Windows中)或“uname -a”来获取操作系统的版本。

1

Apache commons-vfs 抽象化了某些处理过程,所以你只需处理OsOsFamily。然而,内部仍然使用 System.getProperty(..) 来获取这些值。我不知道 JVM 获取这些值的其他方法。

如果有人能够更改传递给 JVM 的属性,那么你将面临比更改奇怪属性更大的问题。

您能否详细说明您所说的安全性?安全性是指对谁的安全?


1

你可以使用exec来尝试运行一些无害的程序,这些程序可以在一个操作系统或另一个操作系统中存在,例如在Windows中使用"c:\windows\system\dir.exe",在*nix系统中使用"/bin/ls",看看它们是否成功运行或失败。但当然,如果有人知道你正在做这件事并试图破坏它,他们可能会创建带有这些名称的可执行文件。

你究竟想要保护什么?如果有人故意搞砸了你的应用程序的启动,然后它因为你试图运行不存在的命令而崩溃,那么适当的反应不是 "如果你的脚痛,就别对自己开枪" 吗?如果你只是在运行操作系统命令,那么用户显然能够在你的应用程序之外访问这些命令,所以我不明白撒谎关于操作系统如何影响安全性。


安全问题是“复制保护”。在不同于初始媒介(如USB存储设备)上运行应用程序应该是有一定难度的。 - Wouter Lievens
1
我建议您重新措辞问题,询问您真正想知道的内容。 - Thorbjørn Ravn Andersen
1
我现在只是好奇。这可能与回答你的问题无关,但是知道操作系统如何帮助呢?如果有人插入USB,将程序复制到他们的硬盘驱动器上并尝试运行它,他们仍然处于相同的操作系统下。如果在USB上编写您期望的操作系统并为不同的操作系统销售不同版本,那么我想您可以防止某些人购买Windows版本并在Linux上运行,但这只是盗版候选者的一小部分。我怀疑大多数想要制作非法副本的人都会为同一操作系统制作它们。 - Jay
哦,等等,也许我现在明白你的意图了。你有一个程序在运行,可以在Windows、Linux、Mac或其他版本上运行,它会检查你是否真的是从内存棒中运行,如果失败了,你就拒绝运行。所以,如果有人能够欺骗你的操作系统和你的“棒子检查”没有运行,他们就可以绕过你的副本保护。是这样吗?但如果是这样,难道你不能简单地编写你的程序,如果棒子检查没有成功运行,你就将其视为失败吗?那么,如果他们对操作系统撒谎,就是失败了。 - Jay
你描述问题得很好。是的,我可以使用这个线程中的提示来找出他们是否在谎称操作系统,并在这种情况下失败。 - Wouter Lievens
那么为什么要费尽心思来验证操作系统呢?只需说:“如果存储器检查未能成功运行,则失败。如果他们对操作系统撒谎,以使您的程序无法工作,则也将是失败。” - Jay

0
现代Linux系统有一个特殊的文件/proc/version,它提供了内核版本ID。因此,如果该文件存在并且与合适的模式匹配(例如,以单词Linux开头),你就可以增加在Linux计算机上运行的可能性。相反,如果不存在或不符合该模式,则可以确定你没有在Linux上运行。

0

除了使用您提到的属性之外,VM中没有公共API可用。

我希望,由于Java应该是安全的,VM会忽略从命令行覆盖这些值的任何尝试,但事实并非如此。

您可以尝试使用SecurityManager来禁止写入这些值,但我猜想,那么该值将变为空(因为VM也无法更改它)。


我非常确定虚拟机所做的设置不会受到任何安全管理器的限制。 - Michael Borgwardt

0

你也可以通过System.getenv()检查环境变量,但用户在启动之前也可以自由修改这些变量。只是在Windows上,将变量作为命令行的一部分进行修改并不那么容易,以便启动应用程序。


0
如果安全是您的首要目标,您可能希望尝试添加一些JNI。首先,在Linux或OS X中尝试加载DLL时,我敢打赌它会崩溃/无法加载。其次,从该DLL中,您可以调用操作系统的API,因此不受Java环境提供的限制。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接