Содержание статьи

Ес­ли ты ког­да‑нибудь пуб­ликовал при­ложе­ние в App Store и спус­тя пару недель находил его взло­ман­ную вер­сию на каком‑то сом­нитель­ном ресур­се, эта статья для тебя. Здесь я раз­беру реаль­ные тех­ники защиты iOS-при­ложе­ний от реверс‑инжи­нирин­га, взло­ма и модифи­каций.

Мы раз­берем, как работа­ют ата­ки на мобиль­ные при­ложе­ния, какие инс­тру­мен­ты исполь­зуют взлом­щики и, глав­ное, как про­тивос­тоять этим угро­зам на прак­тике. Мы прой­дем путь от базовых методов обфуска­ции кода до прод­винутых тех­ник runtime-защиты, анти­отладки и детек­та джей­лбрей­ка.

Я поделюсь при­мера­ми кода, которые мож­но сра­зу внед­рить в про­ект, и рас­ска­жу о под­водных кам­нях, с которы­ми стал­кивал­ся лич­но. Эти зна­ния при­годят­ся при раз­работ­ке фин­тех‑при­ложе­ний, игр с внут­рииг­ровыми покуп­ками, кор­поратив­ных решений — сло­вом, вез­де, где важ­на безопас­ность и целос­тность кода.

 

Зачем это вообще нужно?

Ког­да я толь­ко начинал работать с iOS, мне казалось, что плат­форма Apple дос­таточ­но зак­рыта сама по себе. App Store Review, код‑под­пись, песоч­ница — раз­ве это­го недос­таточ­но? Прак­тика показа­ла обратное.

При­ложе­ния взла­мыва­ют по раз­ным при­чинам. Кто‑то хочет обой­ти плат­ные фун­кции или внут­рииг­ровые покуп­ки. Кто‑то ищет API-клю­чи и токены, что­бы зло­упот­реблять ими поз­же. Кто‑то прос­то изу­чает твое при­ложе­ние, что­бы соз­дать кон­курен­тный про­дукт. А иног­да зло­умыш­ленни­ки модифи­циру­ют легитим­ное при­ложе­ние, добав­ляя в него вре­донос­ный код, и рас­простра­няют через сто­рон­ние магази­ны.

Джей­лбрейк зна­читель­но упро­щает взлом. На устрой­ствах с джей­лом мож­но запус­кать про­изволь­ный код, перех­ватывать сис­темные вызовы, инжектить биб­лиоте­ки в про­цес­сы. Но даже без джей­лбрей­ка воз­можнос­тей дос­таточ­но — дам­пинг при­ложе­ния из памяти, ста­тичес­кий ана­лиз бинар­ника, манипу­ляции с фай­ловой сис­темой через резер­вную копию.

 

Что такое реверс-инжиниринг и как он работает

Ре­верс‑инжи­ниринг iOS-при­ложе­ния — это про­цесс извле­чения информа­ции о его внут­реннем устрой­стве без дос­тупа к исходно­му коду. Цель может быть раз­ной: понять алго­рит­мы работы, най­ти уяз­вимос­ти, извлечь сек­ретные дан­ные или модифи­циро­вать поведе­ние.

Ти­пич­ный порядок дей­ствий ата­кующе­го выг­лядит так:

  1. По­луче­ние IPA-фай­ла. Легитим­ный спо­соб — ска­чать свое куп­ленное при­ложе­ние с устрой­ства через iTunes или через iMazing. Нелеги­тим­ный — исполь­зовать спе­циали­зиро­ван­ные сер­висы, которые пре­дос­тавля­ют дек­рипто­ван­ные IPA.
  2. Дек­рипто­вание бинар­ника. При­ложе­ния из App Store зашиф­рованы FairPlay DRM. На джей­лбрейк‑устрой­стве инс­тру­мен­ты вро­де Clutch или frida-ios-dump поз­воля­ют дам­пить при­ложе­ние из памяти в дек­рипто­ван­ном виде.
  3. Ста­тичес­кий ана­лиз. С помощью Hopper, IDA Pro или Ghidra бинар­ник дизас­сем­бли­рует­ся. Код на Swift час­тично деоб­фусци­рует­ся бла­года­ря мета­информа­ции, которую ком­пилятор оставля­ет в бинар­нике. Objective-C вооб­ще чита­ется поч­ти как исходни­ки — все име­на методов, клас­сов и про­токо­лов сох­раня­ются.
  4. Ди­нами­чес­кий ана­лиз. Запуск при­ложе­ния под отладчи­ком (LLDB), исполь­зование Frida для runtime-хуков, перех­ват сетевых зап­росов через Charles или Burp Suite.
  5. Мо­дифи­кация. Пат­чинг бинар­ника (изме­нение инс­трук­ций про­цес­сора), инжект собс­твен­ных биб­лиотек, под­мена ресур­сов, перес­борка IPA и уста­нов­ка через сай­дло­адинг.

Я как‑то про­водил экспе­римент с собс­твен­ным при­ложе­нием. Уста­новил Hopper, заг­рузил туда свой бинар­ник — и был край­не удив­лен. Все наз­вания методов, струк­туры клас­сов, даже стро­ковые кон­стан­ты с API-эндпо­инта­ми были вид­ны как на ладони. Вот тог­да и приш­ло понима­ние, что защита нуж­на.

 

Базовые принципы защиты

Преж­де чем перей­ти к кон­крет­ным тех­никам, важ­но понять филосо­фию. Абсо­лют­ной защиты не сущес­тву­ет. Любой механизм мож­но обой­ти, если у ата­кующе­го дос­таточ­но вре­мени, зна­ний и мотива­ции. Наша задача — мак­сималь­но усложнить про­цесс, сде­лать его эко­номи­чес­ки невыгод­ным.

Я при­дер­жива­юсь прин­ципа эше­лони­рован­ной обо­роны (defense in depth). Одна тех­ника лег­ко обхо­дит­ся. Десять тех­ник, при­менен­ных одновре­мен­но, зас­тавят зло­умыш­ленни­ка пот­ратить дни или недели работы. Час­то это­го дос­таточ­но, что­бы он перек­лючил­ся на более лег­кие цели.

Вто­рой важ­ный момент — баланс безопас­ности и про­изво­дитель­нос­ти. Некото­рые тех­ники защиты могут замед­лить при­ложе­ние или усложнить раз­работ­ку. Нуж­но оце­нивать рис­ки кон­крет­но для тво­его про­дук­та. Игра с покуп­ками за реаль­ные день­ги тре­бует серь­езной защиты. Если у тебя прос­тая ути­лита для заметок, то, воз­можно, не сто­ит парить­ся.

 

Обфускация кода

Об­фуска­ция — это пре­обра­зова­ние кода таким обра­зом, что­бы он оста­вал­ся фун­кци­ональ­но иден­тичным, но стал менее понят­ным при ана­лизе. В мире iOS это слож­нее, чем в Android, потому что в Apple не пре­дос­тавили готовых инс­тру­мен­тов для обфуска­ции.

 

Инструменты обфускации для Swift/Objective-C

Для Swift есть нес­коль­ко инс­тру­мен­тов:

  • SwiftShield — опен­сор­сное решение, которое пере­име­новы­вает клас­сы, методы, свой­ства перед ком­пиляци­ей;
  • Obfuscator-LLVM — форк ком­пилято­ра LLVM со встро­енной обфуска­цией на уров­не IR.

Чес­тно говоря, с эти­ми инс­тру­мен­тами я работал мало. SwiftShield тре­бует акку­рат­ной нас­трой­ки и может ломать про­екты с боль­шим количес­твом зависи­мос­тей. Obfuscator-LLVM — мощ­ная шту­ка, но тре­бует перес­борки все­го про­екта custom-ком­пилято­ром, что усложня­ет CI/CD.

Для Objective-C мож­но исполь­зовать скрип­товую обфуска­цию — авто­мати­чес­кое пере­име­нова­ние клас­сов и методов перед ком­пиляци­ей с помощью регуляр­ных выраже­ний. Но это хруп­кое решение.

 

Symbol Stripping

Пер­вое и самое прос­тое — это strip symbols. Xcode по умол­чанию вклю­чает эту опцию для Release-кон­фигура­ции. В Build Settings выс­тавля­ешь

Strip Debug Symbols During Copy: YES
Strip Linked Product: YES
Symbols Hidden by Default: YES

Это уда­ляет отла­доч­ную информа­цию из бинар­ника. Име­на фун­кций, не вхо­дящие в пуб­личный API, будут замене­ны адре­сами памяти. Селек­торы Objective-C все рав­но оста­нут­ся видимы­ми (они нуж­ны для работы runtime), но фун­кции на C и часть кода на Swift ста­нут менее чита­емы­ми.

 

Ручная обфускация критичных участков

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

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

func validateLicense(_ key: String) -> Bool {
return checkFormat(key) && verifyChecksum(key) && pingServer(key)
}

Об­фусци­руем:

func a(_ s: String) -> Bool {
return b(s) && c(s) && d(s)
}
private func b(_ s: String) -> Bool {
// Проверка формата
}
private func c(_ s: String) -> Bool {
// Проверка контрольной суммы
}
private func d(_ s: String) -> Bool {
// Пинг сервера
}

Да, это выг­лядит страш­но и нечита­емо даже для тебя. Но в этом и смысл. Толь­ко кри­тич­ные учас­тки, не весь про­ект.

 

Обфускация строк

Стро­ковые кон­стан­ты в бинар­нике — это подарок для ата­кующе­го. API-клю­чи, URL эндпо­интов, сооб­щения об ошиб­ках — все лежит в plaintext в сек­ции __cstring.

Прос­тей­шее решение — шиф­рование строк. Соз­даешь фун­кцию, которая рас­шифро­выва­ет стро­ку в runtime:

func decrypt(_ encrypted: [UInt8], key: UInt8) -> String {
let decrypted = encrypted.map { $0 ^ key }
return String(bytes: decrypted, encoding: .utf8) ?? ""
}
let apiKey = decrypt([0x48, 0x65, 0x6c, 0x6c, 0x6f], key: 0x20)

Ключ шиф­рования мож­но хра­нить в коде (тоже обфусци­рован­но) или генери­ровать динами­чес­ки. Да, это XOR-шиф­рование, которое лома­ется за секун­ды, но луч­ше, чем ничего. Для более серь­езной защиты можешь исполь­зовать AES с клю­чом, собира­емым из раз­ных час­тей кода.

Я про­бовал авто­мати­зиро­вать это через скрипт Build Phase. Скрипт находит все стро­ковые литера­лы в про­екте, шиф­рует их, заменя­ет вызова­ми фун­кции рас­шифров­ки. Работа­ет, но тре­бует осто­рож­ности — лег­ко сло­мать интерфей­сы и локали­зацию.

 

Антиотладка (anti-debugging)

От­ладчик — глав­ный инс­тру­мент при динами­чес­ком ана­лизе. Ата­кующий запус­кает при­ложе­ние под LLDB, ста­вит брейк‑пой­нты, изу­чает сос­тояние памяти, пошаго­во выпол­няет код. Наша задача — детек­тировать отладчик и либо завер­шить при­ложе­ние, либо изме­нить его поведе­ние.

 

Проверка через sysctl

Сис­темный вызов sysctl поз­воля­ет зап­росить информа­цию о про­цес­се. Флаг P_TRACED ука­зыва­ет, что про­цесс отсле­жива­ется отладчи­ком:

import Foundation
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 result = sysctl(&mib, u_int(mib.count), &info, &size, nil, 0)
if result != 0 {
return false
}
return (info.kp_proc.p_flag & P_TRACED) != 0
}
if isDebuggerAttached() {
fatalError("Debugger detected")
}

Эту про­вер­ку желатель­но делать не один раз при стар­те, а пери­оди­чес­ки в ран­тай­ме. Ата­кующий может при­соеди­нить­ся к про­цес­су уже пос­ле запус­ка.

 

Защита через ptrace

Сис­темный вызов ptrace поз­воля­ет про­цес­су зап­ретить при­соеди­нение отладчи­ка. Вызыва­ешь ptrace(PT_DENY_ATTACH, 0, 0, 0), и всё — LLDB боль­ше не смо­жет при­соеди­нить­ся к про­цес­су.

#import <sys/types.h>
#import <sys/sysctl.h>
#import <dlfcn.h>
typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);
void disablePtrace() {
void *handle = dlopen(NULL, RTLD_GLOBAL | RTLD_NOW);
ptrace_ptr_t ptrace_ptr = (ptrace_ptr_t)dlsym(handle, "ptrace");
ptrace_ptr(31, 0, 0, 0); // PT_DENY_ATTACH = 31
dlclose(handle);
}

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    1 Комментарий
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии