//Attach.h

/*
 *  Attach.h
 *  MachOView
 *
 *  Created by fG! on 08/09/13.
 *  reverser@put.as
 *
 */

#ifndef machoview_Attach_h
#define machoview_Attach_h

#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <mach/vm_map.h>
#include <mach-o/loader.h>
//获取指定pid对就节镜像大小
int64_t get_image_size(mach_vm_address_t address, pid_t pid, uint64_t *vmaddr_slide);
//找到入口
kern_return_t find_main_binary(pid_t pid, mach_vm_address_t *main_address);
//从 pid读取指定地址的指定大小数据到缓冲区
kern_return_t dump_binary(mach_vm_address_t address, pid_t pid, uint8_t *buffer, uint64_t aslr_slide);

#endif


//相关函数用法 
//mach_vm_protect
//kern_return_t mach_vm_protect
//(
// vm_map_t target_task,
// mach_vm_address_t address,
// mach_vm_size_t size,
// boolean_t set_maximum,
// vm_prot_t new_protection
// );
//对address到address+size这一段的内存设置内存保护策略,new_protection就是最后设置成为的保护机制
//mach_vm_write
//kern_return_t mach_vm_write
//(
// vm_map_t target_task,
// mach_vm_address_t address,
// vm_offset_t data,
// mach_msg_type_number_t dataCnt
// );
//对address指向的内存改写内容
// Ports
//Ports是一种Mach提供的task之间相互交互的机制,通过Ports可以完成类似进程间通信的行为。每个Ports都会有自己的权限。
//#!c
//#define MACH_PORT_RIGHT_SEND        ((mach_port_right_t) 0)
//#define MACH_PORT_RIGHT_RECEIVE     ((mach_port_right_t) 1)
//#define MACH_PORT_RIGHT_SEND_ONCE   ((mach_port_right_t) 2)
//#define MACH_PORT_RIGHT_PORT_SET    ((mach_port_right_t) 3)
//#define MACH_PORT_RIGHT_DEAD_NAME   ((mach_port_right_t) 4)
//#define MACH_PORT_RIGHT_LABELH          ((mach_port_right_t) 5)
//#define MACH_PORT_RIGHT_NUMBER      ((mach_port_right_t) 6)
//Ports可以在不同的task之间传递,通过传递可以赋予其他task对ports的操作权限。例如POC中使用的就是在父进程与子进程之间传递Port得到了对内存操作的权限

Attach.mm

/*
 *  Attach.mm
 *  MachOView
 *
 *  Created by fG! on 08/09/13.
 *  reverser@put.as
 *
 *  Contains functions for the attach to process feature.
 *
 */

#include "Attach.h"

#include <stdio.h>

/* local functions */
//读内存的相应信息
static kern_return_t readmem(mach_vm_offset_t *buffer, mach_vm_address_t address, mach_vm_size_t size, pid_t pid, vm_region_basic_info_data_64_t *info);

