
Вывод из журнала ядра после компиляции и запуска example/open1_hook.c
xnuspy - это модуль Pongoos, который устанавливает новый системный вызов, xnuspy_ctl , который позволяет поднимать функции ядра из пользователя. Он поддерживает iOS 13.x, iOS 14.x и iOS 15.x на чекра1n 0,12,2 и выше. Устройства 4K не поддерживаются.
Этот модуль полностью одели KTRR/KPP и позволяет создавать память RWX внутри EL1. Не используйте это на своем ежедневном драйвере.
Требуется libusb : brew install libusb
make в каталоге верхнего уровня. Это построит погрузчик и модуль.
Добавьте их перед make .
XNUSPY_DEBUG=1kprintf ).XNUSPY_SERIAL=1IOLog .XNUSPY_LEAKED_PAGE_LIMIT=n64 . Больше информации можно найти при отладке панику ядра.XNUSPY_TRAMP_PAGES=n XNUSPY_DEBUG и XNUSPY_SERIAL не зависят друг от друга.
После того, как вы все построили, попросите CHEACHRA1N Загрузить ваше устройство на оболочку Pongo: /Applications/checkra1n.app/Contents/MacOS/checkra1n -p
В том же каталоге вы построили погрузчик и модуль, выполните loader/loader module/xnuspy . После этого Xnuspy сделает свое дело, и через несколько секунд ваше устройство будет загружаться. loader будет ждать еще пару секунд после выпуска xnuspy-getkernelv в случае использования Seprom.
Иногда пара моих телефонов застряла в «загрузке» после запуска KPF CHECHRA1N. Мне еще предстоит выяснить, что вызывает это, но если это произойдет, попробуйте еще раз. Кроме того, если устройство висит после bootx , попробуйте еще раз. Наконец, маркировка скомпилированного кода xnuspy_ctl как исполняемый файл на моем iPhone X под управлением iOS 13.3.1, немного пятна, но добивается 100% случаев на других моих телефонах. Если вы паникуете с помощью инструкции ядра, прерванного при выполнении программы крючка, попробуйте еще раз.
xnuspy будет исправлять системный вызов enosys , чтобы указать на xnuspy_ctl_tramp . Это небольшой батут, который отмечает скомпилированный код xnuspy_ctl как исполняемый файл и ветвл. Вы можете найти реализацию xnuspy_ctl в module/el1/xnuspy_ctl/xnuspy_ctl.c и примеры в каталоге example .
Внутренний include/xnuspy/ is xnuspy_ctl.h , заголовок, который определяет константы для xnuspy_ctl . Он предназначен для включения во все программы, которые подключают функции ядра.
Вы можете использовать sysctlbyname , чтобы выяснить, какой системный вызов был исправлен:
size_t oldlen = sizeof(long);
long SYS_xnuspy_ctl = 0;
sysctlbyname("kern.xnuspy_ctl_callnum", &SYS_xnuspy_ctl, &oldlen, NULL, 0);
Этот системный вызов принимает четыре аргумента, flavor , arg1 , arg2 и arg3 . Вкус может быть XNUSPY_CHECK_IF_PATCHED , XNUSPY_INSTALL_HOOK , XNUSPY_REGISTER_DEATH_CALLBACK , XNUSPY_CALL_HOOKME , XNUSPY_CACHE_READ , XNUSPY_KREAD , XNUSPY_KWRITE , или XNUSPY_GET_CURRENT_THREAD . Значение следующих трех аргументов зависит от вкуса.
XNUSPY_CHECK_IF_PATCHED Это существует, чтобы вы могли проверить, присутствует ли xnuspy_ctl . Вызывая его с этим ароматом, приведет к тому, что он вернется 999 . Значения других аргументов игнорируются.
XNUSPY_INSTALL_HOOK Я разработал этот вкус, чтобы соответствовать API MSHookFunction . arg1 - это UNSLID -адрес функции ядра, которую вы хотите поднять. Если вы поставите поставленный адрес, вы, скорее всего, будете паниковать. arg2 -это указатель на вашу функцию замены, совместимую с ABI. arg3 является указателем для xnuspy_ctl для copyout адреса батута, который представляет исходную функцию ядра. Это может быть NULL , если вы не собираетесь позвонить в оригинал.
XNUSPY_REGISTER_DEATH_CALLBACKЭтот вкус позволяет вам зарегистрировать необязательный «обратный вызов смерти», функция xnuspy позвонит, когда ваша программа Hook выходит. Это дает вам возможность очистить все, что вы создали из своих крючков ядра. Если вы создали какие -либо потоки ядра, вы бы сказали им прекратить в этой функции.
Ваш обратный вызов не вызывается асинхронно, поэтому, если вы блокируете, вы предотвращаете выполнение потока сбора мусора Xnuspy.
arg1 - это указатель на вашу функцию обратного вызова. Значения других аргументов игнорируются.
XNUSPY_CALL_HOOKME hookme - это небольшая сборочная заглушка, которая экспортирует Xnuspy через кеш Xnuspy для вас. Вызов xnuspy_ctl с этим ароматом приведет к тому, что hookme будет вызван, предоставив вам возможность легко получить выполнение кода ядра без необходимости подключить фактическую функцию ядра.
arg1 - это аргумент, который будет передаваться hookme , когда он будет вызван. Это может быть NULL .
XNUSPY_CACHE_READ Этот вкус дает вам возможность прочитать из кеша Xnuspy. Он содержит много полезных вещей, таких как kprintf , current_proc , kernel_thread_start , некоторые функции LIBC и слайд ядра, так что вам не нужно их сами. Для получения полного списка идентификаторов кеша, проверьте example/xnuspy_ctl.h .
arg1 - это один из идентификаторов кэша, определенных в xnuspy_ctl.h и arg2 является указателем для xnuspy_ctl для copyout адреса или значения того, что вы запросили. Значения других аргументов игнорируются.
XNUSPY_KREADЭтот вкус дает вам простой способ прочитать память ядра из пользователя без TFP0.
arg1 - это виртуальный адрес ядра, arg2 - это адрес буфера пользователя, а arg3 - это размер этого буфера пользователя. arg3 байты будут написаны с arg1 до arg2 .
XNUSPY_KWRITEЭтот вкус дает вам простой способ написать в память ядра из пользователя без TFP0.
arg1 - это виртуальный адрес ядра, arg2 - это адрес буфера пользователя, а arg3 - это размер этого буфера пользователя. arg3 байты будут написаны с arg2 в arg1 .
XNUSPY_GET_CURRENT_THREADЭтот аромат предоставляет пользователю адрес ядра призывного потока.
arg1 является указателем для xnuspy_ctl для copyout возвращаемого значения current_thread . Значения других аргументов игнорируются.
Для всех ароматов, кроме XNUSPY_CHECK_IF_PATCHED , 0 возвращается на успех. По ошибке возвращается -1 , и errno установлен. XNUSPY_CHECK_IF_PATCHED не возвращает никаких ошибок. Xnu's mach_to_bsd_errno используется для преобразования kern_return_t в соответствующую errno .
XNUSPY_INSTALL_HOOK errno настроен на ...
EEXIST if:arg1 .ENOMEM если:unified_kalloc вернул NULL .ENOSPC if:xnuspy_tramp , структура данных, внутренняя для Xnuspy. Это не должно произойти, если вы не подключаете сотни функций ядра одновременно . Если вам нужно больше функциональных крючков, проверьте ограничения.ENOTSUP если:ENOENT if:mh_for_addr не смог определить заголовок MACH-O, соответствующий arg2 в адресном пространстве вызывающего абонента.EFAULT if:EIO if:mach_make_memory_entry_64 не возвращал запись памяти для всей определенной сегментов __TEXT and __DATA заголовка Mach-O. errno также зависит от возвращаемого значения vm_map_wire_external , mach_vm_map_external , mach_make_memory_entry_64 , copyin , copyout и, если применимо, единовременная функция инициализации.
Если этот аромат возвращает ошибку, функция целевого ядра не была зацеплена. Если вы прошли не NULL указатель для arg3 , он может быть или не инициализирован. Небезопасно использовать, если это было.
XNUSPY_REGISTER_DEATH_CALLBACK errno настроен на ...
ENOENT if:Если этот вкус возвращает ошибку, ваш обратный вызов смерти не был зарегистрирован.
XNUSPY_CALL_HOOKME errno настроен на ...
ENOTSUP если:hookme слишком далеко от памяти, содержащей структуры xnuspy_tramp . Это определяется внутри Pongoos и может произойти только в том случае, если Xnuspy пришлось отступать в неиспользованный код, уже внутри ядра. В этом случае вызов hookme почти наверняка вызовет панику ядра, и вам придется выяснить еще одну функцию ядра. Если этот аромат возвращает ошибку, hookme не вызвали.
XNUSPY_CACHE_READ errno настроен на ...
EINVAL if:arg1 не представляет ничего в кэше.arg1 был IO_LOCK , но ядро - iOS 14.4.2 или ниже или ios 15.x.arg1 был IPC_OBJECT_LOCK , но ядро IOS 15.x.arg1 был IPC_PORT_RELEASE_SEND , но ядро iOS 14,5 или выше.arg1 был IPC_PORT_RELEASE_SEND_AND_UNLOCK , но ядро IOS 14.4.2 или ниже.arg1 был KALLOC_CANBLOCK , но ядро - ios 14.x или выше.arg1 был KALLOC_EXTERNAL , но ядро - iOS 13.x.arg1 был KFREE_ADDR , но ядро IOS 14.x или выше.arg1 был KFREE_EXT , но ядро IOS 13.x.arg1 был PROC_REF , но ядро iOS 14,8 или ниже.arg1 был PROC_REF_LOCKED , но ядро IOS 15.x.arg1 был PROC_RELE , но ядро IOS 14,8 или ниже.arg1 был PROC_RELE_LOCKED , но ядро IOS 15.x.arg1 был VM_MAP_UNWIRE , но ядро IOS 15.X.arg1 был VM_MAP_UNWIRE_NESTED , но ядро iOS 14,8 или ниже. errno также зависит от возвращаемого значения copyout и, если применимо, возвращаемое значение функции одноразовой инициализации.
Если этот вкус возвращает ошибку, указатель, который вы передали для arg2 не был инициализирован.
XNUSPY_KREAD и XNUSPY_KWRITE errno настроен на ...
EFAULT if:arg1 или arg2 . Если вы скомпилированы с XNUSPY_DEBUG=1 , сообщение о нем печатается в журнале ядра.Если этот вкус возвращает ошибку, память ядра не была прочитана/написана.
XNUSPY_GET_CURRENT_THREAD Если copyout не удается, errno устанавливается на свое возвращаемое значение.
При написании функций замены было легко забыть, что я писал код ядра. Вот несколько вещей, которые нужно помнить, когда вы пишете крючки:
__TEXT вашей программы . Вы будете паниковать, если, например, вы случайно вызовите printf вместо kprintf . Вам нужно переосмыслить любую функцию LIBC, которую вы хотите позвонить, если эта функция еще не доступна через XNUSPY_CACHE_READ . Вы можете создавать указатели функций для других функций ядра и вызовать их.PAGE_SIZE расширяется до vm_page_size , а не постоянной. Вам необходимо отключить сковороду (на A10+, которую я также не рекомендую делать), прежде чем читать эту переменную, или вы будете паниковать.-fno-stack-protector и -D_FORTIFY_SOURCE=0 В некоторых случаях устройство должно будет прочитать ___stack_chk_guard , предоставив еще один указатель пользователя, который будет паниковать на A10+.Скимирование https://developer.apple.com/library/archive/documentation/darwin/conceptual/kernelprogramming/style/style.html также рекомендуется.
Ошибки неизбежны при написании кода, поэтому в конечном итоге вы будете вызвать панику ядра. Паника не обязательно означает, что есть ошибка в Xnuspy, поэтому перед открытием проблемы, пожалуйста, убедитесь, что вы все еще паникуете, когда вы ничего не делаете, кроме как вызовите исходную функцию и возвращаете ее значение (при необходимости). Если вы все еще паникуете, то это, вероятно, ошибка Xnuspy (и, пожалуйста, откройте проблему), но если нет, то с вашей заменой что -то не так.
Поскольку Xnuspy на самом деле не перенаправляет исполнение на страницы EL0, отладка паники не так проста. Откройте module/el1/xnuspy_ctl/xnuspy_ctl.c , и прямо перед единственным вызовом kwrite_instr в xnuspy_install_hook , добавьте звонок в IOSleep на пару секунд. Это сделано, чтобы убедиться, что есть достаточно времени, прежде чем устройство паникует для размножения журналов. Повторнокомпилируйте Xnuspy с XNUSPY_DEBUG=1 make -B и снова загрузите модуль. После загрузки модуля, если вы еще этого не сделали, составьте klog из klog/ . Загрузите его на свое устройство и сделайте stdbuf -o0 ./klog | grep shared_mapping_kva . Запустите программу крючка снова и посмотрите на линию от klog , которая выглядит так:
shared_mapping_kva: dist 0x7af4 uaddr 0x104797af4 umh 0x104790000 kmh 0xfffffff00c90c000
Если вы устанавливаете более одного крючка, будет более одного события. В этом случае dist и uaddr будут различаться, но umh и kmh не будут. kmh указывает на начало картирования ядра в сегменте __TEXT вашей программы. Бросьте свою программу крючка в свой любимый разборщик и повторно ее, чтобы его заголовок Mach-O был по адресу kmh . Для Ida Pro это Edit -> Segments -> Rebase program... с пузырькой Image base . После того, как ваше устройство снова панится и перезагружается, если есть адреса, которые соответствуют картированию ядра вашей замены в журнале паники, они будут соответствовать разборке. Если нет, то у вас, вероятно, есть какая -то тонкая коррупция памяти внутри вашей замены.
xnuspy также не имеет возможности узнать, выполняется ли поток ядра (или будет выполнять) на картирование __ext вашей программы __TEXT Program после того, как ваши крючки удалены. Одна из вещей Xnuspy, чтобы справиться с этим, - это не разбираться в этом картировании сразу после того, как ваша программа Hook Die Die. Вместо этого он добавлен к концу очереди. После того, как потока сбора мусора Xnuspy замечает, что в этой очереди будет превышено установленное предел в отношении того, сколько страниц содержится в этой очереди, он начнет сдеваться с передней части очереди и будет продолжаться до тех пор, пока этот предел больше не будет превышен. По умолчанию этот предел составляет 1 МБ или 64 страницы.
Хотя это очень помогает, чем больше становятся сегменты __TEXT и __DATA вашей программы крюка, тем менее вероятно, что Xnuspy выигрывает эту гонку. Если вы регулярно паникуете и имеете несколько большую программу крюка, попробуйте увеличить этот предел, добавив XNUSPY_LEAKED_PAGE_LIMIT=n перед make . Это установит этот предел на n страниц, а не 64.
xnuspy оставляет за собой одну страницу статической памяти ядра перед сапогами XNU для своих структур xnuspy_tramp , позволяя одновременно зацепить 225 функций ядра. Если вы хотите больше, вы можете добавить XNUSPY_TRAMP_PAGES=n перед make . Это даст Xnuspy резервировать n страниц статической памяти для структур xnuspy_tramp . Однако, если Xnuspy должен вернуться к неиспользуемому коду, уже в ядре, то это игнорируется. Когда это происходит, подробно описано в том, как это работает.
По какой -то причине журналы из os_log_with_args не отображаются в потоке, выведенном из инструмента командной строки oslog . Журналы из kprintf тоже не делают это, но их можно увидеть с dmesg . Тем не менее, dmesg не является живым кормом, поэтому я написал klog , инструмент, который показывает журналы kprintf в реальном времени. Найдите это в klog/ . Я настоятельно рекомендую использовать это вместо спама dmesg для ваших сообщений kprintf .
Если вы open: Resource busy после запуска klog , запустите эту команду launchctl unload /System/Library/LaunchDaemons/com.apple.syslogd.plist и попробуйте еще раз.
К сожалению, вы не сможете увидеть никаких NSLog , если atm_diagnostic_config=0x20000000 установлен в Bootargs XNU. klog зависит от этого аргумента загрузки. Если вы хотите вернуть NSLog , удалите этот аргумент загрузки с pongo_send_command inside loader.c .
Xnuspy будет управлять этим для вас. После того, как процесс выходит, все крючки ядра, которые были установлены этим процессом, удаляются в течение секунды или около того.
Большинство фреймворков для подключения функций имеют некоторую минимальную длину, которая делает заданную функцию подключенной. xnuspy имеет этот предел, только если вы планируете вызвать исходную функцию , а первая инструкция по функции зацепления не является B . В этом случае минимальная длина составляет восемь байтов. В противном случае не существует минимальной длины.
Xnuspy использует X16 и X17 для своих батутов, поэтому функции ядра, которые ожидают, что они сохранятся в разных вызовах функций, не могут быть подключены (не так много, которые этого ожидают). Если функция, которую вы хотите подключить, начинается с BL , и вы намереваетесь вызвать оригинал, вы можете сделать это только в том случае, если выполнение исходной функции не изменяет X17 .
xnuspy_ctl выполнит единовременную инициализацию при первом вызове после свежей загрузки. Это единственная часть Xnuspy, которая является гонкой, поскольку я не могу статически инициализировать блокировку чтения/записи, которую я использую. После возврата первого вызова любые будущие вызовы будут GuarentEed, чтобы быть безопасными.
Это упрощено, но хорошо отражает основную идею. Функциональный крючок в Xnuspy - это структура, которая находится на записи, подлежащей исполняемой памяти ядра. В большинстве случаев это память возвращается alloc_static внутри Pongoos. Это можно сводить на это:
struct {
uint64_t replacement;
uint32_t tramp[2];
uint32_t orig[10];
};
В тех случаях, когда replacement является виртуальным адресом ядра (подробно описано в более позднем возрасте), tramp -это небольшой батут, который повторно направляет выполнение на replacement , а orig -более крупный, более сложный батут, который представляет исходную функцию.
Одна из первых вещей, которые делает Xnuspy, - это определить, где замена EL0 находится в адресном пространстве призывных процессов. Это сделано, чтобы функции ядра можно было зацепить из динамических библиотек. Заголовок Mach-O, который соответствует адресу этой замены, сохраняется.
После того, как общее отображение пользовательского корея __TEXT и __DATA -сегменты этого заголовка (а также любой сегмент между этими, если таковые имеются). __TEXT используется, поэтому вы можете вызвать другие функции из своих крючков. __DATA используется, поэтому изменения в глобальных переменных рассматриваются как EL1, так и EL0.
Поскольку это отображение представляет собой копию __TEXT и __DATA один на один, легко выяснить адрес функции замены пользователя. Учитывая адрес заголовка Mach -O u Process, адрес начала общего картирования k и адрес функции замены пользователя r , мы применяем следующую формулу: replacement = k + (r - u)
После этого replacement является виртуальным адресом ядра функции замены пользователя на общем отображении и записывается в структуру функции крючка. xnuspy не перенаправляет выполнение по адресу EL0 функции замены, потому что это чрезвычайно небезопасно: это не только ставит нас в милость планировщика, но и не дает нам контроля над сценарием, когда процесс с крюком ядра умирает, в то время как поток ядра все еще выполняется при замене.
Наконец, общее отображение помечено как исполняемое, и собирается безусловная непосредственная ветвь ( B ). Он направляет выполнение на начало tramp , и это заменяет первую инструкцию функции ядра, которая сейчас подготовлена. К сожалению, это ограничивает нас от разветвления до конструкций зацепления более чем в 128 МБ от данной функции ядра. Xnuspy действительно проверяет этот сценарий перед загрузкой и возвращается к неиспользованному коду, уже в ядре, чтобы структуры крючка могли бы проживать, вместо этого, если это обнаружит, что это может произойти.
Я делаю все возможное, чтобы убедиться, что патчфинды работают, поэтому, если что -то не работает, пожалуйста, откройте проблему.