Введение

Реверс-инженеринг приложений — серьезная угроза для разработчиков iOS. Он может привести к утечке интеллектуальной собственности, подделке приложений, созданию вредоносного программного обеспечения и другим проблемам. В этой статье мы обсудим методы и практики, которые помогут защитить ваше приложение от реверс-инженеринга.

1. Использование финализированных классов (final)

Классы, объявленные как final в Swift, не могут быть наследованы и переопределены, что затрудняет модификацию класса злоумышленниками. Они также способствуют улучшению производительности, поскольку компилятор может оптимизировать вызовы методов.

Пример кода:

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

2. Обнаружение Jailbreak

Приложениям необходимо определять, установлены ли на устройстве инструменты для Jailbreak. Это можно сделать, проверив наличие общих путей, специфичных для Jailbreak, или нестандартных библиотек. Если обнаружен Jailbreak, приложение может отказаться от работы или предпринять другие меры безопасности.

Пример проверки:

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. Сокрытие кода и обфускация

Используйте инструменты для обфускации кода, которые делают исходный код менее понятным и трудным для анализа. Методы включают изменение имен переменных и функций, удаление метаданных, использование непрямых методов вызова и так далее.

Пример обфускации строк в коде:

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

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

4. Control Flow Obfuscation

Пример изменения логики выполнения программы:

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

Так же стоить заметить что использование #if DEBUG "не пустит" код обернутый в это значение в продакшен

5. Anti-Debugging Techniques

Пример использования ptrace для предотвращения отладки:

import Darwin

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

Darwin — это ядро операционной системы, на котором основаны macOS и iOS. Он предоставляет низкоуровневые функции, включая работу с файлами, сетью и операциями на уровне системы. Подключение Darwin в Swift дает доступ к этим системным вызовам и функциям, аналогично использованию стандартных библиотек C на платформах Apple.

ptrace — это системный вызов, используемый для отладки и изменения работы других программ. Он позволяет одному процессу "прикрепиться" к другому, контролировать его выполнение, изменять его память и регистры и т.д. Это мощный инструмент, который часто используется в отладчиках, таких как GDB или LLDB.

PT_DENY_ATTACH — это специфический запрос, который можно отправить через ptrace, чтобы предотвратить прикрепление отладчиков к данному процессу. При вызове ptrace(PT_DENY_ATTACH, 0, 0, 0) приложение сообщает системе, что к нему не должны прикрепляться отладчики. Это делает более сложным для злоумышленников использование отладчиков для анализа или изменения работы приложения во время его выполнения.

6. Шифрование данных

Пример шифрования данных с помощью CryptoKit:

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

7. Runtime Integrity Checks

Пример проверки целостности кода:

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

8. Сертификация кода и проверка подлинности

Убедитесь, что ваше приложение использует механизмы, такие как SSL pinning и ATS (App Transport Security), для обеспечения безопасного соединения и предотвращения MITM-атак (атаки по типу "человек посередине").

9. Регулярное обновление и патчинг

Будьте в курсе последних уязвимостей и обновлений безопасности. Регулярно обновляйте свое приложение, чтобы исправлять известные уязвимости и улучшать механизмы защиты.

10. Более глубокое понимание

Для более глубокого понимания защиты iOS-приложений от реверс-инженеринга, важно рассмотреть дополнительные методы и техники. Вот несколько подробных стратегий и практик:

10.1 Усиление Кода (Code Hardening)

Это техники, которые делают исполняемый файл приложения более устойчивым к модификациям и анализу. Примеры включают:

  • Checksums: Используйте контрольные суммы для обнаружения модификаций в вашем коде.

  • Runtime Integrity Checks: Реализуйте проверки целостности во время выполнения, чтобы определить, был ли изменен код или поведение приложения.

10.2 Стратегии Шифрования

Шифруйте данные и ресурсы в приложении, чтобы затруднить извлечение и анализ информации.

  • File-level Encryption: Шифруйте важные файлы в приложении.

  • String Encryption: Шифруйте строки и ключи API в вашем коде, чтобы они не были легко доступны (пример есть выше). Либо используйте для хранения важных данных Keychain.

10.3 Advanced Obfuscation Techniques

Помимо базовой обфускации, используйте сложные методы для усиления безопасности кода.

  • Control Flow Obfuscation: Изменяет порядок инструкций, усложняя понимание логики программы (пример есть выше).

  • Symbol Stripping: Удалите символы отладки и имена, чтобы затруднить понимание структуры и функций приложения.

10.4 Anti-Tampering Mechanisms

Реализуйте механизмы, которые обнаруживают и реагируют на попытки изменения кода приложения.

  • Self-Healing Code: Разработайте код, способный обнаруживать изменения и восстанавливать первоначальное состояние.

  • Anti-Debugging: Используйте техники, которые затрудняют отладку приложения потенциальными злоумышленниками (пример есть выше).

