Protecting iOS applications from reverse engineering

Introduction

Reverse engineering of applications is a serious threat to iOS developers. It can lead to intellectual property leakage, application counterfeiting, malware creation, and other problems. In this article, we will discuss methods and practices that will help protect your application from reverse engineering.

1. Using finalized classes (final)

Classes declared as final in Swift cannot be inherited or overridden, making it difficult for attackers to modify the class. They also improve performance because the compiler can optimize method calls.

Example code:

final class SecureClass {
    func secureMethod() {
        // Безопасный код
    }
}

2. Jailbreak detection

Applications need to detect whether jailbreak tools are installed on the device. This can be done by checking for Jailbreak-specific common paths or non-standard libraries. If a Jailbreak is detected, the application may refuse to function or take other security measures.

Check example:

func isJailbroken() -> Bool {
    if FileManager.default.fileExists(atPath: "/Applications/Cydia.app") ||
       FileManager.default.fileExists(atPath: "/Library/MobileSubstrate/MobileSubstrate.dylib") {
        return true
    } else {
        return false
    }
}

3. Code hiding and obfuscation

Use code obfuscation tools that make the source code less clear and difficult to analyze. Methods include changing variable and function names, removing metadata, using indirect method calls, and so on.

An example of obfuscation of strings in code:

// Исходная строка
let apiKey = "mySecretAPIKey123"

// Обфусцированная строка
let obfuscatedApiKey = String(apiKey.reversed())

4. Control Flow Obfuscation

An example of changing the program execution logic:

func complexLogic() {
    #if DEBUG
    print("Debug mode")
    #else
    let a = 3
    let b = a * 2
    print("Релиз с измененным потоком управления \(b)")
    #endif
}

It’s also worth noting that using #if DEBUG will not allow code wrapped in this value into production

5. Anti-Debugging Techniques

An example of using ptrace to prevent debugging:

import Darwin

func disableDebugging() {
    let PT_DENY_ATTACH = 31
    ptrace(PT_DENY_ATTACH, 0, 0, 0)
}

Darwin is the operating system kernel on which macOS and iOS are based. It provides low-level functions including file, network, and system-level operations. Including Darwin in Swift gives access to these system calls and functions, similar to using the standard C libraries on Apple platforms.

ptrace is a system call used to debug and change the behavior of other programs. It allows one process to “attach” to another, control its execution, modify its memory and registers, etc. This is a powerful tool that is often used in debuggers such as GDB or LLDB.

PT_DENY_ATTACH is a specific request that can be sent via ptrace to prevent debuggers from attaching to a given process. By calling ptrace(PT_DENY_ATTACH, 0, 0, 0), the application tells the system that debuggers should not be attached to it. This makes it more difficult for attackers to use debuggers to analyze or change the behavior of an application while it is running.

6. Data encryption

Example of data encryption using CryptoKit:

import CryptoKit
func encryptString(string: String, key: String) -> String? {
// шифрования строки
}

7. Runtime Integrity Checks

Example of checking code integrity:

func verifyIntegrity() -> Bool {
    // Проверка что бинарный файл не изменен
    return true // Возврат результат проверки
}

8. Code Certification and Authentication

Make sure your application uses mechanisms such as SSL pinning and ATS (App Transport Security) to ensure a secure connection and prevent MITM (man-in-the-middle) attacks.

9. Regular updates and patching

Stay up to date with the latest vulnerabilities and security updates. Update your app regularly to fix known vulnerabilities and improve security mechanisms.

10. Deeper understanding

To gain a deeper understanding of protecting iOS applications from reverse engineering, it is important to consider additional methods and techniques. Here are some detailed strategies and practices:

10.1 Code Hardening

These are techniques that make the application's executable file more resistant to modification and analysis. Examples include:

  • Checksums: Use checksums to detect modifications in your code.

  • Runtime Integrity Checks: Implement run-time integrity checks to determine whether application code or behavior has changed.

10.2 Encryption Strategies

Encrypt data and resources in your application to make information difficult to extract and analyze.

  • File-level Encryption: Encrypt important files within the app.

  • String Encryption: Encrypt strings and API keys in your code so they are not easily accessible (there is an example above). Or use Keychain to store important data.

10.3 Advanced Obfuscation Techniques

Beyond basic obfuscation, use sophisticated techniques to enhance code security.

  • Control Flow Obfuscation: Changes the order of instructions, making the program logic more difficult to understand (there is an example above).

  • Symbol Stripping: Remove debug symbols and names to make it difficult to understand the application's structure and functionality.

10.4 Anti-Tampering Mechanisms

Implement mechanisms that detect and respond to attempts to change application code.

  • Self-Healing Code: Develop code that can detect changes and restore the original state.

  • Anti-Debugging: Use techniques that make it difficult for potential attackers to debug the application (there is an example above).

10.5 Runtime and environment

Assessing and reacting to the environment in which the application operates is also important.

  • Runtime Environment Checks: Check if the runtime has been modified, such as detecting emulators or jailbreaks (there is an example above).

  • Geofencing and Time checks: Determine if the application is being used in a suspicious or unusual geographic region or time.

10.6 Use of Third Party Tools and Services

There are specialized tools and services that can help protect applications.

  • Integrate application and device management solutions.

  • Use reliable libraries and frameworks that specialize in application security (there are enough of them, I won’t list them, there is Google, but as a minus you will depend on them, and they are not free if we are talking about a serious decision)

Conclusion

Using finalized classes, jailbreak detection, code obfuscation, code certification, and regular updates are just some of the measures that will help keep your application safe from attackers. It's important to stay on top of new attack and defense techniques to ensure your application remains secure and reliable.

These examples demonstrate the variety of methods and approaches that can be used to protect applications from reverse engineering. It is important to combine and tailor them to your specific application and security requirements to ensure maximum protection. And remember that security is not a one-time action, but a continuous process of updating and improving protection measures.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *