Тема сегодняшней статьи «Взлом программ». Я покажу, как взламывать программы Взлом программ macOS с помощью IDA 7.2 и Hiew.
Еще по теме: Как взломать программу в формате MSI
В начале вкратце поговорим, что именно будем ломать.
Как взламывают программы macOS
Мы уже знаем, что все исполняемые файлы и библиотеки под актуальные версии ОС Windows называются EXE/DLL и имеют структуру MZ-PE. В макОсь используется формат Mach-O (сокращение от Mach object), который является потомком формата a.out, который macOS получила в наследство от Unix.
Всем известно что, Apple любит иногда переходить с одного семейства процов на другое, из-за этого меняется и архитектура программ. Начав с PowerPC, Apple в середине нулевых переметнулась в стан Intel, после чего в недавнем прошлом корпорация решила перейти на платформу ARM.
Дабы пользователи поменьше страдали от подобных метаний, был взят на вооружение мультипроцессорный формат Fat binary («жирный бинарник»), который может содержать код одновременно под несколько процессоров. Такой модуль может работать как под Intel, так и под ARM.
Что такое Mach-O? Обычно данный модуль состоит из трех областей:
- Первая область (Заголовок) представляет информацию о бинарном файле: тип процессора, порядок байтов, количество команд загрузки и т. п.
- Вторая область — команды загрузки — это можно сказать оглавление, в котором описано положение сегментов, динамическая таблица символов и т.д. Все команды загрузки содержат метаданные, такие как тип команды, ее название, позиция в бинарном файле.
- Третья область — это данные, как правило самая большая часть объектного файла. Это часть содержит код и другу информацию.
Мультипроцессорный модуль может состоять из нескольких обычных модулей Mach-O, заточенных под разные процы (обычно это i386 и x86_64, ARM или ARM64). Структура модуля очень проста — после Fat header, в котором описаны входящие в модуль блоки Mach-O, идет код этих блоков, расположенный подряд.
В рамках этой статьи, не буду погружаться глубоко в описание всех секций и полей этого формата. Если интересно, можете погуглить сами.
Взлом программ macOS с помощью IDA и Hiew
Итак, теперь, когда вы достаточно узнали о теории, давайте рассмотрим практический пример. У меня есть некий установленный иллюстраторовский плагин, который надо отучить от самоубийства после окончания триала.
Предположим также, что доступа к компьютеру с macOS, на котором установлен данный плагин, у нас нет, как и другого компа с macOS под рукой — только возможность переписывать файлы.
Находим в папке выбранного плагина подпапку Contents\MacOS, а в ней — исполняемый модуль. В нашем случае это динамическая библиотека Fat Mach-O file, о чем можно понять по сигнатуре CA FE BA BE.
Intel
Загружаем наш файл в IDA Pro: нам будет предложено на выбор два (точнее три) способа загрузки данного файла: Fat Mach-O file, 1.X86_64 и Fat Mach-O file, 2.ARM64. Третий вариант, бинарный файл, нам неинтересен.
Начнем с самого простого и знакомого всем пользователям Windows варианта: выбираем Intel X86_64. Бегло пробежавшись по списку названий функций, обнаруживаем имя checkPersonalize2_tryout.
Так как у нас триал, данная функция вполне может оказаться проверкой на его валидность. Смотрим, откуда она вызывается — ага, из функции с еще более подозрительным названием _checkUser:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
__text:0000000000006A62 mov edi, esi ; SPPlugin * __text:0000000000006A64 mov rsi, rbx ; _UserData_NSD_ * __text:0000000000006A67 call __Z24checkPersonalize2_tryoutP8SPPluginP14_UserData_NSD_Ph ; checkPersonalize2_tryout(SPPlugin *,_UserData_NSD_ *,uchar *) __text:0000000000006A6C test eax, eax __text:0000000000006A6E jnz short loc_6A95 __text:0000000000006A70 cmp [rbp+var_21], 0 __text:0000000000006A74 jz short loc_6A95 __text:0000000000006A76 __text:0000000000006A76 loc_6A76: ; CODE XREF: _checkUser:loc_6A5D↑j __text:0000000000006A76 mov rax, [r12] __text:0000000000006A7A mov qword ptr [rax], 0 __text:0000000000006A81 mov byte ptr [rax+63Dh], 1 __text:0000000000006A88 mov qword ptr [rax+648h], 0 __text:0000000000006A93 jmp short loc_6A99 __text:0000000000006A95 ; ---------------- __text:0000000000006A95 __text:0000000000006A95 loc_6A95: ; CODE XREF: _checkUser+198↑j __text:0000000000006A95 ; _checkUser+19E↑j ... __text:0000000000006A95 mov [rbp+var_21], 0 |
Поскольку загрузить программу в отладчик и дойти до этого места мы не можем, пробуем догадаться, какой вариант возвращаемого значения eax нас устраивает больше. Выражения в квадратных скобках byte ptr [rax+63Dh] и qword ptr [rax+648h] похожи на установку полей некоей структуры или свойств объекта.
Поискав по коду чуть выше, мы увидим такую конструкцию:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
__text:00000000000069E4 cmp byte ptr [rbx+63Dh], 0 __text:00000000000069EB jnz loc_6A99 __text:00000000000069F1 __text:00000000000069F1 loc_69F1: ; CODE XREF: _checkUser+124↑j __text:00000000000069F1 mov [rbp+var_21], 0 __text:00000000000069F5 cmp byte ptr [rbx+653h], 0 __text:00000000000069FC jz short loc_6A35 __text:00000000000069FE cmp byte ptr [rbx+650h], 0 __text:0000000000006A05 jz short loc_6A5D __text:0000000000006A07 mov edi, 8 ; unsigned __int64 __text:0000000000006A0C call __Znwm ; operator new(ulong) __text:0000000000006A11 mov r14, rax __text:0000000000006A14 mov ecx, 22h ; '"' __text:0000000000006A19 mov rdi, rsp __text:0000000000006A1C mov rsi, rbx __text:0000000000006A1F rep movsq __text:0000000000006A22 mov rdi, rax ; this __text:0000000000006A25 call __ZN11AboutDialogC1E14_UserData_NSD_ ; AboutDialog::AboutDialog(_UserData_NSD_) __text:0000000000006A2A mov rax, [r14] __text:0000000000006A2D mov rdi, r14 __text:0000000000006A30 call qword ptr [rax+8] __text:0000000000006A33 jmp short loc_6A99 |
По поведению программы мы помним, что о просрочке триала сигнализирует диалог About, внезапно выскакивающий при загрузке программы, ненулевое же значение байта по адресу [rbx+63Dh] инициирует обход данной ветки.
Выходит, что правильной является ветка, в которой этому байту присваивается значение 1 начиная со смещения __text:0000000000006A76 (изначально этот байт инициализируется в 0). Не мудрствуя лукаво, просто закорачиваем весь кусок кода вызова процедуры checkPersonalize2_tryout, установив перед ним безусловный переход на loc_6A76:
1 2 3 4 5 6 7 8 9 10 11 12 |
__text:0000000000006A5D jmp loc_6A76 __text:0000000000006A62 ; ---------------- __text:0000000000006A62 mov edi, esi ; SPPlugin * __text:0000000000006A64 mov rsi, rbx ; _UserData_NSD_ * __text:0000000000006A67 call __Z24checkPersonalize2_tryoutP8SPPluginP14_UserData_NSD_Ph ; checkPersonalize2_tryout(SPPlugin *,_UserData_NSD_ *,uchar *) __text:0000000000006A6C test eax, eax __text:0000000000006A6E jnz short loc_6A95 __text:0000000000006A70 cmp [rbp+var_21], 0 __text:0000000000006A74 jz short loc_6A95 __text:0000000000006A76 __text:0000000000006A76 loc_6A76: ; CODE XREF: _checkUser:loc_6A5D↑j __text:0000000000006A76 mov rax, [r12] |
ARM
Итак, с частью кода, ответственной за х86, мы вроде разобрались, попробуем сделать то же самое с армовской частью. Снова загружаем этот модуль в IDA, на сей раз выбрав при загрузке вариант Fat Mach-O file, 2.ARM64.
Мы видим, что функции _checkUser и checkPersonalize2_tryout присутствуют и в этой части кода, вышеописанное место вызова в переводе на армовский ассемблер выглядит вот так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
__text:000000000000716C MOV X2, SP __text:0000000000007170 MOV X0, X19 __text:0000000000007174 MOV X1, X20 __text:0000000000007178 BL __Z24checkPersonalize2_tryoutP8SPPluginP14_UserData_NSD_Ph ; checkPersonalize2_tryout(SPPlugin *,_UserData_NSD_ *,uchar *) __text:000000000000717C LDRB W8, [SP,#0x150+var_150] __text:0000000000007180 CMP W0, #0 __text:0000000000007184 CCMP W8, #0, #4, EQ __text:0000000000007188 B.NE loc_7194 __text:000000000000718C __text:000000000000718C loc_718C ; CODE XREF: _checkUser+1A0↑j __text:000000000000718C STRB WZR, [SP,#0x150+var_150] __text:0000000000007190 B loc_71A4 __text:0000000000007194 ; ---------------- __text:0000000000007194 __text:0000000000007194 loc_7194 ; CODE XREF: _checkUser+1D0↑j __text:0000000000007194 LDR X8, [X22] __text:0000000000007198 MOV W9, #1 __text:000000000000719C STRB W9, [X8,#0x3A0] __text:00000000000071A0 STR XZR, [X8,#0x3A8] __text:00000000000071A4 __text:00000000000071A4 loc_71A4 ; CODE XREF: _checkUser+118↑j __text:00000000000071A4 MOV W0, #0 |
Рассмотрев этот код повнимательнее, мы видим, что в армовском коде аналогом поля [rax+63Dh] служит байт по адресу [X8,#0x3A0], ибо именно ему присваивается единичка при удачном вызове checkPersonalize2_tryout. Поэтому, дабы не изобретать велосипед, действуем тем же способом, что и ранее — закорачиваем кусок кода, вставляя перед ним безусловный переход на loc_7194:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
__text:000000000000716C B loc_7194 __text:0000000000007170 MOV X0, X19 __text:0000000000007174 MOV X1, X20 __text:0000000000007178 BL __Z24checkPersonalize2_tryoutP8SPPluginP14_UserData_NSD_Ph ; checkPersonalize2_tryout(SPPlugin *,_UserData_NSD_ *,uchar *) __text:000000000000717C LDRB W8, [SP,#0x150+var_150] __text:0000000000007180 CMP W0, #0 __text:0000000000007184 CCMP W8, #0, #4, EQ __text:0000000000007188 B.NE loc_7194 __text:000000000000718C __text:000000000000718C loc_718C ; CODE XREF: _checkUser+1A0↑j __text:000000000000718C STRB WZR, [SP,#0x150+var_150] __text:0000000000007190 B loc_71A4 __text:0000000000007194 ; ---------------- __text:0000000000007194 __text:0000000000007194 loc_7194 ; CODE XREF: _checkUser+1D0↑j __text:0000000000007194 LDR X8, [X22] |
Патчинг плагина
Теперь, когда мы разобрались, что и где следует менять, нужно внести эти самые изменения. Самое простое, что у нас есть под рукой — маленький DOS-овский шестнадцатеричный редактор Hiew, который, помимо простого байтового редактирования, умеет дизассемблировать и ассемблировать код для Intel и даже для ARM.
К сожалению, про ARM64, который нам нужен, и Fat Mach-O он ничего не знает, поэтому придется немного поработать руками, используя на практике описанную выше теорию.
Открыв заголовок модуля, мы видим в нем две секции Mach-O с абсолютными смещениями 8000h и 17С000h. Так и есть, по первому смещению сидит сигнатура секции CF FA ED FE и код процессора 07 00 00 01 — это интеловская часть. По второму смещению сигнатура та же, но код процессора другой 0C 00 00 01 — это ARM.
Прибавляем к 8000h смещение из IDA — 6A5Dh, и получаем EA5Dh — смещение до первого патча в интеловской части. Переключаемся через Ctrl-F1 в 64-битный режим и правим искомый jmp. Теперь внесем изменения в армовскую часть. Тут есть небольшая сложность. Смещение до патча 17С000h+716Ch=18316Ch мы нашли, однако при переключении в режим ARM дизассемблера через Shift-F1 код совсем другой, Hiew не понимает актуальный ARM64.
Попробуем вычислить и поправить искомый опкод руками. Открываем спецификацию (если очень лениво искать, то можно просто посмотреть в IDA по соседним командам) — опкод команды безусловного перехода 14h (последний байт команды). Первыми байтами идет смещение до адреса перехода в 32-битных командах. Считаем: 7194h-716Ch=28h делим на 4 байта и получаем 0Ah — искомое смещение для перехода. В результате код исправленной команды выглядит так:
1 |
__text:000000000000716C 0A 00 00 14 B loc_7194 |
Итак, мы пропатчили обе части модуля, однако радоваться рано. При переписывании измененного модуля на место старого программа выдает ошибку. Оно и понятно: macOS делали параноики, каждый модуль должен быть подписан, а при изменении любого байта подпись, разумеется, слетает. По счастью, параноики оставили нам возможность заново подписать модуль на маке из терминала. Для этого после замены модуля нужно зайти в терминал и набрать следующую команду:
1 |
sudo codesign --force --deep -sign - <полный путь к пропатченному модулю> |
По идее, можно вообще убрать подпись через stripcodesig или даже до копирования на мак, но это получается не всегда. Например, начиная с macOS Catalina, может потребоваться удалить приложение из карантина, для этого в терминале придется набрать следующую команду:
1 |
sudo xattr -rd com.apple.quarantine <полный путь к пропатченному модулю> |
К сожалению, совсем без доступа к телу маку не обойтись — как минимум придется переписывать и подписывать патченные модули. Конечно, можно было бы перепаковать установочный образ плагина или попробовать натянуть виртуалку с macOS под Windows, но эти способы сильно сложнее. Мы же справились с поставленной задачей успешно, а главное — с минимальными усилиями.
Еще по теме: Взлом программы с защитой Enigma
А можешь на заказ софтинку ломануть?