#pragma mark Public functions
//通过迭代内存区域查找主二进制文件,假设只有一个二进制文件带有MH_EXECUTE文件类型
//获取加载基址在区域中的入口地址  这里不是程序入口 下面这个结构体的entryoff才是真正的入口
//如: entryoff =0xA0 74 90 +加载基址 0x1 00 00 00 00=0x1 00 A0 74 90
//struct entry_point_command {
//    uint32_t  cmd;    /* LC_MAIN only used in MH_EXECUTE filetypes */
//    uint32_t  cmdsize;    /* 24 */
//    uint64_t  entryoff;    /* file (__TEXT) offset of main() */
//    uint64_t  stacksize;/* if not zero, initial stack size */
//};
kern_return_t
find_main_binary(pid_t pid, mach_vm_address_t *main_address)
{
  vm_map_t targetTask = 0;
  kern_return_t kr = 0;
    //获取任务端口
  if (task_for_pid(mach_task_self(), pid, &targetTask))
  {
    NSLog(@"[ERROR]权限不够或者没有签名\n");
    return KERN_FAILURE;
  }
  //区域
  vm_address_t iter = 0;
    //从 0区域开始
  while (1)
  {
    struct mach_header mh = {0};
      //区域基址
    vm_address_t addr = iter;
      //区域大小
    vm_size_t lsize = 0;
    uint32_t depth;
    mach_vm_size_t bytes_read = 0;
    struct vm_region_submap_info_64 info;
    mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
      //与mach_vm_region相似 只是可以指定深度
    if (vm_region_recurse_64(
         //任务虚拟内存地址
         targetTask,
         //vm区域的起始地址
         &addr,
         //大小
         &lsize,
         //深度
         &depth,
         //返回的信息
         (vm_region_info_t)&info,
         //条目
         &count))
    {
      break;
    }
      //按 size 大小读取与给定的 target_task 相同区域中的数据
    kr = mach_vm_read_overwrite(
        //任务虚拟内存地址
        targetTask,
        //vm的起始地址
        (mach_vm_address_t)addr,
        //大小
        (mach_vm_size_t)sizeof(struct mach_header),
        //缓冲区
        (mach_vm_address_t)&mh,
        //实际大小
        &bytes_read
        );
      //判断它的实际大小
    if (kr == KERN_SUCCESS && bytes_read == sizeof(struct mach_header))
    {
      //在MH_EXECUTE 文件类型中只有一个
        //4277009103  == 0xfeedface
      if ( (mh.magic == MH_MAGIC || mh.magic == MH_MAGIC_64) && mh.filetype == MH_EXECUTE)
      {
#if DEBUG
        NSLog(@"Found main binary mach-o image @ %p!\n", (void*)addr);
#endif
          //保存地址
        *main_address = addr;
        break;
      }
    }
      //下一区域
    iter = addr + lsize;
  }
  return KERN_SUCCESS;
}
//获取指定地址镜像大小(文件中)  vmaddr_slide为偏移
int64_t
get_image_size(mach_vm_address_t address, pid_t pid, uint64_t *vmaddr_slide)
{
  vm_region_basic_info_data_64_t region_info = {0};
  //分配缓冲区 读取文件头
  //这不是完全正确的因为 64 位的有额外的 4 字节
  //这里就没做处理
  struct mach_header header = {0};
    //获取指定地址的内存数据与内存的相关作息
    //获取文件头
  if (readmem((mach_vm_offset_t*)&header, address, sizeof(struct mach_header), pid, ®ion_info))
  {
    NSLog(@"Can't read header!");
    return -1;
  }
  //文件标识
  if (header.magic != MH_MAGIC && header.magic != MH_MAGIC_64)
  {
		printf("[ERROR] Target is not a mach-o binary!\n");
    return -1;
  }
  //用于保存所有节的总大小(文件)
  int64_t imagefilesize = -1;
  //读取加载命令
  uint8_t *loadcmds = (uint8_t*)malloc(header.sizeofcmds);
    //文件头大小
  uint16_t mach_header_size = sizeof(struct mach_header);
    //判断 mach头
  if (header.magic == MH_MAGIC_64)
  {
      //文件头大小
    mach_header_size = sizeof(struct mach_header_64);
  }
    //获取指定地址的内存数据与内存的相关作息
    //获取所有加载命令节
  if (readmem(
              //缓冲区
              (mach_vm_offset_t*)loadcmds,
              //加载命令基址
              address+mach_header_size,
              //加载命令总大小
              header.sizeofcmds,
              //pid
              pid,
              //内存信息
              ®ion_info))
  {
    NSLog(@"Can't read load commands");
      //释放
    free(loadcmds);
    return -1;
  }
  
  ///处理和检索链接地址和大小
  uint8_t *loadCmdAddress = 0;
    //所有命令的基址
  loadCmdAddress = (uint8_t*)loadcmds;
    //加载命令结构体
  struct load_command *loadCommand    = NULL;
    //节结构体
  struct segment_command *segCmd      = NULL;
    //节结构体 64位
  struct segment_command_64 *segCmd64 = NULL;
    //遍历所有的加载命令
  for (uint32_t i = 0; i < header.ncmds; i++)
  {
      //加载命令
    loadCommand = (struct load_command*)loadCmdAddress;
      // 判断是否为节加载命令(32位)
    if (loadCommand->cmd == LC_SEGMENT)
    {
        //获取节的加载命令
      segCmd = (struct segment_command*)loadCmdAddress;
        //节的名称
      if (strncmp(segCmd->segname, "__PAGEZERO", 16) != 0)
      {
        if (strncmp(segCmd->segname, "__TEXT", 16) == 0)
        {
          *vmaddr_slide = address - segCmd->vmaddr;
        }
          //节文件大小
        imagefilesize += segCmd->filesize;
      }
    }
      // 64位节
    else if (loadCommand->cmd == LC_SEGMENT_64)
    {
      segCmd64 = (struct segment_command_64*)loadCmdAddress;
      if (strncmp(segCmd64->segname, "__PAGEZERO", 16) != 0)
      {
        if (strncmp(segCmd64->segname, "__TEXT", 16) == 0)
        {
          *vmaddr_slide = address - segCmd64->vmaddr;
        }
          //在文件中的大小
        imagefilesize += segCmd64->filesize;
      }
    }
    //下一个命令
    loadCmdAddress += loadCommand->cmdsize;
  }
  free(loadcmds);
    //返回所有节大小和
  return imagefilesize;
}

//将二进制文件每个段转储到所分配的缓冲区中  aslr_slide为偏移
kern_return_t
dump_binary(mach_vm_address_t address, pid_t pid, uint8_t *buffer, uint64_t aslr_slide)
{
    //内存信息结构体
  vm_region_basic_info_data_64_t region_info = {0};
    //文件头
  struct mach_header header = {0};
    //获取指定地址的内存数据与内存的相关作息
  if (readmem((mach_vm_offset_t*)&header, address, sizeof(struct mach_header), pid, ®ion_info))
  {
    NSLog(@"Can't read header!");
    return KERN_FAILURE;
  }
  //文件头标识
  if (header.magic != MH_MAGIC && header.magic != MH_MAGIC_64)
  {
    printf("[ERROR] Target is not a mach-o binary!\n");
    exit(1);
  }
    //在LINKEDIT中读取头信息
  uint8_t *loadcmds = (uint8_t*)malloc(header.sizeofcmds);
  //文件头大小
  uint16_t mach_header_size = sizeof(struct mach_header);
    //判断位数
  if (header.magic == MH_MAGIC_64)
  {
    mach_header_size = sizeof(struct mach_header_64);
  }
    //检索加载命令
    //获取指定地址的内存数据与内存的相关作息
  if (readmem((mach_vm_offset_t*)loadcmds, address+mach_header_size, header.sizeofcmds, pid, ®ion_info))
  {
    NSLog(@"Can't read load commands");
    return KERN_FAILURE;
  }
  
  //处理和检索链接地址和大小
  uint8_t *loadCmdAddress = 0;
    //加载命令基址
  loadCmdAddress = (uint8_t*)loadcmds;
  struct load_command *loadCommand    = NULL;
  struct segment_command *segCmd      = NULL;
  struct segment_command_64 *segCmd64 = NULL;
    //遍历加载命令
  for (uint32_t i = 0; i < header.ncmds; i++)
  {
    loadCommand = (struct load_command*)loadCmdAddress;
    if (loadCommand->cmd == LC_SEGMENT)
    {
      segCmd = (struct segment_command*)loadCmdAddress;
      if (strncmp(segCmd->segname, "__PAGEZERO", 16) != 0)
      {
#if DEBUG
        printf("[DEBUG] Dumping %s at %llx with size %x (buffer:%x)\n", segCmd->segname, segCmd->vmaddr+aslr_slide, segCmd->filesize, (uint32_t)*buffer);
#endif
          //获取指定地址的内存数据与内存的相关作息
        readmem((mach_vm_offset_t*)buffer, segCmd->vmaddr+aslr_slide, segCmd->filesize, pid, ®ion_info);
      }
        //指向下一节
      buffer += segCmd->filesize;
    }
    else if (loadCommand->cmd == LC_SEGMENT_64)
    {
      segCmd64 = (struct segment_command_64*)loadCmdAddress;
      if (strncmp(segCmd64->segname, "__PAGEZERO", 16) != 0)
      {
#if DEBUG
        printf("[DEBUG] Dumping %s at %llx with size %llx (buffer:%x)\n", segCmd64->segname, segCmd64->vmaddr+aslr_slide, segCmd64->filesize, (uint32_t)*buffer);
#endif
          //获取指定地址的内存数据与内存的相关作息
        readmem((mach_vm_offset_t*)buffer, segCmd64->vmaddr+aslr_slide, segCmd64->filesize, pid, ®ion_info);
      }
        //指向下一节
      buffer += segCmd64->filesize;
    }
    // 下一个
    loadCmdAddress += loadCommand->cmdsize;
  }
  free(loadcmds);
  return KERN_SUCCESS;
}

#pragma mark Local functions
//获取指定地址的内存数据与内存的相关作息
static kern_return_t
readmem(mach_vm_offset_t *buffer, mach_vm_address_t address, mach_vm_size_t size, pid_t pid, vm_region_basic_info_data_64_t *info)
{
  // get task for pid
  vm_map_t port;
  
  kern_return_t kr;
  #if DEBUG
      printf("[DEBUG] Readmem of address %llx to buffer %llx with size %llx\n", address, buffer, size);
  #endif
    //获取任务端口
  if (task_for_pid(mach_task_self(), pid, &port))
  {
    fprintf(stderr, "[ERROR]权限不够或者没有签名\n");
    return KERN_FAILURE;
  }
  
  mach_msg_type_number_t info_cnt = sizeof (vm_region_basic_info_data_64_t);
  mach_port_t object_name;
  mach_vm_size_t size_info;
  mach_vm_address_t address_info = address;
//    kern_return_t mach_vm_region
//    (
//     vm_map_t target_task,
//     mach_vm_address_t *address,
//     mach_vm_size_t *size,
//     vm_region_flavor_t flavor,
//     vm_region_info_t info,
//     mach_msg_type_number_t *infoCnt,
//     mach_port_t *object_name
//     );
    //获取map指向的任务内,address地址起始的VM region(虚拟内存区域)的信息。目前标记为flavor只有VM_BASIC_INFO_64
 //获得的info的数据结构如下
  kr = mach_vm_region(
                      //任务虚拟地址
                      port,
                      //vm 的起始地址
                      &address_info,
                      //大小
                      &size_info,
                    //  struct vm_region_basic_info_64 {
                    //      vm_prot_t        protection;
                    //      vm_prot_t        max_protection;
                    //      vm_inherit_t        inheritance;
                    //      boolean_t        shared;
                    //      boolean_t        reserved;
                    //      memory_object_offset_t    offset;
                    //      vm_behavior_t        behavior;
                    //      unsigned short        user_wired_count;
                    //  };
                      //方式
                      VM_REGION_BASIC_INFO_64,
                      //返回信息
                      (vm_region_info_t)info,
                      //条目
                      &info_cnt,
                      &object_name);
  if (kr)
  {
    fprintf(stderr, "[ERROR] mach_vm_region failed with error %d\n", (int)kr);
    return KERN_FAILURE;
  }
  
  //实际读取
  mach_vm_size_t nread;
    //按 size 大小读取与给定的 target_task 相同区域中的数据
  kr = mach_vm_read_overwrite(
      //任务虚拟地址
      port,
      //vm 的起始地址
      address,
      //大小
      size,
      //缓冲区
      (mach_vm_address_t)buffer,
      //实际大小
      &nread);
  if (kr)
  {
    fprintf(stderr, "[ERROR]读取失败%d\n", kr);
    return KERN_FAILURE;
  }
  else if (nread != size)
  {
    fprintf(stderr, "[ERROR] 读取失败,请求大小 size: 0x%llx 实际: 0x%llx\n", size, nread);
    return KERN_FAILURE;
  }
  return KERN_SUCCESS;
}