
example/open1_hook.c 컴파일하고 실행 한 후 커널 로그에서 출력
XNUSPY는 새로운 시스템 호출 xnuspy_ctl 설치하는 Pongoos 모듈로 사용자 공간에서 커널 기능을 연결할 수 있습니다. CheckRA1N 0.12.2 이상에서 iOS 13.x, iOS 14.x 및 iOS 15.x를 지원합니다. 4K 장치는 지원되지 않습니다.
이 모듈은 KTRR/KPP를 완전히 중성화하고 EL1 내에서 RWX 메모리를 생성 할 수 있습니다. 일일 운전자에게 이것을 사용하지 마십시오.
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 서로 의존하지 않습니다.
모든 것을 구축 한 후에는 checkra1n을 Pongo Shell : /Applications/checkra1n.app/Contents/MacOS/checkra1n -p 로 부팅하십시오.
동일한 디렉토리에서 로더와 모듈을 만들었습니다. loader/loader module/xnuspy 수행하십시오. 그렇게하면 Xnuspy는 그 일을하고 몇 초 후에 장치가 부팅됩니다. Seprom을 악용 해야하는 경우 xnuspy-getkernelv 발행 한 후 loader 몇 초 더 기다립니다.
때로는 Checkra1n의 KPF가 실행 된 후 몇 개의 전화가 "부팅"에 갇히게됩니다. 나는 이것의 원인을 아직 알지 못했지만 그것이 발생하면 다시 시도하십시오. 또한 bootx 후 장치가 매달려 있으면 다시 시도하십시오. 마지막으로, iOS 13.3.1을 실행하는 iPhone X에서 컴파일 된 xnuspy_ctl 코드를 실행 파일로 표시하는 것은 약간 스패성이지만 다른 휴대 전화에서 100%를 성공시킵니다. 후크 프로그램을 실행할 때 커널 명령을 가져 오면 낙태가 발생하면 다시 시도하십시오.
XNUSPY는 xnuspy_ctl_tramp 가리키기 위해 enosys 시스템 호출을 패치합니다. 이것은 컴파일 된 xnuspy_ctl 코드를 실행 파일로 표시하고 분기를 표시하는 작은 트램폴린입니다. module/el1/xnuspy_ctl/xnuspy_ctl.c 에서 xnuspy_ctl 의 구현을 찾을 수 있으며 example 디렉토리에서 예제를 찾을 수 있습니다.
내부 include/xnuspy/ is xnuspy_ctl.h is 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 나는 MSHookFunction 의 API와 일치하도록이 맛을 설계했습니다. arg1 연결하려는 커널 기능의 미끄러운 주소입니다. 미끄러짐 주소를 제공하면 당황 할 것입니다. arg2 는 ABI 호환 교체 기능에 대한 포인터입니다. arg3 xnuspy_ctl 원래 커널 함수를 나타내는 트램폴린의 주소를 copyout 위한 포인터입니다. 원본을 호출하지 않으려면 널 NULL 될 수 있습니다.
XNUSPY_REGISTER_DEATH_CALLBACK이 맛을 사용하면 옵션 "Death Callback"을 등록 할 수 있습니다. hook 프로그램이 종료 될 때 xnuspy가 호출하는 함수입니다. 커널 후크에서 만든 모든 것을 정리할 수있는 기회를 제공합니다. 커널 스레드를 만든 경우이 기능에서 종료하도록 지시합니다.
콜백은 비동기식으로 호출되지 않으므로 차단하면 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 함수 및 커널 슬라이드와 같은 많은 유용한 사항이 포함되어 있으므로 직접 찾을 필요가 없습니다. 캐시 ID의 전체 목록은 example/xnuspy_ctl.h 확인하십시오.
arg1 은 xnuspy_ctl.h 에 정의 된 캐시 ID 중 하나이며 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 의 포인터로서 current_thread 의 반환 값을 copyout . 다른 인수의 가치는 무시됩니다.
XNUSPY_CHECK_IF_PATCHED 제외한 모든 풍미의 경우 0 성공시 반환됩니다. 오류시 -1 이 반환되고 errno 설정됩니다. XNUSPY_CHECK_IF_PATCHED 오류를 반환하지 않습니다. XNU의 mach_to_bsd_errno kern_return_t 적절한 errno 로 변환하는 데 사용됩니다.
XNUSPY_INSTALL_HOOK 와 관련된 오류 errno 는 ...
EEXIST if :arg1 로 표시된 미끄러운 커널 함수에 대한 후크는 이미 존재합니다.ENOMEM if :unified_kalloc NULL 을 반환했습니다.ENOSPC if :xnuspy_tramp structs가 없습니다. 수백 개의 커널 기능을 동시에 연결하지 않으면 발생하지 않아야합니다. 더 많은 기능 후크가 필요한 경우 한도를 확인하십시오.ENOTSUP if :ENOENT if :mh_for_addr 발신자의 주소 공간 내부의 arg2 에 해당하는 Mach-O 헤더를 결정할 수 없었습니다.EFAULT if :EIO if :mach_make_memory_entry_64 결정된 mach-o 헤더의 __TEXT 및 __DATA 세그먼트 전체에 대한 메모리 항목을 반환하지 않았습니다. errno 또한 vm_map_wire_external , mach_vm_map_external , mach_make_memory_entry_64 , copyin , copyout 및 해당되는 경우 일회성 초기화 기능의 반환 값에 따라 다릅니다.
이 맛이 오류를 반환하면 대상 커널 함수가 연결되지 않았습니다. arg3 에 대해 널 포인터 NULL 아닌 포인터를 통과 한 경우 초기화되었거나 초기화되지 않았을 수도 있습니다. 그렇다면 사용하는 것은 안전하지 않습니다.
XNUSPY_REGISTER_DEATH_CALLBACK 과 관련된 오류 errno 는 ...
ENOENT if :이 맛이 오류를 반환하면 데스 콜백이 등록되지 않았습니다.
XNUSPY_CALL_HOOKME 와 관련된 오류 errno 는 ...
ENOTSUP if :hookme xnuspy_tramp 구조를 포함하는 메모리에서 너무 멀리 떨어져 있습니다. 이것은 Pongoos 내부에서 결정되며 Xnuspy가 KernelCache 내부의 미사용 코드로 떨어져야하는 경우에만 발생할 수 있습니다. 이 경우 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 세그먼트 외부에있는 사용자 공간 코드를 실행할 수 없습니다 . 예를 들어 실수로 kprintf 대신 printf 호출하는 경우 당황합니다. XNUSPY_CACHE_READ 통해 해당 함수를 아직 사용할 수없는 경우 호출하려는 LIBC 기능을 다시 구현해야합니다. 그러나 다른 커널 기능에 대한 기능 포인터를 생성하고이를 호출 할 수 있습니다.PAGE_SIZE 상수가 아닌 vm_page_size 로 확장됩니다. 이 변수를 읽기 전에 PAN (A10+에서도 권장하지 않는 A10+)을 비활성화해야합니다. 그렇지 않으면 당황하게됩니다.-fno-stack-protector 및 -D_FORTIFY_SOURCE=0 으로 코드를 컴파일해야합니다. 경우에 따라 장치는 다른 사용자 공간 포인터를 사용하여 ___stack_chk_guard 읽어야합니다.스키밍 https://developer.apple.com/library/archive/documentation/darwin/conceptual/kernelprogramming/style/style.html도 권장됩니다.
코드를 작성할 때 버그는 불가피하므로 결국 커널 공황이 발생합니다. 공황이 반드시 xnuspy와 함께 버그가 있다는 것을 의미하지는 않으므로 문제를 열기 전에 원래 기능을 호출하고 그 값을 반환 할 때도 여전히 공황 상태를 유지하십시오 (필요한 경우). 여전히 당황한다면 xnuspy 버그 일 가능성이 높습니다 (문제를 열어주십시오). 그러나 그렇지 않은 경우 교체에 문제가 있습니다.
Xnuspy는 실제로 EL0 페이지로 실행을 리디렉션하지 않기 때문에 공황 디버깅은 간단하지 않습니다. module/el1/xnuspy_ctl/xnuspy_ctl.c 열고 xnuspy_install_hook 에서 kwrite_instr 에 대한 유일한 호출 직전에 IOSleep 에 몇 초 동안 전화를 추가하십시오. 이것은 로그가 전파하기 위해 장치 패닉 전에 충분한 시간이 있는지 확인하기 위해 수행됩니다. 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 세그먼트를 매핑하는 시작을 가리 킵니다. Hook 프로그램을 좋아하는 분리기에 넣고 Mach-O 헤더가 kmh 의 주소에 있도록 Rebase를 다시 제출하십시오. Ida Pro의 경우 Image base 버블 링 된 Edit -> Segments -> Rebase program... 입니다. 장치 패닉 및 재부팅 후에는 공황 로그에서 커널의 교체품 매핑에 해당하는 주소가 있으면 분해와 일치합니다. 없다면, 당신은 아마도 당신의 교체품 내에 어떤 종류의 미묘한 기억 부패가있을 것입니다.
Xnuspy는 또한 훅이 제거 된 후 커널의 __TEXT 세그먼트 매핑에서 커널 스레드가 여전히 실행중인 (또는 실행)인지 알 수있는 방법이 없습니다. Xnuspy가 이것을 다루기 위해하는 일 중 하나는 후크 프로그램이 죽은 직후이 매핑을 처리하지 않는 것입니다. 대신 대기열 끝에 추가됩니다. Xnuspy의 쓰레기 수집 스레드가 해당 대기열에서 몇 페이지 분량의 매핑이 보유 된 수와 관련하여 정해진 제한이 초과되면 큐 앞면에서 거래를 시작하고 그 한계가 더 이상 초과되지 않을 때까지 계속됩니다. 기본적 으로이 한도는 1MB 또는 64 페이지입니다.
이것이 엄청나게 도움이되지만, 훅 프로그램의 __TEXT 및 __DATA 세그먼트가 클수록 Xnuspy 가이 레이스에서 이길 가능성이 줄어 듭니다. 정기적으로 당황하고 다소 큰 후크 프로그램이있는 경우, make 전에 XNUSPY_LEAKED_PAGE_LIMIT=n 추가 하여이 한계를 늘리십시오. 이 제한은 64가 아닌 n 페이지로 설정됩니다.
XNUSPY는 xnuspy_tramp 스트리트를 위해 XNU 부츠 전에 정적 커널 메모리의 한 페이지를 보유하여 동시에 225 개의 커널 기능을 동시에 연결할 수 있습니다. 더 많은 것을 원한다면 make 전에 XNUSPY_TRAMP_PAGES=n 추가 할 수 있습니다. 이것은 Xnuspy에게 xnuspy_tramp 구조에 대한 정적 메모리의 n 페이지를 예약하도록 지시합니다. 그러나 Xnuspy가 KernelCache 내부의 미사용 코드로 돌아 가야한다면 이는 무시됩니다. 이런 일이 발생하면 작동 방식에 자세히 설명되어 있습니다.
어떤 이유로, os_log_with_args 의 로그는 명령 줄 도구 oslog 에서 출력 된 스트림에 표시되지 않습니다. kprintf 의 로그는 그것을 만들지 않지만 dmesg 에서는 볼 수 있습니다. 그러나 dmesg 라이브 피드가 아니므로 klog kprintf 로그를 실시간으로 표시하는 도구를 썼습니다. klog/ 에서 찾으십시오. kprintf 메시지를 위해 dmesg 스팸하는 대신 사용하는 것이 좋습니다.
open: Resource busy klog 실행 한 후 바쁘게 바쁘게 수행하면이 명령을 실행 launchctl unload /System/Library/LaunchDaemons/com.apple.syslogd.plist 실행하고 다시 시도하십시오.
불행히도, atm_diagnostic_config=0x20000000 XNU의 Bootargs에 설정된 If NSLog 를 볼 수 없습니다. klog 이 부팅 인수가 존재하는 것에 달려 있습니다. NSLog 다시 원한다면 loader.c 내부의 pongo_send_command 에서 해당 부팅 인수를 제거하십시오.
Xnuspy가 당신을 위해 이것을 관리 할 것입니다. 프로세스가 종료되면 해당 프로세스에 의해 설치된 모든 커널 후크는 1 초 이내에 제거됩니다.
대부분의 기능 후킹 프레임 워크에는 주어진 기능을 연결할 수있는 최소 길이가 있습니다. Xnuspy는 원래 함수를 호출하려는 경우 에만 이 제한을 가지고 있으며 후크 기능의 첫 번째 명령은 B 아닙니다. 이 경우 최소 길이는 8 바이트입니다. 그렇지 않으면 최소 길이가 없습니다.
XNUSPY는 트램폴린에 X16 과 X17 사용하므로 기능 호출을 통해 지속될 것으로 예상하는 커널 기능은 연결될 수 없습니다 (이를 기대하는 사람은 많지 않습니다). 연결하려는 기능이 BL 로 시작하고 원본을 호출하려는 경우 원래 함수를 실행하면 X17 수정하지 않는 경우에만 가능합니다.
xnuspy_ctl 새로운 부팅 후 처음으로 호출 될 때 일회성 초기화를 수행합니다. 이것은 내가 사용하는 읽기/쓰기 잠금을 정적으로 초기화 할 수 없기 때문에 인종가 가능한 Xnuspy의 유일한 부분입니다. 첫 번째 통화가 반환 된 후, 향후 통화는 스레드 안전성으로 보증됩니다.
이것은 단순화되지만 주요 아이디어를 잘 포착합니다. Xnuspy의 기능 후크는 쓰기 가능, 실행 가능한 커널 메모리에 상주하는 구조입니다. 대부분의 경우, 이것은 Pongoos 내부의 alloc_static 에 의해 반환 된 메모리입니다. 이것으로 끓일 수 있습니다.
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 의 주소, 공유 매핑 k 의 시작 주소 및 사용자 교체 함수 r 의 주소를 고려할 때 다음 공식을 적용합니다. replacement = k + (r - u)
그 후, replacement 는 공유 매핑에서 사용자의 교체 함수의 커널 가상 주소이며 기능 후크 구조에 기록됩니다. XNUSPY는 대체 기능의 EL0 주소로 실행을 다시 지시하지 않습니다. 왜냐하면 그것은 매우 안전하지 않기 때문입니다. 스케줄러의 자비에 우리를 우리에게 적용 할뿐만 아니라 커널 후크가있는 프로세스가 여전히 사라지는 동안 커널 스레드가 여전히 실행되는 시나리오를 제어 할 수 없습니다.
마지막으로, 공유 매핑은 실행 파일로 표시되고 무조건적 인 즉각 지점 ( B )이 조립됩니다. 그것은 tramp 의 시작으로 실행을 지시하며, 현재 진행된 커널 기능의 첫 번째 명령을 대체하는 것입니다. 불행히도, 이것은 우리가 지정된 커널 기능에서 128MB 이상 떨어진 분기에서 후크 구조로 제한합니다. Xnuspy는 부팅하기 전에이 시나리오를 확인하고 훅 구조가 발생할 수있는 경우 대신 KernelCache에 사용되지 않은 코드로 돌아갑니다.
패치 파인더가 작동하는지 확인하기 위해 최선을 다하므로 작동하지 않으면 문제를여십시오.