iOS Anti-dump Feasibility Research Report

Author: ChatGPT(GPT-4) & iHTCboy

Abstract: This article introduces how to prevent iOS App from being dumped, including multi-layer defense strategies such as code obfuscation, encryption, and integrity checking, as well as solutions such as server-side verification, dynamic loading, API security, and multi-factor authentication. In addition, back-end solutions such as monitoring and alerting, regular security audits, and security training can also improve App security. Finally, implementations for banning jailbroken devices are introduced, along with new technology solutions such as DeviceCheck and App Attest API.

I. Introduction

On the iOS platform, protecting the source code security of apps is an important task for developers. Since the App may contain sensitive information and important algorithms, it is particularly important to prevent the source code from being illegally obtained and tampered with. It is necessary to adopt certain security policies to prevent the source code of App from being illegally decompiled or dumped. This article will investigate the anti-dump technology of iOS SDK, and put forward some suggestions and feasible solutions.

This report will discuss the iOS SDK anti-dump technical solution from four aspects:

  1. A technical solution that can prevent dump
  2. The protection scheme in the case of not being able to prevent dump
  3. Security policy in the case where dump cannot be prevented and cannot be completely avoided
  4. Future Prospects for Other New Technology Solutions

2. Common iOS dump techniques

To prevent it, you first need to understand those attack techniques. So let's first introduce common iOS dump techniques.

On the iOS platform, attackers may use various technical means to crack, analyze and attack App. Here are some common iOS dump techniques:

1.  Decompilation: Decompilation is the process of converting binary files back to source code. For iOS Apps, attackers can use decompilation tools such as Hopper, IDA Pro, Ghidra, etc. to statically analyze the App to obtain sensitive information such as source codes and resource files.

2. 动态分析: 动态分析是在 App 运行时进行的分析过程。攻击者可以利用工具,如 Frida、Cycript、GDB 等,对运行中的 App 进行实时分析、修改和调试,从而突破安全防护措施。

3. 反调试: 反调试是防止调试器附加到 App 的过程。攻击者可能会使用反调试技术,如 ptrace、sysctl 等,来检测和绕过 App 的调试防护。

4. 内存 dump: 内存 dump 是从运行中的 App 中提取内存数据的过程。攻击者可以利用如 Clutch、Fridump 等工具从 App 的内存中提取敏感数据和关键信息。

5. 二进制破解: 二进制破解是修改 App 的二进制文件以突破安全限制的过程。攻击者可以使用工具,如 Hex Fiend、MachOView 等,直接修改 App 的二进制文件以实现特定目的。

6. 代码注入: 代码注入是将恶意代码插入到 App 中以实现特定功能的过程。攻击者可以使用如 Theos、MobileSubstrate 等技术将恶意代码注入到 App 中,实现代码执行、功能修改等目的。

7. 交叉编译: 攻击者可能会使用如 LLVM、clang 等交叉编译器,将 App 编译为其他平台的可执行文件,以便在其他平台上进行分析和调试。

8. 虚拟化: 攻击者可以使用虚拟化技术,如 QEMU、Corellium 等,在虚拟环境中运行 iOS App ,实现对 App 的分析和调试,避免被实际设备的安全措施检测到。

下面,针对几个不太熟悉的技术进行介绍。

2.1 动态分析

Frida、Cycript 和 GDB 是常见的动态分析工具,攻击者可以使用这些工具在运行时对 iOS App 进行实时分析、修改和调试。

接下来我们详细介绍这三种工具:

Frida

Frida 是一个跨平台的动态代码插桩框架,支持多种操作系统,包括 iOS。它允许开发者和安全研究员在运行时注入自己的代码片段,以便对目标程序进行实时分析和修改。攻击者可以使用 Frida 对 iOS App 进行以下操作:

  • 检测和拦截函数调用
  • 修改函数参数和返回值
  • 调用和覆盖原生函数
  • 搜索和读写内存
  • 遍历对象和类结构

Frida 提供了丰富的 API 和脚本语言支持,如 Python、JavaScript 和 Swift,使得攻击者可以轻松地编写自定义脚本,以实现特定目的。

Cycript

Cycript 是一个用于 iOS 平台的动态分析工具,允许在运行时探查和修改 Objective-C 和 JavaScript App 。攻击者可以使用 Cycript 进行以下操作:

  • 在运行时执行 Objective-C 和 JavaScript 代码
  • 检查和修改 App 的对象和变量
  • 调用和重写方法
  • 动态加载和卸载框架

Cycript 的交互式命令行界面使得攻击者可以轻松地实时探查和修改目标 App 。

GDB

