加固技术一路“升级打怪”,会封顶于第五代虚机源码保护技术吗?

目前加固对于App开发人员来说,不管是App Store的审核4.3问题,还是为了防止逆向工程、篡改、反编译等问题,加固都算是一个必备的选择了。

但是加固技术在一步步升级的同时,其固有的安全缺陷和兼容性问题却始终未能得到解决。

目前,加固技术已经发展到第五代--虚机源码保护,使用代码类型更广泛,App保护级别更高,兼容性更强。那么,这一波技术升级,能解决上面的安全和兼容性问题吗?我们先挨个看看各代技术的优势和缺陷吧。

第一代加固技术:动态加载

第一代加固技术中的动态加载功能通常被用于保护应用程序中的核心代码,以及防止恶意攻击者对代码进行反编译、逆向工程等操作。动态加载功能通常包括以下几个步骤:

1.加密:将应用程序中的核心代码进行加密处理,以防止恶意攻击者对代码进行解密操作。

2.动态加载:在应用程序启动时,动态加载器会将加密后的代码加载到内存中,并对其进行解密操作。

3.代码完整性检查:在加载代码后,动态加载器会对代码进行完整性检查,以确保代码没有被篡改或修改。

4.运行时保护:在应用程序运行期间,动态加载器会对代码进行保护,以防止攻击者利用漏洞对代码进行攻击或修改。

核心代码

用Java代码简单写了一个动态加载的核心代码:

import java.io.File;
import java.io.FileInputStream;
import java.security.MessageDigest;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class DynamicLoader {

    private static final String KEY = "mysecretkey"; // 加密密钥
    private static final String FILE_PATH = "/path/to/encrypted/code"; // 加密后的代码文件路径
    private static final String MD5_CHECKSUM = "1234567890abcdef1234567890abcdef"; // 加密前的代码的MD5校验和

    public static void main(String[] args) throws Exception {
        byte[] encryptedCode = loadEncryptedCode(); // 加载加密后的代码
        byte[] decryptedCode = decrypt(encryptedCode); // 解密代码
        checkCodeIntegrity(decryptedCode); // 检查代码完整性
        runCode(decryptedCode); // 运行代码
    }

    // 加载加密后的代码
    private static byte[] loadEncryptedCode() throws Exception {
        File file = new File(FILE_PATH);
        FileInputStream fis = new FileInputStream(file);
        byte[] buffer = new byte[(int) file.length()];
        fis.read(buffer);
        fis.close();
        return buffer;
    }

    // 解密代码
    private static byte[] decrypt(byte[] encryptedData) throws Exception {
        Cipher cipher = Cipher.getInstance("AES");
        SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), "AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        return cipher.doFinal(encryptedData);
    }

    // 检查代码完整性
    private static void checkCodeIntegrity(byte[] code) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] checksum = md.digest(code);
        String hex = bytesToHex(checksum);
        if (!hex.equals(MD5_CHECKSUM)) {
            throw new Exception("Code has been modified");
        }
    }

    // 运行代码
    private static void runCode(byte[] code) throws Exception {
        // 通过反射执行代码
        Class clazz = new MyClassLoader().defineClass(null, code, 0, code.length);
        clazz.getDeclaredMethod("run").invoke(null);
    }

    // 将字节数组转换为十六进制字符串
    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    // 自定义ClassLoader,用于加载解密后的代码
    private static class MyClassLoader extends ClassLoader {
        public Class defineClass(String name, byte[] code, int off, int len) {
            return super.defineClass(name, code, off, len);
        }
    }

}

PS0:由于动态加载的实现方式可能因操作系统等因素而异,以上代码示例仅供参考,不能直接用于实际应用。(自己根据需求更改)

PS1:【multidex组件的加固原理】:Android的DEX文件在设计之初程序普遍较小,所以在DEX文件设计时,只允许包含65535个函数引用。而随着Android应用的发展,大量的应用的代码已经超过了65535的限制,为了解决这个问题,Android5.0之后原生支持加载多个dex,而为了对旧版本的兼容,Android提供了multidex组件。该组件的实现原理与上面介绍的是一致的。

缺陷和对抗

第一代加固技术的缺陷是依赖Java的动态加载机制,而这个机制要求关键逻辑(Payload)部分必须解压,并且释放到文件系统,这就给了攻击机会去获取对应的文件。虽然可以通过关键逻辑(Payload)被加载后,被从文件系统删除,用于防止被复制,但是攻击者可以拦截对应的删除函数,阻止删除。

而关键逻辑(Payload)会被加密后保存,可用于对抗静态分析,但是攻击者可以通过自定义虚拟机,拦截动态加载机制所使用的关键函数,在这个函数内部,复制文件系统中的关键逻辑(Payload)文件。

第二代加固技术—不落地加载

第二代加固技术——不落地加载(Non-Resident Load,简称NRL)技术是一种加固技术,用于保护软件代码免受反向工程和恶意代码注入等攻击。它的核心思想是将软件代码分为两部分:一个不需要保护的加载器和一个需要保护的主程序,加载器负责将主程序加载到内存中并执行,主程序则通过加密和混淆等方式保护代码的安全性。

相比于动态加载,不落地加载在APK修改方面已经完善,能做到对开发的零干扰。

主要流程

NRL技术的主要流程如下:

1.加载器加载:加载器先将主程序的加密部分解密,然后将解密后的主程序加载到内存中。这个过程中,加载器需要读取主程序文件并将其解密。

2.反调试:为了防止被调试,主程序需要执行一些反调试的操作。例如,它可以检测调试器是否存在,或者检测是否有其他程序在对其进行调试。

3.解密和解压:主程序在内存中运行之前,需要进行解密和解压缩。这个过程中,主程序会调用相应的解密和解压缩函数,对自身进行解密和解压缩。

4.恢复IAT:IAT(Import Address Table)是用于动态链接库中的函数导入的数据结构,它记录了函数在内存中的地址。为了保护代码,主程序需要恢复IAT,并将函数地址解密和更新到IAT中。

5.启动主程序:主程序经过以上的准备工作之后,可以开始执行了。

核心代码

以下是一个不落地加载的核心代码示例,使用AWS Lambda作为后端处理函数。

在本示例中,使用AWS Lambda处理来自API网关的HTTP请求。API网关是一个完全托管的服务,可以将HTTP请求路由到不同的AWS Lambda函数。

要使用这个示例,需要一个AWS账户和AWS CLI(命令行界面)。

首先,创建一个新的Lambda函数:

  1. 打开AWS控制台,进入AWS Lambda控制台。
  2. 单击“创建函数”,然后选择“从模板创建函数”。
  3. 选择“api-gateway-authorizer-python”模板。
  4. 输入函数名称,并选择一个可用的区域。
  5. 单击“创建函数”。

接下来,创建一个API网关:

  1. 打开AWS控制台,进入API Gateway控制台。
  2. 单击“创建API”,然后选择“REST API”。
  3. 选择“新API”并输入API名称。
  4. 单击“创建API”。

现在,将API网关与Lambda函数关联:

  1. 在API Gateway控制台中,选择您创建的API网关。
  2. 在左侧菜单中,选择“资源”。
  3. 单击“创建资源”。
  4. 输入资源名称,并单击“创建资源”。
  5. 选择新创建的资源,并在“操作”下拉菜单中选择“创建方法”。
  6. 选择“POST”方法,并单击“确认”。
  7. 在“POST - Setup”页面中,选择“使用Lambda代理集成”并选择您的Lambda函数。
  8. 单击“保存”。

ok,我们已经将API网关与Lambda函数关联,并准备好处理来自API网关的HTTP请求。

下面是一个示例代码,用来处理Lambda函数中的请求,计算两个数字的和:

import json

def lambda_handler(event, context):
    # 解析请求正文中的JSON数据
    body = json.loads(event['body'])
    num1 = int(body['num1'])
    num2 = int(body['num2'])
    
    # 计算两个数字的和
    result = num1 + num2
    
    # 返回结果
    response = {
        "statusCode": 200,
        "body": json.dumps({
            "result": result
        })
    }
    
    return response

缺陷与对抗

不落地加载技术并不是完美的,存在一些缺陷和对抗方法:

  1. 病毒和恶意软件可能会利用不落地加载技术来隐藏它们自己的代码,从而避免被杀毒软件和安全监测系统检测到。相应的对抗包括使用反病毒软件和安全监测系统来检测和清除恶意软件。
  2. 在使用不落地加载技术时,应用程序的代码和数据是以明文形式存储在磁盘上的,这意味着攻击者可以轻松地使用工具来查看或修改这些数据。相应的对抗包括对于需要保护的敏感数据和代码,可以使用加密技术来保护它们以及使用虚拟化技术来隔离应用程序的运行环境,以防止恶意软件修改应用程序的代码和数据和使用硬件安全模块(HSM)来保护关键数据和代码,以防止它们被未经授权的人员访问和修改。
  3. 一些恶意软件会通过修改系统配置文件或在启动过程中拦截不落地加载技术来绕过它的保护。相应的对抗包括实施安全的启动过程,包括使用数字签名验证和安全启动链(Secure Boot),以确保系统和应用程序的完整性和安全性。

第三代加固技术—指令抽离

由于第二代加固技术仅仅对文件级别进行加密,其带来的问题是内存中的Payload是连续的,可以被攻击者轻易获取。第三代加固技术对这部分进行了改进,将保护级别降到了函数级别。

指令抽离技术是一种安全性较高的代码加固技术,它将敏感指令从程序代码中提取出来,形成一个独立的指令库,程序运行时通过调用这个指令库来完成敏感操作,从而避免了指令在程序中明文存在的风险。

指令抽离技术的加固流程:

  1. 分析程序代码,确定哪些指令需要抽离。通常来说,涉及加密、解密、授权认证、协议处理等敏感操作的指令都需要被抽离。
  2. 编写指令库,将敏感指令实现为一个独立的代码库,并进行加密处理,确保指令库的安全性。
  3. 修改程序代码,将敏感指令的调用替换为调用指令库中的相应指令。
  4. 对指令库进行签名和校验,确保指令库的完整性和安全性。
  5. 隐藏指令库的位置和调用方式,增加攻击者的攻击难度。
  6. 对程序进行混淆处理,增加攻击者分析代码的难度。
  7. 对程序进行安全性测试和漏洞扫描,确保程序的安全性。
  8. 对程序进行发布和更新,及时修复已知漏洞,确保程序的持续安全性。

核心代码

展示一个简单的示例代码:

// 原始程序代码
void encrypt(char* data, int len, char* key) {
  // 执行加密操作
}

// 指令抽离后的程序代码
#include "encrypt_lib.h"

void encrypt(char* data, int len, char* key) {
  encrypt_lib(data, len, key);
}

缺陷与对抗

缺陷:

  1. 需要对程序进行修改:指令抽离技术需要对程序进行修改,将敏感指令的调用替换为调用指令库中的相应指令,这可能会影响程序的性能和稳定性。
  2. 增加程序的复杂性:指令抽离技术会增加程序的复杂性和开发难度,需要进行全面的测试和评估,确保加固后的程序仍然满足业务需求和性能要求。
  3. 指令库的安全性:指令库的安全性非常重要,如果指令库被攻击者窃取或篡改,可能导致程序的安全性被破坏。
  4. 反向工程:攻击者可以使用反向工程技术来分析程序和指令库,发现和利用漏洞,从而破坏程序的安全性。

对抗:

  1. 优化指令库:指令库的安全性非常重要,需要采用加密、签名、校验等措施来保护指令库的安全性。
  2. 混淆程序代码:通过对程序代码进行混淆,可以增加攻击者分析代码的难度,从而提高程序的安全性。
  3. 加强安全测试:对程序进行全面的安全测试和漏洞扫描,及时发现和修复安全漏洞,确保程序的安全性。
  4. 多层加密和验证:可以在指令抽离的基础上增加多层加密和验证,从而增加攻击者攻击的难度和成本。

第四代加固技术—指令转换/VMP

第三代加固技术在函数级别的保护,使用Android虚拟机内的解释器执行代码,带来可能被记录的缺陷,第四代加固技术使用自己的解释器来避免第三代的缺陷。而自定义的解释器无法对Android系统内的其他函数进行直接调用,必须使用JAVA的JNI接口进行调用。其主要实现由两种:

A、DEX文件内的函数被标记为native,内容被抽离并转换成一个符合JNI要求的动态库。 动态库内通过JNI和Android系统进行交互。

B、DEX文件内的函数被标记为native,内容被抽离并转换成自定义的指令格式,该格式使用自定义接收器执行,和A一样需要使用JNI和Android系统进行调用。

兼容性第四代VMP加固技术一般配合第三代加固技术使用,所以第三代的所有兼容性问题,指令转换/VMP加固也存在。缺陷与对抗不论使用指令转换/VMP加固的A方案或者B方案,其必须通过虚拟机提供的JNI接口与虚拟机进行交互,攻击者可以直接将指令转换/VMP加固方案当作黑盒,通过自定义的JNI接口对象,对黑盒内部进行探测、记录和分析,进而得到完整DEX程序。

加固技术一路“升级打怪”,会封顶于第五代虚机源码保护技术吗?_第1张图片


(第四代加固DEX文件恢复)另外,第四代VMP加固技术只实现Java代码保护,没有做到使用VMP技术来保护C/C++等代码,安全保护能力有所欠缺。

下一代加固技术—虚机源码保护

跟第四代的VMP加固技术相比,虚机源码保护加固是用虚机技术保护所有的代码,包括Java,Kotlin,C/C++,Objective-C,Swift等多种代码,具备极高的兼容性;使App得到更高安全级别的保护,运行更加稳定。

虚机源码保护为用户提供一套完整的工具链,首先把用户待保护的核心代码编译成中间的二进制文件,随后生成独特的虚机源码保护执行环境和只能在该环境下执行的运行程序。

虚机源码保护会在App内部隔离出独立的执行环境,该核心代码的运行程序在此独立的执行环境里运行。即便App本身被破解,这部分核心代码仍然不可见。

加固技术一路“升级打怪”,会封顶于第五代虚机源码保护技术吗?_第2张图片

(虚机源码保护加固流程)生成的虚机源码保护拥有独特的可变指令集,极大的提高了指令跟踪、逆向分析的难度。

同时,虚机源码保护还提供了反调试能力和监控能力。虚机源码保护可以通过自身的探针感知到环境的变化,实时探测到外界对本环境的调试、注入等非正常执行流程变化,将调试动作引入程序陷阱,并发出警报,进而进行实时更新,提高安全强度。

结语

加固技术发展及其攻防对抗的更迭,伴随着互联网技术发展不断升级。

作为“正义”的一方,我们深信邪不能胜正,而虚机源码保护加固作为当前领先的加固技术,在未来很长一段时间,能够为App提供足够强度的保护,为企业和开发者的业务发展保驾护航。

免费试用:加固产品

你可能感兴趣的:(加固工具,安全)