一、背景 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 #include #include 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 { // 循环比较内存内容是否是"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 \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 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); }
*转载请注明来自游戏安全实验室(https://gslab.qq.com)
|
最新评论
发表评论