DexHunter在ART虚拟机模式下的脱壳原理分析
本文博客地址: http://blog.csdn.net/qq1084283172/article/details/78494620
DexHunter脱壳工具在Dalvik虚拟机模式下的脱壳原理分析,在前面的博客中已经提到了,但是自我感觉分析和理解的还不够透彻,后面又对DexHunter脱壳工具的源码再阅读了几遍,总算是把DexHunter脱壳工具在Dalvik虚拟机模式下和ART虚拟机模式下的脱壳原理理解清楚了。关于DexHunter脱壳工具在Dalvik虚拟机模式下的脱壳原理分析,已经准备了一篇博客想详细和深入的讲一讲,一直搁置了没有完成;本来还计划把DexHunter脱壳工具在ART虚拟机模式下的脱壳原理也详细说一说,看来是不可能了。有关Android加固脱壳和Hook的好几个源码工程都看的差不多了,因为各种事情和状态不好,懒得动笔了。
在理解DexHunter脱壳工具在ART虚拟机模式下的脱壳原理之前,建议先阅读一下老罗的博客了解一下Android的ART虚拟机模式下的类加载和dex文件的优化过程,《Android运行时ART加载OAT文件的过程分析》这篇博客需要好好读一读。DexHunter在ART虚拟机模式下的脱壳编译配置主要是针对源码文件 /art/runtime/class_linker.cc 进行修改,以Android
4.4.4 r1的源码文件为示例 http://androidxref.com/4.4.4_r1/xref/art/runtime/class_linker.cc,然后对Android系统源码进行重新编译生成Android系统的镜像文件,刷到手机设备或者Android模拟器上使用,用来进行DexHunter在ART虚拟机模式下的脱壳操作。
//-----------------------added begin-----------------------//
/**
*
// OAT文件里面的oatdata段的开始储存着一个OAT头OatHeader
const OatHeader& OatFile::GetOatHeader() const {
return *reinterpret_cast<const OatHeader*>(Begin());
}
// OAT文件里面的oatdata段开始地址
const byte* OatFile::Begin() const {
CHECK(begin_ != NULL);
return begin_;
}
// OAT文件里面的oatexec段的结束地址
const byte* OatFile::End() const {
CHECK(end_ != NULL);
return end_;
}
// OAT文件里面的oatdata段的开始储存着一个OAT头,这个OAT头通过类OatHeader描述
class PACKED(4) OatHeader {
public:
......
private:
uint8_t magic_[4]; // 标识OAT文件的魔法数,等于‘oat\n’
uint8_t version_[4]; // OAT文件版本号,目前的值等于‘007、0’。
uint32_t adler32_checksum_; // OAT头部检验和
// 本地机指令集,有四种取值,分别为 kArm(1)、kThumb2(2)、kX86(3)和kMips(4)。
InstructionSet instruction_set_;
// OAT文件包含的DEX文件个数。
uint32_t dex_file_count_;
// oatexec段开始位置与oatdata段开始位置的相对偏移值。
uint32_t executable_offset_;
// 在由打包在应用程序里面的classes.dex生成的OAT文件的oatdata段头部中,下述七个成员变量的值均等于0。
uint32_t interpreter_to_interpreter_bridge_offset_;
uint32_t interpreter_to_compiled_code_bridge_offset_;
uint32_t jni_dlsym_lookup_offset_;
uint32_t portable_resolution_trampoline_offset_;
uint32_t portable_to_interpreter_bridge_offset_;
uint32_t quick_resolution_trampoline_offset_;
uint32_t quick_to_interpreter_bridge_offset_;
// 用来创建Image空间的OAT文件的检验和。
uint32_t image_file_location_oat_checksum_;
// 用来创建Image空间的OAT文件的oatdata段在内存的位置。
uint32_t image_file_location_oat_data_begin_;
// 用来创建Image空间的文件的路径的大小。
uint32_t image_file_location_size_;
// 用来创建Image空间的文件的路径的在内存中的地址。
uint8_t image_file_location_data_[0]; // note variable width data at end
......
};
// 参考:http://blog.csdn.net/Luoshengyang/article/details/39307813
**/
#include <asm/siginfo.h>
#define LOGI
static char dexname[100]={0};
static char dumppath[100]={0};
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{
const DexFile* dex_file;
mirror::ClassLoader* class_loader;
ClassLinker* cl;
}param;
// 类成员信息
struct DexField {
uint32_t delta_fieldIdx;
uint32_t accessFlags;
};
// 类方法信息
struct DexMethod {
uint32_t delta_methodIdx;
uint32_t accessFlags;
uint32_t codeOff;
};
struct DexClassDataHeader {
uint32_t staticFieldsSize;
uint32_t instanceFieldsSize;
uint32_t directMethodsSize;
uint32_t virtualMethodsSize;
};
// 整个类的方法和成员信息
struct DexClassData {
DexClassDataHeader header;
DexField* staticFields;
DexField* instanceFields;
DexMethod* directMethods;
DexMethod* virtualMethods;
};
// 等待dump任务执行的等待定时器
void timer_thread(::sigval_t)
{
timer_flag=false;
timer_delete(timerId);
#ifdef LOGI
LOG(INFO)<<"GOT IT time up";
#endif
}
// 读取脱壳配置文件
void* ReadThread(void *arg){
FILE *fp = NULL;
while (dexname[0]==0||dumppath[0]==0) {
// 打开apk脱壳配置文件/data/dexname
// 一些加固会检测这个配置文件/data/dexname
fp=fopen("/data/dexname", "r");
if (fp==NULL) {
sleep(1);
continue;
}
// 获取被加固的dex文件内存加载的文件路径
fgets(dexname, 99, fp);
dexname[strlen(dexname)-1]=0;
// 保存被加固dex文件的dump路径
fgets(dumppath, 99, fp);
dumppath[strlen(dumppath)-1]=0;
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;
}
// leb128数据的写
void writeUnsignedLeb128(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;
}
}
}
// leb128数据的字节长度
int unsignedLeb128Size(uint32_t data)
{
int count = 0;
do {
data >>= 7;
count++;
} while (data != 0);
return count;
}
// 获取DexClassDataHeader结构体的各个成员的数据信息
void dexReadClassDataHeader(const uint8_t** pData,
DexClassDataHeader *pHeader) {
pHeader->staticFieldsSize = DecodeUnsignedLeb128(pData);
pHeader->instanceFieldsSize = DecodeUnsignedLeb128(pData);
pHeader->directMethodsSize = DecodeUnsignedLeb128(pData);
pHeader->virtualMethodsSize = DecodeUnsignedLeb128(pData);
}
// 获取DexClassData结构体DexFiled的数据信息
void dexReadClassDataField(const uint8_t** pData, DexField* pField) {
pField->delta_fieldIdx = DecodeUnsignedLeb128(pData);
pField->accessFlags = DecodeUnsignedLeb128(pData);
}
// 获取DexClassData结构体DexMethod的数据信息
void dexReadClassDataMethod(const uint8_t** pData, DexMethod* pMethod) {
pMethod->delta_methodIdx = DecodeUnsignedLeb128(pData);
pMethod->accessFlags = DecodeUnsignedLeb128(pData);
pMethod->codeOff = DecodeUnsignedLeb128(pData);
}
// 从内存dex文件中读取指定DexFile::ClassDef对应的DexClassData即类成员和类方法描述数据到申请内存空间中
DexClassData* dexReadClassData(const uint8_t** pData) {
DexClassDataHeader header;
if (*pData == NULL) {
return NULL;
}
// 获取DexClassDataHeader结构体的各个成员的数据信息()
dexReadClassDataHeader(pData,&header);
// 计算DexClassData结构体及其所有DexFiled和所有DexMethod占用的内存空间
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);
if (result == NULL) {
return NULL;
}
// 更新指针的位置,用于存放类静态成员变量、类实例成员变量、类直接方法以及类虚拟方法
uint8_t* ptr = ((uint8_t*) result) + sizeof(DexClassData);
result->header = header;
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;
}
// 获取DexClassData中类静态变量和类实例变量以及类直接方法和类虚方法的数据
// 存放到指定的申请内存空间中
for (uint32_t i = 0; i < header.staticFieldsSize; i++) {
dexReadClassDataField(pData, &result->staticFields[i]);
}
for (uint32_t i = 0; i < header.instanceFieldsSize; i++) {
dexReadClassDataField(pData, &result->instanceFields[i]);
}
for (uint32_t i = 0; i < header.directMethodsSize; i++) {
dexReadClassDataMethod(pData, &result->directMethods[i]);
}
for (uint32_t i = 0; i < header.virtualMethodsSize; i++) {
dexReadClassDataMethod(pData, &result->virtualMethods[i]);
}
return result;
}
// 将整个DexClassData所表示的类数据leb128编码写入到申请的内存空间中
uint8_t* dexEncodeClassData(DexClassData *pData, int& len)
{
len=0;
// 获取整个DexClassData所表示类数据的内存占用大小
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].delta_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].delta_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].delta_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].delta_methodIdx);
len+=unsignedLeb128Size(pData->virtualMethods[i].accessFlags);
len+=unsignedLeb128Size(pData->virtualMethods[i].codeOff);
}
}
// 根据整个DexClassData所示类数据的大小申请内存空间
uint8_t * store = (uint8_t *) malloc(len);
if (!store) {
// 申请内存失败的情况
return NULL;
}
uint8_t * result=store;
// 将整个DexClassData所表示的类数据写入到申请的内存空间中
writeUnsignedLeb128(&store,pData->header.staticFieldsSize);
writeUnsignedLeb128(&store,pData->header.instanceFieldsSize);
writeUnsignedLeb128(&store,pData->header.directMethodsSize);
writeUnsignedLeb128(&store,pData->header.virtualMethodsSize);
// 类静态成员变量
if (pData->staticFields) {
for (uint32_t i = 0; i < pData->header.staticFieldsSize; i++) {
writeUnsignedLeb128(&store,pData->staticFields[i].delta_fieldIdx);
writeUnsignedLeb128(&store,pData->staticFields[i].accessFlags);
}
}
// 类实例变量
if (pData->instanceFields) {
for (uint32_t i = 0; i < pData->header.instanceFieldsSize; i++) {
writeUnsignedLeb128(&store,pData->instanceFields[i].delta_fieldIdx);
writeUnsignedLeb128(&store,pData->instanceFields[i].accessFlags);
}
}
// 类直接方法
if (pData->directMethods) {
for (uint32_t i=0; i<pData->header.directMethodsSize; i++) {
writeUnsignedLeb128(&store,pData->directMethods[i].delta_methodIdx);
writeUnsignedLeb128(&store,pData->directMethods[i].accessFlags);
writeUnsignedLeb128(&store,pData->directMethods[i].codeOff);
}
}
// 类虚拟方法
if (pData->virtualMethods) {
for (uint32_t i=0; i<pData->header.virtualMethodsSize; i++) {
writeUnsignedLeb128(&store,pData->virtualMethods[i].delta_methodIdx);
writeUnsignedLeb128(&store,pData->virtualMethods[i].accessFlags);
writeUnsignedLeb128(&store,pData->virtualMethods[i].codeOff);
}
}
free(pData);
return result;
}
// 根据try...catch{}的处理语句计算DexFile::CodeItem结构体的结束地址
uint8_t* codeitem_end(const uint8_t **pData)
{
uint32_t num_of_list = DecodeUnsignedLeb128(pData);
for (;num_of_list>0;num_of_list--) {
int32_t num_of_handlers=DecodeSignedLeb128(pData);
int num=num_of_handlers;
if (num_of_handlers<=0) {
num=-num_of_handlers;
}
for (; num > 0; num--) {
DecodeUnsignedLeb128(pData);
DecodeUnsignedLeb128(pData);
}
if (num_of_handlers<=0) {
DecodeUnsignedLeb128(pData);
}
}
return (uint8_t*)(*pData);
}
// 内存dump加固的dex文件的类数据信息
void* DumpClass(void *parament)
{
while (timer_flag) {
sleep(5);
}
// 获取当前art虚拟机的运行时Runtime
Runtime* runtime = Runtime::Current();
// 附加到art虚拟机线程
runtime->AttachCurrentThread("ClassDumper", false, NULL,false);
// 获取当前art虚拟机的线程描述结构体Thread
Thread *self=Thread::Current();
#ifdef LOGI
LOG(INFO)<<"GOT IT DumpingClass";
#endif
#ifdef LOGI
// 获取当前时间
uint64_t time = MilliTime();
LOG(INFO)<<"GOT IT begin "<<time<<" ms";
#endif
// 当前Dex文件的内存镜像OAT描述相关的结构体
const DexFile &dex_file=(*((struct arg*)parament)->dex_file);
mirror::ClassLoader *class_loader=((struct arg*)parament)->class_loader;
ClassLinker *cl = ((struct arg*)parament)->cl;
char *path = new char[100];
strcpy(path, dumppath);
// 拼接字符串得到文件路径 xxxx/classdef
strcat(path, "classdef");
// 打开文件xxxx/classdef
FILE *fp = fopen(path, "wb+");
strcpy(path, dumppath);
// 拼接字符串得到文件路径xxxx/extra
strcat(path, "extra");
// 打开文件xxxx/extra
FILE *fp1 = fopen(path,"wb+");
uint32_t mask=0x3ffff;
char padding=0;
const char* header="Landroid";
bool pass=false;
// 锁
Locks::mutator_lock_->SharedLock(self);
// 获取当前dex文件的ClassDef的数量
size_t num_class_defs = dex_file.NumClassDefs();
// 获取整个OAT文件的文件大小
uint32_t total_pointer = dex_file.Size();
uint32_t rec = total_pointer;
// 获取OAT文件内存4字节对齐后的文件大小
while (total_pointer&3) {
total_pointer++;
}
// 获取OAT文件4字节对齐需要填充'0'的数量
int inc = total_pointer - rec;
// 获取OAT文件里dex文件的ClassDef结构体表Table的结束地址
uint32_t start = dex_file.class_defs_off_ + sizeof(DexFile::ClassDef)*num_class_defs;
// 获取OAT文件的文件大小即文件偏移
uint32_t end = dex_file.Size();
// 遍历获取OAT文件里的dex文件的ClassDef并加载
for (size_t i = 0; i < num_class_defs; i++)
{
// 获取OAT文件里的dex文件的第i个ClassDef结构体
const DexFile::ClassDef &class_def = dex_file.GetClassDef(i);
// 获取ClassDef结构体描述的类的类签名字符串,例如:Landroid/xxx/yyy;
const char* descriptor = dex_file.GetClassDescriptor(class_def);
// 描述dump的类是否需要额外的类数据信息
bool need_extra = false;
// ART下dex文件的类内存加载后的描述结构体为mirror::Class
mirror::Class* klass=NULL;
const byte * data=NULL;
DexClassData* pData = NULL;
#ifdef LOGI
LOG(INFO) << "GOT IT " << descriptor;
#endif
// 过滤掉Android系统("Landroid")的类和没有类数据的无效类(这两中情况不处理)
if(!strncmp(header, descriptor, 8)||!class_def.class_data_off_)
{
pass = true;
// 此种情况,need_extra = false
goto classdef;
}
// 查找指定类签名描述的目标类
/** mirror::Class* **/klass = cl->FindClass(descriptor, class_loader);
// 查找的目标类没有找到的情况
if (!klass) {
#ifdef LOGI
LOG(INFO)<<"GOT IT class Find Fail";
#endif
// 异常处理
self->ClearException();
continue;
}
// 成功查找到指定类签名描述的目标类
if(klass){
if(cl->EnsureInitialized(klass, true, true)){
#ifdef LOGI
LOG(INFO)<<"GOT IT "<<descriptor<<" Initialized";
#endif
}else{
self->ClearException();
}
}
// OAT文件里dex文件的Class_Data数据存放在OAT文件的结尾或者Class_Def结构体表开始地址之前位置
if(class_def.class_data_off_<start || class_def.class_data_off_>end)
{
#ifdef LOGI
LOG(INFO)<<"GOT IT class data off exceeding "<<descriptor;
#endif
// 需要额外的处理Class_Def
need_extra=true;
}
// 获取Class_Def描述的类的ClassData数据class_data_item
data = dex_file.GetClassData(class_def);
// 读取OAT文件里dex文件的类描述DexClassData描述的类成员和类方法的数据到申请的内存空间中
pData = dexReadClassData(&data);
if (!pData) {
// 失败情况
continue;
}
// 针对类直接方法的处理
if (pData->directMethods) {
// 遍历art下类的直接方法
for (uint32_t i = 0; i < pData->header.directMethodsSize; i++) {
// art::mirror::ArtMethod是art下dex文件类经过内存加载后的描述结构体
// 获取指定类的第i个直接类方法描述结构体art::mirror::ArtMethod
art::mirror::ArtMethod *method = klass->GetDirectMethod(i);
// 获取类方法的函数属性值AccessFlag
uint32_t ac = (method->GetAccessFlags()) & mask;
// 获取类方法的字节码偏移地址CodeItemOffset
uint32_t codeitem_off = method->GetCodeItemOffset();
// 通过类方法的DexMethodIndex获取到类方法的名称字符串
uint32_t dex_method_idx = method->GetDexMethodIndex();
const char * name = dex_file.GetMethodName(dex_file.GetMethodId(dex_method_idx));
// 判断dex文件中的类方法函数属性值AccessFlag与实际类运行时art::mirror::ArtMethod中函数属性值是否一致
if (ac != pData->directMethods[i].accessFlags)
{
#ifdef LOGI
LOG(INFO)<<"GOT IT direct method AF changed "<<name;
#endif
// 腾讯加固有这种情况,运行时native函数恢复为java函数
need_extra=true;
// 进行类方法函数属性的更正(以运行时为准)
pData->directMethods[i].accessFlags=ac;
}
// 根据类方法实际运行时的codeitem_off与dex文件中的codeOff偏移地址的不同,进行类方法字节码DexCode偏移地址的修正
if (codeitem_off != pData->directMethods[i].codeOff&&((codeitem_off>=start&&codeitem_off<=end)||codeitem_off==0)) {
#ifdef LOGI
LOG(INFO)<<"GOT IT direct method code changed "<<name;
#endif
need_extra=true;
// 以类方法实际运行时的codeitem_off为准,对dex文件中的类方法字节码DexCode的偏移地址进行修正
pData->directMethods[i].codeOff=codeitem_off;
}
// 类方法的字节码DexCode被存放在了OAT文件的文件末尾或者dex文件的ClassDef结构体表开始地址之前的位置
// 阿里加固出现过这种情况
if ((codeitem_off<start || codeitem_off>end) && codeitem_off!=0) {
#ifdef LOGI
LOG(INFO)<<"GOT IT direct method code changed "<<name;
#endif
need_extra=true;
// 统计将dex文件的类方法字节码DexFile::CodeItem从OAT文件结尾的位置开始存放(注意内存4字节对齐)
pData->directMethods[i].codeOff = total_pointer;
// 获取类方法字节码描述结构体指针DexFile::CodeItem
const DexFile::CodeItem * code = dex_file.GetCodeItem(codeitem_off);
// 类方法字节码描述结构体DexFile::CodeItem存放的起始地址
uint8_t *item=(uint8_t *) code;
int code_item_len = 0;
// 判断类方法是否有try...catch{}语句
if (code->tries_size_) {
// 获取有try...catch{}语句情况下,类方法的字节码DexFile::CodeItem结构体的结束地址
const byte *handler_data = (const byte *)(DexFile::GetTryItems(*code, code->tries_size_));
uint8_t * tail = codeitem_end(&handler_data);
// 有try...catch{}语句情况下,类方法的字节码DexFile::CodeItem结构体的字节大小
code_item_len = (int)(tail - item);
}else{
// 无try...catch{}语句情况下,类方法的字节码DexFile::CodeItem结构体的字节大小
code_item_len = 16+code->insns_size_in_code_units_*2;
}
// 将不在正常dex文件偏移存放位置的类方法字节码DexFile::CodeItem结构体的数据写入到xxxx/extra文件中
fwrite(item, 1, code_item_len, fp1);
fflush(fp1);
// 更新存放类方法字节码DexFile::CodeItem的文件偏移指针
total_pointer += code_item_len;
// 进行内存4字节对齐的填充处理
while (total_pointer&3) {
fwrite(&padding,1,1,fp1);
fflush(fp1);
total_pointer++;
}
#ifdef LOGI
LOG(INFO)<<"GOT IT total_pointer "<<total_pointer;
#endif
}
}
}
// 针对类虚方法的处理(和上面类直接方法的处理一样)
if (pData->virtualMethods) {
// 遍历类的虚方法
for (uint32_t i=0; i<pData->header.virtualMethodsSize; i++) {
art::mirror::ArtMethod *method = klass->GetVirtualMethod(i);
uint32_t ac = (method->GetAccessFlags()) & mask;
uint32_t codeitem_off = method->GetCodeItemOffset();
uint32_t dex_method_idx = method->GetDexMethodIndex();
const char * name = dex_file.GetMethodName(dex_file.GetMethodId(dex_method_idx));
// 类方法函数属性accessFlags的修复
if (ac != pData->virtualMethods[i].accessFlags)
{
#ifdef LOGI
LOG(INFO)<<"GOT IT virtual method AF changed "<<name;
#endif
need_extra=true;
pData->virtualMethods[i].accessFlags=ac;
}
// 类方法codeitem_off的修正,以类方法运行时的数据为准
if (codeitem_off!=pData->virtualMethods[i].codeOff&&((codeitem_off>=start&&codeitem_off<=end)||codeitem_off==0)) {
#ifdef LOGI
LOG(INFO)<<"GOT IT virtual method code changed "<<name;
#endif
need_extra=true;
pData->virtualMethods[i].codeOff=codeitem_off;
}
// 类方法的字节码DexCode被存放在了OAT文件的文件末尾或者dex文件的ClassDef结构体表开始地址之前的位置
if ((codeitem_off<start || codeitem_off>end)&&codeitem_off!=0) {
#ifdef LOGI
LOG(INFO)<<"GOT IT virtual method code changed "<<name;
#endif
need_extra=true;
// 统计将dex文件的类方法字节码DexFile::CodeItem从OAT文件结尾的位置开始存放(注意内存4字节对齐)
pData->virtualMethods[i].codeOff = total_pointer;
const art::DexFile::CodeItem * code = dex_file.GetCodeItem(codeitem_off);
uint8_t *item=(uint8_t *) code;
int code_item_len = 0;
// 判断类方法是否有try...catch{}语句
if (code->tries_size_) {
const byte *handler_data = (const byte *)(DexFile::GetTryItems(*code, code->tries_size_));
uint8_t * tail=codeitem_end(&handler_data);
// 有try...catch{}语句情况下,类方法的字节码DexFile::CodeItem结构体的字节大小
code_item_len = (int)(tail-item);
}else{
// 无try...catch{}语句情况下,类方法的字节码DexFile::CodeItem结构体的字节大小
code_item_len = 16+code->insns_size_in_code_units_*2;
}
// 将不在正常dex文件偏移存放位置的类方法字节码DexFile::CodeItem结构体的数据写入到xxxx/extra文件中
fwrite(item,1,code_item_len,fp1);
fflush(fp1);
// 更新存放类方法字节码DexFile::CodeItem的文件偏移指针
total_pointer+=code_item_len;
// 进行内存4字节对齐的填充处理
while (total_pointer&3) {
fwrite(&padding,1,1,fp1);
fflush(fp1);
total_pointer++;
}
#ifdef LOGI
LOG(INFO)<<"GOT IT total_pointer "<<total_pointer;
#endif
}
}
}
// 上面是针对dex文件类方法的字节码DexFile::CodeItem的修正处理并保存到xxxx/extra文件中
// 以及对dex文件的ClassDef对应的ClassData的数据修正处理(以运行时的类描述信息为准)
// 下面是针对dex文件的
classdef:
DexFile::ClassDef *temp = (DexFile::ClassDef*) malloc(sizeof(DexFile::ClassDef));
if (!temp) {
continue;
}
temp->class_idx_ = class_def.class_idx_;
temp->pad1_=class_def.pad1_;
temp->pad2_=class_def.pad2_;
temp->access_flags_=class_def.access_flags_;
temp->annotations_off_= class_def.annotations_off_;
temp->class_data_off_=class_def.class_data_off_;
temp->interfaces_off_=class_def.interfaces_off_;
temp->source_file_idx_=class_def.source_file_idx_;
temp->static_values_off_=class_def.static_values_off_;
temp->superclass_idx_=class_def.superclass_idx_;
if (pass) {
// Android系统类和无效类的情况处理
temp->class_data_off_=0;
temp->annotations_off_=0;
}
uint8_t *p = (uint8_t *)temp;
if (need_extra) {
// dex文件的类DexClassData需要修正情况
int class_data_len = 0;
// 将DexClassData所表示的类数据pData进行leb128编码写入到申请的内存空间中
// class_data_len为保存DexClassData进行leb128编码后的数据长度
uint8_t *out = dexEncodeClassData(pData, class_data_len);
if (!out) {
continue;
}
// 修正DexFile::ClassDef中指向DexClassData的文件偏移指针
temp->class_data_off_ = total_pointer;
#ifdef LOGI
LOG(INFO)<<"GOT IT write extra";
#endif
// 将正确修正后的dex文件的类DexClassData数据保存到xxxx/extra文件中
fwrite(out, 1, class_data_len, fp1);
fflush(fp1);
// 更新在xxxx/extra文件中存放DexClassData或者DexFile::CodeItem的文件偏移指针
total_pointer += class_data_len;
// 内存4字节对齐的填充处理
while (total_pointer&3) {
fwrite(&padding, 1, 1, fp1);
fflush(fp1);
total_pointer++;
}
#ifdef LOGI
LOG(INFO)<<"GOT IT total_pointer "<<total_pointer;
#endif
free(out);
} else {
if (pData) {
free(pData);
}
}
#ifdef LOGI
LOG(INFO)<<"GOT IT write classdef";
#endif
// 将根据上面xxxx/extra文件中存放的DexClassData数据修正的DexFile::ClassDef结构体数据保存到xxxx/classdef文件中
fwrite(p, sizeof(DexFile::ClassDef), 1, fp);
fflush(fp);
free(temp);
}
// 释放锁
Locks::mutator_lock_->SharedUnlock(self);
// 关闭文件
fclose(fp1);
fclose(fp);
#ifdef LOGI
LOG(INFO)<<"GOT IT ClassDumped";
#endif
self->SetState(kSleeping);
// 取消对art虚拟机线程的附加
runtime->DetachCurrentThread();
// 需要脱壳的dex文件的内存dump已经全部完成,后面是dump后的dex文件重组处理
// 拼接字符串得到文件路径xxxx/whole.dex
strcpy(path,dumppath);
strcat(path,"whole.dex");
// 打开文件xxxx/whole.dex(用以存放dump重组后的dex文件)
fp = fopen(path,"wb+");
// 设置文件指针在文件开头
rewind(fp);
int fd=-1;
int r=-1;
int len=0;
char *addr=NULL;
struct stat st;
strcpy(path, dumppath);
strcat(path,"part0");
// 打开文件xxxx/part0
fp1 = fopen(path,"rb");
char reg=0;
// 读取文件xxxx/part0中的数据内容保存到文件xxxx/whole.dex中
for (uint32_t i = 0; i < 16; i++) {
fread(®, 1, 1, fp1);
fwrite(®, 1, 1, fp);
fflush(fp);
}
fclose(fp1);
strcpy(path,dumppath);
strcat(path,"part1");
// 打开文件xxxx/part1
fd = open(path, O_RDONLY, 0666);
if (fd == -1) {
return NULL;
}
// 获取文件xxxx/part1的数据大小
r = fstat(fd, &st);
if(r == -1){
close(fd);
return NULL;
}
len = st.st_size;
// 为文件xxxx/part1创建内存映射进行文件的读取操作
addr = (char*)mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
// 将文件xxxx/part1的数据内容写入保存到文件xxxx/whole.dex中
fwrite(addr, 1, len, fp);
// 刷新文件流
fflush(fp);
// 取消内存映射
munmap(addr,len);
close(fd);
#ifdef LOGI
LOG(INFO)<<"GOT IT part1 over ";
#endif
strcpy(path, dumppath);
strcat(path, "classdef");
// 打开文件xxxx/classdef
fd = open(path, O_RDONLY, 0666);
if (fd==-1) {
return NULL;
}
// 获取文件xxxx/classdef的数据大小
r = fstat(fd,&st);
if(r==-1){
close(fd);
return NULL;
}
len=st.st_size;
// 为文件xxxx/classdef创建内存映射实现文件的读取操作
addr = (char*)mmap(NULL,len,PROT_READ,MAP_PRIVATE,fd,0);
// 将文件xxxx/classdef的数据内容写入到文件xxxx/whole.dex中
fwrite(addr,1,len,fp);
fflush(fp);
munmap(addr,len);
close(fd);
#ifdef LOGI
LOG(INFO)<<"GOT IT classdef over ";
#endif
strcpy(path,dumppath);
strcat(path,"data");
// 打开文件xxxx/data
fd = open(path, O_RDONLY, 0666);
if (fd==-1) {
return NULL;
}
// 获取文件xxxx/data的文件大小
r = fstat(fd, &st);
if(r==-1){
close(fd);
return NULL;
}
len=st.st_size;
// 为文件xxxx/data创建文件内存映射
addr=(char*)mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
// 将文件xxxx/data的数据内容写入到文件xxxx/whole.dex中
fwrite(addr,1,len,fp);
fflush(fp);
munmap(addr,len);
close(fd);
#ifdef LOGI
LOG(INFO)<<"GOT IT data over ";
#endif
// 对 xxxx/data 进行内存4字节对齐的'0'填充处理
while (inc>0) {
fwrite(&padding, 1, 1, fp);
fflush(fp);
inc--;
}
strcpy(path, dumppath);
strcat(path,"extra");
// 打开文件xxxx/extra
fd = open(path,O_RDONLY,0666);
if (fd==-1) {
return NULL;
}
// 获取文件xxxx/extra的文件大小
r = fstat(fd,&st);
if(r == -1){
close(fd);
return NULL;
}
len=st.st_size;
// 为文件xxxx/extra创建文件内存映射实现文件的读取操作
addr = (char*)mmap(NULL,len,PROT_READ,MAP_PRIVATE,fd,0);
// 将文件xxxx/extra的数据内容保存写入到文件xxxx/whole.dex中
fwrite(addr, 1, len, fp);
fflush(fp);
munmap(addr,len);
close(fd);
/****
xxxx/part0
xxxx/part1
xxxx/classdef (dex文件的重组)--------> xxxx/whole.dex
xxxx/data
xxxx/extra
*****/
#ifdef LOGI
LOG(INFO)<<"GOT IT extra over ";
#endif
fclose(fp);
delete path;
#ifdef LOGI
time = MilliTime();
LOG(INFO)<<"GOT IT end "<<time<<" ms";
#endif
return NULL;
}
//-----------------------added end-----------------------//
// art下的类加载函数
mirror::Class* ClassLinker::DefineClass(const char* descriptor,
mirror::ClassLoader* class_loader,
const DexFile& dex_file,
const DexFile::ClassDef& dex_class_def) {
//-----------------------added begin-----------------------//
int uid=::art::GetUid();
// compiler用来指定当前要创建的ART虚拟机是用来将DEX字节码编译成本地机器指令的
// 因此排除掉Runtime::Current()->IsCompiler()为true的优化dex文件的art虚拟机情况
// 参考:http://blog.csdn.net/Luoshengyang/article/details/39307813
if (Runtime::Current()->IsCompiler()) {
goto there;
}
// 排除掉系统进程(uid == 0的情况)
if (uid) {
if (readable) {
// 锁
pthread_mutex_lock(&read_mutex);
if (readable) {
readable=false;
pthread_mutex_unlock(&read_mutex);
pthread_t read_thread;
// 创建线程,读取脱壳配置文件/data/dexname的信息
// 获取需要脱壳dex文件的加载路径dexname以及存放脱壳后dex文件dump文件路径dumppath
pthread_create(&read_thread, NULL, ReadThread, NULL);
}else{
pthread_mutex_unlock(&read_mutex);
}
}
}
// 排除掉uid==0的进程以及被dump的加固dex文件的内存加载路径不能为空
if(uid && strcmp(dexname,"")){
// dex_file.GetLocation().c_str()获取到art下dex文件的加载路径
// 通过加固dex文件的加载路径判断是否是需要脱壳的dex文件
char * res = strstr(dex_file.GetLocation().c_str(), dexname);
if (res && flag) {
pthread_mutex_lock(&mutex);
if (flag) {
flag=false;
pthread_mutex_unlock(&mutex);
char * temp = new char[100];
strcpy(temp, dumppath);
// 拼接字符串得到文件路径 xxxx/part0
strcat(temp,"part0");
// 打开文件xxxx/part0
FILE *fp = fopen(temp, "wb+");
// ART下的OAT文件是一个私有的ELF文件格式
// 获取OAT文件的内存映射的起始地址即dex优化后私有ELF文件的内存映射地址
const byte *addr = dex_file.Begin();
int length = 16;
// 将OAT文件(即私有ELF文件的)前16字节数据保存到xxxx/part0中
for (int i = 0; i < 16; i++) {
fwrite(addr+i, 1 ,1 ,fp);
fflush(fp);
}
fclose(fp);
strcpy(temp, dumppath);
// 得到路径字符串xxxx/part1
strcat(temp,"part1");
// 打开文件xxxx/part1
fp = fopen(temp, "wb+");
// 内存地址指针移动到OAT文件即私有ELF文件的第17个字节的位置
addr = dex_file.Begin() + 16;
// 获取到OAT文件里即私有ELF文件的第17个字节到dex文件class_defs_off_开始地址之间的数据长度
length = dex_file.class_defs_off_ - 16;
// 将OAT文件里即私有ELF文件的第17个字节到dex文件class_defs_off_开始地址的数据写入到xxxx/part1中
fwrite(addr, 1, length, fp);
// 刷新文件流
fflush(fp);
fclose(fp);
// 拼接得到字符串xxxx/data
strcpy(temp, dumppath);
strcat(temp,"data");
// 打开文件xxxx/data
fp = fopen(temp,"wb+");
// 将OAT文件里的dex文件指针移动到dex文件的ClassDef结构体表Table的结尾位置
addr = dex_file.Begin()+dex_file.class_defs_off_+sizeof(DexFile::ClassDef)*dex_file.NumClassDefs();
// 获取OAT文件里dex文件的ClassDef结构体表Table的结尾位置到OAT文件的结尾数据长度
length=dex_file.Size()-dex_file.class_defs_off_-sizeof(DexFile::ClassDef)*dex_file.NumClassDefs();
// 将OAT文件里dex文件的ClassDef结构体表Table的结束位置到OAT文件的结尾数据写入到xxxx/data文件中
fwrite(addr, 1, length, fp);
fflush(fp);
fclose(fp);
delete temp;
// 当前dex文件所在的class_loader
param.class_loader = class_loader;
// 当前dex文件的内存加载的镜像描述结构体
param.dex_file = &dex_file;
// 当前dex文件所在的ClassLinker
param.cl = this;
pthread_t dumpthread;
// 创建线程对需要脱壳dex的OAT文件里的类数据进行内存dump处理
pthread_create(&dumpthread, NULL, DumpClass, (void*)¶m);
}else{
pthread_mutex_unlock(&mutex);
}
}
}
//-----------------------added end-----------------------//
there:
Thread* self = Thread::Current();
SirtRef<mirror::Class> klass(self, NULL);
// Load the class from the dex file.
if (UNLIKELY(!init_done_)) {
// finish up init of hand crafted class_roots_
if (strcmp(descriptor, "Ljava/lang/Object;") == 0) {
klass.reset(GetClassRoot(kJavaLangObject));
} else if (strcmp(descriptor, "Ljava/lang/Class;") == 0) {
klass.reset(GetClassRoot(kJavaLangClass));
} else if (strcmp(descriptor, "Ljava/lang/String;") == 0) {
klass.reset(GetClassRoot(kJavaLangString));
} else if (strcmp(descriptor, "Ljava/lang/DexCache;") == 0) {
klass.reset(GetClassRoot(kJavaLangDexCache));
} else if (strcmp(descriptor, "Ljava/lang/reflect/ArtField;") == 0) {
klass.reset(GetClassRoot(kJavaLangReflectArtField));
} else if (strcmp(descriptor, "Ljava/lang/reflect/ArtMethod;") == 0) {
klass.reset(GetClassRoot(kJavaLangReflectArtMethod));
} else {
klass.reset(AllocClass(self, SizeOfClass(dex_file, dex_class_def)));
}
} else {
klass.reset(AllocClass(self, SizeOfClass(dex_file, dex_class_def)));
}
if (UNLIKELY(klass.get() == NULL)) {
CHECK(self->IsExceptionPending()); // Expect an OOME.
return NULL;
}
klass->SetDexCache(FindDexCache(dex_file));
LoadClass(dex_file, dex_class_def, klass, class_loader);
// Check for a pending exception during load
if (self->IsExceptionPending()) {
klass->SetStatus(mirror::Class::kStatusError, self);
return NULL;
}
ObjectLock lock(self, klass.get());
klass->SetClinitThreadId(self->GetTid());
{
// Add the newly loaded class to the loaded classes table.
mirror::Class* existing = InsertClass(descriptor, klass.get(), Hash(descriptor));
if (existing != NULL) {
// We failed to insert because we raced with another thread. Calling EnsureResolved may cause
// this thread to block.
return EnsureResolved(self, existing);
}
}
// Finish loading (if necessary) by finding parents
CHECK(!klass->IsLoaded());
if (!LoadSuperAndInterfaces(klass, dex_file)) {
// Loading failed.
klass->SetStatus(mirror::Class::kStatusError, self);
return NULL;
}
CHECK(klass->IsLoaded());
// Link the class (if necessary)
CHECK(!klass->IsResolved());
if (!LinkClass(klass, NULL, self)) {
// Linking failed.
klass->SetStatus(mirror::Class::kStatusError, self);
return NULL;
}
CHECK(klass->IsResolved());
/*
* We send CLASS_PREPARE events to the debugger from here. The
* definition of "preparation" is creating the static fields for a
* class and initializing them to the standard default values, but not
* executing any code (that comes later, during "initialization").
*
* We did the static preparation in LinkClass.
*
* The class has been prepared and resolved but possibly not yet verified
* at this point.
*/
Dbg::PostClassPrepare(klass.get());
return klass.get();
}
DexHunter在ART虚拟机模式下的脱壳原理分析的更多相关文章
- DexHunter在Dalvik虚拟机模式下的脱壳原理分析
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78494671 在前面的博客<DexHunter的原理分析和使用说明(一)&g ...
- android 脱壳 之 dvmDexFileOpenPartial断点脱壳原理分析
android 脱壳 之 dvmDexFileOpenPartial断点脱壳原理分析 导语: 笔者主要研究方向是网络通信协议的加密解密, 对应用程序加固脱壳技术很少研究, 脱壳壳经历更是经历少之甚少. ...
- 基于Frida框架打造Art模式下的脱壳工具(OpenMemory)的原理分析
本文博客地址:https://blog.csdn.net/QQ1084283172/article/details/80956614 作者dstmath在看雪论坛公布一个Android的art模式下基 ...
- PHP CLI模式下的多进程应用分析
PHP在非常多时候不适合做常驻的SHELL进程, 他没有专门的gc例程, 也没有有效的内存管理途径. 所以假设用PHP做常驻SHELL, 你会常常被内存耗尽导致abort而unhappy 并且, 假设 ...
- 【odoo】【知识杂谈】单一实例多库模式下定时任务的问题分析
欢迎转载,但需标注出处,谢谢! 背景: 有客户反应有个别模块下的定时任务没有正常执行,是否是新装的模块哪些有问题?排查后发现,客户是在一台服务器上跑着一个odoo容器,对应多个数据库.个别库的定时任务 ...
- ART模式下基于dex2oat脱壳的原理分析
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78513483 一般情况下,Android Dex文件在加载到内存之前需要先对dex ...
- ART模式下基于Xposed Hook开发脱壳工具
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78092365 Dalvik模式下的Android加固技术已经很成熟了,Dalvik ...
- Dalvik模式下System.loadLibrary函数的执行流程分析
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78212010 Android逆向分析的过程中免不了碰到Android so被加固的 ...
- Dalvik模式下在Android so库文件.init段、.init_array段构造函数上下断点
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78244766 在前面的博客<在Android so文件的.init..ini ...
随机推荐
- 反射的常用API
反射的常用API 加载程序集 Assembly assembly = Assembly.Load("程序集名称"); // 从前目录加载程序集,提供程序集名称,无后缀 Assemb ...
- Java 常用类——StringBuffer&StringBuilder【可变字符序列】
一.字符串拼接问题 由于 String 类的对象内容不可改变,所以每当进行字符串拼接时,总是会在内存中创建一个新的对象. Demo: 1 public class StringDemo { 2 pub ...
- Linux下查看文件内容的几种常用命令
[常用] 1,cat 由第一行开始显示内容,并将所有内容输出 cat的功能是将文件从第一行开始连续的将内容输出在屏幕上.但是cat并不常用,原因是当文件大,行数比较多时,屏幕无法全部容下时,只 ...
- NumPy 将停止支持 Python 2
NumPy 项目宣布将停止支持 Python 2.Python 核心团队已经决定在 2020 年停止支持 Python 2,而 NumPy 项目自 2010 年以来同时支持 Python 2 和 Py ...
- C# 基础 - 委托、事件
1. 委托 sequenceDiagram 方法->>委托: 返回值和入参一样 委托->>方法: 调用委托就是调用绑定的方法 delegate int NumTest(int ...
- 用实战玩转pandas数据分析(一)——用户消费行为分析(python)
CD商品订单数据的分析总结.根据订单数据(用户的消费记录),从时间维度和用户维度,分析该网站用户的消费行为.通过此案例,总结订单数据的一些共性,能通过用户的消费记录挖掘出对业务有用的信息.对其他产 ...
- webpack4.x 从零开始配置vue 项目(一)基础搭建项目
序 现在依旧记得第一次看到webpack3.x 版本配置时候的状态 刚开始看到这些真的是一脸懵.希望这篇文章能帮到刚开始入门的同学. webpack 是什么? webpack是一个模块化打包工具,w ...
- Codeforces Round #553 B. Dima and a Bad XOR
题面: 传送门 题目描述: 题意很简单:在一个N*M的矩阵中(N行M列),问是否可以:每行选一个整数,使他们的异或和大于0.如果不可以,输出"NIE":如果可以,输出"T ...
- windows如何上传ios app到appstore
我们在hbuilderx这些开发工具打包好ios app后,需要将这个app提交appstore才能让用户下载安装. 上传IOS APP主要是通过苹果开发者中心来上传,然后借助香蕉云编上传工具来上传就 ...
- POJ1979_Red and Black(JAVA语言)
思路:bfs裸题. 对这种迷宫问题的bfs,我们把坐标点用一个class来存储,并放入队列进行求解. //一直接收不了输入,找了一个多小时的问题,居然是行和列搞反了ORZ Red and Black ...