安卓 dex 通用脱壳技术研究(二)
0x03 DexHunter代码分析
DexHunter 实现中,只需要修改一处文件:dalvik\vm\native\dalvik_system_DexFile.cpp
下面是BeyondCompare比对:
首先看一下DexHunter的设计原理:
APP 启动时,通过freature string定位dex在内存中位置,并读取classdef块之前的内存为part1,读取classdef之后的内存为data。遍历class_def_item结构,生成文件classdef,并通过code_item_off判断具体的类方法是否在dex范围内,若不在,则写extra文件。
描述几个问题:
从哪里dump出dex文件
dex文件打开时
类加载时
类初始化时
类方法调用时
DexHunter中,我们关注,ClassLoader.loadClass->Dalvik_dalvik_system_DexFile_defineClassNative这个函数,它实现了类的加载,实现过程如下:
选择脱壳的时机应是在APP的第一个类加载的时候,为什么呢?
类加载之前,类的内容是在内存当中的
当类初始化时,该内存的内容可能会被动态修改
在一个类方法被调用前,code_item或指令肯定是可用的
那如何做呢?
我们要主动加载并初始化所有的类;
因此,我们代码的注入点,应该是Dalvik_dalvik_system_DexFile_defineClassNative()函数的clazz = dvmDefineClass(pDvmDex, descriptor, loader);语句之前;即在APP加载第一个类之前完成;通过dvmDefineClass主动遍历class_def_item加载每个类,并调用dvmIsClassInitialized和dvmInitClass函数初始化之。
初始化完成之后,内存中的就是将执行的代码,像梆梆加固针对每个方法进行的加密,会在运行时解密、运行完成后清理内存并再次加密,通过这种方法就可以过掉;因为我们模拟了这样一次调用过程;
下面是我加入注释的代码:
//------------------------added begin----------------------// #include <asm/siginfo.h>
#include "libdex/DexClass.h"
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h> static char dexname[100]={0}; //feature string
static char dumppath[100]={0}; //dump的文件路径
static bool readable=true; static pthread_mutex_t read_mutex;
static bool flag=true;
static pthread_mutex_t mutex;
static bool timer_flag=true;
static timer_t timerId; struct arg{
DvmDex* pDvmDex;
Object * loader;
}param; void timer_thread(sigval_t)
{
timer_flag=false;
timer_delete(timerId);
ALOGI("GOT IT time up");
} void* ReadThread(void *arg){
FILE *fp = NULL;
while (dexname[0]==0||dumppath[0]==0) {
fp=fopen("/data/dexname", "r");
if (fp==NULL) {
sleep(1);
continue;
}
fgets(dexname,99,fp); //读feature string
dexname[strlen(dexname)-1]=0;
fgets(dumppath,99,fp);
dumppath[strlen(dumppath)-1]=0; //取dump路径
fclose(fp);
fp=NULL;
} struct sigevent sev; sev.sigev_notify=SIGEV_THREAD;
sev.sigev_value.sival_ptr=&timerId;
sev.sigev_notify_function=timer_thread;
sev.sigev_notify_attributes = NULL; timer_create(CLOCK_REALTIME,&sev,&timerId); struct itimerspec ts;
ts.it_value.tv_sec=5;
ts.it_value.tv_nsec=0;
ts.it_interval.tv_sec=0;
ts.it_interval.tv_nsec=0; timer_settime(timerId,0,&ts,NULL); return NULL;
} /*
这里是class_data_item的前4项,称为ClassDataHeader
Dex File->class_defs->class_def_item(class_data_offset)->class_data_item->ClassDataHeader
*/
void ReadClassDataHeader(const uint8_t** pData, DexClassDataHeader *pHeader)
{
pHeader->staticFieldsSize = readUnsignedLeb128(pData);
pHeader->instanceFieldsSize = readUnsignedLeb128(pData);
pHeader->directMethodsSize = readUnsignedLeb128(pData);
pHeader->virtualMethodsSize = readUnsignedLeb128(pData);
} /*
下面两个函数,分别读class_data_item Header下的内容,分Field和Method
*/
void ReadClassDataField(const uint8_t** pData, DexField* pField)
{
pField->fieldIdx = readUnsignedLeb128(pData);
pField->accessFlags = readUnsignedLeb128(pData);
} void ReadClassDataMethod(const uint8_t** pData, DexMethod* pMethod)
{
pMethod->methodIdx = readUnsignedLeb128(pData);
pMethod->accessFlags = readUnsignedLeb128(pData);
pMethod->codeOff = readUnsignedLeb128(pData);
} /*
解析class_data_item结构,使用到上面3个函数,分别解析,Header、Field和Method部分
*/
DexClassData* ReadClassData(const uint8_t** pData)
{ DexClassDataHeader header; if (*pData == NULL) {
return NULL;
} //读取 class_data_item的Header
ReadClassDataHeader(pData, &header); size_t resultSize = sizeof(DexClassData) + (header.staticFieldsSize * sizeof(DexField)) + (header.instanceFieldsSize * sizeof(DexField)) + (header.directMethodsSize * sizeof(DexMethod)) + (header.virtualMethodsSize * sizeof(DexMethod)); DexClassData* result = (DexClassData*) malloc(resultSize); //result指向class_data_item并返回 if (result == NULL) {
return NULL;
} uint8_t* ptr = ((uint8_t*) result) + sizeof(DexClassData); //指向class_data_item的staic_fields偏移 result->header = header; //以下依次读class_data_item的staticFields,instanceFields,directMethods和virtualMethods域大小————————begain
if (header.staticFieldsSize != 0) {
result->staticFields = (DexField*) ptr;
ptr += header.staticFieldsSize * sizeof(DexField);
} else {
result->staticFields = NULL;
} if (header.instanceFieldsSize != 0) {
result->instanceFields = (DexField*) ptr;
ptr += header.instanceFieldsSize * sizeof(DexField);
} else {
result->instanceFields = NULL;
} if (header.directMethodsSize != 0) {
result->directMethods = (DexMethod*) ptr;
ptr += header.directMethodsSize * sizeof(DexMethod);
} else {
result->directMethods = NULL;
} if (header.virtualMethodsSize != 0) {
result->virtualMethods = (DexMethod*) ptr;
} else {
result->virtualMethods = NULL;
}
//以下依次读class_data_item的staticFields,instanceFields,directMethods和virtualMethods域大小————————end
//以下依次读staticFields,instanceFields,directMethods,virtualMethods域内容————————begain
for (uint32_t i = 0; i < header.staticFieldsSize; i++) {
ReadClassDataField(pData, &result->staticFields[i]);
} for (uint32_t i = 0; i < header.instanceFieldsSize; i++) {
ReadClassDataField(pData, &result->instanceFields[i]);
} for (uint32_t i = 0; i < header.directMethodsSize; i++) {
ReadClassDataMethod(pData, &result->directMethods[i]);
} for (uint32_t i = 0; i < header.virtualMethodsSize; i++) {
ReadClassDataMethod(pData, &result->virtualMethods[i]);
}
//以下依次读staticFields,instanceFields,directMethods,virtualMethods域内容————————end return result;
} /*
class_data_item中的一些域是用LEB128算法编码的
*/
void writeLeb128(uint8_t ** ptr, uint32_t data)
{
while (true) {
uint8_t out = data & 0x7f;
if (out != data) {
*(*ptr)++ = out | 0x80;
data >>= 7;
} else {
*(*ptr)++ = out;
break;
}
}
} /*
此函数读取class_data_item,并将内容用writeLeb128转码后返回
*/
uint8_t* EncodeClassData(DexClassData *pData, int& len)
{
len=0; len+=unsignedLeb128Size(pData->header.staticFieldsSize);
len+=unsignedLeb128Size(pData->header.instanceFieldsSize);
len+=unsignedLeb128Size(pData->header.directMethodsSize);
len+=unsignedLeb128Size(pData->header.virtualMethodsSize); if (pData->staticFields) {
for (uint32_t i = 0; i < pData->header.staticFieldsSize; i++) {
len+=unsignedLeb128Size(pData->staticFields[i].fieldIdx);
len+=unsignedLeb128Size(pData->staticFields[i].accessFlags);
}
} if (pData->instanceFields) {
for (uint32_t i = 0; i < pData->header.instanceFieldsSize; i++) {
len+=unsignedLeb128Size(pData->instanceFields[i].fieldIdx);
len+=unsignedLeb128Size(pData->instanceFields[i].accessFlags);
}
} if (pData->directMethods) {
for (uint32_t i=0; i<pData->header.directMethodsSize; i++) {
len+=unsignedLeb128Size(pData->directMethods[i].methodIdx);
len+=unsignedLeb128Size(pData->directMethods[i].accessFlags);
len+=unsignedLeb128Size(pData->directMethods[i].codeOff);
}
} if (pData->virtualMethods) {
for (uint32_t i=0; i<pData->header.virtualMethodsSize; i++) {
len+=unsignedLeb128Size(pData->virtualMethods[i].methodIdx);
len+=unsignedLeb128Size(pData->virtualMethods[i].accessFlags);
len+=unsignedLeb128Size(pData->virtualMethods[i].codeOff);
}
} uint8_t * store = (uint8_t *) malloc(len); if (!store) {
return NULL;
} uint8_t * result=store; writeLeb128(&store,pData->header.staticFieldsSize);
writeLeb128(&store,pData->header.instanceFieldsSize);
writeLeb128(&store,pData->header.directMethodsSize);
writeLeb128(&store,pData->header.virtualMethodsSize); if (pData->staticFields) {
for (uint32_t i = 0; i < pData->header.staticFieldsSize; i++) {
writeLeb128(&store,pData->staticFields[i].fieldIdx);
writeLeb128(&store,pData->staticFields[i].accessFlags);
}
} if (pData->instanceFields) {
for (uint32_t i = 0; i < pData->header.instanceFieldsSize; i++) {
writeLeb128(&store,pData->instanceFields[i].fieldIdx);
writeLeb128(&store,pData->instanceFields[i].accessFlags);
}
} if (pData->directMethods) {
for (uint32_t i=0; i<pData->header.directMethodsSize; i++) {
writeLeb128(&store,pData->directMethods[i].methodIdx);
writeLeb128(&store,pData->directMethods[i].accessFlags);
writeLeb128(&store,pData->directMethods[i].codeOff);
}
} if (pData->virtualMethods) {
for (uint32_t i=0; i<pData->header.virtualMethodsSize; i++) {
writeLeb128(&store,pData->virtualMethods[i].methodIdx);
writeLeb128(&store,pData->virtualMethods[i].accessFlags);
writeLeb128(&store,pData->virtualMethods[i].codeOff);
}
} free(pData);
return result;
} uint8_t* codeitem_end(const u1** pData)
{
uint32_t num_of_list = readUnsignedLeb128(pData);
for (;num_of_list>0;num_of_list--) {
int32_t num_of_handlers=readSignedLeb128(pData);
int num=num_of_handlers;
if (num_of_handlers<=0) {
num=-num_of_handlers;
}
for (; num > 0; num--) {
readUnsignedLeb128(pData);
readUnsignedLeb128(pData);
}
if (num_of_handlers<=0) {
readUnsignedLeb128(pData);
}
}
return (uint8_t*)(*pData);
}
代码未完,下一篇继续;
安卓 dex 通用脱壳技术研究(二)的更多相关文章
- 安卓 dex 通用脱壳技术研究(一)
注:以下4篇博文中,部分图片引用自DexHunter作者zyqqyz在slide.pptx中的图片,版本归原作者所有: 0x01 背景介绍 安卓 APP 的保护一般分为下列几个方面: JAVA/C代码 ...
- 安卓 dex 通用脱壳技术研究(四)
/* 当第一个类执行到此函数时,我们在dvmDefineClass执行之前,也就是第一个类加载之前 注入我们的dump代码:即DumpClass()函数 */ static void ...
- 安卓 dex 通用脱壳技术研究(三)
/* 此为DexHunter实现的主要功能,进行内存dump,将class_def_items中dump出classdef和extra部分 */ void* DumpClass(void *p ...
- 基于.net的分布式系统限流组件 C# DataGridView绑定List对象时,利用BindingList来实现增删查改 .net中ThreadPool与Task的认识总结 C# 排序技术研究与对比 基于.net的通用内存缓存模型组件 Scala学习笔记:重要语法特性
基于.net的分布式系统限流组件 在互联网应用中,流量洪峰是常有的事情.在应对流量洪峰时,通用的处理模式一般有排队.限流,这样可以非常直接有效的保护系统,防止系统被打爆.另外,通过限流技术手段,可 ...
- 20145307陈俊达_安卓逆向分析_Xposed的hook技术研究
20145307陈俊达_安卓逆向分析_Xposed的hook技术研究 引言 其实这份我早就想写了,xposed这个东西我在安卓SDK 4.4.4的时候就在玩了,root后安装架构,起初是为了实现一些屌 ...
- 【转】手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)
1.引言 特别说明:本文内容仅用于即时通讯技术研究和学习之用,请勿用于非法用途.如本文内容有不妥之处,请联系JackJiang进行处理! 我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群 ...
- 手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)
1.引言 特别说明:本文内容仅用于即时通讯技术研究和学习之用,请勿用于非法用途.如本文内容有不妥之处,请联系JackJiang进行处理! 我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群 ...
- Ngnix技术研究系列2-基于Redis实现动态路由
上篇博文我们写了个引子: Ngnix技术研究系列1-通过应用场景看Nginx的反向代理 发现了新大陆,OpenResty OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台 ...
- Nginx技术研究系列2-基于Redis实现动态路由
上篇博文我们写了个引子: Ngnix技术研究系列1-通过应用场景看Nginx的反向代理 发现了新大陆,OpenResty OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台 ...
随机推荐
- Js replace() 方法笔记
最近捣鼓着学习Js,发现replace()真的很有用,替换功能杠杠的棒. 接下来看看我遇到的问题: 有两个随机给出的字符串,字符串1'xxxxxx',字符串2'====T'(这两个用作示例,其他为随机 ...
- SQL调优(SQL TUNING)并行查询提示(Hints)之pq_distribute的使用
pq_distribute提示通常被用于提升数据仓库中分区表间的连接操作性能. pq_distribute提示允许你确定参与连接的表数据行在生产和消费并行查询服务进程间如何分配. pq_distrib ...
- 更改pip源至国内镜像
更改pip源至国内镜像 经常在使用Python的时候需要安装各种模块,而pip是很强大的模块安装工具,但是由于国外官方pypi经常被墙,导致不可用,所以我们最好是将自己使用的pip源更换一下,这样 ...
- Windows下定时任务
windows计划任务 1.写一个PHP程序,命名为test.php,内容如下所示: <? $fp = fopen("test.txt", "a+"); ...
- [LeetCode] 20. Valid Parentheses ☆
转载:https://leetcode.windliang.cc/leetCode-20-Valid%20Parentheses.html 描述 Given a string containing j ...
- Qt Widgets——工具栏和状态栏
本文主要涉及QSizeGrip ,QStatusBar ,QToolBar QToolBar 工具栏默认位于菜单栏下方,其上添加一个个action按钮,用于执行动作 绝大多谢以前都涉及过,只列出 QT ...
- nop 4.1 Widget 探究- 视图组件
1. 系统默认自带了一个NivoSlider 的Wdget. 在Nop.Web项目首页的HomePageTop里 这个写法是 ASP.NET Core MVC 中的新特性 视图组件,与局部视图相似,但 ...
- 把旧系统迁移到.Net Core 2.0 日记 (12) --发布遇到的问题
1. 开发时是在Mac+MySql, 尝试发布时是在SQL2005+Win 2008 (第一版) 在Startup.cs里,数据库连接要改,分页时netcore默认是用offset关键字分页, 如果用 ...
- ButterKnife没有Generate ButterKnife Injections问题
Butterknife 一键自动生成findviewbyid和onclick的代码. 步骤如下: 一: 二: 三: 完成! 如果没有Generate ButterKnife Injections选择项 ...
- ubuntu ssh前后台切换命令相关
后台运行:命令+& 例如 sleep 60 & jobs -l 显示job的pid和状态 ps 显示用户进程 将第一个job切换回前台:fg 1 放到后台:bg 1 cltr + z ...