• drizzleDumper的原理分析和使用说明


    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/53561622

    在前面的博客中已经介绍了Android的脱壳工具DexExtractor的原理和使用说明,接下来就来分析一下另一个Android的脱壳工具drizzleDumper的原理和使用说明。drizzleDumper脱壳工具的作者是Drizzle.Risk,他是在strazzere大神的android-unpacker脱壳工具的基础上修改过来的drizzleDumper,他在完成drizzleDumper脱壳工具的时候,对某数字加固、ijiami、bangbang加固进行了脱壳测试,效果比较理想。drizzleDumper脱壳工具是一款基于内存特征搜索的dex文件dump脱壳工具。


    一、drizzleDumper脱壳工具的相关链接和讨论

    github地址:https://github.com/DrizzleRisk/drizzleDumper#drizzledumper

    freebuf地址:http://www.freebuf.com/sectool/105147.html

    看雪地址:http://bbs.pediy.com/showthread.php?goto=nextoldest&nojs=1&t=213174

    android-unpacker地址:https://github.com/strazzere/android-unpacker/tree/master/native-unpacker


    二、drizzleDumper脱壳工具的原理分析(见代码的注释):

    drizzleDumper工作的原理是root环境下,通过ptrace附加需要脱壳的apk进程,然后在脱壳的apk进程的内存中进行dex文件的特征搜索,当搜索到dex文件时,进行dex文件的内存dump

    drizzleDumper.h头文件

    /*
     * drizzleDumper Code By Drizzle.Risk
     * file: drizzleDumper.h
     */
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <dirent.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdarg.h>
    #include <string.h>
    #include <errno.h>
    #include <sys/ptrace.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <linux/user.h>
    
    #ifdef HAVE_STDINT_H
    #include <stdint.h>    /* C99 */
    typedef uint8_t             u1;
    typedef uint16_t            u2;
    typedef uint32_t            u4;
    typedef uint64_t            u8;
    typedef int8_t              s1;
    typedef int16_t             s2;
    typedef int32_t             s4;
    typedef int64_t             s8;
    #else
    typedef unsigned char       u1;
    typedef unsigned short      u2;
    typedef unsigned int        u4;
    typedef unsigned long long  u8;
    typedef signed char         s1;
    typedef signed short        s2;
    typedef signed int          s4;
    typedef signed long long    s8;
    #endif
    
    /*
     * define kSHA1DigestLen
     */
    enum { kSHA1DigestLen = 20,
           kSHA1DigestOutputLen = kSHA1DigestLen*2 +1 };
    
    /*
     * define DexHeader
     */
    typedef struct DexHeader {
        u1  magic[8];           /* includes version number */
        u4  checksum;           /* adler32 checksum */
        u1  signature[kSHA1DigestLen]; /* SHA-1 hash */
        u4  fileSize;           /* length of entire file */
        u4  headerSize;         /* offset to start of next section */
        u4  endianTag;
        u4  linkSize;
        u4  linkOff;
        u4  mapOff;
        u4  stringIdsSize;
        u4  stringIdsOff;
        u4  typeIdsSize;
        u4  typeIdsOff;
        u4  protoIdsSize;
        u4  protoIdsOff;
        u4  fieldIdsSize;
        u4  fieldIdsOff;
        u4  methodIdsSize;
        u4  methodIdsOff;
        u4  classDefsSize;
        u4  classDefsOff;
        u4  dataSize;
        u4  dataOff;
    } DexHeader;
    
    //#define ORIG_EAX 11
    static const char* static_safe_location = "/data/local/tmp/";
    static const char* suffix = "_dumped_";
    
    typedef struct {
      uint32_t start;
      uint32_t end;
    } memory_region;
    
    uint32_t get_clone_pid(uint32_t service_pid);
    
    uint32_t get_process_pid(const char* target_package_name);
    
    char *determine_filter(uint32_t clone_pid, int memory_fd);
    
    int find_magic_memory(uint32_t clone_pid, int memory_fd, memory_region *memory ,const char* file_name);
    
    int peek_memory(int memory_file, uint32_t address);
    
    int dump_memory(const char *buffer , int len , char each_filename[]);
    
    int attach_get_memory(uint32_t pid);
    

    drizzleDumper.c实现文件

    /*
     * drizzleDumper Code By Drizzle.Risk
     * file: drizzleDumper.c
     */
    
    #include "drizzleDumper.h"
    
    
    // 主函数main
    int main(int argc, char *argv[]) {
    
      printf("[>>>]  This is drizzleDumper [<<<]
    ");
      printf("[>>>]    code by Drizzle     [<<<]
    ");
      printf("[>>>]        2016.05         [<<<]
    ");
      
      // 脱壳工具drizzleDumper在工作的实收需要3个参数(需要脱壳的apk的package_name、脱壳等待的时间wait_times(s))
      if(argc <= 1) 
      {
        printf("[*]  Useage : ./drizzleDumper package_name wait_times(s)
    [*]  The wait_times(s) means how long between the two Scans, default 0s  
    [*]  if successed, you can find the dex file in /data/local/tmp
    [*]  Good Luck!
    ");
        return 0;
      }
    
      // 由于脱壳的原理是基于进程的ptrace,需要有root权限
      if(getuid() != 0) 
      {
        printf("[*]  Device Not root!
    ");
        return -1;
      }
    
      double wait_times = 0.01;
      // 脱壳工具drizzleDumper在工作的实收需要3个参数(需要脱壳的apk的package_name、脱壳等待的时间wait_times(s))
      if(argc >= 3)
      {
    	// 获取加固脱壳的等待时间
      	wait_times = strtod(argv[2], NULL);
    	printf("[*]  The wait_times is %ss
    ", argv[2]);
      }
      
      // 获取需要被脱壳的加固apk的包名
      char *package_name = argv[1];
      printf("[*]  Try to Find %s
    ", package_name);
    
      uint32_t pid = -1;
    
      int i = 0;
      int mem_file;
      uint32_t clone_pid;
      char *extra_filter;
      char *dumped_file_name;
    
      // 进入循环
      while(1)
      {
          // 休眠等待一段时间
    	  sleep(wait_times);
    	  
    	  pid = -1;
    	  // 获取加固需要被脱壳的apk的进程pid
    	  pid = get_process_pid(package_name);
          // 判断获取的进程pid是否有效
    	  if(pid < 1 || pid == -1)
    	  {
    		  continue;
    	  }
    	  printf("[*]  pid is %d
    ", pid);
    
          // 获取进程pid的一个线程tid,方便后面进行ptrace附加
    	  clone_pid = get_clone_pid(pid);
    	  if(clone_pid <= 0) 
    	  {
    	    continue;
    	  }
    	  printf("[*]  clone pid is %d
    ", clone_pid);
    
    	  memory_region memory;
    	  printf("[*]  ptrace [clone_pid] %d
    ", clone_pid);
    	  
    	  // 对指定pid进程的克隆即tid进程ptrace附加,获取指定pid进程的内存模块基址
    	  mem_file = attach_get_memory(clone_pid);
    	  // 对获取到的内存有效数据的进行校验3次即最多进行3次脱壳尝试
    	  if(mem_file == -10201) 
    	  {
    	    continue;
    	  }
    	  else if(mem_file == -20402)
    	  {
    	     //continue;
    	  }
    	  else if(mem_file == -30903)
    	  {
    	     //continue
    	  }
    	  
    	  /****
    	   *static const char* static_safe_location = "/data/local/tmp/";
    	   *static const char* suffix = "_dumped_";
    	   ****/
    	
    	  // 申请内存空间保存内存dump出来的dex文件的名称
    	  dumped_file_name = malloc(strlen(static_safe_location) + strlen(package_name) + strlen(suffix));
    	  // 格式化生成存dump出来的dex文件的名称
    	  sprintf(dumped_file_name, "%s%s%s", static_safe_location, package_name, suffix);
    	  
    	  printf("[*]  Scanning dex ...
    ");
    	  
    	  // 通过ptrace附件目标pid进程,在目标进程的pid中进行dex文件的搜索然后进行内存dump
    	  if(find_magic_memory(clone_pid, mem_file, &memory, dumped_file_name) <= 0)
    	  {
    	    printf("[*]  The magic was Not Found!
    ");
    		ptrace(PTRACE_DETACH, clone_pid, NULL, 0);
    		close(mem_file);
    	    continue;
    	  }
    	  else
    	  {
    		 // dex的内存dump成功,跳出循环
    		 close(mem_file);
    		 ptrace(PTRACE_DETACH, clone_pid, NULL, 0);
    		 break;
    	  }
       }
    
      printf("[*]  Done.
    
    ");
      return 1;
    }
    
    // 获取指定进程的一个线程tid
    uint32_t get_clone_pid(uint32_t service_pid)
    {
      DIR *service_pid_dir;
      char service_pid_directory[1024];
      
      // 格式化字符串
      sprintf(service_pid_directory, "/proc/%d/task/", service_pid);
      // 查询指定进程的pid的线程TID的信息
      if((service_pid_dir = opendir(service_pid_directory)) == NULL)
      {
        return -1;
      }
    
      struct dirent* directory_entry = NULL;
      struct dirent* last_entry = NULL;
    
      // 获取指定pid进程的线程TID
      while((directory_entry = readdir(service_pid_dir)) != NULL)
      {
        last_entry = directory_entry;
      }
      if(last_entry == NULL)
        return -1;
    
      closedir(service_pid_dir);
    
      // 返回获取到的指定pid的线程tid
      return atoi(last_entry->d_name);
    }
    
    
    // 通过运行的apk的名称的获取进程的pid
    uint32_t get_process_pid(const char *target_package_name)
    {
      char self_pid[10];
      sprintf(self_pid, "%u", getpid());
    
      DIR *proc = NULL;
    
      if((proc = opendir("/proc")) == NULL)
        return -1;
    
      struct dirent *directory_entry = NULL;
      while((directory_entry = readdir(proc)) != NULL)
      {
    
        if (directory_entry == NULL)
          return -1;
    
        if (strcmp(directory_entry->d_name, "self") == 0 || strcmp(directory_entry->d_name, self_pid) == 0)
            continue;
    
          char cmdline[1024];
          snprintf(cmdline, sizeof(cmdline), "/proc/%s/cmdline", directory_entry->d_name);
          FILE *cmdline_file = NULL;
          if((cmdline_file = fopen(cmdline, "r")) == NULL)
    		  continue;
    
          char process_name[1024];
          fscanf(cmdline_file, "%s", process_name);
          fclose(cmdline_file);
    
          if(strcmp(process_name, target_package_name) == 0)
          {
    		 closedir(proc);
             return atoi(directory_entry->d_name);
          }
        }
    
        closedir(proc);
        return -1;
    }
    
    //  在目标进程的内存空间中进行dex文件的搜索
    int find_magic_memory(uint32_t clone_pid, int memory_fd, memory_region *memory , const char *file_name) {
    	
      int ret = 0;
      char maps[2048];
      
      // 格式化字符串得到/proc/pid/maps
      snprintf(maps, sizeof(maps), "/proc/%d/maps", clone_pid);
    
      FILE *maps_file = NULL;
      // 打开文件/proc/pid/maps,获取指定pid进程的内存分布信息
      if((maps_file = fopen(maps, "r")) == NULL)
      {
        printf(" [+] fopen %s Error  
    " , maps);
        return -1;
      }
    
       char mem_line[1024];
       // 循环读取文件/proc/pid/maps中的pid进程的每一条内存分布信息
       while(fscanf(maps_file, "%[^
    ]
    ", mem_line) >= 0)
       {
    	 char mem_address_start[10]={0};
    	 char mem_address_end[10]={0};
    	 char mem_info[1024]={0};
    
    	 // 解析pid进程的的内存分布信息--内存分布起始地址、内存分布结束地址等
    	 sscanf(mem_line, "%8[^-]-%8[^ ]%*s%*s%*s%*s%s", mem_address_start, mem_address_end, mem_info);
    	 memset(mem_line , 0 ,1024);
    	 
    	 // 获取内存分布起始地址的大小
    	 uint32_t mem_start = strtoul(mem_address_start, NULL, 16);
    	 memory->start = mem_start;
    	 // 获取内存分布结束地址的大小
    	 memory->end = strtoul(mem_address_end, NULL, 16);
    	 // 获取实际的内存区间大小
    	 int len =  memory->end - memory->start;
    	 // 过滤掉不符合条件的内存分布区间
    	 if(len <= 10000)
    	 {//too small
    		continue;
    	 }
    	 else if(len >= 150000000)
    	 {//too big
    		 continue;
    	 }
    
    	  char each_filename[254] = {0};
    	  char randstr[10] = {0};
    	  sprintf(randstr ,"%d", rand()%9999);
    
    	  // 拼接字符串得到dump的dex文件的生成名称
    	  strncpy(each_filename , file_name , 200);	//防溢出
    	  strncat(each_filename , randstr , 10);
    	  strncat(each_filename , ".dex" , 4);
    
    	   // 先将pid进程内存文件句柄的指针置文件开头
    	   lseek64(memory_fd , 0 , SEEK_SET);	
    	   // 设置pid进程内存文件句柄的指针为内存分布起始地址
    	   off_t r1 = lseek64(memory_fd , memory->start , SEEK_SET);
    	   if(r1 == -1)
    	   {
    		   //do nothing
    	   }
    	   else
    	   {
    		  // 根据内存分布区间的大小申请内存空间
    		  char *buffer = malloc(len);
    		  // 读取pid进程的指定区域的内存数据
    		  ssize_t readlen = read(memory_fd, buffer, len);
    		  printf("meminfo: %s ,len: %d ,readlen: %d, start: %x
    ", mem_info, len, readlen, memory->start);
    		  
    		  // 对读取的内存分布区域的数据进行dex文件的扫描和查找
    		  if(buffer[1] == 'E' && buffer[2] == 'L' && buffer[3] == 'F')
    		  {
    			free(buffer);
    
    			continue;
    		  }
    		  
    		  // 查找到dex文件所在的内存区域
    		  if(buffer[0] == 'd' && buffer[1] == 'e' && buffer[2] == 'x' && buffer[3] == '
    '  && buffer[4] == '0' && buffer[5] == '3')
    		  {
    			printf(" [+] find dex, len : %d , info : %s
    " , readlen , mem_info);
    
    			DexHeader header;
    			char real_lenstr[10]={0};
    
    			// 获取内存区域中dex文件的文件头信息
    			memcpy(&header , buffer ,sizeof(DexHeader));
    			sprintf(real_lenstr , "%x" , header.fileSize);
    
    			// 通过dex文件头信息,获取到整个dex文件的大小
    			long real_lennum = strtol(real_lenstr , NULL, 16);
    			printf(" [+] This dex's fileSize: %d
    ", real_lennum);
    
    			// 对dex文件所在的内存区域进行内存dump
    			if(dump_memory(buffer , len , each_filename)  == 1)
    			{
    			  // 打印dump的dex文件的名称
    			  printf(" [+] dex dump into %s
    ", each_filename);
    			  free(buffer);
    			  continue;
    			}
    			else
    			{
    			 printf(" [+] dex dump error 
    ");
    			}
    
    			}
    			
    			free(buffer);
    		   }
    
    		   // 前面的内存方法搜索没有查找dex文件的内存,尝试下面的内存+8位置进行搜索
    		   // 具体什么原因没太明白??
    		   lseek64(memory_fd , 0 , SEEK_SET);	//保险,先归零
    		   r1 = lseek64(memory_fd , memory->start + 8 , SEEK_SET); //不用 pread,因为pread用的是lseek
    		   if(r1 == -1)
    		   {
    			   continue;
    		   }
    		   else
    		   {
    			  char *buffer = malloc(len);
    			  ssize_t readlen = read(memory_fd, buffer, len);
    
    			  if(buffer[0] == 'd' && buffer[1] == 'e' && buffer[2] == 'x' && buffer[3] == '
    '  && buffer[4] == '0' && buffer[5] == '3')
    			  {
    				printf(" [+] Find dex! memory len : %d 
    " , readlen);
    
    				DexHeader header;
    				char real_lenstr[10]={0};
    
    				// 获取内存dex文件的文件头信息
    				memcpy(&header , buffer ,sizeof(DexHeader));
    				sprintf(real_lenstr , "%x" , header.fileSize);
    
    				// 通过dex文件头信息,获取到整个dex文件的大小
    				long real_lennum = strtol(real_lenstr , NULL, 16);
    				printf(" [+] This dex's fileSize: %d
    ", real_lennum);
    
    				// 对dex文件所在的内存区域进行内存dump
    				if(dump_memory(buffer , len , each_filename)  == 1)
    				{
    					printf(" [+] dex dump into %s
    ", each_filename);
    					free(buffer);
    					continue;	//如果本次成功了,就不尝试其他方法了
    				}
    				else
    				{
    				 printf(" [+] dex dump error 
    ");
    				}
    			  }
    			  
    			  free(buffer);
    		   }
    		}
    	fclose(maps_file);
    	
    	return ret;
    }
    
    
    // 从内存中dump数据到文件中
    int dump_memory(const char *buffer , int len , char each_filename[])
    {
    	int ret = -1;
    	
    	// 创建文件
    	FILE *dump = fopen(each_filename, "wb");
    	// 将需要dump的内存数据写入到/data/local/tmp文件路径下
    	if(fwrite(buffer, len, 1, dump) != 1)
    	{
      	    ret = -1;
    	}
    	else
    	{
    		ret = 1;
    	}
    
    	fclose(dump);
     	return ret;
    }
    
    // 获取指定附加pid进程的内存模块基址
    int attach_get_memory(uint32_t pid) {
    	
      char mem[1024];
      bzero(mem,1024);
      
      // 格式化字符串得到字符串/proc/pid/mem
      snprintf(mem, sizeof(mem), "/proc/%d/mem", pid);
    
      int ret = -1;
      int mem_file;
      
      // 尝试ptrace附加目标pid进程
      ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
      // 对ptrace附加目标pid进程的操作结果进行判断
      if (0 != ret)
      {
    	  int err = errno;	//这时获取errno
    	  if(err == 1) //EPERM
    	  {
    		  return -30903;	//代表已经被跟踪或无法跟踪
    	  }
    	  else
    	  {
    		  return -10201;	//其他错误(进程不存在或非法操作)
    	  }
      }
      else
      {
    	  // ptrace附加目标进程pid成功,获取指定pid进程的内存模块基址
    	  // 获取其它进程的内存模块基址,需要root权限
    	  if(!(mem_file = open(mem, O_RDONLY)))
    	  {
    	    return -20402;  	//打开错误
    	  }
      }
      
      return mem_file;
    }
    

    drizzleDumper的编译配置文件Android.mk

    LOCAL_PATH := $(call my-dir)
    
    TARGET_PIE := true
    NDK_APP_PIE := true
    
    include $(CLEAR_VARS)
    
    # 需要编译的源码文件
    LOCAL_SRC_FILES := 
      drizzleDumper.c
    LOCAL_C_INCLUDE := 
      drizzleDumper.h 
      definitions.h
    
    LOCAL_MODULE := drizzleDumper
    LOCAL_MODULE_TAGS := optional
    
    # Allow execution on android-16+
    # 支持PIE
    LOCAL_CFLAGS += -fPIE
    LOCAL_LDFLAGS += -fPIE -pie
    
    # 编译生成可执行ELF文件
    include $(BUILD_EXECUTABLE)
    
    include $(call all-makefiles-under,$(LOCAL_PATH))
    

    三、drizzleDumper的使用说明

    关于drizzleDumper的使用,作者已经在freebuf的文章中已经讲的很详细了,具体的修改的地方也指出来了。



    四、下面就使用nexcus 5的已经root的真机进行drizzleDumper的脱壳实战(以com.qihoo.freewifi为例)

    在cmd控制台的条件下,执行cd命令进入到存放drizzleDumper的文件夹,然后将drizzleDumper文件推送到android手机的/data/local/tmp文件夹下并赋予可执行权限,然后根据每种android加固的特点,选择需要脱壳的apk和drizzleDumper运行的先后顺序,调整能够脱壳成功的过程。这里使用的com.qihoo.freewifi为例,先运行com.qihoo.freewifi程序,然后adb shell条件下su提权执行drizzleDumper的脱壳操作,等待2秒。

    cd xxxxx/drizzleDumper
    
    adb push drizzleDumper /data/local/tmp
    adb shell chmod 0777 /data/local/tmp/drizzleDumper
    
    adb shell						#进入androd系统的shell
    su							#获取root权限
    ./data/local/tmp/drizzleDumper com.qihoo.freewifi 2	#执行脱壳操作




    说明:对脱壳是否成功,这个估计有一定的概率性,主要的目的是学习工具作者的脱壳思想和方法,自己去实践,不管怎样谢谢工具的作者Drizzle.Risk,代码中有理解错误的地方希望大牛不吝赐。


    编译好的drizzleDumper文件和代码的打包下载地址:http://download.csdn.net/detail/qq1084283172/9707768


    参考网址

    http://www.freebuf.com/sectool/105147.html

    https://github.com/DrizzleRisk/drizzleDumper




  • 相关阅读:
    linux常用命令:
    解决css添加padding后元素变长的问题
    Hbase常用命令
    集群部署的三种方式(hadoop集群部署三种方式)
    linux编译安装指定依赖的软件包
    vue使用Element隐藏侧边栏进度条
    css相对于父容器,固定放在底部并撑满
    java中23种设计模式
    adb remount'的作用是什么?在什么情况下有用?
    java常用http请求库
  • 原文地址:https://www.cnblogs.com/csnd/p/11800663.html
Copyright © 2020-2023  润新知