GDB(GNU Debugger)是一个功能强大的源代码级调试器,支持多种平台,包括 iOS。攻击者可以使用 GDB 对 iOS App 进行以下操作:

  • 设置断点和单步执行
  • 检查和修改内存、寄存器和变量
  • 跟踪函数调用和返回
  • 监控异常和信号
  • 检查和修改汇编代码

GDB 提供了丰富的命令和扩展接口,使得攻击者可以对目标 App 进行深入的分析和调试。

2.2 反调试

ptrace 和 sysctl 是两种与调试和进程管理相关的系统调用。它们在底层操作系统中实现,并为用户级程序提供了一定程度的控制和信息。

ptrace

ptrace(process trace)是一种 UNIX 和类 UNIX 系统中的进程跟踪和调试功能。它允许一个进程观察和控制另一个进程的执行。通过 ptrace,调试器可以捕获目标进程的系统调用、信号处理和其他事件,以及检查和修改目标进程的内存和寄存器。

原理:当调试器使用 ptrace 附加到目标进程时,操作系统会将调试器的 ID 与目标进程关联。调试器可以使用 ptrace 请求来控制和监控目标进程。当目标进程遇到相关事件(如系统调用、信号处理等)时,操作系统会将事件通知给调试器,调试器可以根据需要进行相应操作。

sysctl

sysctl 是一种用于获取和设置内核参数的接口。它用于访问内核中的数据结构,以获取和修改系统的运行时配置。sysctl 主要用于系统管理和监控任务,但在某些情况下,它也可用于检测和绕过 App 的调试防护。

原理:sysctl 通过内核和用户空间之间的通信接口实现。用户空间的程序可以通过 sysctl 系统调用或特定的虚拟文件系统(如 /proc 和 /sys)访问内核参数。在 iOS 中,例如开发者可以使用 sysctl 函数查询进程信息,如进程列表、内存使用情况等。此外,通过检查 sysctl 返回的数据,攻击者可能会检测到调试器的存在,从而采取相应措施绕过调试防护。

dlopen / dlsym

dlopen 和 dlsym 是动态链接库加载和符号解析的函数。它们允许运行时动态加载共享库并解析库中的符号(如函数和变量)。这些功能可以用于实现插件系统、动态代码执行和运行时代码替换等。

mmap / munmap

mmap 和 munmap 是用于内存映射管理的系统调用。它们允许进程将文件或其他资源映射到虚拟内存空间,以便直接访问和操作。mmap 可用于实现内存共享、文件 I/O 优化和动态代码加载等。

getpid / getppid

getpid 和 getppid 是用于获取进程 ID 和父进程 ID 的系统调用。它们可以用于进程管理和监控,以及分析程序的运行环境和上下文。

fork / exec

fork 和 exec 是用于创建新进程并执行程序的系统调用。fork 用于创建当前进程的副本,而 exec 用于替换当前进程的映像并执行新程序。这些功能可用于实现多任务、并发执行和进程间通信等。

kqueue / epoll

kqueue(BSD 系统)和 epoll(Linux 系统)是用于异步 I/O 事件通知的高效接口。它们允许进程在没有阻塞的情况下监控文件描述符、信号和其他事件。这些功能可用于实现高性能的网络服务器、事件驱动程序和异步任务处理等。

2.3 调试技术

1. 断点

断点允许调试器在特定位置暂停程序的执行。当程序达到断点时,调试器会中断程序执行,允许开发者或攻击者检查当前状态(例如变量值和调用堆栈),并逐步执行代码。断点通常通过替换目标位置的原始指令(例如使用 INT 3 指令)来设置,当处理器执行该指令时,将触发软件中断,将控制权交还给调试器。

2. 单步执行

单步执行允许调试器逐行或逐指令地执行程序,这有助于开发者或攻击者理解程序的执行过程。单步执行的原理是在每一步都设置临时断点,并在执行完一条指令后将控制权返回给调试器。

3. 函数跟踪

函数跟踪允许调试器记录程序中的函数调用顺序。这对于分析程序的执行流程和理解函数之间的依赖关系非常有用。函数跟踪可以通过在函数入口处设置断点并捕获调用堆栈信息来实现。

4. 内存读写监控

调试器可以监控程序的内存访问,以便查找和分析程序中的数据存储和传输。内存读写监控的原理是将目标内存页面设置为不可访问,当程序访问这些页面时,触发硬件或软件异常。调试器捕获此异常,并分析引起异常的访问,然后将内存页面的访问权限恢复,以便程序继续执行。

5. 反汇编和代码分析

调试器可以将程序的机器代码反汇编为汇编语言表示,以便更好地理解程序的执行过程。通过对反汇编代码进行分析,开发者或攻击者可以识别感兴趣的代码区域,例如加密算法、密钥存储和验证逻辑等。

6. 动态插桩

动态插桩允许在程序运行时插入额外的代码,以便捕获和修改程序的行为。这可以通过修改程序的指令指针或使用特定的插桩框架(如 Frida)来实现。动态插桩可以用于实时分析和修改程序的数据和执行流程。

7. 符号解析

调试器可以使用符号表(如果可用)将程序的内部地址解析为更具可读性的函数名和变量名。这有助于开发者或攻击者更容易地理解程序的结构和功能。符号解析的原理是通过查找程序的符号表(如果可用)或使用其他可用的调试信息,将程序的内部地址与源代码中的变量名、函数名等相关联。这有助于开发者或攻击者更容易地理解程序的结构和功能。在 iOS 开发中,调试器如 LLDB 可用于解析符号信息。

iOS 实践

在 iOS 中,这些调试技术通常结合专用的调试器和工具使用。以下是一些在 iOS 中应用这些调试技术的方法和工具:

1. 断点: 在 Xcode 中,开发者可以方便地设置断点。Xcode 集成了一个强大的调试器(LLDB),可以在 Objective-C 和 Swift 代码中设置和管理断点。

2. 单步执行: 在 Xcode 的 LLDB 调试器中,开发者可以逐行或逐指令地执行代码。Xcode 提供了“Step Over”、“Step Into”和“Step Out”按钮,以便在调试过程中控制代码的执行。

3. 函数跟踪: 在 LLDB 调试器中,可以使用“bt”(backtrace)命令获取当前线程的调用堆栈。这有助于分析函数调用顺序以及识别潜在问题。

4. 内存读写监控: LLDB 支持内存读写监控。可以使用“watchpoint set variable”命令为特定内存地址设置监视点。当该地址被访问或修改时,LLDB 会中断程序执行并通知开发者。

5. 反汇编和代码分析: 在 LLDB 中,可以使用“disassemble”命令查看反汇编的汇编代码。另外,还可以使用第三方工具,如 Hopper Disassembler 和 IDA Pro,进行更深入的代码分析。

6. 动态插桩: 在 iOS 中,可以使用 Frida、Cycript 等工具进行动态插桩。这些工具允许开发者和安全研究员在运行时注入自定义脚本,以实时分析和修改 App 的行为。

7. 符号解析: 在 LLDB 调试器中,可以使用“image lookup”命令查找符号信息。这有助于将内部地址解析为更具可读性的函数名和变量名。

三、能防 dump 的技术方案

能防 dump 的技术方案,意思是做了就可以 “防止” dump,并不是真能防止 dump 操作。

1. 代码混淆:通过对源代码进行混淆处理,使得源代码难以阅读和理解,增加攻击者分析代码的难度。

2. 加密技术:对关键数据和代码进行加密处理,确保在被 dump 时,攻击者无法直接获取敏感信息。

3. 完整性检测:对 App 代码进行完整性检测,如发现代码被篡改,则触发相应的安全策略。

4. 运行环境检测:检测当前 App 运行环境是否安全,如发现越狱设备或调试环境,则触发相应的安全策略。

3.1 代码混淆

对源代码进行混淆处理,包括变量名、函数名、类名等的替换,以及控制流混淆等。这使得源代码难以阅读和理解,增加攻击者分析代码的难度。

一个简单的混淆示例:

class HelloWorld {
    func greeting() {
        print("Hello, World!")
    }
}

let helloWorld = HelloWorld()
helloWorld.greeting()

混淆后的代码:

class A1B2C3 {
    func D4E5F6() {
        print("Hello, World!")
    }
}

let a1b2c3 = A1B2C3()
a1b2c3.D4E5F6()

3.2 加密技术

对关键数据和代码进行加密处理,确保在被 dump 时,攻击者无法直接获取敏感信息。

一个使用 AES 加密的示例:

import CryptoSwift

let key = "1234567890123456" // 16 字节的密钥
let iv = "abcdefghijklmnop" // 16 字节的初始化向量

let plaintext = "Sensitive Data"

do {
    let encrypted = try AES(key: key.bytes, blockMode: CBC(iv: iv.bytes), padding: .pkcs7).encrypt(plaintext.bytes)
    let encryptedData = Data(encrypted)
    print("Encrypted data: \(encryptedData)")

    let decrypted = try AES(key: key.bytes, blockMode: CBC(iv: iv.bytes), padding: .pkcs7).decrypt(encrypted)
    let decryptedData = Data(decrypted)
    print("Decrypted data: \(String(data: decryptedData, encoding: .utf8)!)")
} catch {
    print("Error: \(error)")
}

3.3 完整性检测

对 App 代码进行完整性检测,如发现代码被篡改,则触发相应的安全策略。

一个简单的完整性检测示例:

func verifyIntegrity() -> Bool {
    // 预先计算的 App 代码哈希值
    let expectedHash = "f0e4c2f76c58916ec258f246851bea0918b26a25"
    // 实际计算的 App 代码哈希值
    let actualHash = calculateHash()

    return expectedHash == actualHash
}

