如何加固基于Java的程序以防止黑客攻击

文章旨在帮助那些认为标准混淆器可以保护其知识产权不被盗用的初级开发人员,而不是培训黑客。

案例1:认证服务器

客户端-服务器模型早在互联网出现之前就已经存在,并且仍然广泛使用。同时,有一个传言说,在没有客户端-服务器部分的情况下无法构建软件保护,但接下来我们将展示这只是一个谬论。

下图显示了服务器-客户端数据流的图表。出于清晰起见,程序片段位于图表外部。所谓的关键点已经标出,并将从后面的内容中得到解释。

0_NNz4P8GaP3mJqJu3.jpg

对于认证服务器可靠性的谬论非常普遍,因此我们将从这开始讲解。我们有一个服务器,它接收来自程序的请求,并在检查后发送响应:程序是否可以继续工作或需要付费注册?我们通过搜索与局域网或互联网通信相关的类(即java.net.*包中的类,如HttpURLConnection等)来找到简单获取请求的关键点。


让我们从开发人员的角度和黑客的角度来看待这个图表。在此模型中使用的应用程序中有两个重要点:发送请求的位置和从服务器接收响应的位置。

原始Java方法的片段,用于执行请求-响应操作:

boolean authenticate(String requestURL, String params) {
    URL url = null;
    HttpURLConnection conn = null;
    try {
        url = new URL(requestURL);
        conn = (HttpURLConnection) url.openConnection();
        conn.connect();
    } catch (IOException e) {
        showError("Failed connect to " + requestURL + ".");
        return false; // Warning
    }

    String response = "";

    if (conn != null) {
        try (OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream())) {
            writer.write(params); // Warning
            writer.flush(); // Warning
            writer.close();
        } catch (IOException e) {
            showError("Failed write params " + params + ".");
            return false; // Warning
        }

        try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
            String line = "";

            while ((line = reader.readLine()) != null) {
                response += line + "\n";
            }

            reader.close();
        } catch (IOException e) {
            showError("Failed read " + response + ".");
            return false; // Warning
        }

        if (conn != null)
            conn.disconnect();
    }
    return response.indexOf("activated") > -1; // Error
}

容易受攻击的命令后面跟随着注释。

与黑客所看到的混淆后的 Java 方法相同:

    static boolean Z(String a, String aa) {
          String var2 = a;
          HttpURLConnection var4 = null;
          try {
               (var4 = (HttpURLConnection)(new URL(var2)).openConnection()).connect();
          } catch (IOException var36) {
               return true; /* false; */
          }
          String var6 = "";
          if (var4 != null) {
               Object var5;
               Throwable var10000;
               try {
                    a = null;
                    var5 = null;
                    try {
                         OutputStreamWriter var3 = new OutputStreamWriter(var4.getOutputStream());
                         try {
//                              var3.write(a);
//                              var3.flush();
                              var3.close();
                         } finally {
                              if (var3 != null) {
                                   var3.close();
                              }
                         }
                    } catch (Throwable var38) {
                         if (a == null) {
                              var10000 = var38;
                         } else {
                              if (a != var38) {
                                   a.addSuppressed(var38);
                              }
                              var10000 = a;
                         }
                         // throw var10000;
                    }
               } catch (IOException var39) {
                    z("Failed connect to " + a + ".");
                    return true; /* false; */
               }
               HttpURLConnection var45;
               label534: {
                    try {
                         a = null;
                         var5 = null;
                         try {
                              BufferedReader var43 = new BufferedReader(new InputStreamReader(
                                                                        var4.getInputStream()));
                              boolean var21 = false;
                              try {
                                   var21 = true;
                                   a = "";
                                   BufferedReader var44 = var43;
                                   while(true) {
                                        if ((a = var44.readLine()) == null) {
                                             var43.close();
                                             var21 = false;
                                             break;
                                        }
                                        var6 = var6 + a + "\n";
                                        var44 = var43;
                                   }
                              } finally {
                                   if (var21) {
                                        if (var43 != null) {
                                             var43.close();
                                        }
                                   }
                              }
                              if (var43 != null) {
                                   var45 = var4;
                                   var43.close();
                                   break label534;
                              }
                         } catch (Throwable var41) {
                              if (a == null) {
                                   var10000 = var41;
                              } else {
                                   if (a != var41) {
                                        a.addSuppressed(var41);
                                   }
                                   var10000 = a;
                              }
                              // throw var10000;
                         }
                    } catch (IOException var42) {
                         z("Failed to read " + var2 + ".");
                         return true; /* false; */
                    }
                    var45 = var4;
               }
               if (var45 != null) {
                    var4.disconnect();
               }
          }
          return true; /* var6.indexOf(z("0'%-'%%!5")) > -1; */
	}

很容易看出,通过将第 97 行以及之前的所有return...s替换为return true, 以及注释第 19 行和第 20 行,我们就得到了程序的免费版本。请注意,服务器尝试确定当前谁正在申请身份验证以及有多少此类请求将失败(这是多个人的许可证的情况)。

在混淆的代码中,找到方法的输出可能要困难得多,但是使用探路者方法,或者更好地像狗寻找猎物一样,迟早可以找到它。黑客从类HttpURLConnection变量或类似类变量的第一个定义开始,沿着线索到达关键点:var4 -> var4 -> ... var3 -> var3 ... -> var4 -> return ...  混淆器引入的其余垃圾可以被忽略。在这种情况下,就像在许多其他情况下一样,程序的安全性仅取决于黑客的聪明才智,而不取决于您的努力。我们将在下一部分讨论这个问题。

案例2:支付并运行

以下是Pay and Play图表,显示对应的组件和数据流。

Pay and Play图表

本模型没有试用版。首先付款,然后获取服务。黑客无法进行任何攻击。

对于开发人员来说,下面的句子已经成为常规规则:“几乎任何代码在足够的时间和努力下都可以被逆向工程。混淆器可以使反向工程变得更加困难和经济上不切实际。”但这是一个错误的观点。存在一种所谓的“偷一次,卖很多次”的攻击。黑客不是为自己而是为了出售而入侵程序。例如,你仍然可以以几乎零成本在线购买非法副本的MS Office、Windows 7或10以及许多其他软件。

因此,黑客需要购买该程序并执行之前描述的客户端-服务器方案中的步骤:将接受激活密钥的相应行替换为返回true,并删除付款订单中的命令。

为了抵御这种最简单的攻击,开发人员使用从服务器(激活密钥)接收的任何Java加密算法的密钥加密部分代码。加密部分包括密钥验证和成功后启动主程序的过程。以下是代码片段,包括解密和加载类的代码。

这段代码在这里以及接下来的部分中都使用了。认证器

public class Authenticator {
    public Authenticator(byte[] key, long ... l) {
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        IvParameterSpec iv = new IvParameterSpec(key);
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
            CipherInputStream cis = new CipherInputStream(getClass().getResourceAsStream("anyfolder/anyname"), cipher);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            copyStream(cis, baos);
            byte[] bytes = baos.toByteArray();
            Class<?> klass = new ClassLoader() {
                Class<?> define(byte[] bytes, int offset, int length) {
                    Class<?> klass = super.defineClass(null, bytes, 0, length);
                    return klass;
                }
            }.define(bytes, 0, bytes.length);
            klass.getConstructor().newInstance();
        } catch (Throwable t) {
            System.exit(1);
        }
    }
}

和启动器,应使用相同的激活密钥进行加密,并放置在任何资源文件夹中。

public class Launcher {
    args = Preloader.args;
    public Launcher() {
        Application.run(args);
    }
}

其中封装了Preloader类,该类设置args值和在付款确认后从服务器接收到的激活密钥。

public class Preloader {
    private static byte[] key;
    public static String[] args;

    public static void main(String[] args) {
        receiveConfirmation();
        encryptClass();
        new Authenticator(key);
    }

    private static void receiveConfirmation() {
        String confirm = responce();
        String[] parts = confirm.split(":");
        key = hexToBytes(parts[0]);
    }

    private static void encryptClass() {
        IvParameterSpec iv = new IvParameterSpec(key);
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
            InputStream is = Preloader.class.getResourceAsStream("/Launcher.class");
            CipherInputStream cis = new CipherInputStream(is, cipher);
            String file = "C:\\Workspaces\\anyfolder\\anyname";
            File targetFile = new File(file);
            OutputStream outStream = new FileOutputStream(targetFile);
            copyStream(cis, outStream);
            cis.close();
            outStream.close();
        } catch (Throwable t) {
            System.exit(1);
        }
    }

    private static byte[] hexToBytes(String hex) {
        byte[] bytes = new byte[hex.length() / 2];
        for (int i= 0; i < bytes.length; i++) {
            try {
                bytes[i] = (byte) (0xff & Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16));
            } catch (Exception e) {
                return null;
            }
        }
        return bytes;
    }

    private static void copyStream(InputStream in, OutputStream out) {
        byte[] buffer = new byte[4096];
        int read;
        try {
            while ((read = in.read(buffer)) != -1) {
                out.write(buffer, 0, read);
            }
        } catch (IOException e) {
            System.exit(1);
        }
    }

    private static void sendPaymentOrder(float sum) {
        ...
    }

    private static String responce() {
        ...    
    }
}

这种保护方法的优点是明显的:

  • 没有可以被修改的逻辑表达式和变量。

  • 路径追踪在认证器类结束,无法伪造或规避。

  • 没有进入主程序的入口点,甚至没有名称。

我们暂且不涉及复制保护的问题。

重命名变量、文件和类不会影响安全级别,但可能会产生某种印象。

案例3:"Time Bomb"

下图显示了Time Bomb模型的部件和数据流。

0_NNz4P8GaP3mJqJu3.jpg

此模型适用于限时试用和订阅。

在这里,逆时针计时器充当服务器。它位于程序内部,因为将其放置在服务器上会使我们返回到之前的方案。请求是"剩余多少时间",响应是时间 > 0 ? 运行 : 退出。关键点在于计数器本身和计数器的输出。类似于这样的请求->调用计数器->响应->切换过期/否->

Launcher类中添加了静态字段start和period,并对构造函数进行了小幅修改。

public class Launcher {
    private static long start;
    private static long period;
    private static String[] args;
    
    public class Application {
        public static void main(String[] args) {
            ...
        }
    }

    static {
        args = Preloader.args;
        start = Preloader.start;
        period = Preloader.period;
    }

    public Launcher() {
        if (System.currentTimeMillis() - start < period) {
            Application.main(args);
        } else {
            System.exit(1);
        }
    }
}

与案例1不同,黑客无法使用与时间相关的Java关键字(方法)以及要更改的行或行来找到Launcher类。

因此,必须可靠地隐藏此类以防止黑客攻击。目前,加密是最合适的手段。我们采取以下措施:首先,加密Launcher.class字节,其次,将它们移动到/anyfolder文件夹中,将类重命名为任何名称,然后使用与案例 2中的Authenticator类相同的密钥解密。

public class Authenticator {
    public Authenticator(byte[] key, long ... l) {
        ...
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            ...
            klass.getConstructor().newInstance();
        } catch (Throwable t) {
            System.exit(1);
        }
    }
}

Preloader和Launcher类是Pay and Play模型类的修改版本;分别添加了工作开始时间和持续时间参数start和period,以及总金额。

但是,所有试图通过加密隐藏激活密钥的尝试都受挫,因为密钥和类都会在JVM内存中解密。黑客可以使用内存转储获取所需的数据。首先,它使用Java Tools API,下面是代码:

DumperAgent:

public class DumperAgent implements ClassFileTransformer {
    public static void premain(String args, Instrumentation instr) {
        agentmain(args, instr);
    }
    public static void agentmain(String agentArgs, Instrumentation instr) {
        instr.addTransformer(new DumperAgent(), true);
        Class<?>[] classes = instr.getAllLoadedClasses();
        try {
            instr.retransformClasses(classes);
        } catch (UnmodifiableClassException e) {}
    }
    public byte[] transform(ClassLoader loader, String className,
            Class<?> redefinedClass, ProtectionDomain protDomain, byte[] classBytes) {
        dumpClass(className, classBytes);
        return null;
    }
    private static void dumpClass(String className, byte[] classBytes) {
        try {
            className = className.replace("/", File.separator);
            // ...
            FileOutputStream fos = new FileOutputStream(fileName);
            fos.write(classBytes);
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Preloader and Launcher classes are modifications of the Pay and Play model classes; parameters have been added for the Start and Time of work, start and period, respectively, as well as sum.

public class Preloader {
    private static float sum = 1000000.00f; // added for Case 3
    private static byte[] key;
    public static String[] args;
    public static long start; // added for Case 3
    public static long period; // added for Case 3

    public static void main(String[] args) {
        sendPaymentOrder(sum); // added for Case 3
        receiveConfirmation();
        encryptClass(); 
        new Authenticator(key, start, period);
    }
    
    private static void receiveConfirmation() {
        String confirm = responce();
        String[] parts = confirm.split(":");
        key = hexToBytes(parts[0]);
        start = Long.parseLong(parts[1]); // added for Case 3
        period = Long.parseLong(parts[2]); // added for Case 3
    }

    private static void encryptClass() {
        IvParameterSpec iv = new IvParameterSpec(key);
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
            InputStream is = Preloader.class.getResourceAsStream("/Launcher.class");
            CipherInputStream cis = new CipherInputStream(is, cipher);
            String file = "C:\\Workspaces\\anyfolder\\anyname";
            File targetFile = new File(file);
            OutputStream outStream = new FileOutputStream(targetFile);
            copyStream(cis, outStream);
            cis.close();
            outStream.close();
        } catch (Throwable t) {
            System.exit(1);
        }
    }

    private static byte[] hexToBytes(String hex) {
        ...
        return bytes;
    }

    private static void copyStream(InputStream in, OutputStream out) {
        ...
    }

    private static void sendPaymentOrder(float sum) {
        ...
    }
}

但是,所有通过加密隐藏 激活密钥的尝试都会因密钥和类在 JVM 内存中被解密这一事实而受挫。黑客可以使用内存转储来获取他需要的数据。首先,它使用 Java 工具 API。代码如下:

public class DumperAgent implements ClassFileTransformer {
    public static void premain(String args, Instrumentation instr) {
        agentmain(args, instr);
    }

    public static void agentmain(String agentArgs, Instrumentation instr) {
        instr.addTransformer(new DumperAgent(), true);
        Class<?>[] classes = instr.getAllLoadedClasses();
        try {
            instr.retransformClasses(classes);
        }
        catch (UnmodifiableClassException e) {}
    }

    public byte[] transform(ClassLoader loader, String className,
            Class<?> redefinedClass, ProtectionDomain protDomain, byte[] classBytes) {
        dumpClass(className, classBytes);
        return null;
    }

    private static void dumpClass(String className, byte[] classBytes) {
        try {
            className = className.replace("/", File.separator);
            // ...
            FileOutputStream fos = new FileOutputStream(fileName);
            fos.write(classBytes);
            fos.close();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

And Attacher: 

public class Attacher {
    private static String pathToAttacherJar, pid;

    public static void main(String[] args) throws Exception {
        VirtualMachine vm = VirtualMachine.attach(pid);
        vm.loadAgent(pathToAttacherJar, null);
    }
}

不幸的是,这次尝试也没有成功。对 Java 运行时参数的简单分析会检测到不需要的参数,并且程序会在初始化时停止工作,而没有时间加载任何内容。

   static {
        RuntimeMXBean mxBean = ManagementFactory.getRuntimeMXBean();
        if (mxBean.getInputArguments().contains("-XX:-DisableAttachMechanism") ||
                            mxBean.getInputArguments().contains("-javaagent:")) {
            System.exit(1);
        }
    }

这个片段应该放在通常包含main(String[] args)方法的第一个类中。

你能做什么?

首先,你需要学会以黑客的眼光来看待问题。这意味着要识别你的程序所属的架构,寻找现有的漏洞,以及可能存在的程序漏洞和如何进行攻击。

记住,你总是可以找到针对任何攻击的保护措施。同时也要记住,“偷一次,多次出售”的攻击存在,但“做好保护,长期保护”的防御也同样存在。

不要仅仅依赖混淆器、服务器或加密技术。只保护关键点和程序的重要部分。利用程序本身的结构,从外部看待它。安全性应与你的程序设计一样精心设计。

结束语

  • 代码保护是一个过程,而不是最终结果。新的黑客攻击方法正在被发明,新版本的JVM发布,允许更多地操纵JVM内存等等。这类似于病毒与杀毒软件之间的战争。

  • 换句话说,绝对的武器和绝对的保护都不存在,并且也无法存在。任何新的攻击方法都会引起相应的防御方法来抵御这种攻击。

  • 在几乎所有安全算法开发的情况下,应使用类似于量子物理学中的观察效应的入侵检测过程。任何观察(干预)、重置、远程代理等操作都会对观察环境造成干扰,这可以被注意到并采取保护措施。

  • 因此,我们有一个攻防系统的经典例子。这意味着对于每一次攻击行动,都会有相应的防御行动,反之亦然。请注意,防御始终处于最佳位置。文章来源地址https://www.toymoban.com/diary/java/670.html

到此这篇关于如何加固基于Java的程序以防止黑客攻击的文章就介绍到这了,更多相关内容可以在右上角搜索或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

原文地址:https://www.toymoban.com/diary/java/670.html

如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用
分享使用wp-env在Ubuntu上设置WordPress测试环境
上一篇 2024年01月05日 22:19
探索大型语言模型的安全风险
下一篇 2024年01月06日 00:38

相关文章

  • 小程序安全性加固:如何保护用户数据和防止恶意攻击

    第一章:引言   在当今数字化时代,移动应用程序的使用已经成为人们日常生活中的重要组成部分。小程序作为一种轻量级的应用程序形式,受到了广泛的欢迎。然而,随着小程序的流行,安全性问题也日益凸显。用户数据泄露和恶意攻击威胁着用户的隐私和安全。本文将重

    2024年02月12日
    浏览(23)
  • 移动应用数据安全性:如何防止应用程序被黑客攻击和数据泄露?

    在移动应用成为人们生活中不可或缺的一部分的今天,数据安全性已经成为一个非常重要的问题。随着黑客攻击和数据泄露事件的频繁发生,用户对于移动应用程序的信任度也在逐渐下降。本文将探讨移动应用数据安全性的重要性,并提供一些有效的技术措施来防止应用程序

    2024年02月08日
    浏览(27)
  • SpringCloud微服务实战——搭建企业级开发框架:微服务安全加固—自定义Gateway拦截器实现防止SQL注入/XSS攻击

     SQL注入是常见的系统安全问题之一,用户通过特定方式向系统发送SQL脚本,可直接自定义操作系统数据库,如果系统没有对SQL注入进行拦截,那么用户甚至可以直接对数据库进行增删改查等操作。   XSS全称为Cross Site Script跨站点脚本攻击,和SQL注入类似,都是通过特定方

    2024年02月03日
    浏览(26)
  • 【Python】查看当前 GPU一些资源信息 | 区块链 面试题:区块链技术中,如何防止“双花”攻击?| 共识机制,区块确认,交易签名,UTXO模型,51%攻击防护

      “当你低落时,就请穿上节日盛装。”     🎯作者主页: 追光者♂🔥          🌸个人简介:   💖[1] 计算机专业硕士研究生💖   🌿[2] 2023年城市之星领跑者TOP1(哈尔滨)🌿   🌟[3] 2022年度博客之星人工智能领域TOP4🌟   🏅[4] 阿里云社区特邀专家博主🏅   🏆[5]

    2024年02月06日
    浏览(23)
  • 【黑客技术】Hping攻击实验

    请勿攻击公网!请勿攻击公网!请勿攻击公网! 一切责任自负!一切责任自负!一切责任自负! hping3是一款面向TCP/IP协议的免费的数据包生成和分析工具。Hping是用于对防火墙和网络执行安全审计和测试的工具之一。(换句话说,它本身是一个安全测试工具,也可以被黑客用

    2024年02月11日
    浏览(21)
  • 布线技术 三种弱电布线系统防止雷电攻击

    雷击时是天然灾害,而且他的威力很大,对线路也会造成严重的伤害。一些电缆以及弱电设备,如果没有一定的保护措施,就很容易招来雷电的袭击,不但会造成经济损失,更严重的会对人的生命安全造成威胁。那么我们可以怎么样操作防止遭遇雷电的侵犯呢?三种弱电布线系

    2024年02月07日
    浏览(29)
  • 如何防止网络安全攻击

    为了防止网络安全攻击,以下是一些常见的防御措施和建议: 使用强密码:确保使用足够长、复杂且随机的密码,并定期更改密码。不要在多个账户中重复使用相同的密码。 更新和修补软件:定期更新操作系统、应用程序和安全补丁,以及及时修复安全漏洞,以防止攻击者

    2024年02月09日
    浏览(28)
  • 如何防止CSRF攻击

    随着互联网的高速发展,信息安全问题已经成为企业最为关注的焦点之一,而前端又是引发企业安全问题的高危据点。在移动互联网时代,前端人员除了传统的 XSS、CSRF 等安全问题之外,又时常遭遇网络劫持、非法调用 Hybrid API 等新型安全问题。当然,浏览器自身也在不断在

    2024年02月13日
    浏览(21)
  • 如何有效防止服务器被攻击?

    随着互联网的快速发展,服务器安全问题日益引起人们的关注。近期,全球范围内频繁发生的服务器攻击事件引发了广泛关注。为了保护企业和个人的数据安全,有效防止服务器被攻击已成为迫在眉睫的任务。 首先,及时更新服务器的操作系统和软件非常关键。厂商经常发布

    2024年02月10日
    浏览(28)
  • 如何使用WAF防止DDoS攻击?

    DDoS攻击已经成为互联网领域中最严重的安全威胁之一。无论是企业还是个人网站,都面临着DDoS攻击的风险。为了保护自己的网站安全,并避免DDoS攻击对自身造成的危害,采用WAF防止DDoS攻击是非常必要的。 WAF是Web应用程序防火墙,是一种网络安全技术,可以在应用程序层面

    2024年02月14日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包