iOS 模块遍历方法

发表于2015-08-25
评论0 4.4k浏览

 

一、背景

iOS遍历模块有两种办法,一种是通过mach_vm_region系统API,这是推荐的方法;另一种是通过_dyld_get_image_*系列API获取模块信息,不过这种方法不是线程安全的。下面方面介绍两种方法的实现。

 

二、模块遍历实现

1. 通过mach_vm_region系统API获取

参考代码:

https://github.com/davidrhodus/misc/blob/master/iOS-internals/vmmap.c

下面以该代码来讲解下遍历模块实现方法:

#include <mach/vm_map.h>

#include <stdio.h>

 

#include <mach-o/dyld_images.h>

 

int g_pid = 0; // required in iOS 6 (read below)

 

struct dyld_image_info *g_dii = NULL;

int               g_imageCount;

 

unsigned char *readProcessMemory (int pid, mach_vm_address_t addr, mach_msg_type_number_t *size)

{

              // Helper function to read process memory (a la Win32 API of same name)

              // To make it easier for inclusion elsewhere, it takes a pid, and

              // does the task_for_pid by itself. Given that iOS invalidates task ports

              // after use, it's actually a good idea, since we'd need to reget anyway

 

              task_t              t;

              task_for_pid(mach_task_self(),pid, &t);

              mach_msg_type_number_t  dataCnt = size;

              vm_offset_t readMem;

 

              // Use vm_read, rather than mach_vm_read, since the latter is different

              // in iOS.

    // 获取内存

              kern_return_t kr = vm_read(t,        // vm_map_t target_task,

                                                                                                                addr,     // mach_vm_address_t address,

                                                                                                                *size,     // mach_vm_size_t size

                                                                                                                &readMem,     //vm_offset_t *data,

                                                                                                                size);     // mach_msg_type_number_t *dataCnt

 

              if (kr)

              {

                            // DANG..

                            fprintf (stderr, "Unable to read target task's memory @%p - kr 0x%x¥n" , addr, kr);

                            return NULL;

              }

 

              return ( (unsigned char *) readMem);

}

 

kern_return_t mach_vm_read (vm_map_t, mach_vm_address_t, mach_vm_size_t, vm_offset_t *, mach_msg_type_number_t *);

 

void findListOfBinaries(task_t  t, mach_vm_address_t    addr, int size)

{

              kern_return_t kr;

              mach_msg_type_number_t  dataCnt = size;

    // 读取指定内存地址上指定大小的内容

              unsigned char *readData = readProcessMemory (g_pid, addr, &dataCnt);

 

              int machsig = 0xfeedface;

 

              // Checking only 0xfeedfa is a simple way to catch both 64-bit (facf) and 32-bit (face) headers

              // Machine endianness is automatically taken care of, too..

 

    // 判断是否是macho头

              if (readData && memcmp (readData + 1, ((unsigned char *) &machsig) + 1 , 3) == 0)

              {

                            // This is a Mach header

                            int i = 0;

 

                            // A MUCH better way would be to iterate through the LC and find the name of dyld

                            // but this would require my machlib.c (closed source) and really get the same result.

                            // This works because on both iOS and OS X dyld is at /usr/lib.

                            for (i = 0; i <dataCnt; i++)

                            {

            // 循环比较内存内容是否是"lib/dyld"

                                          if (memcmp(readData+i, "lib/dyld", 8) == 0)

                                          {

                                                        unsigned int dyld_all_image_infos_offset ;

                                                        int imageCount = 0;

 

                // 拷贝导入模块信息

                                                        memcpy (&dyld_all_image_infos_offset, readData+DYLD_ALL_IMAGE_INFOS_OFFSET_OFFSET, sizeof (unsigned int));

 

 

                                                        struct dyld_all_image_infos *dyldaii ;

                                                        // Safeguard: should check that dyld_all_image_infos_offset is < size..

 

              

                                                        if (dyld_all_image_infos_offset > size)

                                                        {

                                                                      // This is to be expected, since the dyld_all_image_infos is in a data region

 

                                                                      //printf ("Offset %x is greater than region size : %x¥n", dyld_all_image_infos_offset, size);

                                                                      dataCnt = sizeof(dyld_all_image_infos);

                                                                      readData = readProcessMemory (g_pid, addr + dyld_all_image_infos_offset , &dataCnt);

 

                                                                      if (!readData)

                                                                      {

                                                                                    return;

                                                                      }

                                                                     

                                                                      dyldaii = (struct dyld_all_image_infos *) readData;

 

                                                        }

                                                        else

                                                        {

                                                                      dyldaii = (struct dyld_all_image_infos *) (readData +dyld_all_image_infos_offset);

                                                        }

 

 

                                                        printf ("Version: %d, %d images at offset %p¥n",

                                                        dyldaii->version, dyldaii->infoArrayCount, dyldaii->infoArray);

 

                                                        // Go to dyldaii->infoArray address

 

                                                        imageCount = dyldaii->infoArrayCount;

                                              // 保存导入模块信息

                dataCnt = imageCount * sizeof(struct dyld_image_info);

                                                        g_dii = (struct dyld_image_info *) malloc (dataCnt);

                                                        g_imageCount = imageCount;

                                                        readData = readProcessMemory(g_pid, dyldaii->infoArray, &dataCnt);

                                                        if (!readData)

                                                        {

                                                                      return;

                                                        }

 

                                                        struct dyld_image_info *dii = (struct dyld_image_info *) readData;

 

                                                                      // We don't need i anymore, anyway

                                                        for (i = 0; i < imageCount; i++)

                                                        {

                                                                      dataCnt = 1024;

                                                                      char *imageName = readProcessMemory (g_pid, dii[i].imageFilePath, &dataCnt);

                                                                      if (imageName)

                                                                      {

                                                                                    g_dii[i].imageFilePath = strdup(imageName);

                                                                      }

                                                                      else

                                                                      {

                                                                                    g_dii[i].imageFilePath = NULL;

                                                                      }

                                                                     

                                                                      g_dii[i].imageLoadAddress = dii[i].imageLoadAddress;

                                                        }

 

                                                        break;

                                          }

                            }

              }

}

 

char *behavior_to_text (vm_behavior_t              b)

{

              switch (b)

              {

                            case VM_BEHAVIOR_DEFAULT: return("default");

                            case VM_BEHAVIOR_RANDOM:  return("random");

                            case VM_BEHAVIOR_SEQUENTIAL: return("fwd-seq");

                            case VM_BEHAVIOR_RSEQNTL: return("rev-seq");

                            case VM_BEHAVIOR_WILLNEED: return("will-need");

                            case VM_BEHAVIOR_DONTNEED: return("will-need");

                            case VM_BEHAVIOR_FREE: return("free-nowb");

                            case VM_BEHAVIOR_ZERO_WIRED_PAGES: return("zero-wire");

                            case VM_BEHAVIOR_REUSABLE: return("reusable");

                            case VM_BEHAVIOR_REUSE: return("reuse");

                            case VM_BEHAVIOR_CAN_REUSE: return("canreuse");

                            default: return ("?");

              }

}

char *protection_bits_to_rwx (vm_prot_t p)

{

              // previous version of this somehow lost the "p&", always returning rwx..

              static char returned[4];

 

              returned[0] = (p &VM_PROT_READ    ? 'r' : '-');

              returned[1] = (p &VM_PROT_WRITE   ? 'w' : '-');

              returned[2] = (p & VM_PROT_EXECUTE ? 'x' : '-');

              returned[3] = '¥0';

 

              // memory leak here. No biggy

              return (strdup(returned));

}

 

const char *unparse_inheritance (vm_inherit_t i)

{

              switch (i)

              {

                            case VM_INHERIT_SHARE:

                            return "share";

                            case VM_INHERIT_COPY:

                            return "copy";

                            case VM_INHERIT_NONE:

                            return "none";

                            default:

                            return "???";

              }

}

 

macosx_debug_regions (task_t task, mach_vm_address_t address, int max)