func calculateHash() -> String {
    // 计算 App 代码哈希值的逻辑,可以使用 SHA1、SHA256 等哈希算法
    // ...
}

3.4 运行环境检测

检测当前 App 运行环境是否安全,如发现越狱设备或调试环境,则触发相应的安全策略。

一个简单的运行环境检测示例:

// 越狱设备
func isJailbroken() -> Bool {
    let fileManager = FileManager.default
    let paths = ["/Applications/Cydia.app",
                "/Library/MobileSubstrate/MobileSubstrate.dylib",
                "/bin/bash", "/usr/sbin/sshd", "/etc/apt"]
		for path in paths {
		    if fileManager.fileExists(atPath: path) {
		        return true
		    }
		}
		return false
}

// 调试环境
func isBeingDebugged() -> Bool {
		var info = kinfo_proc()
		var size = MemoryLayout<kinfo_proc>.size
		var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
		if sysctl(&mib, 4, &info, &size, nil, 0) == -1 {
		    print("Error: Could not get process info.")
		    return false
		}

		return (info.kp_proc.p_flag & P_TRACED) != 0
}

func checkEnvironment() {
		if isJailbroken() {
				print("Warning: Running on a jailbroken device.")
				// 触发相应的安全策略
		}
		if isBeingDebugged() {
		    print("Warning: Running in a debugging environment.")
		    // 触发相应的安全策略
		}
}

checkEnvironment()

3.5 ptrace 反调试

ptrace 是一种用于跟踪和操纵其他进程的系统调用。攻击者可以使用 ptrace 附加到目标进程,以便在运行时读写内存、设置断点和单步执行等。为了防止这种调试行为,开发者可以在 App 中使用 ptrace,使其在检测到调试器时拒绝附加。这种方法通常称为 "anti-ptrace"。

一个简单的反 ptrace 示例:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <unistd.h>
#include <signal.h>

void anti_ptrace() {
    if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) {
        printf("Debugger detected! Exiting...\n");
        // Kill the process when a debugger is detected
        kill(getpid(), SIGKILL);
    }
}

int main() {
    anti_ptrace();

    printf("Hello, World!\n");
    return 0;
}

ptrace(PTRACE_TRACEME, 0, NULL, NULL) 是一个使用 ptrace 系统调用的特殊用法。在这种情况下,它的目的是让当前进程( App )被其父进程(调试器)跟踪。下面是各个参数的含义:

  1. PTRACE_TRACEME:这是 ptrace 系统调用的一个请求类型。PTRACE_TRACEME 用于告诉内核,让当前进程的父进程跟踪当前进程。换句话说,它允许调试器控制和监视 App 的执行。
  2. 0:这是 ptrace 调用的第二个参数,表示要跟踪的目标进程的 ID。在 PTRACE_TRACEME 的情况下,我们设置为 0,表示当前进程( App )是要跟踪的目标。
  3. NULL:这是 ptrace 调用的第三个参数,通常表示要读/写的地址或数据。在 PTRACE_TRACEME 的情况下,此参数不使用,因此设置为 NULL。
  4. NULL:这是 ptrace 调用的第四个参数,通常用于传递附加信息或数据。在 PTRACE_TRACEME 的情况下,此参数不使用,因此设置为 NULL。

在这个例子中,我们使用 PTRACE_TRACEME 请求调用 ptrace。正常情况下,只有父进程(即调试器)才能对子进程(即 App )发出 PTRACE_TRACEME 请求。但是,如果 App 本身发出 PTRACE_TRACEME 请求并成功,那么意味着没有调试器附加到 App 。如果 PTRACE_TRACEME 请求失败(返回 -1),则说明已有调试器附加到 App 。这时,我们可以采取相应措施,如输出警告信息、终止进程或执行其他安全策略。

需要注意的是,虽然反 ptrace 可以提高 App 的安全性,但它并不是绝对可靠的。经验丰富的攻击者可能会使用更复杂的技术来绕过反 ptrace 保护,例如通过修改 ptrace 函数的实现或使用其他调试接口。

绕过反 ptrace 保护:

修改 ptrace 函数的实现:攻击者可以使用动态链接器(如 LD_PRELOAD)将自定义版本的 ptrace 函数加载到 App 中。自定义版本的 ptrace 函数可以在检测到 PTRACE_TRACEME 请求时不执行任何操作,从而绕过反 ptrace 保护。以下是一个简单的示例:

#include <sys/ptrace.h>
#include <errno.h>

long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data) {
    if (request == PTRACE_TRACEME) {
        // 当检测到 PTRACE_TRACEME 请求时,不执行任何操作
        return 0;
    }

    // 对于其他 ptrace 请求,调用原始的 ptrace 函数
    return syscall(SYS_ptrace, request, pid, addr, data);
}

要使用此自定义 ptrace 函数,攻击者可以将其编译为共享库(如 libcustom_ptrace.so),然后在启动 App 时设置 LD_PRELOAD 环境变量,以便在 App 中使用自定义版本的 ptrace 函数。

$ gcc -shared -fPIC custom_ptrace.c -o libcustom_ptrace.so
$ LD_PRELOAD=./libcustom_ptrace.so ./target_app

因此,为了确保 App 的安全,最好采用多层防御策略,结合其他安全措施,如代码混淆、加密和完整性检查等。

3.6 sysctl 反调试

sysctl 是一个用于查询和修改内核参数的系统调用。它提供了许多用于获取进程相关信息的选项,其中一些选项可以用来检测调试状态。

在 macOS 和 iOS 上,可以使用 sysctl 系统调用检查进程标志(例如 P_TRACED),以确定目标进程是否正在被调试。

以下是一个简单的示例:

#include <stdio.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <unistd.h>

// 使用 sysctl 检测当前进程是否正在被调试
bool is_being_debugged() {
    int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
    struct kinfo_proc info;
    size_t info_size = sizeof(info);

    if (sysctl(mib, 4, &info, &info_size, NULL, 0) == -1) {
        perror("sysctl");
        return false;
    }
    return (info.kp_proc.p_flag & P_TRACED) != 0;
}

int main() {
    if (is_being_debugged()) {
        printf("This process is being debugged!\\n");
        // 在检测到调试行为时,采取措施,如终止 App 
        return 1;
    } else {
        printf("This process is not being debugged.\\n");
    }

    //  App 正常执行
    return 0;
}

在提供的代码示例中,bool is_being_debugged() 函数用于检测当前进程是否正在被调试。这个函数没有参数。

以下是对这个函数中涉及的每个变量和步骤的详细解释:

  1. int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};mib 是一个整数数组,包含 4 个元素,用于 sysctl 系统调用。这些元素分别表示: a. CTL_KERN:表示我们要查询的是内核相关的参数。 b. KERN_PROC:表示我们要查询的是进程相关的参数。 c. KERN_PROC_PID:表示我们要根据进程 ID 查询特定进程的信息。 d. getpid():这是一个系统调用,用于获取当前进程的 ID。这意味着我们将查询当前进程的信息。

  2. struct kinfo_proc info;:声明一个 kinfo_proc 结构体变量 info,用于存储 sysctl 返回的进程信息。

  3. size_t info_size = sizeof(info);:声明一个 size_t 类型的变量 info_size,用于存储 info 结构体的大小。sysctl 函数需要这个信息来填充 info 结构体。

  4. if (sysctl(mib, 4, &info, &info_size, NULL, 0) == -1) {...}:调用 sysctl 函数来获取进程信息。sysctl 函数的参数如下: a. mib:表示查询的参数数组。 b. 4:表示 mib 数组的长度。 c. &info:指向 kinfo_proc 结构体的指针,sysctl 将把查询结果存储到这个结构体中。 d. &info_size:指向 info_size 变量的指针,告诉 sysctl 函数 info 结构体的大小。sysctl 函数还会通过这个指针返回实际填充的数据大小。 e. NULL, 0:这两个参数表示我们不需要设置任何内核参数,只需要获取进程信息。 如果 sysctl 函数返回 -1,表示发生了错误。在这种情况下,函数通过 perror("sysctl") 输出错误信息,并返回 false

  5. return (info.kp_proc.p_flag & P_TRACED) != 0;:检查进程标志中的 P_TRACED 标志。info.kp_proc.p_flag 包含进程的标志,我们使用按位与运算符(&)检查 P_TRACED 标志是否被设置。如果该标志被设置(结果不等于 0),则表示该进程正在被调试,函数返回 true;否则,返回 false

这段代码通过 sysctl 查询当前进程的 kinfo_proc 结构体,然后检查 P_TRACED 标志。如果 P_TRACED 标志被设置,则表示该进程正在被调试。

请注意,这种检测方法可能会受到攻击者的绕过,因为经验丰富的攻击者可能会使用更高级的技术,如修改 sysctl 的实现或使用其他调试接口。

绕过反 sysctl 保护

修改 sysctl 的实现:攻击者可以使用动态库注入或其他代码注入技术,以便在运行时替换或修改 sysctl 函数的实现。这样,即使 App 使用 sysctl 来检测调试行为,攻击者仍然可以控制其输出,从而避免被检测。例如,攻击者可以使用以下代码片段来实现自定义的 sysctl 函数,将 P_TRACED 标志设置为 0:

