drizzleDumper的原理分析和使用说明

时间:2023-03-08 21:56:02
https://blog.****.net/qq1084283172/article/details/53561622
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.****.net/QQ1084283172/article/details/53561622

本文博客地址:http://blog.****.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头文件

  1. /*
  2. * drizzleDumper Code By Drizzle.Risk
  3. * file: drizzleDumper.h
  4. */
  5. #include <stdlib.h>
  6. #include <stdio.h>
  7. #include <dirent.h>
  8. #include <fcntl.h>
  9. #include <unistd.h>
  10. #include <stdarg.h>
  11. #include <string.h>
  12. #include <errno.h>
  13. #include <sys/ptrace.h>
  14. #include <sys/types.h>
  15. #include <sys/wait.h>
  16. #include <unistd.h>
  17. #include <linux/user.h>
  18. #ifdef HAVE_STDINT_H
  19. #include <stdint.h> /* C99 */
  20. typedef uint8_t u1;
  21. typedef uint16_t u2;
  22. typedef uint32_t u4;
  23. typedef uint64_t u8;
  24. typedef int8_t s1;
  25. typedef int16_t s2;
  26. typedef int32_t s4;
  27. typedef int64_t s8;
  28. #else
  29. typedef unsigned char u1;
  30. typedef unsigned short u2;
  31. typedef unsigned int u4;
  32. typedef unsigned long long u8;
  33. typedef signed char s1;
  34. typedef signed short s2;
  35. typedef signed int s4;
  36. typedef signed long long s8;
  37. #endif
  38. /*
  39. * define kSHA1DigestLen
  40. */
  41. enum { kSHA1DigestLen = 20,
  42. kSHA1DigestOutputLen = kSHA1DigestLen*2 +1 };
  43. /*
  44. * define DexHeader
  45. */
  46. typedef struct DexHeader {
  47. u1 magic[8]; /* includes version number */
  48. u4 checksum; /* adler32 checksum */
  49. u1 signature[kSHA1DigestLen]; /* SHA-1 hash */
  50. u4 fileSize; /* length of entire file */
  51. u4 headerSize; /* offset to start of next section */
  52. u4 endianTag;
  53. u4 linkSize;
  54. u4 linkOff;
  55. u4 mapOff;
  56. u4 stringIdsSize;
  57. u4 stringIdsOff;
  58. u4 typeIdsSize;
  59. u4 typeIdsOff;
  60. u4 protoIdsSize;
  61. u4 protoIdsOff;
  62. u4 fieldIdsSize;
  63. u4 fieldIdsOff;
  64. u4 methodIdsSize;
  65. u4 methodIdsOff;
  66. u4 classDefsSize;
  67. u4 classDefsOff;
  68. u4 dataSize;
  69. u4 dataOff;
  70. } DexHeader;
  71. //#define ORIG_EAX 11
  72. static const char* static_safe_location = "/data/local/tmp/";
  73. static const char* suffix = "_dumped_";
  74. typedef struct {
  75. uint32_t start;
  76. uint32_t end;
  77. } memory_region;
  78. uint32_t get_clone_pid(uint32_t service_pid);
  79. uint32_t get_process_pid(const char* target_package_name);
  80. char *determine_filter(uint32_t clone_pid, int memory_fd);
  81. int find_magic_memory(uint32_t clone_pid, int memory_fd, memory_region *memory ,const char* file_name);
  82. int peek_memory(int memory_file, uint32_t address);
  83. int dump_memory(const char *buffer , int len , char each_filename[]);
  84. int attach_get_memory(uint32_t pid);

drizzleDumper.c实现文件

  1. /*
  2. * drizzleDumper Code By Drizzle.Risk
  3. * file: drizzleDumper.c
  4. */
  5. #include "drizzleDumper.h"
  6. // 主函数main
  7. int main(int argc, char *argv[]) {
  8. printf("[>>>] This is drizzleDumper [<<<]\n");
  9. printf("[>>>] code by Drizzle [<<<]\n");
  10. printf("[>>>] 2016.05 [<<<]\n");
  11. // 脱壳工具drizzleDumper在工作的实收需要3个参数(需要脱壳的apk的package_name、脱壳等待的时间wait_times(s))
  12. if(argc <= 1)
  13. {
  14. 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");
  15. return 0;
  16. }
  17. // 由于脱壳的原理是基于进程的ptrace,需要有root权限
  18. if(getuid() != 0)
  19. {
  20. printf("[*] Device Not root!\n");
  21. return -1;
  22. }
  23. double wait_times = 0.01;
  24. // 脱壳工具drizzleDumper在工作的实收需要3个参数(需要脱壳的apk的package_name、脱壳等待的时间wait_times(s))
  25. if(argc >= 3)
  26. {
  27. // 获取加固脱壳的等待时间
  28. wait_times = strtod(argv[2], NULL);
  29. printf("[*] The wait_times is %ss\n", argv[2]);
  30. }
  31. // 获取需要被脱壳的加固apk的包名
  32. char *package_name = argv[1];
  33. printf("[*] Try to Find %s\n", package_name);
  34. uint32_t pid = -1;
  35. int i = 0;
  36. int mem_file;
  37. uint32_t clone_pid;
  38. char *extra_filter;
  39. char *dumped_file_name;
  40. // 进入循环
  41. while(1)
  42. {
  43. // 休眠等待一段时间
  44. sleep(wait_times);
  45. pid = -1;
  46. // 获取加固需要被脱壳的apk的进程pid
  47. pid = get_process_pid(package_name);
  48. // 判断获取的进程pid是否有效
  49. if(pid < 1 || pid == -1)
  50. {
  51. continue;
  52. }
  53. printf("[*] pid is %d\n", pid);
  54. // 获取进程pid的一个线程tid,方便后面进行ptrace附加
  55. clone_pid = get_clone_pid(pid);
  56. if(clone_pid <= 0)
  57. {
  58. continue;
  59. }
  60. printf("[*] clone pid is %d\n", clone_pid);
  61. memory_region memory;
  62. printf("[*] ptrace [clone_pid] %d\n", clone_pid);
  63. // 对指定pid进程的克隆即tid进程ptrace附加,获取指定pid进程的内存模块基址
  64. mem_file = attach_get_memory(clone_pid);
  65. // 对获取到的内存有效数据的进行校验3次即最多进行3次脱壳尝试
  66. if(mem_file == -10201)
  67. {
  68. continue;
  69. }
  70. else if(mem_file == -20402)
  71. {
  72. //continue;
  73. }
  74. else if(mem_file == -30903)
  75. {
  76. //continue
  77. }
  78. /****
  79. *static const char* static_safe_location = "/data/local/tmp/";
  80. *static const char* suffix = "_dumped_";
  81. ****/
  82. // 申请内存空间保存内存dump出来的dex文件的名称
  83. dumped_file_name = malloc(strlen(static_safe_location) + strlen(package_name) + strlen(suffix));
  84. // 格式化生成存dump出来的dex文件的名称
  85. sprintf(dumped_file_name, "%s%s%s", static_safe_location, package_name, suffix);
  86. printf("[*] Scanning dex ...\n");
  87. // 通过ptrace附件目标pid进程,在目标进程的pid中进行dex文件的搜索然后进行内存dump
  88. if(find_magic_memory(clone_pid, mem_file, &memory, dumped_file_name) <= 0)
  89. {
  90. printf("[*] The magic was Not Found!\n");
  91. ptrace(PTRACE_DETACH, clone_pid, NULL, 0);
  92. close(mem_file);
  93. continue;
  94. }
  95. else
  96. {
  97. // dex的内存dump成功,跳出循环
  98. close(mem_file);
  99. ptrace(PTRACE_DETACH, clone_pid, NULL, 0);
  100. break;
  101. }
  102. }
  103. printf("[*] Done.\n\n");
  104. return 1;
  105. }
  106. // 获取指定进程的一个线程tid
  107. uint32_t get_clone_pid(uint32_t service_pid)
  108. {
  109. DIR *service_pid_dir;
  110. char service_pid_directory[1024];
  111. // 格式化字符串
  112. sprintf(service_pid_directory, "/proc/%d/task/", service_pid);
  113. // 查询指定进程的pid的线程TID的信息
  114. if((service_pid_dir = opendir(service_pid_directory)) == NULL)
  115. {
  116. return -1;
  117. }
  118. struct dirent* directory_entry = NULL;
  119. struct dirent* last_entry = NULL;
  120. // 获取指定pid进程的线程TID
  121. while((directory_entry = readdir(service_pid_dir)) != NULL)
  122. {
  123. last_entry = directory_entry;
  124. }
  125. if(last_entry == NULL)
  126. return -1;
  127. closedir(service_pid_dir);
  128. // 返回获取到的指定pid的线程tid
  129. return atoi(last_entry->d_name);
  130. }
  131. // 通过运行的apk的名称的获取进程的pid
  132. uint32_t get_process_pid(const char *target_package_name)
  133. {
  134. char self_pid[10];
  135. sprintf(self_pid, "%u", getpid());
  136. DIR *proc = NULL;
  137. if((proc = opendir("/proc")) == NULL)
  138. return -1;
  139. struct dirent *directory_entry = NULL;
  140. while((directory_entry = readdir(proc)) != NULL)
  141. {
  142. if (directory_entry == NULL)
  143. return -1;
  144. if (strcmp(directory_entry->d_name, "self") == 0 || strcmp(directory_entry->d_name, self_pid) == 0)
  145. continue;
  146. char cmdline[1024];
  147. snprintf(cmdline, sizeof(cmdline), "/proc/%s/cmdline", directory_entry->d_name);
  148. FILE *cmdline_file = NULL;
  149. if((cmdline_file = fopen(cmdline, "r")) == NULL)
  150. continue;
  151. char process_name[1024];
  152. fscanf(cmdline_file, "%s", process_name);
  153. fclose(cmdline_file);
  154. if(strcmp(process_name, target_package_name) == 0)
  155. {
  156. closedir(proc);
  157. return atoi(directory_entry->d_name);
  158. }
  159. }
  160. closedir(proc);
  161. return -1;
  162. }
  163. // 在目标进程的内存空间中进行dex文件的搜索
  164. int find_magic_memory(uint32_t clone_pid, int memory_fd, memory_region *memory , const char *file_name) {
  165. int ret = 0;
  166. char maps[2048];
  167. // 格式化字符串得到/proc/pid/maps
  168. snprintf(maps, sizeof(maps), "/proc/%d/maps", clone_pid);
  169. FILE *maps_file = NULL;
  170. // 打开文件/proc/pid/maps,获取指定pid进程的内存分布信息
  171. if((maps_file = fopen(maps, "r")) == NULL)
  172. {
  173. printf(" [+] fopen %s Error \n" , maps);
  174. return -1;
  175. }
  176. char mem_line[1024];
  177. // 循环读取文件/proc/pid/maps中的pid进程的每一条内存分布信息
  178. while(fscanf(maps_file, "%[^\n]\n", mem_line) >= 0)
  179. {
  180. char mem_address_start[10]={0};
  181. char mem_address_end[10]={0};
  182. char mem_info[1024]={0};
  183. // 解析pid进程的的内存分布信息--内存分布起始地址、内存分布结束地址等
  184. sscanf(mem_line, "%8[^-]-%8[^ ]%*s%*s%*s%*s%s", mem_address_start, mem_address_end, mem_info);
  185. memset(mem_line , 0 ,1024);
  186. // 获取内存分布起始地址的大小
  187. uint32_t mem_start = strtoul(mem_address_start, NULL, 16);
  188. memory->start = mem_start;
  189. // 获取内存分布结束地址的大小
  190. memory->end = strtoul(mem_address_end, NULL, 16);
  191. // 获取实际的内存区间大小
  192. int len = memory->end - memory->start;
  193. // 过滤掉不符合条件的内存分布区间
  194. if(len <= 10000)
  195. {//too small
  196. continue;
  197. }
  198. else if(len >= 150000000)
  199. {//too big
  200. continue;
  201. }
  202. char each_filename[254] = {0};
  203. char randstr[10] = {0};
  204. sprintf(randstr ,"%d", rand()%9999);
  205. // 拼接字符串得到dump的dex文件的生成名称
  206. strncpy(each_filename , file_name , 200); //防溢出
  207. strncat(each_filename , randstr , 10);
  208. strncat(each_filename , ".dex" , 4);
  209. // 先将pid进程内存文件句柄的指针置文件开头
  210. lseek64(memory_fd , 0 , SEEK_SET);
  211. // 设置pid进程内存文件句柄的指针为内存分布起始地址
  212. off_t r1 = lseek64(memory_fd , memory->start , SEEK_SET);
  213. if(r1 == -1)
  214. {
  215. //do nothing
  216. }
  217. else
  218. {
  219. // 根据内存分布区间的大小申请内存空间
  220. char *buffer = malloc(len);
  221. // 读取pid进程的指定区域的内存数据
  222. ssize_t readlen = read(memory_fd, buffer, len);
  223. printf("meminfo: %s ,len: %d ,readlen: %d, start: %x\n", mem_info, len, readlen, memory->start);
  224. // 对读取的内存分布区域的数据进行dex文件的扫描和查找
  225. if(buffer[1] == 'E' && buffer[2] == 'L' && buffer[3] == 'F')
  226. {
  227. free(buffer);
  228. continue;
  229. }
  230. // 查找到dex文件所在的内存区域
  231. if(buffer[0] == 'd' && buffer[1] == 'e' && buffer[2] == 'x' && buffer[3] == '\n' && buffer[4] == '0' && buffer[5] == '3')
  232. {
  233. printf(" [+] find dex, len : %d , info : %s\n" , readlen , mem_info);
  234. DexHeader header;
  235. char real_lenstr[10]={0};
  236. // 获取内存区域中dex文件的文件头信息
  237. memcpy(&header , buffer ,sizeof(DexHeader));
  238. sprintf(real_lenstr , "%x" , header.fileSize);
  239. // 通过dex文件头信息,获取到整个dex文件的大小
  240. long real_lennum = strtol(real_lenstr , NULL, 16);
  241. printf(" [+] This dex's fileSize: %d\n", real_lennum);
  242. // 对dex文件所在的内存区域进行内存dump
  243. if(dump_memory(buffer , len , each_filename) == 1)
  244. {
  245. // 打印dump的dex文件的名称
  246. printf(" [+] dex dump into %s\n", each_filename);
  247. free(buffer);
  248. continue;
  249. }
  250. else
  251. {
  252. printf(" [+] dex dump error \n");
  253. }
  254. }
  255. free(buffer);
  256. }
  257. // 前面的内存方法搜索没有查找dex文件的内存,尝试下面的内存+8位置进行搜索
  258. // 具体什么原因没太明白??
  259. lseek64(memory_fd , 0 , SEEK_SET); //保险,先归零
  260. r1 = lseek64(memory_fd , memory->start + 8 , SEEK_SET); //不用 pread,因为pread用的是lseek
  261. if(r1 == -1)
  262. {
  263. continue;
  264. }
  265. else
  266. {
  267. char *buffer = malloc(len);
  268. ssize_t readlen = read(memory_fd, buffer, len);
  269. if(buffer[0] == 'd' && buffer[1] == 'e' && buffer[2] == 'x' && buffer[3] == '\n' && buffer[4] == '0' && buffer[5] == '3')
  270. {
  271. printf(" [+] Find dex! memory len : %d \n" , readlen);
  272. DexHeader header;
  273. char real_lenstr[10]={0};
  274. // 获取内存dex文件的文件头信息
  275. memcpy(&header , buffer ,sizeof(DexHeader));
  276. sprintf(real_lenstr , "%x" , header.fileSize);
  277. // 通过dex文件头信息,获取到整个dex文件的大小
  278. long real_lennum = strtol(real_lenstr , NULL, 16);
  279. printf(" [+] This dex's fileSize: %d\n", real_lennum);
  280. // 对dex文件所在的内存区域进行内存dump
  281. if(dump_memory(buffer , len , each_filename) == 1)
  282. {
  283. printf(" [+] dex dump into %s\n", each_filename);
  284. free(buffer);
  285. continue; //如果本次成功了,就不尝试其他方法了
  286. }
  287. else
  288. {
  289. printf(" [+] dex dump error \n");
  290. }
  291. }
  292. free(buffer);
  293. }
  294. }
  295. fclose(maps_file);
  296. return ret;
  297. }
  298. // 从内存中dump数据到文件中
  299. int dump_memory(const char *buffer , int len , char each_filename[])
  300. {
  301. int ret = -1;
  302. // 创建文件
  303. FILE *dump = fopen(each_filename, "wb");
  304. // 将需要dump的内存数据写入到/data/local/tmp文件路径下
  305. if(fwrite(buffer, len, 1, dump) != 1)
  306. {
  307. ret = -1;
  308. }
  309. else
  310. {
  311. ret = 1;
  312. }
  313. fclose(dump);
  314. return ret;
  315. }
  316. // 获取指定附加pid进程的内存模块基址
  317. int attach_get_memory(uint32_t pid) {
  318. char mem[1024];
  319. bzero(mem,1024);
  320. // 格式化字符串得到字符串/proc/pid/mem
  321. snprintf(mem, sizeof(mem), "/proc/%d/mem", pid);
  322. int ret = -1;
  323. int mem_file;
  324. // 尝试ptrace附加目标pid进程
  325. ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
  326. // 对ptrace附加目标pid进程的操作结果进行判断
  327. if (0 != ret)
  328. {
  329. int err = errno; //这时获取errno
  330. if(err == 1) //EPERM
  331. {
  332. return -30903; //代表已经被跟踪或无法跟踪
  333. }
  334. else
  335. {
  336. return -10201; //其他错误(进程不存在或非法操作)
  337. }
  338. }
  339. else
  340. {
  341. // ptrace附加目标进程pid成功,获取指定pid进程的内存模块基址
  342. // 获取其它进程的内存模块基址,需要root权限
  343. if(!(mem_file = open(mem, O_RDONLY)))
  344. {
  345. return -20402; //打开错误
  346. }
  347. }
  348. return mem_file;
  349. }

drizzleDumper的编译配置文件Android.mk

  1. LOCAL_PATH := $(call my-dir)
  2. TARGET_PIE := true
  3. NDK_APP_PIE := true
  4. include $(CLEAR_VARS)
  5. # 需要编译的源码文件
  6. LOCAL_SRC_FILES := \
  7. drizzleDumper.c
  8. LOCAL_C_INCLUDE := \
  9. drizzleDumper.h \
  10. definitions.h
  11. LOCAL_MODULE := drizzleDumper
  12. LOCAL_MODULE_TAGS := optional
  13. # Allow execution on android-16+
  14. # 支持PIE
  15. LOCAL_CFLAGS += -fPIE
  16. LOCAL_LDFLAGS += -fPIE -pie
  17. # 编译生成可执行ELF文件
  18. include $(BUILD_EXECUTABLE)
  19. include $(call all-makefiles-under,$(LOCAL_PATH))

三、drizzleDumper的使用说明

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

drizzleDumper的原理分析和使用说明

四、下面就使用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秒。

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

drizzleDumper的原理分析和使用说明

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

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

参考网址

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

https://github.com/DrizzleRisk/drizzleDumper

jpg改rar drizzleDumper的原理分析和使用说明