基于dalvik模式下的Xposed Hook开发的某加固脱壳工具
来源:互联网 发布:linux安全加固脚本 编辑:程序博客网 时间:2024/05/18 05:57
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/77966109
这段时间好好的学习了一下Android加固相关的知识和流程也大致把Android加固的一些思路理清楚了,不巧在博客上看到了这篇文章《某加固使用xposed脱壳》感觉还不错,正好有时间可以学习一下这款基于Xposed Hook框架开发的某加固脱壳工具,原作者的博客已经找不到但是作者已经给出了主要的实现代码,我在作者给出的原代码基础上稍微做了一下修改和优化并且在Android Nexus 5手机上测试某加固的脱壳,成功得到了某加固解密后的dex文件。在进行源码分析之前,你需要了解Xposed Hook框架模块编写相关的知识,还不了解的可以先看下我前面的博客《Xposed框架之函数Hook学习》。Android加固的实现是基于DexClassLoader的加载流程来实现的,具体详细的流程后面有时间再整理一下,因此这里使用Xposed Hook框架来脱某加固壳的工具也是基于DexClassLoader的加载流程来实现的。
下图是DexClassLoader在整个java层的实现流程:
从DexClassLoader到openDexFileNative函数的整个流程下来,DexClassLoader的java层实现全部完成,openDexFileNative之后是由native层函数实现,暂时不关心;dex文件的加固是基于DexClassLoader的加载流程而来的,dex文件优化为odex文件后加载到apk进程的内存中返回是mCookie值,这个mCookie值就是dex文件加载到内存之后的镜像描述结构体指针DexOrJar* ,DexOrJar结构体的实现如下图所示:
http://androidxref.com/4.4.4_r1/xref/dalvik/vm/native/dalvik_system_DexFile.cpp#DexOrJar
/* * Internal struct for managing DexFile. */struct DexOrJar {// 描述的dex文件或者jar文件的路径 char* fileName;// 是否是dex文件的标识 bool isDex; bool okayToFree;// 描述dex文件内存加载镜像odex文件的结构体 RawDexFile* pRawDexFile;// 描述内存加载后的jar文件的结构体 JarFile* pJarFile; u1* pDexMemory; // malloc()ed memory, if any};
openDexFileNative在native层对应的实现函数是 Dalvik_dalvik_system_DexFile_openDexFileNative。
http://androidxref.com/4.4.4_r1/xref/dalvik/vm/native/dalvik_system_DexFile.cpp#151
// 对应函数的注册结构体const DalvikNativeMethod dvm_dalvik_system_DexFile[] = { { "openDexFileNative", "(Ljava/lang/String;Ljava/lang/String;I)I", Dalvik_dalvik_system_DexFile_openDexFileNative }, { "openDexFile", "([B)I", Dalvik_dalvik_system_DexFile_openDexFile_bytearray }, { "closeDexFile", "(I)V", Dalvik_dalvik_system_DexFile_closeDexFile }, { "defineClassNative", "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;", Dalvik_dalvik_system_DexFile_defineClassNative }, { "getClassNameList", "(I)[Ljava/lang/String;", Dalvik_dalvik_system_DexFile_getClassNameList }, { "isDexOptNeeded", "(Ljava/lang/String;)Z", Dalvik_dalvik_system_DexFile_isDexOptNeeded }, { NULL, NULL, NULL },};
/* * private static int openDexFileNative(String sourceName, String outputName, * int flags) throws IOException * * Open a DEX file, returning a pointer to our internal data structure. * * "sourceName" should point to the "source" jar or DEX file. * * If "outputName" is NULL, the DEX code will automatically find the * "optimized" version in the cache directory, creating it if necessary. * If it's non-NULL, the specified file will be used instead. * * TODO: at present we will happily open the same file more than once. * To optimize this away we could search for existing entries in the hash * table and refCount them. Requires atomic ops or adding "synchronized" * to the non-native code that calls here. * * TODO: should be using "long" for a pointer. */static void Dalvik_dalvik_system_DexFile_openDexFileNative(const u4* args, JValue* pResult){ StringObject* sourceNameObj = (StringObject*) args[0]; StringObject* outputNameObj = (StringObject*) args[1]; DexOrJar* pDexOrJar = NULL; JarFile* pJarFile; RawDexFile* pRawDexFile; char* sourceName; char* outputName; if (sourceNameObj == NULL) { dvmThrowNullPointerException("sourceName == null"); RETURN_VOID(); } sourceName = dvmCreateCstrFromString(sourceNameObj); if (outputNameObj != NULL) outputName = dvmCreateCstrFromString(outputNameObj); else outputName = NULL; /* * We have to deal with the possibility that somebody might try to * open one of our bootstrap class DEX files. The set of dependencies * will be different, and hence the results of optimization might be * different, which means we'd actually need to have two versions of * the optimized DEX: one that only knows about part of the boot class * path, and one that knows about everything in it. The latter might * optimize field/method accesses based on a class that appeared later * in the class path. * * We can't let the user-defined class loader open it and start using * the classes, since the optimized form of the code skips some of * the method and field resolution that we would ordinarily do, and * we'd have the wrong semantics. * * We have to reject attempts to manually open a DEX file from the boot * class path. The easiest way to do this is by filename, which works * out because variations in name (e.g. "/system/framework/./ext.jar") * result in us hitting a different dalvik-cache entry. It's also fine * if the caller specifies their own output file. */ if (dvmClassPathContains(gDvm.bootClassPath, sourceName)) { ALOGW("Refusing to reopen boot DEX '%s'", sourceName); dvmThrowIOException( "Re-opening BOOTCLASSPATH DEX files is not allowed"); free(sourceName); free(outputName); RETURN_VOID(); } /* * Try to open it directly as a DEX if the name ends with ".dex". * If that fails (or isn't tried in the first place), try it as a * Zip with a "classes.dex" inside. */ if (hasDexExtension(sourceName) && dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) { ALOGV("Opening DEX file '%s' (DEX)", sourceName); pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar)); pDexOrJar->isDex = true; pDexOrJar->pRawDexFile = pRawDexFile; pDexOrJar->pDexMemory = NULL; } else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) { ALOGV("Opening DEX file '%s' (Jar)", sourceName); pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar)); pDexOrJar->isDex = false; pDexOrJar->pJarFile = pJarFile; pDexOrJar->pDexMemory = NULL; } else { ALOGV("Unable to open DEX file '%s'", sourceName); dvmThrowIOException("unable to open DEX file"); } if (pDexOrJar != NULL) { pDexOrJar->fileName = sourceName; addToDexFileTable(pDexOrJar); } else { free(sourceName); } free(outputName); RETURN_PTR(pDexOrJar);}
基于Xposed Hook开发的某加固脱壳工具是使用Xposed Hook框架Hook掉 DexClassLoader加载dex文件流程中类dalvik.system.DexFile的方法loadDex函数,等到loadDex函数返回时拿到dex文件内存加载后的mCookie值,然后内存dump出mCookie值描述的dex文件的值。类dalvik.system.DexFile的loadDex函数的实现如下:
http://androidxref.com/4.4.4_r1/xref/libcore/dalvik/src/main/java/dalvik/system/DexFile.java#141
/** * Open a DEX file, specifying the file in which the optimized DEX * data should be written. If the optimized form exists and appears * to be current, it will be used; if not, the VM will attempt to * regenerate it. * * This is intended for use by applications that wish to download * and execute DEX files outside the usual application installation * mechanism. This function should not be called directly by an * application; instead, use a class loader such as * dalvik.system.DexClassLoader. * * @param sourcePathName * Jar or APK file with "classes.dex". (May expand this to include * "raw DEX" in the future.) * @param outputPathName * File that will hold the optimized form of the DEX data. * @param flags * Enable optional features. (Currently none defined.) * @return * A new or previously-opened DexFile. * @throws IOException * If unable to open the source or output file. */ static public DexFile loadDex(String sourcePathName, String outputPathName, int flags) throws IOException { /* * TODO: we may want to cache previously-opened DexFile objects. * The cache would be synchronized with close(). This would help * us avoid mapping the same DEX more than once when an app * decided to open it multiple times. In practice this may not * be a real issue. */ return new DexFile(sourcePathName, outputPathName, flags); }
1.某加固脱壳工具Xposed Hook模块的编写,xxx.yyy.zzz为需要被脱壳的apk应用的包名,Xposed Hook掉类dalvik.system.DexFile的方法loadDex函数,获取到dex文件内存加载后的mCookie值,然后进行内存dex文件的dump处理。
package com.xposeddemo;import java.lang.reflect.Field;import dalvik.system.DexFile;import de.robv.android.xposed.IXposedHookLoadPackage;import de.robv.android.xposed.XC_MethodHook;import de.robv.android.xposed.XposedBridge;import de.robv.android.xposed.XposedHelpers;import de.robv.android.xposed.callbacks.XC_LoadPackage;import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;public class Module implements IXposedHookLoadPackage {// native方法在libnativelib.so库文件中实现public native void dumpdex(int cookie);// 内部类class dumpThread implements Runnable { int cookide; public dumpThread(int cookide){ // 保存dex文件的mCookie值 this.cookide = cookide; } @Override public void run() { try { // 休眠5s 时间足够壳修复dex Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // 从内存中dump出解密后的内存dex文件 dumpdex(cookide); }}@Overridepublic void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {// 判断是否是要Hook的包名(xxx.yyy.zzz为需要脱壳的apk的包名)if (lpparam.packageName.equals("xxx.yyy.zzz")){XposedBridge.log("Loaded App:" + lpparam.packageName); // 加载动态库文件libnativelib.so System.load("/data/data/com.xposeddemo/lib/libnativelib.so");// 对类dalvik.system.DexFile的方法loadDex进行java Hook操作 // 获取到需要脱壳apk解密dex文件加载后返回的mCookie值 // 根据mCookie值进行内存dex文件的dump操作loadhooklib(lpparam);}} private void loadhooklib(XC_LoadPackage.LoadPackageParam lpparam) { // 对类dalvik.system.DexFile的方法loadDex进行dalvik模式下的java Hook操作// /libcore/dalvik/src/main/java/dalvik/system/DexFile.java// static public DexFile loadDex(String sourcePathName, String outputPathName, int flags)// http://androidxref.com/4.4.4_r1/xref/libcore/dalvik/src/main/java/dalvik/system/DexFile.java#141 XposedHelpers.findAndHookMethod(DexFile.class.getName(), lpparam.classLoader, "loadDex", String.class, String.class, int.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (!param.hasThrowable()) { int falg = (Integer) param.args[2]; // 加载的dex文件的路径 String sourcePathName = (String) param.args[0]; // dex被优化后的odex文件的存放路径 String outputPathName = (String) param.args[1]; XposedBridge.log("sourcePathName:" + sourcePathName + " outputPathName:" + outputPathName + " falg:" + falg); // 获取dex文件被loadDex后返回的DexFile文件对象 Object object = param.getResult(); if (object instanceof DexFile) { // 通过类反射获取DexFile类的私有成员mCookie的调用Field Field field = ((DexFile) object).getClass().getDeclaredField("mCookie"); // 设置有权限 field.setAccessible(true); // 获取到DexFile类的私有成员mCookie的值 int cookie = field.getInt(object); // 恢复权限 field.setAccessible(false); System.out.println("cookie:" + String.format("%x", cookie)); // 创建线程对需要脱壳的apk进程进行内存dex的dump操作 Thread thread = new Thread(new Module.dumpThread(cookie)); // 启动线程 thread.start(); } } } }); }}
2.内存dump函数实现在动态库文件libnativelib.so中实现,先通过jni函数反射调用java方法获取到手机设备的scard卡的文件路径,然后将内存dump的dex文件保存到手机设备的scard卡文件路径中。原作者在nativelib.cpp文件的源码实现中使用了C++的stl模板库函数,考虑到代码中字符串的处理比较简单,去掉了C++的stl模板库函数的使用,直接使用C语言的相关函数替换掉了,并且做了一些小调整;原作者在处理 code_off偏移超过dex文件大小的加固类型脱壳时比较暴力,直接保存三倍dex文件长度;如果dex文件内存dump时,想处理的精细一些可以参考一下dexHunter代码的实现。
#include <jni.h>#include <stdio.h>#include <unistd.h>//#include <string>#include <android/log.h>#include "Object.h"//using std::string;char* getExternalStorageDirectory(JNIEnv* env);void printinfo(const char* tag, const char* fmt, ...);char* jstringTostring(JNIEnv* env, jstring str){ char* rtn = NULL; jclass clsstring = env->FindClass("java/lang/String"); jstring strencode = env->NewStringUTF("utf-8"); jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr = (jbyteArray)env->CallObjectMethod(str, mid, strencode); jsize alen = env->GetArrayLength(barr); jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE); if (alen > 0) { rtn = (char*)malloc(alen + 1); memcpy(rtn, ba, alen); rtn[alen] = 0; } env->ReleaseByteArrayElements(barr, ba, 0); return rtn;}// 通过jni函数反射调用java方法获取到设备的scard卡的文件路径char* getExternalStorageDirectory(JNIEnv* env){ jclass Environment = env->FindClass("android/os/Environment"); if (Environment != NULL) { //Messageprint::printinfo("util", "Environment class have found"); jmethodID getExternalStorageDirectoryID = env->GetStaticMethodID(Environment, "getExternalStorageDirectory", "()Ljava/io/File;"); if (getExternalStorageDirectoryID != NULL) { jobject fileobject = env->CallStaticObjectMethod(Environment, getExternalStorageDirectoryID); jclass Fileclass = env->FindClass("java/io/File"); jmethodID getAbsolutePathId = env->GetMethodID(Fileclass, "getAbsolutePath", "()Ljava/lang/String;"); jstring jstringPath = (jstring)env->CallObjectMethod(fileobject, getAbsolutePathId); char* StorageDirectoryPath = jstringTostring(env, jstringPath); return StorageDirectoryPath; } } return NULL;}// 使用了stl的库函数#ifdef __cplusplusextern "C" {#endif// 调用native层实现的jni方法dumpdexJNIEXPORT void JNICALL Java_com_xposeddemo_Module_dumpdex(JNIEnv *env, jobject instance, jint cookie) {DexOrJar* pDexOrJar = (DexOrJar*)cookie;DvmDex* pDvmDex;//打印dex文件的内存加载路径printf("jni", pDexOrJar->fileName);// 判断当前mCookie值是否是dex文件的if (pDexOrJar->isDex){// 得到内存加载的odex文件的信息结构体pDvmDex = pDexOrJar->pRawDexFile->pDvmDex;}else{pDvmDex = pDexOrJar->pJarFile->pDvmDex;}// 获取到描述内存加载的odex文件信息的结构体DexFileDexFile* dexFile = pDvmDex->pDexFile;// 得到内存加载的odex文件的基地址(起始地址)MemMapping mapping = pDvmDex->memMap;printinfo("jni","MemMapping:addr:%x length:%x baseAddr:%x baseLength:%x", mapping.addr, mapping.length, mapping.baseAddr, mapping.baseLength);// 通过jni函数反射调用java方法获取到设备的scard卡的文件路径char* path = getExternalStorageDirectory(env);char szBufferDexPath[128];memset(szBufferDexPath, 0, sizeof(szBufferDexPath));memcpy(szBufferDexPath, path, strlen(path));// 拼接字符串得到dump的dex文件的路径strcat(szBufferDexPath, "/xxxx.dex");printinfo("dump dex path: %s", szBufferDexPath);// F_OK = 0if (!access(szBufferDexPath, F_OK)){// 删除已经存在的文件remove(szBufferDexPath);}// 创建新文件保存dump的dex文件FILE* file = fopen(szBufferDexPath, "wb+");// 保存三倍dex文件长度(比较暴力,可以参考dexhunter的实现代码进行优化)fwrite(mapping.addr,mapping.length*3,1,file);// 关闭文件fclose(file);}#ifdef __cplusplus}#endif// 打印Log日志信息void printinfo(const char* tag, const char* fmt, ...){ va_list ap; char buf[1024]; va_start(ap, fmt); vsnprintf(buf, 1024, fmt, ap); va_end(ap); __android_log_write(ANDROID_LOG_INFO, tag, buf);}
3.jni目录需要的头文件 Object.h
#ifndef HELPTOOLCLIENT_OBJECT_H#define HELPTOOLCLIENT_OBJECT_H#include <stddef.h>#include <cstdint>#include <pthread.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;/* fwd decl */struct DataObject;struct InitiatingLoaderList;struct ClassObject;struct StringObject;struct ArrayObject;struct Method;struct ExceptionEntry;struct LineNumEntry;struct StaticField;struct InstField;struct Field;struct RegisterMap;struct Object;union JValue{ u1 z; s1 b; u2 c; s2 s; s4 i; s8 j; float f; double d; Object* l;};typedef void (*DalvikBridgeFunc)(const u4* args, JValue* pResult, const Method* method, struct Thread* self);enum AccessFlags{ ACC_MIRANDA = 0x8000, // method (internal to VM) JAVA_FLAGS_MASK = 0xffff, // bits set from Java sources (low 16)};typedef void (*DalvikNativeFunc)(const u4* args, JValue* pResult);enum ClassFlags{ CLASS_ISFINALIZABLE = (1 << 31), // class/ancestor overrides finalize() CLASS_ISARRAY = (1 << 30), // class is a "[*" CLASS_ISOBJECTARRAY = (1 << 29), // class is a "[L*" or "[[*" CLASS_ISCLASS = (1 << 28), // class is *the* class Class CLASS_ISREFERENCE = (1 << 27), // class is a soft/weak/phantom ref // only ISREFERENCE is set --> soft CLASS_ISWEAKREFERENCE = (1 << 26), // class is a weak reference CLASS_ISFINALIZERREFERENCE = (1 << 25), // class is a finalizer reference CLASS_ISPHANTOMREFERENCE = (1 << 24), // class is a phantom reference CLASS_MULTIPLE_DEFS = (1 << 23), // DEX verifier: defs in multiple DEXs /* unlike the others, these can be present in the optimized DEX file */ CLASS_ISOPTIMIZED = (1 << 17), // class may contain opt instrs CLASS_ISPREVERIFIED = (1 << 16), // class has been pre-verified};#define EXPECTED_FILE_FLAGS \ (ACC_CLASS_MASK | CLASS_ISPREVERIFIED | CLASS_ISOPTIMIZED)#define SET_CLASS_FLAG(clazz, flag) \ do { (clazz)->accessFlags |= (flag); } while (0)#define CLEAR_CLASS_FLAG(clazz, flag) \ do { (clazz)->accessFlags &= ~(flag); } while (0)#define IS_CLASS_FLAG_SET(clazz, flag) \ (((clazz)->accessFlags & (flag)) != 0)#define GET_CLASS_FLAG_GROUP(clazz, flags) \ ((u4)((clazz)->accessFlags & (flags)))enum MethodFlags{ METHOD_ISWRITABLE = (1 << 31), // the method's code is writable};#define SET_METHOD_FLAG(method, flag) \ do { (method)->accessFlags |= (flag); } while (0)#define CLEAR_METHOD_FLAG(method, flag) \ do { (method)->accessFlags &= ~(flag); } while (0)#define IS_METHOD_FLAG_SET(method, flag) \ (((method)->accessFlags & (flag)) != 0)#define GET_METHOD_FLAG_GROUP(method, flags) \ ((u4)((method)->accessFlags & (flags)))enum ClassStatus{ CLASS_ERROR = -1, CLASS_NOTREADY = 0, CLASS_IDX = 1, /* loaded, DEX idx in super or ifaces */ CLASS_LOADED = 2, /* DEX idx values resolved */ CLASS_RESOLVED = 3, /* part of linking */ CLASS_VERIFYING = 4, /* in the process of being verified */ CLASS_VERIFIED = 5, /* logically part of linking; done pre-init */ CLASS_INITIALIZING = 6, /* class init in progress */ CLASS_INITIALIZED = 7, /* ready to go */};#define CLASS_WALK_SUPER ((unsigned int)(3))#define CLASS_SMALLEST_OFFSET (sizeof(struct Object))#define CLASS_BITS_PER_WORD (sizeof(unsigned long int) * 8)#define CLASS_OFFSET_ALIGNMENT 4#define CLASS_HIGH_BIT ((unsigned int)1 << (CLASS_BITS_PER_WORD - 1))#define _CLASS_BIT_NUMBER_FROM_OFFSET(byteOffset) \ (((unsigned int)(byteOffset) - CLASS_SMALLEST_OFFSET) / \ CLASS_OFFSET_ALIGNMENT)#define CLASS_CAN_ENCODE_OFFSET(byteOffset) \ (_CLASS_BIT_NUMBER_FROM_OFFSET(byteOffset) < CLASS_BITS_PER_WORD)#define CLASS_BIT_FROM_OFFSET(byteOffset) \ (CLASS_HIGH_BIT >> _CLASS_BIT_NUMBER_FROM_OFFSET(byteOffset))#define CLASS_OFFSET_FROM_CLZ(rshift) \ (((int)(rshift) * CLASS_OFFSET_ALIGNMENT) + CLASS_SMALLEST_OFFSET)struct InterfaceEntry{ ClassObject* clazz; int* methodIndexArray;};struct Object{ ClassObject* clazz; u4 lock;};#define DVM_OBJECT_INIT(obj, clazz_) \ dvmSetFieldObject(obj, OFFSETOF_MEMBER(Object, clazz), clazz_)struct DataObject : Object{ u4 instanceData[1];};struct StringObject : Object{ u4 instanceData[1]; int length() const; int utfLength() const; ArrayObject* array() const; const u2* chars() const;};struct ArrayObject : Object{ u4 length; u8 contents[1];};struct InitiatingLoaderList{ Object** initiatingLoaders; int initiatingLoaderCount;};struct Field{ ClassObject* clazz; /* class in which the field is declared */ const char* name; const char* signature; /* e.g. "I", "[C", "Landroid/os/Debug;" */ u4 accessFlags;};struct StaticField : Field{ JValue value; /* initially set from DEX for primitives */};struct InstField : Field{ int byteOffset;};#define CLASS_FIELD_SLOTS 4enum PrimitiveType{ PRIM_NOT = 0, /* value is a reference type, not a primitive type */ PRIM_VOID = 1, PRIM_BOOLEAN = 2, PRIM_BYTE = 3, PRIM_SHORT = 4, PRIM_CHAR = 5, PRIM_INT = 6, PRIM_LONG = 7, PRIM_FLOAT = 8, PRIM_DOUBLE = 9,};// java类的描述结构体struct ClassObject : Object{ u4 instanceData[CLASS_FIELD_SLOTS]; const char* descriptor; char* descriptorAlloc; u4 accessFlags; u4 serialNumber; void* pDvmDex; ClassStatus status; ClassObject* verifyErrorClass; u4 initThreadId; size_t objectSize; ClassObject* elementClass; int arrayDim; PrimitiveType primitiveType; ClassObject* super; Object* classLoader; InitiatingLoaderList initiatingLoaderList; int interfaceCount; ClassObject** interfaces; int directMethodCount; Method* directMethods; int virtualMethodCount; Method* virtualMethods; int vtableCount; Method** vtable; int iftableCount; InterfaceEntry* iftable; int ifviPoolCount; int* ifviPool; int ifieldCount; int ifieldRefCount; // number of fields that are object refs InstField* ifields; u4 refOffsets; /* source file name, if known */ const char* sourceFile; int sfieldCount; StaticField sfields[]; /* MUST be last item */};struct DexProto{ const void * dexFile; /* file the idx refers to */ u4 protoIdx; /* index into proto_ids table of dexFile */};struct Method{ /* the class we are a part of */ ClassObject* clazz; u4 accessFlags; u2 methodIndex; u2 registersSize; /* ins + locals */ u2 outsSize; u2 insSize; /* method name, e.g. "<init>" or "eatLunch" */ const char* name; DexProto prototype; const char* shorty; const u2* insns; /* instructions, in memory-mapped .dex */ int jniArgInfo; DalvikBridgeFunc nativeFunc; bool fastJni; bool noRef; bool shouldTrace; const RegisterMap* registerMap; /* set if method was called during method profiling */ bool inProfile;};enum{ ACC_PUBLIC = 0x00000001, // class, field, method, ic ACC_PRIVATE = 0x00000002, // field, method, ic ACC_PROTECTED = 0x00000004, // field, method, ic ACC_STATIC = 0x00000008, // field, method, ic ACC_FINAL = 0x00000010, // class, field, method, ic ACC_SYNCHRONIZED = 0x00000020, // method (only allowed on natives) ACC_SUPER = 0x00000020, // class (not used in Dalvik) ACC_VOLATILE = 0x00000040, // field ACC_BRIDGE = 0x00000040, // method (1.5) ACC_TRANSIENT = 0x00000080, // field ACC_VARARGS = 0x00000080, // method (1.5) ACC_NATIVE = 0x00000100, // method ACC_INTERFACE = 0x00000200, // class, ic ACC_ABSTRACT = 0x00000400, // class, method, ic ACC_STRICT = 0x00000800, // method ACC_SYNTHETIC = 0x00001000, // field, method, ic ACC_ANNOTATION = 0x00002000, // class, ic (1.5) ACC_ENUM = 0x00004000, // class, field, ic (1.5) ACC_CONSTRUCTOR = 0x00010000, // method (Dalvik only) ACC_DECLARED_SYNCHRONIZED = 0x00020000, // method (Dalvik only) ACC_CLASS_MASK = (ACC_PUBLIC | ACC_FINAL | ACC_INTERFACE | ACC_ABSTRACT | ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM), ACC_INNER_CLASS_MASK = (ACC_CLASS_MASK | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC), ACC_FIELD_MASK = (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | ACC_VOLATILE | ACC_TRANSIENT | ACC_SYNTHETIC | ACC_ENUM), ACC_METHOD_MASK = (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | ACC_SYNCHRONIZED | ACC_BRIDGE | ACC_VARARGS | ACC_NATIVE | ACC_ABSTRACT | ACC_STRICT | ACC_SYNTHETIC | ACC_CONSTRUCTOR | ACC_DECLARED_SYNCHRONIZED),};bool dvmIsPublicMethod(const Method* method){ return (method->accessFlags & ACC_PUBLIC) != 0;}bool dvmIsPrivateMethod(const Method* method){ return (method->accessFlags & ACC_PRIVATE) != 0;}bool dvmIsStaticMethod(const Method* method){ return (method->accessFlags & ACC_STATIC) != 0;}bool dvmIsSynchronizedMethod(const Method* method){ return (method->accessFlags & ACC_SYNCHRONIZED) != 0;}bool dvmIsDeclaredSynchronizedMethod(const Method* method){ return (method->accessFlags & ACC_DECLARED_SYNCHRONIZED) != 0;}bool dvmIsFinalMethod(const Method* method){ return (method->accessFlags & ACC_FINAL) != 0;}bool dvmIsNativeMethod(const Method* method){ return (method->accessFlags & ACC_NATIVE) != 0;}bool dvmIsAbstractMethod(const Method* method){ return (method->accessFlags & ACC_ABSTRACT) != 0;}bool dvmIsSyntheticMethod(const Method* method){ return (method->accessFlags & ACC_SYNTHETIC) != 0;}bool dvmIsMirandaMethod(const Method* method){ return (method->accessFlags & ACC_MIRANDA) != 0;}bool dvmIsConstructorMethod(const Method* method){ return *method->name == '<';}/* Dalvik puts private, static, and constructors into non-virtual table */bool dvmIsDirectMethod(const Method* method){ return dvmIsPrivateMethod(method) || dvmIsStaticMethod(method) || dvmIsConstructorMethod(method);}/* Get whether the given method has associated bytecode. This is the* case for methods which are neither native nor abstract. */bool dvmIsBytecodeMethod(const Method* method){ return (method->accessFlags & (ACC_NATIVE | ACC_ABSTRACT)) == 0;}bool dvmIsProtectedField(const Field* field){ return (field->accessFlags & ACC_PROTECTED) != 0;}bool dvmIsStaticField(const Field* field){ return (field->accessFlags & ACC_STATIC) != 0;}bool dvmIsFinalField(const Field* field){ return (field->accessFlags & ACC_FINAL) != 0;}bool dvmIsVolatileField(const Field* field){ return (field->accessFlags & ACC_VOLATILE) != 0;}bool dvmIsInterfaceClass(const ClassObject* clazz){ return (clazz->accessFlags & ACC_INTERFACE) != 0;}bool dvmIsPublicClass(const ClassObject* clazz){ return (clazz->accessFlags & ACC_PUBLIC) != 0;}bool dvmIsFinalClass(const ClassObject* clazz){ return (clazz->accessFlags & ACC_FINAL) != 0;}bool dvmIsAbstractClass(const ClassObject* clazz){ return (clazz->accessFlags & ACC_ABSTRACT) != 0;}bool dvmIsAnnotationClass(const ClassObject* clazz){ return (clazz->accessFlags & ACC_ANNOTATION) != 0;}bool dvmIsPrimitiveClass(const ClassObject* clazz){ return clazz->primitiveType != PRIM_NOT;}/* linked, here meaning prepared and resolved */bool dvmIsClassLinked(const ClassObject* clazz){ return clazz->status >= CLASS_RESOLVED;}/* has class been verified? */bool dvmIsClassVerified(const ClassObject* clazz){ return clazz->status >= CLASS_VERIFIED;}bool dvmIsClassInitialized(const ClassObject* clazz){ return (clazz->status == CLASS_INITIALIZED);}/* annotation constants */enum{ kDexVisibilityBuild = 0x00, /* annotation visibility */ kDexVisibilityRuntime = 0x01, kDexVisibilitySystem = 0x02, kDexAnnotationByte = 0x00, kDexAnnotationShort = 0x02, kDexAnnotationChar = 0x03, kDexAnnotationInt = 0x04, kDexAnnotationLong = 0x06, kDexAnnotationFloat = 0x10, kDexAnnotationDouble = 0x11, kDexAnnotationString = 0x17, kDexAnnotationType = 0x18, kDexAnnotationField = 0x19, kDexAnnotationMethod = 0x1a, kDexAnnotationEnum = 0x1b, kDexAnnotationArray = 0x1c, kDexAnnotationAnnotation = 0x1d, kDexAnnotationNull = 0x1e, kDexAnnotationBoolean = 0x1f, kDexAnnotationValueTypeMask = 0x1f, /* low 5 bits */ kDexAnnotationValueArgShift = 5,};/* map item type codes */enum{ kDexTypeHeaderItem = 0x0000, kDexTypeStringIdItem = 0x0001, kDexTypeTypeIdItem = 0x0002, kDexTypeProtoIdItem = 0x0003, kDexTypeFieldIdItem = 0x0004, kDexTypeMethodIdItem = 0x0005, kDexTypeClassDefItem = 0x0006, kDexTypeMapList = 0x1000, kDexTypeTypeList = 0x1001, kDexTypeAnnotationSetRefList = 0x1002, kDexTypeAnnotationSetItem = 0x1003, kDexTypeClassDataItem = 0x2000, kDexTypeCodeItem = 0x2001, kDexTypeStringDataItem = 0x2002, kDexTypeDebugInfoItem = 0x2003, kDexTypeAnnotationItem = 0x2004, kDexTypeEncodedArrayItem = 0x2005, kDexTypeAnnotationsDirectoryItem = 0x2006,};/* auxillary data section chunk codes */enum{ kDexChunkClassLookup = 0x434c4b50, /* CLKP */ kDexChunkRegisterMaps = 0x524d4150, /* RMAP */ kDexChunkEnd = 0x41454e44, /* AEND */};/* debug info opcodes and constants */enum{ DBG_END_SEQUENCE = 0x00, DBG_ADVANCE_PC = 0x01, DBG_ADVANCE_LINE = 0x02, DBG_START_LOCAL = 0x03, DBG_START_LOCAL_EXTENDED = 0x04, DBG_END_LOCAL = 0x05, DBG_RESTART_LOCAL = 0x06, DBG_SET_PROLOGUE_END = 0x07, DBG_SET_EPILOGUE_BEGIN = 0x08, DBG_SET_FILE = 0x09, DBG_FIRST_SPECIAL = 0x0a, DBG_LINE_BASE = -4, DBG_LINE_RANGE = 15,};enum{ kSHA1DigestLen = 20, kSHA1DigestOutputLen = kSHA1DigestLen * 2 + 1};/** Direct-mapped "header_item" struct.*/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;};/** Direct-mapped "map_item".*/struct DexMapItem{ u2 type; /* type code (see kDexType* above) */ u2 unused; u4 size; /* count of items of the indicated type */ u4 offset; /* file offset to the start of data */};/** Direct-mapped "map_list".*/struct DexMapList{ u4 size; /* #of entries in list */ DexMapItem list[1]; /* entries */};/** Direct-mapped "string_id_item".*/struct DexStringId{ u4 stringDataOff; /* file offset to string_data_item */};/** Direct-mapped "type_id_item".*/struct DexTypeId{ u4 descriptorIdx; /* index into stringIds list for type descriptor */};/** Direct-mapped "field_id_item".*/struct DexFieldId{ u2 classIdx; /* index into typeIds list for defining class */ u2 typeIdx; /* index into typeIds for field type */ u4 nameIdx; /* index into stringIds for field name */};/** Direct-mapped "method_id_item".*/struct DexMethodId{ u2 classIdx; /* index into typeIds list for defining class */ u2 protoIdx; /* index into protoIds for method prototype */ u4 nameIdx; /* index into stringIds for method name */};/** Direct-mapped "proto_id_item".*/struct DexProtoId{ u4 shortyIdx; /* index into stringIds for shorty descriptor */ u4 returnTypeIdx; /* index into typeIds list for return type */ u4 parametersOff; /* file offset to type_list for parameter types */};/** Direct-mapped "class_def_item".*/struct DexClassDef{ u4 classIdx; /* index into typeIds for this class */ u4 accessFlags; u4 superclassIdx; /* index into typeIds for superclass */ u4 interfacesOff; /* file offset to DexTypeList */ u4 sourceFileIdx; /* index into stringIds for source file name */ u4 annotationsOff; /* file offset to annotations_directory_item */ u4 classDataOff; /* file offset to class_data_item */ u4 staticValuesOff; /* file offset to DexEncodedArray */};/** Direct-mapped "type_item".*/struct DexTypeItem{ u2 typeIdx; /* index into typeIds */};/** Direct-mapped "type_list".*/struct DexTypeList{ u4 size; /* #of entries in list */ DexTypeItem list[1]; /* entries */};typedef struct DexMapId{ u2 type; /*Section type*/ u2 unused; /*unused*/ u4 size; /* section size*/ u4 offset; /* section offset */} DexMapId;/** Direct-mapped "code_item".** The "catches" table is used when throwing an exception,* "debugInfo" is used when displaying an exception stack trace or* debugging. An offset of zero indicates that there are no entries.*/struct DexCode{ u2 registersSize; u2 insSize; u2 outsSize; u2 triesSize; u4 debugInfoOff; /* file offset to debug info stream */ u4 insnsSize; /* size of the insns array, in u2 units */ u2 insns[1]; /* followed by optional u2 padding */ /* followed by try_item[triesSize] */ /* followed by uleb128 handlersSize */ /* followed by catch_handler_item[handlersSize] */};/** Direct-mapped "try_item".*/struct DexTry{ u4 startAddr; /* start address, in 16-bit code units */ u2 insnCount; /* instruction count, in 16-bit code units */ u2 handlerOff; /* offset in encoded handler data to handlers */};/** Link table. Currently undefined.*/struct DexLink{ u1 bleargh;};/** Direct-mapped "annotations_directory_item".*/struct DexAnnotationsDirectoryItem{ u4 classAnnotationsOff; /* offset to DexAnnotationSetItem */ u4 fieldsSize; /* count of DexFieldAnnotationsItem */ u4 methodsSize; /* count of DexMethodAnnotationsItem */ u4 parametersSize; /* count of DexParameterAnnotationsItem */ /* followed by DexFieldAnnotationsItem[fieldsSize] */ /* followed by DexMethodAnnotationsItem[methodsSize] */ /* followed by DexParameterAnnotationsItem[parametersSize] */};/** Direct-mapped "field_annotations_item".*/struct DexFieldAnnotationsItem{ u4 fieldIdx; u4 annotationsOff; /* offset to DexAnnotationSetItem */};/** Direct-mapped "method_annotations_item".*/struct DexMethodAnnotationsItem{ u4 methodIdx; u4 annotationsOff; /* offset to DexAnnotationSetItem */};/** Direct-mapped "parameter_annotations_item".*/struct DexParameterAnnotationsItem{ u4 methodIdx; u4 annotationsOff; /* offset to DexAnotationSetRefList */};/** Direct-mapped "annotation_set_ref_item".*/struct DexAnnotationSetRefItem{ u4 annotationsOff; /* offset to DexAnnotationSetItem */};/** Direct-mapped "annotation_set_ref_list".*/struct DexAnnotationSetRefList{ u4 size; DexAnnotationSetRefItem list[1];};/** Direct-mapped "annotation_set_item".*/struct DexAnnotationSetItem{ u4 size; u4 entries[1]; /* offset to DexAnnotationItem */};/** Direct-mapped "annotation_item".** NOTE: this structure is byte-aligned.*/struct DexAnnotationItem{ u1 visibility; u1 annotation[1]; /* data in encoded_annotation format */};/** Direct-mapped "encoded_array".** NOTE: this structure is byte-aligned.*/struct DexEncodedArray{ u1 array[1]; /* data in encoded_array format */};/** Lookup table for classes. It provides a mapping from class name to* class definition. Used by dexFindClass().** We calculate this at DEX optimization time and embed it in the file so we* don't need the same hash table in every VM. This is slightly slower than* a hash table with direct pointers to the items, but because it's shared* there's less of a penalty for using a fairly sparse table.*/struct DexClassLookup{ int size; // total size, including "size" int numEntries; // size of table[]; always power of 2 struct { u4 classDescriptorHash; // class descriptor hash code int classDescriptorOffset; // in bytes, from start of DEX int classDefOffset; // in bytes, from start of DEX } table[1];};/** Header added by DEX optimization pass. Values are always written in* local byte and structure padding. The first field (magic + version)* is guaranteed to be present and directly readable for all expected* compiler configurations; the rest is version-dependent.** Try to keep this simple and fixed-size.*/struct DexOptHeader{ u1 magic[8]; /* includes version number */ u4 dexOffset; /* file offset of DEX header */ u4 dexLength; u4 depsOffset; /* offset of optimized DEX dependency table */ u4 depsLength; u4 optOffset; /* file offset of optimized data tables */ u4 optLength; u4 flags; /* some info flags */ u4 checksum; /* adler32 checksum covering deps/opt */ /* pad for 64-bit alignment if necessary */};#define DEX_OPT_FLAG_BIG (1<<1) /* swapped to big-endian */#define DEX_INTERFACE_CACHE_SIZE 128 /* must be power of 2 *//** Structure representing a DEX file.** Code should regard DexFile as opaque, using the API calls provided here* to access specific structures.*/struct DexFile{ /* directly-mapped "opt" header */ const DexOptHeader* pOptHeader; /* pointers to directly-mapped structs and arrays in base DEX */ const DexHeader* pHeader; const DexStringId* pStringIds; const DexTypeId* pTypeIds; const DexFieldId* pFieldIds; const DexMethodId* pMethodIds; const DexProtoId* pProtoIds; const DexClassDef* pClassDefs; const DexLink* pLinkData; /* * These are mapped out of the "auxillary" section, and may not be * included in the file. */ const DexClassLookup* pClassLookup; const void* pRegisterMapPool; // RegisterMapClassPool /* points to start of DEX file data */ const u1* baseAddr; /* track memory overhead for auxillary structures */ int overhead; /* additional app-specific data structures associated with the DEX */ //void* auxData;};struct MemMapping{ void* addr; /* start of data */ size_t length; /* length of data */ void* baseAddr; /* page-aligned base address */ size_t baseLength; /* length of mapping */};struct DvmDex{ /* pointer to the DexFile we're associated with */ DexFile* pDexFile; /* clone of pDexFile->pHeader (it's used frequently enough) */ const DexHeader* pHeader; /* interned strings; parallel to "stringIds" */ struct StringObject** pResStrings; /* resolved classes; parallel to "typeIds" */ struct ClassObject** pResClasses; /* resolved methods; parallel to "methodIds" */ struct Method** pResMethods; /* resolved instance fields; parallel to "fieldIds" */ /* (this holds both InstField and StaticField) */ struct Field** pResFields; /* interface method lookup cache */ struct AtomicCache* pInterfaceCache; /* shared memory region with file contents */ bool isMappedReadOnly; MemMapping memMap; jobject dex_object; /* lock ensuring mutual exclusion during updates */ pthread_mutex_t modLock;};struct JarFile{ u4* Nocare[9]; char* cacheFileName; DvmDex* pDvmDex;};struct RawDexFile{ char* cacheFileName; struct DvmDex* pDvmDex; //DvmDex*};struct DexOrJar{ char* fileName; bool isDex; bool okayToFree; RawDexFile* pRawDexFile; JarFile* pJarFile; u1* pDexMemory; // malloc()ed memory, if any};#endif //HELPTOOLCLIENT_OBJECT_H
4.当前工程ndk编译需要的配置文件Android.mk
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := nativelibLOCAL_SRC_FILES := nativelib.cpp# 支持log日志打印需要加载链接的库 LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog include $(BUILD_SHARED_LIBRARY)
5.使用当前Xposed Hook工具进行某加固脱壳的测试结果。
使用JEB工具反编译脱壳成功后的odex文件,结果如下图:
总之呢,这个工具对付一般免费版的某数字加固保、某加密、某梆梆加固还是可以的,企业估计不行还需要修改和优化,有兴趣的可以测试一下其他的免费版的加固产品,可以把样本和结果反馈下,我修改一下。
重要参考:
某加固使用xposed脱壳
- 基于dalvik模式下的Xposed Hook开发的某加固脱壳工具
- ART模式下基于Xposed Hook开发脱壳工具
- 一个基于xposed和inline hook的一代壳脱壳工具
- 某加固使用xposed脱壳
- 基于Xposed的一款脱壳神器ZjDroid工具原理解析
- 基于Xposed的一款脱壳神器ZjDroid工具原理解析
- Android中Xposed框架篇---基于Xposed的一款脱壳神器ZjDroid工具原理解析
- Dalvik模式下基于Android运行时类加载的函数dexFindClass脱壳
- 基于Xposed的一个简单Hook
- 解决爱加密加固之后使用xposed hook的时候log打印不出来的问题
- [Android 分享] 使用xposed来hook使用360加固的应用
- ART模式下基于dex2oat脱壳的原理分析
- 360加固保的dex脱壳方法
- 360加固保的dex脱壳方法
- Android so加固的简单脱壳
- Android平台dalvik模式下java Hook框架ddi的分析(1)
- Dalvik虚拟机原理及Xposed hook原理
- Dalvik虚拟机原理及Xposed hook原理
- 剑指offer——39.平衡二叉树
- 从Linux程序中执行shell(程序、脚本)并获得输出结果(转)
- html之初识标签
- Vmware下,从Linux系统安装到yum源配置(包括Centos7.X、Rhel7.X)
- Java中private、protected、public和default修饰符的访问限制
- 基于dalvik模式下的Xposed Hook开发的某加固脱壳工具
- 4. Median of Two Sorted Arrays
- CodeChef
- 对Spring的IOC和DI的解释
- IntelliJ IDEA 实现Spring项目的热部署
- 爬取拉勾网
- 哪里可以做机载设备结冰试验,RTCA/DO-160G,Icing Test
- 关于过去,关于现在
- bzoj 1671: [Usaco2005 Dec]Knights of Ni 骑士(BFS)