10.5 Среда исполнения и окружение

Оценка и реакция на окружение, в котором работает приложение, также важны.

  • Runtime Environment Checks: Проверьте, не была ли среда исполнения модифицирована, например, обнаружение эмуляторов или джейлбрейков (пример есть выше).

  • Geofencing and Time checks: Определите, не используется ли приложение в подозрительном или необычном географическом регионе или времени.

10.6 Использование Сторонних Инструментов и Сервисов

Существуют специализированные инструменты и услуги, которые могут помочь в защите приложений.

  • Интегрируйте решения для управления приложениями и устройствами.

  • Используйте надежные библиотеки и фреймворки, специализирующиеся на безопасности приложений (их достаточно, перечислять не буду, есть Гугл, но как минус Вы будите зависеть от них, и они не бесплатны, если речь идет о серьезном решении)

Заключение

Использование финализированных классов, обнаружение Jailbreak, обфускация кода, сертификация кода и регулярное обновление — это только некоторые из мер, которые помогут уберечь ваше приложение от злоумышленников. Важно постоянно следить за новыми методами атак и защиты, чтобы ваше приложение оставалось безопасным и надежным.

Эти примеры демонстрируют разнообразие методов и подходов, которые могут быть использованы для защиты приложений от реверс-инженеринга. Важно сочетать и адаптировать их к вашему конкретному приложению и требованиям безопасности, чтобы обеспечить максимальную защиту. И помните, что безопасность — это не однократное действие, а непрерывный процесс обновления и улучшения мер защиты.

Комментарии (7)


  1. badryuner
    24.06.2024 12:42

    Разве 7 пункт и 10.4 пункт не одно и то же?


    1. quasaryy Автор
      24.06.2024 12:42

      Почти.
      7 - проверки целостности кода
      10.4 - код, способный обнаруживать изменения и восстанавливать первоначальное состояние

      Но в целом, да, те же яица только в профиль.


  1. Bardakan
    24.06.2024 12:42

    1. Использование финализированных классов (final)

    Понятно, что использование модификатора final дает некоторые преимущества, но нужно ли его проставлять явно? Где-то попадалась статья, что компилятор автоматически добавляет этот модификатор к private классам и даже по умолчанию к internal (можно отключить в настройках проекта)


    1. quasaryy Автор
      24.06.2024 12:42

      Отличный вопрос Bardakan!

      Private классы ограничены областью видимости текущего файла и технически не могут быть переопределены из-за этого, Swift не добавляет автоматически final к таким классам. Использование final в таких случаях может быть избыточным.

      Касательно internal классов:
      Нет встроенной опции в Swift, позволяющей автоматически считать все internal классы как final. Если класс не объявлен как final, он может быть переопределен в пределах того же модуля.


      1. Bardakan
        24.06.2024 12:42

        Нашел ту статью:
        https://samwize.com/2023/12/15/should-you-add-final-to-all-your-swift-classes/

        The compiler can also infer for the default internal access level, but only if you enable Whole Module Optimization (WMO) under Building Settings > Compilation Mode > Whole Module.

        И еще одна интересная статья, из которой следует, что final может автоматически применяться даже для `public`:
        https://developer.apple.com/swift/blog/?id=27


        1. quasaryy Автор
          24.06.2024 12:42

          Спасибо за Ваш комментарий и ссылки. Первую статью я не могу никак комментировать, чтобы там ни было написано, так как, не могу ее считать достоверным источником информации, так же как Вы мою.

          Касательно ресурса с официального блога Apple.
          В статье Apple обсуждается, как компилятор Swift может оптимизировать вызовы методов и доступы к свойствам, предполагая, что они не будут переопределены, особенно в контексте Whole Module Optimization. Это несколько отличается от автоматического добавления модификатора final, о чем я писал в своем ответе Вам.

          Мой основной поинт заключался в том, что Swift сам по себе не добавляет модификатор final к декларациям в коде. Вместо этого, компилятор может оптимизировать внутренние вызовы так, как если бы они были помечены как final, особенно при использовании опций компиляции, которые позволяют ему видеть более широкий контекст, например, компиляция всего модуля сразу.

          Мы говорим о немного разных аспектах работы компилятора и модификаторов в Swift. Важно различать автоматическое добавление модификатора final к классам или методам и оптимизации, которые компилятор может применять на основе анализа видимости и использования классов и методов. Когда компилятор выполняет такие оптимизации, он не изменяет исходный код, а просто оптимизирует сгенерированный машинный код. Надеюсь, это проясняет ситуацию.


          1. Bardakan
            24.06.2024 12:42

            Прояснило. Спасибо за информацию