前言

之前写过一篇文章简单对 frida-objc-bridge 原理进行分析,这里分享个使用 objc 代码的实现个只有frida-objc-bridge 的 “frida-mini”。

目前仅支持 M1/M2(arm64) 的版本。

注入动态库

这里我参考kk用rust 写的方案:https://github.com/kekeimiku/PointerSearcher-X/tree/main/inject

原理挺简单的,找 _dlopen 符号地址,拼个加载执行dylib asm 写到一块可执行的内存中再执行下,无聊用 objc 重写了一遍:

#import <Foundation/Foundation.h>
#include <mach-o/dyld.h>
#import <mach-o/dyld_images.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#import <mach/mach_vm.h>
#import <malloc/malloc.h>

#define STACK_SIZE 0x4000

uint64_t find_library_addr(task_t task, const char *library) {
  kern_return_t kr;
  // get the dyld info of the task
  task_dyld_info_data_t dyld_info;
  mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
  kr = task_info(task, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count);
  if (kr != KERN_SUCCESS) {
    return 0;
  }

  // get the dyld_all_image_infos struct
  struct dyld_all_image_infos image_infos;
  mach_vm_size_t size = dyld_info.all_image_info_size;
  kr = mach_vm_read_overwrite(task, dyld_info.all_image_info_addr,
                              dyld_info.all_image_info_size,
                              (mach_vm_address_t)&image_infos, &size);
  if (kr != KERN_SUCCESS) {
    return 0;
  }

  // get the dyld_image_info structs
  size_t module_size =
      image_infos.infoArrayCount * sizeof(struct dyld_image_info);
  struct dyld_image_info *modules =
      (struct dyld_image_info *)malloc(module_size);
  kr = mach_vm_read_overwrite(task, (mach_vm_address_t)image_infos.infoArray,
                              module_size, (mach_vm_address_t)modules, &size);
  if (kr != KERN_SUCCESS) {
    free(modules);
    return 0;
  }

  char buf[512];
  size_t bufSize = sizeof(buf);
  uint64_t out_addr = 0;
  for (int i = 0; i < image_infos.infoArrayCount; i++) {
    // get the path of the library
    kr = mach_vm_read_overwrite(task,
                                (mach_vm_address_t)modules[i].imageFilePath,
                                bufSize, (mach_vm_address_t)buf, &size);
    if (kr != KERN_SUCCESS) {
      continue;
    }

    NSString *filename =
        [[NSString stringWithUTF8String:buf] lastPathComponent];
    // compare the filename with the library name
    if ([filename isEqualToString:[NSString stringWithUTF8String:library]]) {
      out_addr = (uint64_t)modules[i].imageLoadAddress;
      break;
    }
  }

  free(modules);
  return out_addr;
}

// default on 64bit
uint64_t find_symbol_addr(task_t task, mach_vm_address_t library_header_address,
                          const char *symbol) {

  kern_return_t kr;

  // get the macho header of the library
  struct mach_header_64 header;
  mach_vm_size_t size = sizeof(header);
  kr = mach_vm_read_overwrite(task, library_header_address, size,
                              (mach_vm_address_t)&header, &size);
  if (kr != KERN_SUCCESS) {
    return 0;
  }

  // get the dyld info of the task
  task_dyld_info_data_t dyld_info;
  mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
  kr = task_info(task, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count);
  if (kr != KERN_SUCCESS) {
    return 0;
  }

  // get the dyld_all_image_infos struct
  struct dyld_all_image_infos image_infos;
  size = dyld_info.all_image_info_size;
  kr = mach_vm_read_overwrite(task, dyld_info.all_image_info_addr,
                              dyld_info.all_image_info_size,
                              (mach_vm_address_t)&image_infos, &size);
  if (kr != KERN_SUCCESS) {
    return 0;
  }

  struct segment_command_64 seg_linkedit;
  struct segment_command_64 seg_text;
  struct symtab_command symtab;

  // iterate over the load commands
  mach_vm_address_t load_command_address =
      library_header_address + sizeof(header);
  for (uint32_t i = 0; i < header.ncmds; i++) {
    // get the load command
    struct load_command command;
    size = sizeof(command);
    kr = mach_vm_read_overwrite(task, load_command_address, size,
                                (mach_vm_address_t)&command, &size);
    if (kr != KERN_SUCCESS) {
      continue;
    }

    // get the segment command
    if (command.cmd == LC_SEGMENT_64) {
      struct segment_command_64 seg_command;
      size = sizeof(seg_command);
      // read the segment command
      kr = mach_vm_read_overwrite(task, load_command_address, size,
                                  (mach_vm_address_t)&seg_command, &size);
      if (kr != KERN_SUCCESS) {
        continue;
      }

      if (strcmp(seg_command.segname, SEG_TEXT) == 0) {
        size = sizeof(seg_text);
        kr = mach_vm_read_overwrite(task, load_command_address, size,
                                    (mach_vm_address_t)&seg_text, &size);
        if (kr != KERN_SUCCESS) {
          return 0;
        }
      } else if (strcmp(seg_command.segname, SEG_LINKEDIT) == 0) {
        size = sizeof(seg_linkedit);
        kr = mach_vm_read_overwrite(task, load_command_address, size,
                                    (mach_vm_address_t)&seg_linkedit, &size);
        if (kr != KERN_SUCCESS) {
          return 0;
        }
      }
    }
    if (command.cmd == LC_SYMTAB) {
      size = sizeof(symtab);
      kr = mach_vm_read_overwrite(task, load_command_address, size,
                                  (mach_vm_address_t)&symtab, &size);
      if (kr != KERN_SUCCESS) {
        return 0;
      }
    }
    // next load command
    load_command_address += command.cmdsize;
  }

  // TODO
  uint64_t file_slide =
      seg_linkedit.vmaddr - seg_text.vmaddr - seg_linkedit.fileoff;
  // symtab.stroff -> 字符串表在 Mach-O 文件中的偏移
  uint64_t strings = library_header_address + symtab.stroff + file_slide;
  // symtab.symoff -> 符号表在 Mach-O 文件中的偏移
  mach_vm_address_t sym_addr =
      library_header_address + symtab.symoff + file_slide;

  char name[512];
  size = sizeof(name);

  // TODO
  // <https://developer.apple.com/documentation/kernel/nlist_64>
  struct nlist_64 sym;
  mach_vm_size_t sym_size = sizeof(sym);
  mach_vm_address_t symname_offset;

  // iterate over the symbols
  for (uint32_t i = 0; i < symtab.nsyms; i++) {
    // get the symbol
    kr = mach_vm_read_overwrite(task, sym_addr, sym_size,
                                (mach_vm_address_t)&sym, &sym_size);
    if (kr != KERN_SUCCESS) {
      continue;
    }
    // get the symbol name
    symname_offset = strings + sym.n_un.n_strx;
    kr = mach_vm_read_overwrite(task, symname_offset, size,
                                (mach_vm_address_t)name, &size);
    if (kr != KERN_SUCCESS) {
      continue;
    }
    // compare the symbol name with the symbol we are looking for
    if (strcmp(name, symbol) == 0) {
      return (uint64_t)sym.n_value + image_infos.sharedCacheSlide;
    }

    sym_addr += sym_size;
  }

  return 0;
}

uint32_t copyBits(uint32_t reg, uint16_t value) {
  for (int i = 0; i <= 15; i++) {
    BOOL bitToSet = ((value >> i) & 1) != 0;
    reg &= ~(1 << (i + 5));
    reg |= (bitToSet ? 1 : 0) << (i + 5);
  }
  return reg;
}

void write_instruction_address(uint8_t *code, uint length, uint32_t v,
                               uint offset) {
  uint32_t instructions;
  NSData *instructionData = [NSData dataWithBytes:&code[length + offset]
                                           length:4];
  [instructionData getBytes:&instructions length:4];
  instructions = copyBits(instructions, (uint16_t)(v & 0xFFFF));
  memcpy(&code[length + offset], &instructions, 4);
}

NSData *genasm(uint64_t dlopen) {
#if defined(__arm64__)
  uint8_t code[136] = {
      0xFD, 0x7B, 0xBD, 0xA9, 0xF5, 0x0B, 0x00, 0xF9, 0xF4, 0x4F, 0x02, 0xA9,
      0xFD, 0x03, 0x00, 0x91, 0x02, 0x4C, 0x40, 0xA9, 0x08, 0x50, 0x41, 0xA9,
      0x15, 0x10, 0x40, 0xF9, 0xBF, 0x0F, 0x00, 0xF9, 0xE3, 0x03, 0x01, 0xAA,
      0xE1, 0x03, 0x1F, 0xAA, 0xA0, 0x63, 0x00, 0x91, 0x00, 0x01, 0x3F, 0xD6,
      0xEA, 0x03, 0x00, 0xAA, 0xA0, 0x0F, 0x40, 0xF9, 0xEB, 0x03, 0x00, 0xAA,
      0x60, 0x02, 0x3F, 0xD6, 0xA0, 0x02, 0x3F, 0xD6, 0x80, 0x02, 0x3F, 0xD6,
      0xA0, 0x0F, 0x40, 0xF9, 0xF4, 0x4F, 0x42, 0xA9, 0xF5, 0x0B, 0x40, 0xF9,
      0xFD, 0x7B, 0xC3, 0xA8, 0xC0, 0x03, 0x5F, 0xD6, 0x1F, 0x20, 0x03, 0xD5,
      0x1F, 0x20, 0x03, 0xD5, 0x1F, 0x20, 0x03, 0xD5, 0x1F, 0x20, 0x03, 0xD5,
      0x1F, 0x20, 0x03, 0xD5, 0x41, 0x00, 0x80, 0x52, 0xE2, 0xDD, 0x97, 0xD2,
      0xA2, 0xD5, 0xBB, 0xF2, 0x02, 0x00, 0xD6, 0xF2, 0x02, 0x00, 0xF4, 0xF2,
      0x40, 0x00, 0x1F, 0xD6};

  int codeSize = sizeof(code);

  uint32_t beef = (uint32_t)(dlopen & 0x000000000000FFFF);
  uint32_t dead = (uint32_t)((dlopen & 0x00000000FFFF0000) >> 16);
  uint32_t b000 = (uint32_t)((dlopen & 0x0000FFFF00000000) >> 32);
  uint32_t a000 = (uint32_t)((dlopen & 0xFFFF000000000000) >> 48);

  write_instruction_address(code, codeSize, a000, -8);
  write_instruction_address(code, codeSize, b000, -12);
  write_instruction_address(code, codeSize, dead, -16);
  write_instruction_address(code, codeSize, beef, -20);

  return [NSData dataWithBytes:code length:codeSize];
#endif
}

int main(int argc, char *argv[]) {
  if (argc < 3) {
    printf("Usage: %s <pid> <path>\\n", argv[0]);
    return 1;
  }
  pid_t pid = atoi(argv[1]);
  char *path = argv[2];

  @autoreleasepool {
    task_t rtask;
    kern_return_t kr;

    kr = task_for_pid(mach_task_self(), pid, &rtask);
    if (kr != KERN_SUCCESS) {
      printf("[-] Failed to get task port for pid:%d, error: %s\\n", pid,
             mach_error_string(kr));
      return 1;
    }
    printf("[+] Got access to the task port of process: %d\\n", pid);

    uint64_t libdyld = find_library_addr(rtask, "libdyld.dylib");
    uint64_t libsystem_pthread =
        find_library_addr(rtask, "libsystem_pthread.dylib");
    uint64_t libsystem_kernel =
        find_library_addr(rtask, "libsystem_kernel.dylib");
    if (libdyld == 0 || libsystem_pthread == 0 || libsystem_kernel == 0) {
      printf("[-] Failed to find libraries\\n");
      return 1;
    }

    printf("[+] Found libdyld.dylib at: 0x%llx\\n", libdyld);
    printf("[+] Found libsystem_pthread.dylib at: 0x%llx\\n", libsystem_pthread);
    printf("[+] Found libsystem_kernel.dylib at: 0x%llx\\n", libsystem_kernel);

    uint64_t dlopen = find_symbol_addr(rtask, libdyld, "_dlopen");
    uint64_t pthread_create_from_mach_thread = find_symbol_addr(
        rtask, libsystem_pthread, "_pthread_create_from_mach_thread");
    uint64_t pthread_set_self =
        find_symbol_addr(rtask, libsystem_pthread, "__pthread_set_self");
    uint64_t thread_suspend =
        find_symbol_addr(rtask, libsystem_kernel, "_thread_suspend");
    uint64_t mach_thread_self =
        find_symbol_addr(rtask, libsystem_kernel, "_mach_thread_self");
    if (dlopen == 0 || pthread_create_from_mach_thread == 0 ||
        pthread_set_self == 0 || thread_suspend == 0 || mach_thread_self == 0) {
      printf("[-] Failed to find symbols\\n");
      return 1;
    }

    printf("[+] Found _dlopen at: 0x%llx\\n", dlopen);
    printf("[+] Found _pthread_create_from_mach_thread at: 0x%llx\\n",
           pthread_create_from_mach_thread);
    printf("[+] Found __pthread_set_self at: 0x%llx\\n", pthread_set_self);
    printf("[+] Found _thread_suspend at: 0x%llx\\n", thread_suspend);
    printf("[+] Found _mach_thread_self at: 0x%llx\\n", mach_thread_self);

    mach_vm_address_t remote_path;
    printf("[+] Dylib path: %s length: %lu\\n", path, strlen(path));
    mach_vm_allocate(rtask, &remote_path, strlen(path), VM_FLAGS_ANYWHERE);
    mach_vm_write(rtask, remote_path, (vm_offset_t)path, strlen(path));
    mach_vm_protect(rtask, remote_path, strlen(path), 0,
                    VM_PROT_READ | VM_PROT_WRITE);

    printf("[+] Generated asm done\\n");
    NSData *codeasm = genasm(dlopen);

    printf("[+] Wrote asm to remote process\\n");
    mach_vm_address_t remote_code;
    mach_vm_allocate(rtask, &remote_code, [codeasm length], VM_FLAGS_ANYWHERE);
    mach_vm_write(rtask, remote_code, (vm_offset_t)[codeasm bytes],
                  [codeasm length]);
    mach_vm_protect(rtask, remote_code, [codeasm length], 0,
                    VM_PROT_READ | VM_PROT_EXECUTE);

    printf("[+] Wrote parameters to remote process\\n");
    uint64_t parameters[] = {
        remote_code + ([codeasm length] - 24),
        pthread_set_self,
        pthread_create_from_mach_thread,
        thread_suspend,
        mach_thread_self,
    };
    mach_vm_address_t remote_parameters;
    mach_vm_allocate(rtask, &remote_parameters, sizeof(parameters),
                     VM_FLAGS_ANYWHERE);
    mach_vm_write(rtask, remote_parameters, (vm_offset_t)parameters,
                  sizeof(parameters));

    printf("[+] Created remote stack\\n");
    mach_vm_address_t remote_stack;
    mach_vm_allocate(rtask, &remote_stack, STACK_SIZE, VM_FLAGS_ANYWHERE);
    mach_vm_protect(rtask, remote_stack, STACK_SIZE, TRUE,
                    VM_PROT_READ | VM_PROT_WRITE);

    mach_vm_address_t local_stack = remote_stack;
    remote_stack += STACK_SIZE / 2;

#if defined(__arm64__)
    arm_thread_state64_t remote_state;
    remote_state.__x[0] = remote_parameters;
    remote_state.__x[1] = remote_path;
    remote_state.__pc = remote_code;
    remote_state.__sp = remote_stack;
    remote_state.__lr = local_stack;

    printf("[+] Created remote arm64 thread\\n");
    thread_act_t thread;
    thread_create_running(rtask, ARM_THREAD_STATE64,
                          (thread_state_t)&remote_state,
                          ARM_THREAD_STATE64_COUNT, &thread);
#endif
  }

  return 0;
}