{

              kern_return_t kret;

 

              mach_vm_address_t prev_address;

              /* @TODO: warning - potential overflow here - gotta fix this.. */

              vm_region_basic_info_data_t prev_info,info;

              mach_vm_size_t size, prev_size;

 

              mach_port_t object_name;

              mach_msg_type_number_t count;

 

              int nsubregions = 0; //模块总共读的块数

              int num_printed = 0; // 已遍历的模块数

 

              count = VM_REGION_BASIC_INFO_COUNT_64;

              kret = mach_vm_region (task, &address, &size, VM_REGION_BASIC_INFO,

              (vm_region_info_t) &info, &count, &object_name);

 

              if (kret != KERN_SUCCESS)

              {

                            printf ("mach_vm_region: Error %d - %s", kret, mach_error_string(kret));

                            return;

              }

             

    //保存上一次遍历的模块信息

              memcpy (&prev_info, &info, sizeof (vm_region_basic_info_data_t));

              prev_address = address;

              prev_size = size;

              nsubregions = 1; // 已获取1块

 

              for (;;)

              {

                            int print = 0;

                            int done = 0;

 

                            address = prev_address + prev_size;

 

                            /* Check to see if address space has wrapped around. */

                            if (address == 0) // 遍历模块结束

                            {

                                          print = done = 1;

                            }             

 

                            if (!done)

                            {

                                          // Even on iOS, we use VM_REGION_BASIC_INFO_COUNT_64. This works.

                                          count = VM_REGION_BASIC_INFO_COUNT_64;

           

          // 读取从address开始的第一个正确的模块信息,注意这里可能不是一个模块头

                                          kret = mach_vm_region (task, &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t) &info, &count, &object_name);

 

                                          if (kret != KERN_SUCCESS)

                                          {

                                                        /* iOS 6 workaround - attempt to reget the task port to avoiD */

                                                        /* "(ipc/send) invalid destination port" (1000003 or something) */

                                                        task_for_pid(mach_task_self(),g_pid, &task);

                // 重新获取一遍

                                                        kret = mach_vm_region (task, &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t) &info, &count, &object_name);

                                          }

                                         

                                          if (kret != KERN_SUCCESS)

                                          {

                                                        fprintf (stderr,"mach_vm_region failed for address %p - Error: %x¥n", address,(kret));

                                                        size = 0;

                                                        if (address >= 0x4000000)

                                                                      return;

                                                        print = done = 1;

                                          }

                            }

 

        // 如果上一个内存起始地址+内存大小!=当前的地址,说明是新的模块

                            if (address != prev_address + prev_size)

                                          print = 1;

        // 如果内存属性不同,说明是新的模块

                            if ((info.protection != prev_info.protection)

                                          || (info.max_protection != prev_info.max_protection)

                                          || (info.inheritance != prev_info.inheritance)

                                          || (info.shared != prev_info.shared)

                                          || (info.reserved != prev_info.reserved))

                            {

                                          print = 1;

                            }

 

        //如果是新的模块,获取它的其它信息

                            if (print)

                            {

                                          int              print_size;

                                          char *print_size_unit;

                                         

                                          if (num_printed == 0)

                                                        printf ("Region ");

                                          else

                                                        printf ("   ... ");

 

                                          findListOfBinaries(task, prev_address, prev_size);

                                          /* Quick hack to show size of segment, which GDB does not */

                                          print_size = prev_size;

                                          if (print_size > 1024) { print_size /= 1024; print_size_unit = "K"; }

                                          if (print_size > 1024) { print_size /= 1024; print_size_unit = "M"; }

                                          if (print_size > 1024) { print_size /= 1024; print_size_unit = "G"; }

                                          /* End Quick hack */

                                          printf (" %p-%p [%d%s](%s/%s; %s, %s, %s) %s",

                                                        (prev_address),

                                                        (prev_address + prev_size),

                                                        print_size,

                                                        print_size_unit,

                                                        protection_bits_to_rwx (prev_info.protection),

                                                        protection_bits_to_rwx (prev_info.max_protection),

                                                        unparse_inheritance (prev_info.inheritance),

                                                        prev_info.shared ? "shared" : "private",

                                                        prev_info.reserved ? "reserved" : "not-reserved",

                                                        behavior_to_text (prev_info.behavior));

 

                                          if (nsubregions > 1)

                                                        printf (" (%d sub-regions)", nsubregions);

 

                                          printf ("¥n");

 

            // 把此次内存信息保存起来

                                          prev_address = address;

                                          prev_size = size;

                                          memcpy (&prev_info, &info, sizeof (vm_region_basic_info_data_t));

                                          nsubregions = 1;

 

                                          num_printed++;

                            }

                            else

                            {

                                          prev_size += size; //如果不是新的模块,只增加模块大小

                                          nsubregions++;

                            }

 

                            if ((max > 0) && (num_printed >= max))

                            {

                                          printf ("Max %d num_printed %d¥n", max, num_printed);

                                          done = 1;

                            }

 

                            if (done)

                                          break;

              }

}

 

void main(int argc, char **argv)

{

              struct vm_region_basic_info vmr;

              kern_return_t              rc;

              mach_port_t              task;

 

              mach_vm_size_t              size = 8;

              vm_region_info_t              info = (vm_region_info_t) malloc(10000);

              mach_msg_type_number_t              info_count;

              mach_port_t                            object_name;

              mach_vm_address_t              addr =1; //初始值设为1

              int pid;

 

              if (!argv[1])  //需要提供参数1为目标进程的PID

              {

                            printf ("Usage: %s <PID>¥n");

                            exit (1);

              }

             

              pid = atoi(argv[1]); //把参数1转换成pid

              g_pid = pid; // req for iOS 6

              rc = task_for_pid(mach_task_self(),pid, &task); // 获取目标进程的task

 

              if (rc)

              {

                            fprintf (stderr, "task_for_pid() failed with error %d - %s¥n", rc, mach_error_string(rc));

                            exit(1);

              }

             

              printf ("RC %d - Task: %d¥n",rc, task);

 

              macosx_debug_regions (task, addr, 1000);

 

              int i ;

              for ( i = 0; i < g_imageCount; i++)

              {

                            printf("Image: %s loaded @%p¥n",

                            g_dii[i].imageFilePath, g_dii[i].imageLoadAddress);

              }

             

              printf("Done¥n");

}

 

2. 通过_dyld_get_image_*系列API获取

头文件:dyld.h

函数原型:

//获取当前进程加载的模块数。

uint32_t                    _dyld_image_count(void);

//获取内存中某个模块的头部指针。

const struct mach_header*   _dyld_get_image_header(uint32_t image_index);

//获取内存中某个模块的slide值。

intptr_t                    _dyld_get_image_vmaddr_slide(uint32_t image_index);

// 获取内存中某个模块的名称。

const char*                 _dyld_get_image_name(uint32_t image_index);

实现:

#import <mach-o/dyld.h>

int32_t nModNums= _dyld_image_count();

const char *pszModName = NULL;

intptr_t pModSlide = 0;

const struct mach_header* pModHeader = NULL;

   

for (uint32_t i = 0; i < nModNums; i++)

{

pModSlide  = _dyld_get_image_vmaddr_slide(i);

pszModName = _dyld_get_image_name(i);

pModHeader = _dyld_get_image_header(i);

}

 

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引