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 [<<<]\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
jpg改rar
drizzleDumper的原理分析和使用说明的更多相关文章
- DexHunter的原理分析和使用说明(二)
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/53715325 前面的博文<Android通用脱壳工具DexHunter的原理 ...
- DexExtractor的原理分析和使用说明
本文博客链接:http://blog.csdn.net/qq1084283172/article/details/53557894 周末有空就写下博客了,今天来扯一扯Android平台的脱壳工具Dex ...
- DexHunter的原理分析和使用说明(一)
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/53710357 Android通用脱壳工具DexHunter是2015年下半年,大牛 ...
- DexHunter在Dalvik虚拟机模式下的脱壳原理分析
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78494671 在前面的博客<DexHunter的原理分析和使用说明(一)&g ...
- Handler系列之原理分析
上一节我们讲解了Handler的基本使用方法,也是平时大家用到的最多的使用方式.那么本节让我们来学习一下Handler的工作原理吧!!! 我们知道Android中我们只能在ui线程(主线程)更新ui信 ...
- Java NIO使用及原理分析(1-4)(转)
转载的原文章也找不到!从以下博客中找到http://blog.csdn.net/wuxianglong/article/details/6604817 转载自:李会军•宁静致远 最近由于工作关系要做一 ...
- 原子类java.util.concurrent.atomic.*原理分析
原子类java.util.concurrent.atomic.*原理分析 在并发编程下,原子操作类的应用可以说是无处不在的.为解决线程安全的读写提供了很大的便利. 原子类保证原子的两个关键的点就是:可 ...
- Android中Input型输入设备驱动原理分析(一)
转自:http://blog.csdn.net/eilianlau/article/details/6969361 话说Android中Event输入设备驱动原理分析还不如说Linux输入子系统呢,反 ...
- 转载:AbstractQueuedSynchronizer的介绍和原理分析
简介 提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架.该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础.使用的方法是继承,子类通过 ...
随机推荐
- (笔记)Linux下检测网卡与网线连接状态
http://blog.chinaunix.net/space.php?uid=20357359&do=blog&cuid=1798479 Linux下检测网卡与网线连接状态,使用io ...
- Maven存储库
什么是Maven资源库? 在 Maven 术语里存储库是一个目录,即目录中保存所有项目的 jar 库,插件或任何其他项目特定文件,并可以容易由 Maven 使用. Maven库中有三种类型 local ...
- 嵌入式开发之hi3519---i2c EEPROM
http://pdf1.alldatasheetcn.com/datasheet-pdf/view/163283/MICROCHIP/24LC024.html http://www.elecfans. ...
- (转)Live555单线程原理
1. 概述 在live555-Server库中,使用单线程实现了多用户请求视频数据,这似乎多线程才能实现的功能,并且用户请求视频数据各个流程衔接的都十分完美,其执行效率非常高. live555是如何实 ...
- php -- 取整数
PHP取整数函数常用的四种方法,下面收集了四个函数: 经常用到取整的函数,今天小小的总结一下!其实很简单,就是几个函数而已--主要是:ceil,floor,round,intval ceil — 进一 ...
- Qt SDK的x64与x86版本号以及与VS的配合
今天遇到一个奇怪的问题.我用Qt的64位版本号,动态载入一个SDK的dll,不管怎样都是载入失败.QLibrary也没什么有价值的信息. 实在没辙,就用VS2013写了个小程序,用LoadLibrar ...
- MySQL错误ERROR 2002 (HY000): Can't connect to local MySQL server
From: http://www.jb51.net/article/56952.htm 这篇文章主要介绍了MySQL错误ERROR 2002 (HY000): Can't connect to loc ...
- 【FastJSON】使用JSON.toJSONString()-解决FastJson中“$ref 循环引用”的问题
fastjson 是一个 不错的json格式化工具, 但是在使用时,如果 碰到统一地址对象引用,就会用$ref替代 . 怎么去掉ref呢, 解决方法如下: String mapStr = JSONOb ...
- [MVC] 自定义ActionSelector,根据参数选择Action
很多时候我们会根据UI传入的参数,呈现不同的View.也就是对于同一个Action如何根据请求数据返回不同的View.通常情况下我们会按照如下方法来写,例如: [AcceptVerbs(HttpVer ...
- C++标准命名空间std
输入输出要用到这个. 标准C++库的所有的标识符都是在一个名为std的命名空间中定义的,或者说标准头文件(如iostream)中函数.类.对象和类模板是在命名空间 std中定义的.std是standa ...