“objc-bridge”

主要实现了 “frida-objc-bridge” 的 choose 功能,其他的功能 objc runtime 自带的。

// @Base: <https://gist.github.com/samdmarshall/17f4e66b5e2e579fd396>

#import <Foundation/Foundation.h>
#import <mach/mach_vm.h>
#import <malloc/malloc.h>
#import <objc/message.h>
#import <objc/runtime.h>

#if defined(__arm64__)
#define OBJC_ISA_MASK 0xffffffff8ULL
#elif defined(__i386__) // TODO
#define OBJC_ISA_MASK 0x7ffffffffff8ULL
#endif

// TODO
#define CLASS "TEST"

Class cls;
size_t cls_size;

void CanHasObjects(task_t task, void *context, unsigned type,
                   vm_range_t *ranges, unsigned count) {
  unsigned i;
  for (i = 0; i < count; i++) {
    vm_range_t *range = &ranges[i];
    uintptr_t *address = ((uintptr_t *)range->address);
    uintptr_t *isa;

    if (address == NULL) {
      continue;
    }

    isa = (uintptr_t *)address[0];
#ifdef OBJC_ISA_MASK
    isa = (uintptr_t *)((unsigned long long)isa & OBJC_ISA_MASK);
#endif

    if (isa > 0 && range->size >= sizeof(Class) && cls == (Class)isa) {
#ifdef DEBUG
      printf("[+] fond isa(%p)->'%s' instance %p \\n", isa,
             object_getClassName((Class)isa), address);
#endif
      // TODO
      ((void (*)(id, SEL))objc_msgSend)((__bridge id)address,
                                        @selector(dododo));
    }
  }
}

static void __attribute__((constructor)) initialize(void) {
  @autoreleasepool {
    cls = NSClassFromString([NSString stringWithFormat:@"%s", CLASS]);
    if (cls == Nil) {
#ifdef DEBUG
      printf("[-] Class not found\\n");
#endif
      return;
    }

    cls_size = class_getInstanceSize(cls);
    if (cls_size == 0) {
#ifdef DEBUG
      printf("[-] Class Instance size is %zu\\n", cls_size);
#endif
      return;
    }

#ifdef DEBUG
    printf("[+] Class %p Instance size is %zu\\n", cls, cls_size);
#endif

    vm_address_t *zones;
    unsigned count, i = 0;
    kern_return_t r =
        malloc_get_all_zones(mach_task_self(), NULL, &zones, &count);
    if (r == KERN_SUCCESS) {
      for (i = 0; i < count; i++) {
        vm_address_t zone_address = zones[i];
        malloc_zone_t *zone = (malloc_zone_t *)zone_address;

        if (zone != NULL && zone->introspect != NULL) {
          zone->introspect->enumerator(mach_task_self(), NULL,
                                       MALLOC_PTR_IN_USE_RANGE_TYPE,
                                       zone_address, NULL, &CanHasObjects);
        }
      }
    }
  }
}

执行测试

Untitled

总结

hacking for fun!

Powered by Kali-Team