Skip to content

Security: zencrypted/x509

Security

SECURITY.md

Звіт з аналізу безпеки: Chat X.422.1 (Pure Swift)

Цей документ визначає вимоги безпеки для нативної імплементації клієнта X.422.1 UDP Multicast (Pure Swift, Zero Dependencies) та описує механізми самозахисту застосунку (RASP) без залучення сторонніх бібліотек.


1. Концепція Zero-Dependency RASP (SA-11)

Оскільки проект дотримується суворої політики Zero Dependencies, використання сторонніх комерційних або відкритих RASP SDK (як-от Talsec, DexGuard тощо) заборонено. Всі механізми виявлення скомпрометованих середовищ та захисту від реверс-інжинірингу мають бути реалізовані безпосередньо в коді Swift за допомогою системних викликів Darwin API та стандартного SDK iOS/macOS.

Нижче наведено детальні вимоги та готові кодові блоки для інтеграції в додаток.


2. Реалізація механізмів захисту (Code Snippets)

2.1. Захист від динамічного аналізу та дебагу (Anti-Debugging)

Для запобігання підключенню налагоджувачів (наприклад, LLDB) у релізних збірках використовується нативний виклик ptrace з прапорцем PT_DENY_ATTACH (31). Це призведе до негайного завершення процесу, якщо до нього спробують приєднатися.

Додатково виконується перевірка прапорця P_TRACED у структурі процесу за допомогою sysctl.

import Foundation
import Darwin

struct AntiDebug {
    /// Забороняє підключення дебагера до поточного процесу (PT_DENY_ATTACH)
    static func disableDebuggerAttachment() {
        #if !DEBUG
        typealias PtraceType = @convention(c) (CInt, pid_t, caddr_t, CInt) -> CInt
        
        let handle = dlopen(nil, RTLD_NOW)
        if let ptraceSymbol = dlsym(handle, "ptrace") {
            let ptrace = unsafeBitCast(ptraceSymbol, to: PtraceType.self)
            // PT_DENY_ATTACH = 31
            let result = ptrace(31, 0, nil, 0)
            if result != 0 {
                print("🚨 Debugger deny attach failed, exiting.")
                exit(1)
            }
        }
        #endif
    }

    /// Перевіряє, чи дебагер вже підключено до додатка
    static func isDebuggerAttached() -> Bool {
        var info = kinfo_proc()
        var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
        var size = MemoryLayout<kinfo_proc>.stride
        
        let junk = sysctl(&mib, u_int(mib.count), &info, &size, nil, 0)
        if junk != 0 {
            return false
        }
        
        // Перевіряємо прапорець P_TRACED
        return (info.kp_proc.p_flag & P_TRACED) != 0
    }
}

2.2. Виявлення Jailbreak середовища (Anti-Jailbreak)

На джейлбрейкнутому пристрої відсутня ізоляція пісочниці. Перевірка виконується у три етапи:

  1. Пошук відомих Jailbreak-файлів та шляхів (Cydia, Zebra, Sileo, SSH тощо).
  2. Спроба запису та читання файлу поза межами пісочниці додатка (/private/jailbreak.txt).
  3. Перевірка можливості відкрити кастомні URL-схеми джейлбрейк-магазинів.
import Foundation
import UIKit

struct AntiJailbreak {
    static func isDeviceCompromised() -> Bool {
        #if targetEnvironment(simulator)
        return false // Емулятори ігноруємо
        #endif

        // 1. Пошук файлів
        let suspiciousPaths = [
            "/Applications/Cydia.app",
            "/Library/MobileSubstrate/MobileSubstrate.dylib",
            "/bin/bash",
            "/usr/sbin/sshd",
            "/etc/apt",
            "/usr/bin/ssh",
            "/private/var/lib/apt/"
        ]
        
        for path in suspiciousPaths {
            if FileManager.default.fileExists(atPath: path) {
                return true
            }
        }

        // 2. Спроба запису поза пісочницею
        let testString = "JailbreakTest"
        do {
            try testString.write(toFile: "/private/jailbreak.txt", atomically: true, encoding: .utf8)
            // Якщо запис вдався — пристрій зламано
            try FileManager.default.removeItem(atPath: "/private/jailbreak.txt")
            return true
        } catch {
            // Запис заблоковано пісочницею (нормальний стан)
        }

        // 3. Перевірка символічних посилань (Symlinks)
        var s = stat()
        if lstat("/Library/Ringtones", &s) == 0 {
            if (s.st_mode & S_IFLNK) == S_IFLNK {
                // На джейлбрейку папка рингтонів часто переміщується через symlink
                return true
            }
        }

        return false
    }
}

2.3. Виявлення емуляторів та симуляторів (Anti-Simulator)

Запуск на симуляторі дозволяє легко перехоплювати трафік та логувати внутрішні об'єкти пам'яті. У продуктивному середовищі додаток має блокувати запуск на x86_64/arm64 симуляторах.

struct AntiSimulator {
    static func isSimulator() -> Bool {
        #if targetEnvironment(simulator)
        return true
        #else
        return false
        #endif
    }
}

3. Рекомендації щодо архітектурної інтеграції (Audit Guide)

Під час проведення кодового аудиту Swift-додатка, аудитор має перевірити:

  1. Точка запуску перевірок: Всі вищезгадані функції контролю (disableDebuggerAttachment(), isDeviceCompromised(), isSimulator()) мають викликатися безпосередньо в точці входу додатка — перед завантаженням інтерфейсу в chat509App.swift (App.init() або application(_:didFinishLaunchingWithOptions:) в AppDelegate).

  2. Захист від статичного аналізу (Inline compiler directives): Для ускладнення декомпіляції та видалення викликів RASP через Frida/Ghidra, перевірки не повинні мати очевидних назв функцій типу checkJailbreak(). Рекомендовано розмивати логіку перевірок по всій бізнес-логіці та виконувати їх обфусковано, наприклад, через inline-директиви компілятора (@inline(__always)).

  3. Дія при виявленні загрози: При виявленні будь-якої загрози застосунок повинен виконати екстрене завершення процесу через exit(1) або fatalError(), попередньо очистивши ефемерні ключі в RAM (CertificateManager.shared.clearIdentity()).

There aren't any published security advisories