Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
xnuspy — checkra1n 호환 기기를 위한 iOS 커널 함수 후킹 프레임워크 | Kitploit
도구/GitHubGitHub/jsherman212/xnuspy
iOS SecurityExploitationDebuggersBinary Analysis
GitHubjsherman212/xnuspy

xnuspy

checkra1n 호환 기기를 위한 iOS 커널 함수 후킹 프레임워크

저장소 보기
595112534년 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

xnuspy

alt text

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=1
    • xnuspy의 디버그 출력을 커널 로그(kprintf)로 보냅니다.
  • XNUSPY_SERIAL=1
    • xnuspy의 디버그 출력을 IOLog로 보냅니다.
  • XNUSPY_LEAKED_PAGE_LIMIT=n
    • xnuspy가 가비지 컬렉션 스레드가 페이지를 할당 해제하기 전에 누출할 수 있는 페이지 수를 설정합니다. 기본값은 64입니다. 자세한 내용은 커널 패닉 디버깅에서 확인할 수 있습니다.
  • XNUSPY_TRAMP_PAGES=n
    • xnuspy가 트램폴린 구조를 위해 예약할 페이지 수를 설정합니다. 기본값은 1입니다. 자세한 내용은 제한 사항에서 확인할 수 있습니다.

XNUSPY_DEBUG와 XNUSPY_SERIAL은 서로 의존하지 않습니다.

사용법

모든 것을 빌드한 후, checkra1n으로 장치를 pongo 셸로 부팅하십시오: /Applications/checkra1n.app/Contents/MacOS/checkra1n -p

로더와 모듈을 빌드한 동일한 디렉토리에서 loader/loader module/xnuspy를 실행하십시오. 그러면 xnuspy가 작업을 수행하고 몇 초 후에 장치가 부팅됩니다. loader는 xnuspy-getkernelv를 실행한 후 SEPROM을 익스플로잇해야 할 경우를 대비하여 몇 초 더 기다립니다.

알려진 문제

가끔 제 휴대폰 중 일부가 checkra1n의 KPF 실행 후 "Booting"에서 멈추는 경우가 있습니다. 아직 원인을 파악하지 못했지만, 이런 경우 다시 시도하십시오. 또한, bootx 후 장치가 멈추면 다시 시도하십시오. 마지막으로, iOS 13.3.1을 실행하는 iPhone X에서 컴파일된 xnuspy_ctl 코드를 실행 가능으로 표시하는 것이 약간 불안정하지만, 다른 휴대폰에서는 100% 성공합니다. 후크 프로그램을 실행할 때 커널 명령어 페치 중단으로 패닉이 발생하면 다시 시도하십시오.

xnuspy_ctl

xnuspy는 enosys 시스템 콜을 패치하여 xnuspy_ctl_tramp을 가리키도록 합니다. 이는 컴파일된 xnuspy_ctl 코드를 실행 가능으로 표시하고 분기하는 작은 트램폴린입니다. xnuspy_ctl의 구현은 module/el1/xnuspy_ctl/xnuspy_ctl.c에서, 예제는 example 디렉토리에서 찾을 수 있습니다.

include/xnuspy/ 안에는 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);

root@kitploit:~
이 시스템 콜은 `flavor`, `arg1`, `arg2`, `arg3`의 네 인자를 받습니다. 
flavor는 `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` 중 하나일 수 있습니다.
나머지 세 인자의 의미는 flavor에 따라 달라집니다.

## `XNUSPY_CHECK_IF_PATCHED`
이 flavor는 `xnuspy_ctl`이 존재하는지 확인하기 위해 존재합니다. 이 flavor로 호출하면 `999`를 반환합니다. 다른 인자의 값은 무시됩니다.