int custom_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) {
    // 调用原始的 sysctl 函数
    int result = original_sysctl(name, namelen, oldp, oldlenp, newp, newlen);
    // 修改返回的进程标志,清除 P_TRACED 标志
    if (result == 0 && name[0] == CTL_KERN && name[1] == KERN_PROC && name[2] == KERN_PROC_PID) {
        struct kinfo_proc *info = (struct kinfo_proc *)oldp;
        info->kp_proc.p_flag &= ~P_TRACED;
    }
    return result;
}

然后,攻击者可以使用动态库注入或 Tweak 等将此自定义 sysctl 函数替换为 App 中的原始 sysctl 函数。

反反调试(Tweak)

这里针对ptrace、sysctl、syscall来反反调试,做法就很简单了,hook函数,判断参数,返回结果。

#import <substrate.h>
#import <sys/sysctl.h>

static int (*orig_ptrace) (int request, pid_t pid, caddr_t addr, int data);
static int my_ptrace (int request, pid_t pid, caddr_t addr, int data){
    if(request == 31){
		NSLog(@"[AntiAntiDebug] - ptrace request is PT_DENY_ATTACH");
		return 0;
	}
	return orig_ptrace(request,pid,addr,data);
}

static void* (*orig_dlsym)(void* handle, const char* symbol);
static void* my_dlsym(void* handle, const char* symbol){
	if(strcmp(symbol, "ptrace") == 0){
		NSLog(@"[AntiAntiDebug] - dlsym get ptrace symbol");
		return (void*)my_ptrace;
    }
   	return orig_dlsym(handle, symbol);
}

static int (*orig_sysctl)(int * name, u_int namelen, void * info, size_t * infosize, void * newinfo, size_t newinfosize);
static int my_sysctl(int * name, u_int namelen, void * info, size_t * infosize, void * newinfo, size_t newinfosize){
	int ret = orig_sysctl(name,namelen,info,infosize,newinfo,newinfosize);
	if(namelen == 4 && name[0] == 1 && name[1] == 14 && name[2] == 1){
		struct kinfo_proc *info_ptr = (struct kinfo_proc *)info;
        if(info_ptr && (info_ptr->kp_proc.p_flag & P_TRACED) != 0){
            NSLog(@"[AntiAntiDebug] - sysctl query trace status.");
            info_ptr->kp_proc.p_flag ^= P_TRACED;
            if((info_ptr->kp_proc.p_flag & P_TRACED) == 0){
                NSLog(@"[AntiAntiDebug] trace status reomve success!");
            }
        }
	}
	return ret;
}

static void* (*orig_syscall)(int code, va_list args);
static void* my_syscall(int code, va_list args){
	int request;
    va_list newArgs;
    va_copy(newArgs, args);
    if(code == 26){
        request = (long)args;
        if(request == 31){
            NSLog(@"[AntiAntiDebug] - syscall call ptrace, and request is PT_DENY_ATTACH");
            return nil;
        }
    }
    return (void*)orig_syscall(code, newArgs);
}

%ctor{
	MSHookFunction((void *)MSFindSymbol(NULL,"_ptrace"),(void*)my_ptrace,(void**)&orig_ptrace);
	MSHookFunction((void *)dlsym,(void*)my_dlsym,(void**)&orig_dlsym);
	MSHookFunction((void *)sysctl,(void*)my_sysctl,(void**)&orig_sysctl);
	MSHookFunction((void *)syscall,(void*)my_syscall,(void**)&orig_syscall);

	NSLog(@"[AntiAntiDebug] Module loaded!!!");
}

因此,最好采用多层防御策略,并与其他安全措施(如代码混淆、加密、完整性检查等)相结合,以增强 App 的安全性。

3.7 信号反调试

异常处理

通过设置异常处理函数,我们可以捕获调试器引发的异常(如断点、单步执行等),从而实现反调试功能。

#include <signal.h>void signal_handler(int signal) {
    // 处理异常信号,如退出程序
    exit(1);
}

void setup_signal_handler() {
    signal(SIGTRAP, signal_handler); // 断点信号
    signal(SIGSTOP, signal_handler); // 停止信号
}

在 App 入口点调用 setup_signal_handler() 函数,即可设置异常处理函数。

四、不能防 dump 情况下的防护方案

对避免 dump 可能无直接效果,但可以提高 App 的安全性。

  1. 服务器端验证: 将关键功能和敏感数据存储在服务器端,通过 API 请求的方式进行访问,减少客户端的攻击面。

  2. 动态加载: 在运行时动态加载关键代码和资源,使攻击者无法直接从静态文件中获取敏感信息。

  3. API 安全性: 采用 HTTPS 通信,对 API 请求进行签名验证,防止中间人攻击和 API 篡改。

  4. 多因素认证: 在关键操作中引入多因素认证机制,降低单一认证方式被破解的风险。例如短信验证码、人脸识别等。

五、不能防 dump 且无法完全避免的情况下的安全策略

对避免 dump 可能无直接效果,且对安全性不起作用,属于后置的方案策略。

1. 监控与告警: 实时监控 App 的运行状态,如发现异常行为或安全事件,及时触发告警,通知开发者进行处理。定期监控 SDK 的安全状况,收集安全事件和威胁情报。结合实时数据,对防护策略进行调整和优化。

2. 定期安全审计: 定期进行安全审计,检查 App 的安全状况,发现潜在的安全问题,并采取相应的措施进行修复。

3. 安全培训: 提高开发团队的安全意识和技能,确保在开发过程中能够充分考虑到安全因素,从而降低 App 被攻击的风险。定期进行安全培训,以便团队成员了解最新的安全威胁和防护措施。

六、其他新技术方案的未来展望

随着科技的不断发展和进步,一直不断出现创新的安全技术方案,需要时刻关注行业动态,以便在安全挑战时更好地保护 App 的安全。

1. 人工智能与机器学习: 利用人工智能和机器学习技术自动检测代码中的安全问题,提高安全检测的效率和准确性。

2. 差分隐私计算: 隐私计算技术,如同态加密、安全多方计算等,可以在不解密数据的情况下进行数据处理和计算,从而在保护数据隐私的同时,实现数据的有效利用。

3. 自适应安全: 借助人工智能和机器学习技术,实现自适应安全系统,能够根据实时的安全状况和威胁情报,动态调整 App 的安全策略,更有效地防御攻击。

七、可行性方案

针对 App 或 SDK 安全,分析那些技术我们可能帮助我们提升安全性和预防篇安全风险。

7.1 越狱环境

以上所有 dump 方案,都是因为越狱导致可以破解,所以不能越狱就防止99%黑产。

  • 越狱 ==> 拿到系统权限
  • 重签 ==> 拿到 app 的权限

建议:禁止越狱设备使用 App !!!

如果检测到越狱设备,您可以选择禁用某些功能或者限制 App 的使用。如终止app、删除敏感数据、隐藏某些功能等等。

实施方案

在 App 中添加越狱检测代码,以判断设备是否已经越狱。

  1. 检查越狱相关文件和目录:越狱设备通常包含一些特定的文件和目录,例如Cydia、MobileSubstrate等。检查这些文件和目录的存在可以帮助判断设备是否越狱。
  2. 检查应用沙箱保护:越狱设备的应用沙箱保护可能会被破坏。尝试访问受保护的系统目录,例如“/private”,如果能够访问,则可能是越狱设备。
  3. 检查非法安装的应用:越狱设备允许用户安装非法的应用。检查已安装的应用列表,寻找来自非官方来源的应用,可能有助于发现越狱设备。
  4. 检查进程权限:越狱设备上的进程可能具有更高的权限。比较 App 的实际权限和预期权限,如果权限异常,则可能是越狱设备。
  5. 使用私有API:iOS的私有API在越狱设备上可用。尝试调用这些API,如果成功执行,则可能是越狱设备。
  6. 检查动态链接库:越狱设备可能使用动态链接库(如MobileSubstrate)来注入代码。检查运行时环境中加载的动态链接库,寻找异常的库文件,可能有助于发现越狱设备。

另外,针对新的环境变化,如 Mac Catalyst 或 Designed for iPad 等环境,都可能运行 iOS App,需要开发者自行判断是否允许。

检查设备越狱的代码很多,以下是 GitHub 开源仓库 DTTJailbreakDetection 代码示例:

+ (BOOL)isJailbroken
{
#if !(TARGET_IPHONE_SIMULATOR)

    if (@available(iOS 14.0, *)) {
        if ([NSProcessInfo processInfo].isiOSAppOnMac)
        {
            return NO;
        }
    }
    FILE *file = fopen("/Applications/Cydia.app", "r");
    if (file) {
        fclose(file);
        return YES;
    }
    file = fopen("/Library/MobileSubstrate/MobileSubstrate.dylib", "r");
    if (file) {
        fclose(file);
        return YES;
    }
    file = fopen("/bin/bash", "r");
    if (file) {
        fclose(file);
        return YES;
    }
    file = fopen("/usr/sbin/sshd", "r");
    if (file) {
        fclose(file);
        return YES;
    }
    file = fopen("/etc/apt", "r");
    if (file) {
        fclose(file);
        return YES;
    }
    file = fopen("/usr/bin/ssh", "r");
    if (file) {
        fclose(file);
        return YES;
    }
    
    NSFileManager *fileManager = [NSFileManager defaultManager];

    if ([fileManager fileExistsAtPath:@"/Applications/Cydia.app"]) {
        return YES;
    } else if ([fileManager fileExistsAtPath:@"/Library/MobileSubstrate/MobileSubstrate.dylib"]) {
        return YES;
    } else if ([fileManager fileExistsAtPath:@"/bin/bash"]) {
        return YES;
    } else if ([fileManager fileExistsAtPath:@"/usr/sbin/sshd"]) {
        return YES;
    } else if ([fileManager fileExistsAtPath:@"/etc/apt"]) {
        return YES;
    } else if ([fileManager fileExistsAtPath:@"/usr/bin/ssh"]) {
        return YES;
    }
    
    // Check if the app can access outside of its sandbox
    NSError *error = nil;
    NSString *string = @".";
    [string writeToFile:@"/private/jailbreak.txt" atomically:YES encoding:NSUTF8StringEncoding error:&error];
    if (!error) {
        [fileManager removeItemAtPath:@"/private/jailbreak.txt" error:nil];
        return YES;
    }
    
    // Check if the app can open a Cydia's URL scheme
    if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://package/com.example.package"]]) {
        return YES;
    }

