zl程序教程

您现在的位置是:首页 >  其他

当前栏目

drizzleDumper的原理分析和使用说明

原理 分析 说明 使用
2023-09-11 14:14:05 时间

本文博客地址: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 [<<<]\n");
  printf("[>>>]    code by Drizzle     [<<<]\n");
  printf("[>>>]        2016.05         [<<<]\n");
  
  // 脱壳工具drizzleDumper在工作的实收需要3个参数(需要脱壳的apk的package_name、脱壳等待的时间wait_times(s))
  if(argc <= 1) 
  {
    printf("[*]  Useage : ./drizzleDumper package_name wait_times(s)\n[*]  The wait_times(s) means how long between the two Scans, default 0s  \n[*]  if successed, you can find the dex file in /data/local/tmp\n[*]  Good Luck!\n");
    return 0;
  }

  // 由于脱壳的原理是基于进程的ptrace,需要有root权限
  if(getuid() != 0) 
  {
    printf("[*]  Device Not root!\n");
    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\n", argv[2]);
  }
  
  // 获取需要被脱壳的加固apk的包名
  char *package_name = argv[1];
  printf("[*]  Try to Find %s\n", 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\n", pid);

      // 获取进程pid的一个线程tid,方便后面进行ptrace附加
	  clone_pid = get_clone_pid(pid);
	  if(clone_pid <= 0) 
	  {
	    continue;
	  }
	  printf("[*]  clone pid is %d\n", clone_pid);

	  memory_region memory;
	  printf("[*]  ptrace [clone_pid] %d\n", 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 ...\n");
	  
	  // 通过ptrace附件目标pid进程,在目标进程的pid中进行dex文件的搜索然后进行内存dump
	  if(find_magic_memory(clone_pid, mem_file, &memory, dumped_file_name) <= 0)
	  {
	    printf("[*]  The magic was Not Found!\n");
		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.\n\n");
  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  \n" , maps);
    return -1;
  }

   char mem_line[1024];
   // 循环读取文件/proc/pid/maps中的pid进程的每一条内存分布信息
   while(fscanf(maps_file, "%[^\n]\n", 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\n", 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] == '\n'  && buffer[4] == '0' && buffer[5] == '3')
		  {
			printf(" [+] find dex, len : %d , info : %s\n" , 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\n", real_lennum);

			// 对dex文件所在的内存区域进行内存dump
			if(dump_memory(buffer , len , each_filename)  == 1)
			{
			  // 打印dump的dex文件的名称
			  printf(" [+] dex dump into %s\n", each_filename);
			  free(buffer);
			  continue;
			}
			else
			{
			 printf(" [+] dex dump error \n");
			}

			}
			
			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] == '\n'  && buffer[4] == '0' && buffer[5] == '3')
			  {
				printf(" [+] Find dex! memory len : %d \n" , 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\n", real_lennum);

				// 对dex文件所在的内存区域进行内存dump
				if(dump_memory(buffer , len , each_filename)  == 1)
				{
					printf(" [+] dex dump into %s\n", each_filename);
					free(buffer);
					continue;	//如果本次成功了,就不尝试其他方法了
				}
				else
				{
				 printf(" [+] dex dump error \n");
				}
			  }
			  
			  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