## `XNUSPY_INSTALL_HOOK`
이 flavor는 [`MSHookFunction`](http://www.cydiasubstrate.com/api/c/MSHookFunction/)의 API와 일치하도록 설계했습니다.
`arg1`은 후크하려는 커널 함수의 *UNSLID* 주소입니다. 슬라이드된 주소를 제공하면 대부분 패닉이 발생합니다. `arg2`는 ABI 호환 대체 함수의 포인터입니다. `arg3`는 `xnuspy_ctl`이 원래 커널 함수를 나타내는 트램펄린의 주소를 `copyout`하기 위한 포인터입니다. 원래 함수를 호출할 의도가 없다면 `NULL`일 수 있습니다.

## `XNUSPY_REGISTER_DEATH_CALLBACK`
이 flavor는 선택적 "사망 콜백"을 등록할 수 있게 해줍니다. 이 함수는 후크 프로그램이 종료될 때 xnuspy가 호출합니다. 커널 후크에서 생성한 모든 것을 정리할 기회를 제공합니다. 커널 스레드를 생성했다면 이 함수에서 종료하라고 지시해야 합니다.

콜백은 비동기적으로 호출되지 않으므로, 블록하면 xnuspy의 가비지 컬렉션 스레드가 실행되지 못합니다.

`arg1`은 콜백 함수의 포인터입니다. 다른 인자의 값은 무시됩니다.

## `XNUSPY_CALL_HOOKME`
`hookme`는 xnuspy가 후크용으로 xnuspy 캐시를 통해 내보내는 작은 어셈블리 스텁입니다. 이 flavor로 `xnuspy_ctl`을 호출하면 `hookme`가 호출되어, 실제 커널 함수를 후크하지 않고도 쉽게 커널 코드 실행 권한을 얻을 수 있는 방법을 제공합니다.

`arg1`은 `hookme`가 호출될 때 전달될 인자입니다. `NULL`일 수 있습니다.

## `XNUSPY_CACHE_READ`
이 flavor는 xnuspy 캐시에서 읽을 수 있는 방법을 제공합니다. 캐시에는 `kprintf`, `current_proc`, `kernel_thread_start`, 일부 libc 함수, 커널 슬라이드 등 여러 유용한 항목이 포함되어 있어 직접 찾을 필요가 없습니다. 캐시 ID의 전체 목록은 `example/xnuspy_ctl.h`를 확인하세요.

`arg1`은 `xnuspy_ctl.h`에 정의된 캐시 ID 중 하나이며, `arg2`는 `xnuspy_ctl`이 요청한 주소나 값을 `copyout`하기 위한 포인터입니다. 다른 인자의 값은 무시됩니다.

## `XNUSPY_KREAD`
이 flavor는 tfp0 없이 사용자 공간에서 커널 메모리를 쉽게 읽을 수 있는 방법을 제공합니다.

`arg1`은 커널 가상 주소, `arg2`는 사용자 공간 버퍼의 주소, `arg3`는 해당 사용자 공간 버퍼의 크기입니다. `arg3` 바이트가 `arg1`에서 `arg2`로 쓰여집니다.

## `XNUSPY_KWRITE`
이 flavor는 tfp0 없이 사용자 공간에서 커널 메모리에 쉽게 쓸 수 있는 방법을 제공합니다.

`arg1`은 커널 가상 주소, `arg2`는 사용자 공간 버퍼의 주소, `arg3`는 해당 사용자 공간 버퍼의 크기입니다. `arg3` 바이트가 `arg2`에서 `arg1`로 쓰여집니다.

## `XNUSPY_GET_CURRENT_THREAD`
이 flavor는 호출 스레드의 커널 주소를 사용자 공간에 제공합니다.

`arg1`은 `xnuspy_ctl`이 `current_thread`의 반환값을 `copyout`하기 위한 포인터입니다. 다른 인자의 값은 무시됩니다.

### 오류
`XNUSPY_CHECK_IF_PATCHED`를 제외한 모든 flavor에서 성공 시 `0`이 반환됩니다. 오류 시 `-1`이 반환되고 `errno`가 설정됩니다. `XNUSPY_CHECK_IF_PATCHED`는 오류를 반환하지 않습니다. XNU의 `mach_to_bsd_errno`를 사용하여 `kern_return_t`를 적절한 `errno`로 변환합니다.

#### `XNUSPY_INSTALL_HOOK` 관련 오류
`errno`는 다음과 같이 설정됩니다...
- `EEXIST` (이미 존재):
  - `arg1`로 지정된 unslid 커널 함수에 대한 후크가 이미 존재하는 경우.
- `ENOMEM` (메모리 부족):
  - `unified_kalloc`이 `NULL`을 반환한 경우.
- `ENOSPC` (공간 부족):
  - 사용 가능한 `xnuspy_tramp` 구조체가 없는 경우. 이 구조체는 xnuspy 내부 데이터 구조입니다. *동시에* 수백 개의 커널 함수를 후크하지 않는 한 발생하지 않아야 합니다. 더 많은 함수 후크가 필요하면 [제한](#limits)을 참조하세요.
- `ENOTSUP` (지원되지 않음):
  - 호출자가 Mach-O 실행 파일이나 동적 라이브러리가 아닌 경우.
- `ENOENT` (찾을 수 없음):
  - `mh_for_addr`가 호출자 주소 공간 내에서 `arg2`에 해당하는 Mach-O 헤더를 확인할 수 없는 경우.
- `EFAULT` (잘못된 주소):
  - 확인된 Mach-O 헤더가 실제 Mach-O 헤더가 아닌 경우. 거의 발생하지 않을 것입니다.
- `EIO` (입출력 오류):
  - `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`의 반환값과, 해당되는 경우 일회성 초기화 함수에 따라 달라집니다.

이 flavor가 오류를 반환하면 대상 커널 함수는 후크되지 않았습니다. `arg3`에 대해 `NULL`이 아닌 포인터를 전달한 경우 초기화되었을 수도 있고 아닐 수도 있습니다. 초기화된 경우에도 사용하는 것은 안전하지 않습니다.

#### `XNUSPY_REGISTER_DEATH_CALLBACK` 관련 오류
`errno`는 다음과 같이 설정됩니다...
- `ENOENT` (찾을 수 없음):
  - 호출 프로세스가 어떤 커널 함수도 후크하지 않은 경우.

이 flavor가 오류를 반환하면 사망 콜백이 등록되지 않았습니다.

#### `XNUSPY_CALL_HOOKME` 관련 오류
`errno`는 다음과 같이 설정됩니다...
- `ENOTSUP` (지원되지 않음):
  - `hookme`가 `xnuspy_tramp` 구조체가 있는 메모리에서 너무 멀리 떨어져 있는 경우. 이는 pongoOS 내에서 결정되며, xnuspy가 이미 커널 캐시 내부의 사용되지 않은 코드로 대체해야 했던 경우에만 발생할 수 있습니다. 이 경우 `hookme`를 호출하면 거의 확실히 커널 패닉이 발생하므로 다른 커널 함수를 후크할 방법을 찾아야 합니다.

이 flavor가 오류를 반환하면 `hookme`가 호출되지 않았습니다.

#### `XNUSPY_CACHE_READ` 관련 오류
`errno`는 다음과 같이 설정됩니다...
- `EINVAL` (잘못된 인자):
  - `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`의 반환값과, 해당되는 경우 일회성 초기화 함수의 반환값에 따라 달라집니다.

이 flavor가 오류를 반환하면 `arg2`에 전달한 포인터가 초기화되지 않았습니다.

#### `XNUSPY_KREAD` 및 `XNUSPY_KWRITE` 관련 오류
`errno`는 다음과 같이 설정됩니다...
- `EFAULT` (잘못된 주소):
  - `arg1` 또는 `arg2`에 대한 주소 변환이 실패한 경우. `XNUSPY_DEBUG=1`로 컴파일한 경우 해당 메시지가 커널 로그에 출력됩니다.

이 flavor가 오류를 반환하면 커널 메모리가 읽히거나 쓰여지지 않았습니다.

#### `XNUSPY_GET_CURRENT_THREAD` 관련 오류
`copyout`이 실패하면 `errno`는 해당 반환값으로 설정됩니다.

# 중요 정보

### 일반적인 함정
대체 함수를 작성할 때, 커널 코드를 작성하고 있다는 사실을 잊기 쉬웠습니다. 후크를 작성할 때 명심해야 할 몇 가지 사항은 다음과 같습니다:

- *프로그램의 `__TEXT` 세그먼트 외부에 있는 사용자 공간 코드는 절대 실행할 수 없습니다.* 예를 들어, 실수로 `kprintf` 대신 `printf`를 호출하면 패닉이 발생합니다. `XNUSPY_CACHE_READ`를 통해 이미 사용할 수 없는 경우, 호출하려는 libc 함수를 다시 구현해야 합니다. 그러나 다른 커널 함수에 대한 함수 포인터를 만들고 호출하는 것은 가능합니다.
- *사용자 공간 코드에서 일반적으로 사용되는 많은 매크로가 커널에서는 안전하지 않습니다.* 예를 들어, `PAGE_SIZE`는 상수가 아니라 `vm_page_size`로 확장됩니다. 이 변수를 읽기 전에 PAN(10세대 이상, 권장하지 않음)을 비활성화해야 합니다. 그렇지 않으면 패닉이 발생합니다.
- *코드를 `-fno-stack-protector` 및 `-D_FORTIFY_SOURCE=0`으로 컴파일해야 합니다.* 어떤 경우에는 장치가 다른 사용자 공간 포인터를 역참조하여 `___stack_chk_guard`를 읽어야 하며, 10세대 이상에서 패닉이 발생합니다.
- *안전을 위해 후크 프로그램을 컴파일러 최적화 없이 컴파일하세요.*

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`로 xnuspy를 다시 컴파일하고 모듈을 다시 로드하십시오. 모듈을 로드한 후, 아직 하지 않았다면 `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의 경우 `편집 -> 세그먼트 -> 프로그램 재배치...`에서 `Image base`를 선택하십시오. 장치가 패닉하고 다시 재부팅된 후, 패닉 로그에 대체 함수의 커널 매핑에 해당하는 주소가 있으면 디스어셈블리와 일치합니다. 없다면 대체 함수 내에 미묘한 메모리 손상이 있을 가능성이 높습니다.

또한 xnuspy는 후크가 제거된 후에도 커널 스레드가 프로그램의 `__TEXT` 세그먼트의 커널 매핑에서 계속 실행 중인지(또는 실행할 것인지) 알 방법이 없습니다. xnuspy가 이를 처리하는 방법 중 하나는 후크 프로그램이 종료된 후 즉시 이 매핑을 해제하지 않는 것입니다. 대신 큐의 끝에 추가됩니다. xnuspy의 가비지 컬렉션 스레드가 해당 큐에 보유된 매핑의 페이지 수와 관련하여 설정된 제한이 초과되었음을 감지하면 큐의 앞에서부터 해제를 시작하고 제한이 더 이상 초과되지 않을 때까지 계속합니다. 기본적으로 이 제한은 1MB(64페이지)입니다.

이것이 큰 도움이 되지만, 후크 프로그램의 `__TEXT` 및 `__DATA` 세그먼트가 클수록 xnuspy가 이 경쟁에서 이길 가능성은 낮아집니다. 정기적으로 패닉이 발생하고 후크 프로그램이 다소 큰 경우, `make` 앞에 `XNUSPY_LEAKED_PAGE_LIMIT=n`을 추가하여 이 제한을 늘려보십시오. 그러면 제한이 64페이지 대신 `n` 페이지로 설정됩니다.

### 제한
xnuspy는 XNU가 부팅되기 전에 정적 커널 메모리 한 페이지를 `xnuspy_tramp` 구조체용으로 예약하므로, 약 225개의 커널 함수를 동시에 후크할 수 있습니다. 더 많은 함수가 필요하면 `make` 앞에 `XNUSPY_TRAMP_PAGES=n`을 추가할 수 있습니다. 그러면 xnuspy가 `xnuspy_tramp` 구조체를 위해 `n` 페이지의 정적 메모리를 예약합니다. 그러나 xnuspy가 이미 커널 캐시 내부의 사용되지 않은 코드로 대체해야 하는 경우 이 설정은 무시됩니다. 이 경우가 언제 발생하는지는 [작동 방식](#how-it-works)에 자세히 설명되어 있습니다.

### 로깅
어떤 이유로 `os_log_with_args`의 로그는 명령줄 도구 `oslog`에서 출력되는 스트림에 나타나지 않습니다. `kprintf`의 로그도 거기에 도달하지 않지만 `dmesg`로 볼 수 *있습니다*. 그러나 `dmesg`는 실시간 피드가 아니므로 `klog`라는 도구를 작성했습니다. 이 도구는 `kprintf` 로그를 실시간으로 표시합니다. `klog/`에서 찾을 수 있습니다. `kprintf` 메시지에 대해 `dmesg`를 반복적으로 사용하는 대신 이 도구를 사용하는 것을 강력히 권장합니다.

`klog` 실행 후 `open: Resource busy`가 발생하면, `launchctl unload /System/Library/LaunchDaemons/com.apple.syslogd.plist` 명령을 실행하고 다시 시도하십시오.

안타깝게도 XNU의 bootargs에 `atm_diagnostic_config=0x20000000`이 설정되어 있으면 `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는 부팅 전에 이 시나리오를 확인하고, 이런 일이 발생할 수 있다고 판단되면 훅 구조체가 대신 상주할 수 있도록 이미 커널캐시에 있는 사용되지 않은 코드로 대체합니다.

기타 참고 사항

패치파인더가 잘 작동하도록 최선을 다하고 있습니다. 만약 작동하지 않는 부분이 있다면 이슈를 열어 주시기 바랍니다.

도구 다운로드