本文博客地址: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
-
*/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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;
-
-
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;
-
-
-
/*
-
* 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
-
*/
-
-
-
-
-
// 主函數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 