#endif

    return NO;
}

7.2 DeviceCheck 和 App Attest API

DeviceCheckApp Attest 是 Apple 提供的两个用于设备验证和 App 验证的 API。它们旨在提高 App 的安全性和保护用户隐私。

DeviceCheck API

DeviceCheck API 允许开发者在苹果服务器上存储每台设备的少量数据(两个位)。这可以用于识别设备是否曾经与您的服务器进行过交互,而无需获取设备的唯一标识符。

DeviceCheck 数据在 App 被删除和重新安装后仍然保持不变。这对于识别设备的唯一性非常有用。

为了使用 DeviceCheck API,您需要在开发者帐户中启用 DeviceCheck 功能,并在您的 App 和服务器端代码中集成相应的 API。

DeviceCheck 服务返回的两个位(bit0 和 bit1)是针对一台具体的 iOS 设备(iPhone 或 iPad)的,而不是与 Apple ID 账号关联的。这意味着,对于同一台设备上的不同 App ,这两个位都是相同的,而不是针对特定的 Apple ID 账号。这样,开发者可以使用这两个位来存储与特定设备相关的信息,如标记设备是否已经兑换过优惠券或是否参加过活动。

App Attest API

App Attest API 用于验证 iOS App 的完整性和真实性。它可以检测 App 是否被篡改或替换,并确认它是由可信赖的开发者发布的。通过使用 App Attest API,您可以确保您的服务器与真实、未经篡改的 App 进行通信。

App Attest API 为 App 提供了一个签名密钥,用于在 App 与开发者服务器之间的通信中生成签名。开发者服务器可以使用 Apple 提供的验证服务来验证签名,确保收到的请求来自于真实且未被篡改的 App 。

要使用 App Attest API,您需要在开发者帐户中启用 App Attest 功能,并在您的 App 和服务器端代码中集成相应的 API。

八、总结

本报告探讨了防止 iOS dump 的可行性,讨论了几种提高 iOS App 安全性的策略,并强调了监测、审计和安全培训的重要性。报告还研究了一些新兴技术,如人工智能和差分隐私计算,这些技术有可能提高 iOS App 的安全性。最后,报告讨论了这些措施的局限性和挑战,并强调了需要进行持续的研究和开发,以进一步增强 iOS App 的安全性。

在应对 iOS SDK dump 的过程中,开发者需要关注多个方面的技术和策略,从防护到曲线救国,再到提高安全性,都需要采取综合性的措施。此外,随着科技的发展,未来将会出现更多创新的安全技术方案,为开发者提供更强大的安全保障。

要保护好应用程序的安全,开发者需要不断提高自身的安全意识和技能,关注新技术的发展,并将安全融入应用程序的开发、运行和维护全过程。同时,与安全领域的其他从业者保持紧密合作,共同应对不断变化的安全挑战,为用户提供安全、可靠的应用程序。

最后,需要强调的是,本报告的内容仅供参考,实际应用时可能需要根据具体需求进行适当的调整和优化。同时,攻击者可能会采取更复杂的手段绕过这些防护措施,因此开发者需要保持对安全领域的关注,持续改进和完善安全策略。

总之,本报告全面概述了iOS dump 预防,并提供了一系列提高 iOS App 安全性的策略。虽然这些措施的有效性存在一定局限性,但是显然,在安全技术领域进行持续的研究和开发将继续是未来 iOS App 安全的关键。

欢迎大家评论区一起讨论交流~

我们是37手游移动客户端开发团队,致力于为游戏行业提供高质量的SDK开发服务。 欢迎关注我们,了解更多移动开发和游戏 SDK 技术动态~ 技术问题/交流/进群等可以加官方微信 MobileTeam37

参考引用

注:如若转载,请注明来源。

Guess you like

Origin juejin.im/post/7251501966592917563