PMD工具(Ptrace-based Memory Dump)源代码(注释版)

来源:互联网 发布:云计算运维工程师待遇 编辑:程序博客网 时间:2024/05/16 10:43

1. PMD工具

PMD工具是基于ptrace的Android手机内存dump工具,其能够获取Android手机指定进程的内存,并生成相应的map文件和mem文件。

Usage: pid out_dir [-s <image> <offset>]

PMD工具的编译与使用见我的另一篇博客:
http://blog.csdn.net/rzwinters/article/details/75517107

2. Android.mk与Application.mk

  • Android.mk
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := pmdLOCAL_SRC_FILES := pmd.cLOCAL_CPPFLAGS := -O3 -Wall -fPIELOCAL_LDLIBS := -O3 -llog -fPIE -pieLOCAL_C_INCLUDES := /home/richard/android-ndk-r15b/platforms/android-19/arch-arm/usr/include/include $(BUILD_EXECUTABLE)
  • Application.mk
APP_ABI := armeabiAPP_PLATFORM := android-19

3. 源代码(注释版)

#define _LARGEFILE64_SOURCE //for using function lseek64#include <unistd.h>#include <stdlib.h>#include <sys/types.h>#include <sys/ptrace.h>#include <sys/prctl.h>#include <sys/wait.h>#include <linux/user.h>#include <dirent.h>#include <string.h>#include <signal.h>#include <errno.h>#include <stdio.h>#include <fcntl.h>#include <sys/stat.h>#define DROID_VERSION#ifdef DROID_VERSION# include <android/log.h>#endif//#define LOG_ON //for LOG#undef LOG_ON/* Variadic macros for printing log and err info */#ifdef DROID_VERSION# define PMD_LOG(fmt, ...) __android_log_print(ANDROID_LOG_ERROR, "[pmd]", "%s: "fmt, __func__, ##__VA_ARGS__)#else# define PMD_LOG(fmt, ...) fprintf(stderr, "[ ] "__func__ fmt, ##__VA_ARGS__)#endif#define PMD_ERR(prefix, ...) PMD_LOG(prefix": %s\n", ##__VA_ARGS__, strerror(errno))#define n_ARRAY(array) (sizeof(array)/sizeof(array[0]))/* Segments need to be skipped when dumping memory */const char *skip_segments[] = { "/dev/kgsl-3d0"};/* Segements must be injected when dumping memory */const char *must_injects[] = { "/dev/ashmem"};static inline int is_in_string_list (const char* s,                   const char* strings[],                   unsigned n_strings){  unsigned i;  /* Check if s is in the strings */  for(i = 0; i < n_strings; i++)  {    if(strcmp(strings[i], s) == 0) return 1;  }  return 0;}#define is_in_skip_segments(s) is_in_string_list(s, skip_segments, n_ARRAY(skip_segments))#define is_in_must_inject(s)   is_in_string_list(s, must_injects, n_ARRAY(must_injects))static inline size_tget_tids(pid_t **const listptr, size_t *const sizeptr, const pid_t pid){    char     dirname[64];    DIR     *dir;    pid_t   *list;    size_t   size, used = 0;    list = *listptr = NULL;    size = *sizeptr = 0;    /* Open the task dir of the target process in proc filesystem */    sprintf(dirname, "/proc/%d/task/", (int)pid);     dir = opendir(dirname);    if (!dir) {        PMD_ERR("open task dir");        return 0;    }    while (1) {        struct dirent *ent;        int            value;        char           dummy;        /* Read the entries in the task dir */        ent = readdir(dir);        if (!ent)            break;        /* Parse TIDs. Ignore non-numeric entries. */        if (sscanf(ent->d_name, "%d%c", &value, &dummy) != 1)            continue;        /* Ignore obviously invalid entries. */        if (value < 1)            continue;        /* Make sure there is room for another TID. */        if (used >= size) {            size = (used | 127) + 128;            list = realloc(list, size * sizeof list[0]); //expansion            *listptr = list; //write back            *sizeptr = size; //write back        }        /* Add to list. */        list[used++] = (pid_t)value;    }    closedir(dir);     return used; //return, write back}static inline voidcheck_new_tids(pid_t **const listptr, size_t *const usedptr, size_t *const sizeptr,               const pid_t pid, const unsigned char let_run) {  int used = *usedptr;  int max = *sizeptr;  pid_t *list = *listptr;  /* Get current existing TIDs in the target process */  pid_t *new_list;  int new_max;  int new_used = get_tids(&new_list , &new_max, pid);  /* Find new threads of the target process */  int i, j;  for(i = 0; i < new_used; i++) {    unsigned char found = 0;    if(new_list[i] == pid) continue; //avoid PTRACEing the main process for twice    for(j = 0; j < used; j++) {      if(list[j] == new_list[i]) {        found = 1;        break;      }    }    /* PTRACE the new thread */    if(!found) {      PMD_LOG("New Thread %d", new_list[i]);      if(ptrace(PTRACE_ATTACH, new_list[i], NULL, NULL) == -1)        PMD_ERR("ptrace thread %d", new_list[i]);      /* Continue the new thread */      if(let_run)        ptrace(PTRACE_CONT, new_list[i], NULL, NULL);    }  }  /* Write back new list */  free(list);  *listptr = new_list;  *usedptr = new_used;  *sizeptr = new_max;}static inline pid_t wait_and_check_threads(pid_t pid, int* status, pid_t**const tids,                                           size_t*const n_tids, size_t*const max_tids,                                           unsigned char let_run){//#define DEBUG_WAIT //for DEBUG#ifdef DEBUG_WAIT  int cnt = 0;#endif  pid_t stopped = 0;  int stat = 0;  errno = 0;  while(stopped == 0) {    /* Check and PTRACE new threads in the target process */    check_new_tids(tids, n_tids, max_tids, pid, let_run);    /* Check if any thread is stopped */    stopped = waitpid((pid_t)-1, &stat, __WALL|WNOHANG);#ifdef DEBUG_WAIT    if(cnt++ == 10000) {      PMD_ERR("In Wait %d %d", stopped, stat);      cnt = 0;    }#endif  }#ifdef DEBUG_WAIT  PMD_ERR("returning %d %d", stopped, stat);#endif  if(status != NULL)    *status = stat; //return status  return stopped; //return the pid of the stopped thread}int getdata(pid_t child, long addr,             char *str, int len){       char *laddr;    int i, j;    union u {            long val;            char chars[sizeof(long)];    }data;    i = 0;    j = len / sizeof(long);    laddr = str;    /* Read data from the memory of target process, starting from addr, length of len */    /* Each loop read data of sizeof(long) bytes */    while(i < j) {        errno = 0;        data.val = ptrace(PTRACE_PEEKDATA, child,                          (void*)(addr + i * 4), NULL);        if(errno != 0) {          PMD_ERR("read 0x%lx", (addr + i * 4));          return 1;        }        memcpy(laddr, data.chars, sizeof(long)); //save data        ++i;        laddr += sizeof(long); //prepare for the next loop    }    /* Read remaining data */    j = len % sizeof(long);    if(j != 0) {        errno = 0;        data.val = ptrace(PTRACE_PEEKDATA, child,                          (void*)(addr + i * 4), NULL);        if(errno != 0) {          PMD_ERR("read 0x%lx", (addr + i * 4));          return 1;        }        memcpy(laddr, data.chars, j); //save remaining data    }    return 0;}int putdata(pid_t child, long addr,            const char *str, int len){       const char *laddr;    int i, j;    union u {            long val;            char chars[sizeof(long)];    }data;    i = 0;    j = len / sizeof(long);    laddr = str;    /* Write data into the memory of target process, starting from addr, length of len */    /* Each loop write data of sizeof(long) bytes */    while(i < j) {        memcpy(data.chars, laddr, sizeof(long));        if(ptrace(PTRACE_POKEDATA, child,               (void*)(addr + i * 4), (void*)data.val) == -1) //write data          return 1;        ++i;        laddr += sizeof(long); //prepare for the next loop    }    /* Write remaining data */    j = len % sizeof(long);    if(j != 0) {        memcpy(data.chars, laddr, j);        if(ptrace(PTRACE_POKEDATA, child,               (void*)(addr + i * 4), (void*)data.val) == -1) //write remaining data          return 1;    }    return 0;}#define BUF_SZ 8192 // bytesunsigned long buf[BUF_SZ/sizeof(unsigned long)];static inline int dump_region_inject(int out_fd, pid_t pid, off64_t start, off64_t end,                          pid_t**const tids, size_t*const n_tids, size_t*const max_tids){  int ret = 0;  struct pt_regs in_regs, out_regs, save_regs;  /* Save all the current reg values */  ptrace(PTRACE_GETREGS, pid, NULL, &save_regs);  ptrace(PTRACE_GETREGS, pid, NULL, &in_regs);  /* Each loop dump maximum data of BUF_SZ bytes */  while(start != end) {      int read_sz = ((end - start) > BUF_SZ ? BUF_SZ : (end - start));      int i;      /* Each loop read data of sizeof(unsigned long) bytes from the memory of target process */      for(i = 0; i < read_sz / sizeof(unsigned long); i++) {        /* Modify the value of register R3 to start */        in_regs.ARM_r3 = (unsigned long)start;        /* Set the modified reg values back */        ptrace(PTRACE_SETREGS, pid, NULL, &in_regs);        /* Resume all threads */        /* Execute the shell code to load the data at start to register R2, and then stop */        ptrace(PTRACE_CONT, pid, NULL, NULL); // execute shell        int status;        pid_t p;        /* Wait the main process to stop */        do {          p = wait(&status);          //p = wait_and_check_threads(pid, &status, tids, n_tids, max_tids, 0);        } while(p != pid);        /* Check if the main process is stopped by signal SIGTRAP */        if(!(WIFSTOPPED(status)) || WSTOPSIG(status) != SIGTRAP) {          PMD_LOG("Wait returned %d: status %s", p, strsignal(WSTOPSIG(status)));          ret = 1;          goto out;        }        /* Save all the current reg values */        ptrace(PTRACE_GETREGS, pid, NULL, &out_regs);#ifdef LOG_ON        /* Check if PC skipped */        if(out_regs.ARM_pc != in_regs.ARM_pc + 4)        {          PMD_LOG("PC Skip? %lx %lx", out_regs.ARM_pc, in_regs.ARM_pc);          ret = 1;          goto out;        }#endif        buf[i] = out_regs.ARM_r2; //put the loaded data in register R2 into buf        start += sizeof(unsigned long); //prepare for the next loop      }      /* Write the loaded data in buf into the output file */      if(write(out_fd, buf, read_sz) != read_sz)      {        PMD_ERR("write 0x%llx %d", lseek64(out_fd, 0, SEEK_CUR), read_sz);        ret = 2;        goto out;      }  }out:  /* Set the saved reg values back */  ptrace(PTRACE_SETREGS, pid, NULL, &save_regs);  return ret;}static inline int dump_region_ptrace(pid_t child, int out_fd, off64_t start, off64_t end){    /* Each loop dump maximum data of BUF_SZ bytes */    while(start != end) {        register int read_sz = ((end - start) > BUF_SZ ? BUF_SZ : (end - start));        /* Read data from the memory of target process, starting from start, length of read_sz */        if(getdata(child, start, (char*)buf, read_sz) != 0)        {          PMD_ERR("getdata 0x%llx %d", start, read_sz);          return 1;        }        /* Write data into the output file */        if(write(out_fd, buf, read_sz) != read_sz)        {          PMD_ERR("write 0x%llx %d", lseek64(out_fd, 0, SEEK_CUR), read_sz);          return 2;        }        start += read_sz; //prepare for the next loop    }    return 0;}static inline int dump_region_fd(int out_fd, int in_fd, off64_t start, off64_t end){    /* Modify current file offset to start */    if(lseek64(in_fd, start, SEEK_SET) != start)    {      PMD_ERR("seek");      return -1;    }    /* Each loop dump maximum data of BUF_SZ bytes */    while(start != end) {        register int read_sz = ((end - start) > BUF_SZ ? BUF_SZ : (end - start));        /* Read data from the input file, starting from start, length of read_sz */        if(read(in_fd, buf, read_sz) != read_sz)         {          PMD_ERR("read 0x%llx %d", lseek64(in_fd, 0, SEEK_CUR), read_sz);          return 1;        }        /* Write data into the output file */        if(write(out_fd, buf, read_sz) != read_sz)        {          PMD_ERR("write 0x%llx %d", lseek64(out_fd, 0, SEEK_CUR), read_sz);          return 2;        }        start += read_sz; //prepare for the next loop    }#undef BUF_SZ    return 0;}#define ARM_BP_STR   "\xf0\x01\xf0\xe7"#define THUMB_BP_STR "\xbe\xbe\xbe\xbe"static inline void dump_mem(pid_t pid, int mem, int mem_file, FILE* maps, FILE* map_file,                            pid_t**const tids, size_t*const n_tids, size_t*const max_tids){  char buf[BUFSIZ + 1];  char name[BUFSIZ + 1];  off64_t start, end, pos;#ifdef LOG_ON  int regions = 0;#endif/* Define the shell code (ldr instruction + BP instruction) */  #define ARM_LINUX_BP  const char shell[] = "\x00\x20\x93\xe5"  // ldr r2, [r3]#ifdef ARM_LINUX_BP                       ARM_BP_STR; // ARM breakpoint#endif#ifdef THUMB_BP                       THUMB_BP_STR; // Thumb breakpoint#endif  char save[sizeof(shell)];  /* Insert shell code */  /* Save all the current reg values */  struct pt_regs regs;  ptrace(PTRACE_GETREGS, pid, NULL, &regs);  /* Save old instructions at PC */  if(getdata(pid, regs.ARM_pc, save, sizeof(save)) != 0)    return;  /* Write shell code instructions to PC */  if(putdata(pid, regs.ARM_pc, shell, sizeof(shell)) != 0)    return;  /* Traverse every segment in the maps file */  while(fgets(buf, BUFSIZ, maps))  {    name[0] = '\0';    sscanf(buf, "%llx-%llx %*s %*x %*x:%*x %*u %s\n", &start, &end, name);    /* Check if the segment needs to be skipped */    if(is_in_skip_segments(name)) continue;    /* Get the current file offset in mem_file */    pos = lseek64(mem_file, 0, SEEK_CUR);    /* Check if the segment must be injected */    if(is_in_must_inject(name))    {      /* Dump memory using PTRACE */      if(dump_region_ptrace(pid, mem_file, start, end))      {        PMD_ERR("dump ptrace: %llx->%llx %s", start, end, name);        continue;      }    }    // first try to read from proc/mem (WAY MUCH FASTER)    /* Dump memory using mem file */    else if(dump_region_fd(mem_file, mem, start, end))    {      PMD_LOG("injecting for: %llx->%llx %s", start, end, name);      // if that doesn't work, then try inject      /* Dump memory by injecting shell code */      if(dump_region_inject(mem_file, pid, start, end, tids, n_tids, max_tids))      {        PMD_ERR("dump: %llx->%llx %s", start, end, name);        continue;      }    }    /* Write into map_file */    fprintf(map_file, "%llx->%llx->%llx %s\n", start, end, pos, name);#ifdef LOG_ON    regions++;    PMD_LOG("dump [%d] = %s = %llx", regions, buf, pos);#endif  }  /* Set the saved reg values back */  ptrace(PTRACE_SETREGS, pid, NULL, &regs);  /* Write saved old instructions back to PC */  if(putdata(pid, regs.ARM_pc, save, sizeof(save)) != 0)    return;#ifdef LOG_ON  PMD_LOG("Done: dumped %d regions.\n", regions);#else  PMD_LOG("Done.\n");#endif}uintptr_t find_start_of_map(FILE* maps, char* image){  char buf[BUFSIZ + 1];  char name[BUFSIZ + 1];  char perms[5];  uintptr_t start;  /* Find the image (code segment) in the maps file of target process */  while(fgets(buf, BUFSIZ, maps))  {    name[0] = '\0';    sscanf(buf, "%x-%*x %s %*x %*x:%*x %*u %s\n", &start, perms, name);    /* match the name and the perms (code segment) */    if(strstr(name, image) != NULL && strstr(perms, "r-x") == perms)        return start; //return the start address of the image (code segment)  }  return 0;}void hold_for_break(pid_t pid, pid_t**const tids, size_t*const n_tids, size_t*const max_tids,                    FILE* maps, char* image, uintptr_t offset){    /* Find the start address of the image (code segment) in the memory of target process */    uintptr_t addr = find_start_of_map(maps, image);    rewind(maps); //rewind the ptr of maps file    if(addr == 0)    {      PMD_LOG("ERROR! No matching map for %s", image);      return;    }    addr+=offset; //locate the address    /* Define the breakpoint instruction (ARM and Thumb)*/    char bp[] =#ifdef ARM_LINUX_BP                       ARM_BP_STR; // ARM breakpoint#endif#ifdef THUMB_BP                       THUMB_BP_STR; // Thumb breakpoint#endif    char save[sizeof(bp)];    char check[sizeof(bp)];    /* Insert the BP instruction into memory at addr */    /* Save the old instruction at addr */    if(getdata(pid, addr, save, 4) != 0)      return;    /* Write the BP instruction to addr */    if(putdata(pid, addr, bp, 4) != 0)      return;    /* Check if the BP instruction has been inserted */    if(getdata(pid, addr, check, 4) != 0)      return;    if(memcmp(bp, check, 4) != 0) {      PMD_ERR("NOT SETTING BP!!!!!!!!!");      return;    }    PMD_LOG("Put BP on 0x%x", addr);    PMD_LOG("Now holding for BP....");    kill(pid, SIGCONT); //resume all threads    unsigned char hold = 1;    pid_t stopped = 0;    struct pt_regs regs;    while(hold)    {      int status = 0;      /* Check new threads and wait a thread to stop */      stopped = wait_and_check_threads(pid, &status, tids, n_tids, max_tids, 1);       if(stopped == -1) {        PMD_ERR("wait?");        return;      }      ptrace(PTRACE_GETREGS, stopped, NULL, &regs); //get current reg values of the stopped thread      /* Check if the thread is stopped by signal SIGTRAP */      /* OR */      /* Check if the thread is stopped at the inserted BP instruction */      if((WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) ||         (addr - 32 <= regs.ARM_pc && regs.ARM_pc <= addr + 32)) //the thread hit the inserted BP instruction      {        hold = 0;#ifdef LOG_ON        PMD_LOG("Hit BP %d: pc 0x%lx status %s", stopped,                  regs.ARM_pc, strsignal(WSTOPSIG(status)));#else        PMD_LOG("Hit our BP!");#endif      }      else //classify and print the stopped info of the thread      {        int sig = 0;        if (WIFEXITED(status)) { //exit#ifdef LOG_ON          PMD_LOG("Wait Ret: Exited %d: pc 0x%lx status %d", stopped,                  regs.ARM_pc, WEXITSTATUS(status));#endif        } else if (WIFSIGNALED(status)) { //killed by signal#ifdef LOG_ON          PMD_LOG("Wait Ret: Killed by signal %d: pc 0x%lx status %s", stopped,                  regs.ARM_pc, strsignal(WTERMSIG(status)));#endif        } else if (WIFSTOPPED(status)) { //stopped by signal#ifdef LOG_ON          PMD_LOG("Wait Ret: Stopped by signal %d: pc 0x%lx status %s", stopped,                  regs.ARM_pc, strsignal(WSTOPSIG(status)));#endif        }        ptrace(PTRACE_CONT, stopped, NULL, (void*)(sig)); //resume the stopped thread, ignore the signal      }    }    /* Change the PC and set back the modified reg values of the stopped thread in order to resume running */    regs.ARM_pc = addr;    ptrace(PTRACE_SETREGS, stopped, NULL, &regs);    /* Write the saved old instruction back at addr */    putdata(pid, addr, save, 4);    kill(pid, SIGSTOP); // stop other threads    /* Wait until the main process stopped */    do {      stopped = wait_and_check_threads(pid, NULL, tids, n_tids, max_tids, 0);    } while (stopped != pid);}int main(int argc, char *argv[]){    FILE *maps = NULL, *map_file = NULL;    int mem = -1, mem_file = -1;    int error = EXIT_SUCCESS;    pid_t pid;    pid_t *tid = 0;    size_t tids = 0;    size_t tids_max = 0;    int t;    char path[BUFSIZ];    /* Validate input arguments */    if(argc < 3 || argc > 6) {        PMD_LOG("usage: %s pid out_dir [-s <image> <offset>]\n", argv[0]);        return EXIT_FAILURE;    }    /* Get the PID of the target process */    pid = strtol(argv[1], NULL, 10);     /* Create the output map_file */    snprintf(path, sizeof(path), "%s/%d.map", argv[2], pid);    map_file = fopen(path, "w");    if(!map_file) {      PMD_ERR("%s", path);      error = EXIT_FAILURE;      goto clean_up;    }    /* Create the output mem_file */    snprintf(path, sizeof(path), "%s/%d.mem", argv[2], pid);    mem_file = open(path, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);    if(mem_file < 0) {      PMD_ERR("%s", path);      error = EXIT_FAILURE;      goto clean_up;    }    /* PTRACE the target process */    if(ptrace(PTRACE_ATTACH, pid, NULL, NULL) == -1) {      PMD_ERR("ptrace");      error = EXIT_FAILURE;      goto clean_up;    }    /* Get current existing TIDs in the target process */    check_new_tids(&tid, &tids, &tids_max, pid, 0);    /* Open the maps file of the target process in proc filesystem */    snprintf(path, sizeof(path), "/proc/%d/maps", pid);    maps = fopen(path, "r");    if(!maps) {      PMD_ERR("%s", path);      error = EXIT_FAILURE;      goto clean_up;    }    /* Open the mem file of the target process in proc filesystem */    snprintf(path, sizeof(path), "/proc/%d/mem", pid);    mem = open(path, O_RDONLY);    if(mem < 0) {      PMD_ERR("%s", path);      error = EXIT_FAILURE;      goto clean_up;    }    /* Deal with the added arguments: [-s <image> <offset>] */    /* GUESS USAGE: Run the process to the given address (image address + offset) before dumping memory */    if(argc > 3) {      hold_for_break(pid, &tid, &tids, &tids_max, maps, argv[4], strtoll(argv[5], NULL, 0));    }    /* Dump the memory of the target process */    dump_mem(pid, mem, mem_file, maps, map_file, &tid, &tids, &tids_max);clean_up:    /* Detach the main process and all the threads */    ptrace(PTRACE_DETACH, pid, NULL, NULL);    for (t = 0; t < tids; t++) {      if(tid[t] != pid)        ptrace(PTRACE_DETACH, tid[t], NULL, NULL);    }    kill(pid, SIGCONT); //resume the main process    /* Close all the open files */    if(mem >= 0)        close(mem);    if(mem_file >= 0)        close(mem_file);    if(maps)        fclose(maps);    if(map_file)        fclose(map_file);    return error;}
原创粉丝点击