基於dalvik模式下的Xposed Hook開發的某加固脫殼工具
這段時間好好的學習了一下Android加固相關的知識和流程也大致把Android加固的一些思路理清楚了,不巧在部落格上看到了這篇文章《某加固使用xposed脫殼》感覺還不錯,正好有時間可以學習一下這款基於Xposed
Hook框架開發的某加固脫殼工具,原作者的部落格已經找不到但是作者已經給出了主要的實現程式碼,我在作者給出的原始碼基礎上稍微做了一下修改和優化並且在Android Nexus 5手機上測試某加固的脫殼,成功得到了某加固解密後的dex檔案。在進行原始碼分析之前,你需要了解Xposed Hook框架模組編寫相關的知識,還不瞭解的可以先看下我前面的部落格《Xposed框架之函式Hook學習
下圖是DexClassLoader在整個java層的實現流程:
從DexClassLoader到openDexFileNative函式的整個流程下來,DexClassLoader的java層實現全部完成,openDexFileNative之後是由native層函式實現,暫時不關心;dex檔案的加固是基於DexClassLoader的載入流程而來的,dex檔案優化為odex檔案後加載到apk程序的記憶體中返回是mCookie值,這個mCookie值就是dex檔案載入到記憶體之後的映象描述結構體指標DexOrJar*
,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。
// 對應函式的註冊結構體
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函式的實現如下:
/**
* 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);
}
}
@Override
public 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 __cplusplus
extern "C" {
#endif
// 呼叫native層實現的jni方法dumpdex
JNIEXPORT 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檔案資訊的結構體DexFile
DexFile* 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 = 0
if (!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 4
enum 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 := nativelib
LOCAL_SRC_FILES := nativelib.cpp
# 支援log日誌列印需要載入連結的庫
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
include $(BUILD_SHARED_LIBRARY)
5.使用當前Xposed Hook工具進行某加固脫殼的測試結果。
使用JEB工具反編譯脫殼成功後的odex檔案,結果如下圖:
總之呢,這個工具對付一般免費版的某數字加固保、某加密、某梆梆加固還是可以的,企業估計不行還需要修改和優化,有興趣的可以測試一下其他的免費版的加固產品,可以把樣本和結果反饋下,我修改一下。
重要參考: