前言

眾所周知,上次說到了如何脫殼360加固,大致意思就是安裝一個xposed插件,然後自動就會脫殼了,那麼這個插件是如何工作的呢,本次重點說說這個。

上次說道了dumpDex脫殼360加固,其實先說個大概,就是從ndk層和java層,適配不同的系統,hook關鍵函數,然後在運行時將dex文件dump出來。

如果僅僅想知道如何使用,可以參見上一篇

點我:Android逆向之路---脫殼360加固、與xposed hook注意事項

需要的環境

  • 無,看文章就可以了解大致了

(當然你要是想編譯下dumpDex項目,需要如下工具) Android Studio sdk ndk

入口

所有的程序執行的時候都是有個入口的,dumpDex工程也不例外。 由於是個xposed插件,所以我們先看com.wrbug.dumpdex.XposedInit類。

public class XposedInit implements IXposedHookLoadPackage {

//--------略---------

@Override
public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) {
PackerInfo.Type type = PackerInfo.find(lpparam);
if (type == null) {
return;
}
final String packageName = lpparam.packageName;
//這裡主要是各個app只管解析各個app自己的進程的程序
if (lpparam.packageName.equals(packageName)) {
//首先在當前app的指定目錄,創建好目錄,以便於一會兒脫殼存放dex文件
String path = "/data/data/" + packageName + "/dump";
File parent = new File(path);
if (!parent.exists() || !parent.isDirectory()) {
parent.mkdirs();
}
log("sdk version:" + Build.VERSION.SDK_INT);

if (DeviceUtils.isOreo()) {
//api為27或27版本的執行下面一行,進行脫殼
OreoDump.init(lpparam);
} else {
//低版本api執行下面一行進行脫殼
LowSdkDump.init(lpparam,type);
}

}
}
}

已經加好注釋,值得注意的就是,此處程序有分叉了,分別是 OreoDump.init()和LowSdkDump.init() 我們先看OreoDump.init方法

public class OreoDump {

//--------略---------

public static void init(final XC_LoadPackage.LoadPackageParam lpparam) {
Native.dump(lpparam.packageName);
}
}

跟著進入Native.dump(), * 註:不會android ndk也沒關係,可以繼續往下看

Native hook

以下可以先粗略的說一下,主要就是進入了ndk層進行hook,然後進行dump 進入native.cpp文件裡面找到JNICALL Java_com_wrbug_dumpdex_Native_dump方法。

由於切換到了c語言,所以我會幫大家刪除一些代碼的細枝末節,只看主幹。

再次聲明下,以下方法實在當android版本為26或27的時候,會默認進行Native層脫殼

JNIEXPORT void JNICALL Java_com_wrbug_dumpdex_Native_dump
(JNIEnv *env, jclass obj, jstring packageName) {

//在這裡作者考慮到了防止每次app啟動的時候都會dump,因此保存了一個變數is_hook來記錄,如果hook過了的話就會退出程序
static bool is_hook = false;
char *p = (char *) env->GetStringUTFChars(packageName, 0);
if (is_hook) {
__android_log_print(ANDROID_LOG_INFO, TAG, "hooked ignore");
return;
}
init_package_name(p);
env->ReleaseStringChars(packageName, (const jchar *) p);

//這裡由於使用了第三方庫,所以先執行第三方庫的初始化操作,具體第三方庫,見下文
ndk_init(env);

//下面就是重點了,首先以RTLD_NOW模式打開動態庫libart.so,拿到句柄
void *handle = ndk_dlopen("libart.so", RTLD_NOW);
if (handle == NULL) {
__android_log_print(ANDROID_LOG_ERROR, TAG, "Error: unable to find the SO : libart.so");
return;
}
//根據不同的版本,拿到不同的對應的載入的符號
void *open_common_addr = ndk_dlsym(handle, get_open_function_flag());

//--------略---------
//略掉很多分支,單獨說一個,見下文
if (registerInlineHook((uint32_t) open_common_addr, (uint32_t) get_new_open_function_addr(),
(uint32_t **) get_old_open_function_addr()) != ELE7EN_OK) {
__android_log_print(ANDROID_LOG_ERROR, TAG, "register1 hook failed!");
return;
} else {
__android_log_print(ANDROID_LOG_ERROR, TAG, "register1 hook success!");
}
//設置hook標記為true
is_hook = true;
}
registerInlineHook(
(uint32_t) open_common_addr, (uint32_t) get_new_open_function_addr(),
(uint32_t **) get_old_open_function_addr())

已經定位到函數的地址,接下來就是Hook替換以前的函數,替換成我們自己定義的函數,例如下面的函數

static void *new_arm64_open_common(uint8_t *base, size_t size, void *location,
uint32_t location_checksum, void *oat_dex_file,
bool verify,
bool verify_checksum,
void *error_meessage, void *verify_result) {
//--------略---------
//首先在程序運行時,保存dex,完成脫殼
save_dex_file(base, size);
//調用以前的函數,保證程序正確執行,
void *result = old_arm64_open_common(base, size, location, location_checksum,
oat_dex_file, verify, verify_checksum,
error_meessage,
verify_result);
return result;
}

NDK層hook完畢

到此為止NDK層hook分析完畢,分別用了第三方庫hook了android指定版本的載入dex函數的方法,然後在hook的新函數裡面添加到保存到dump目錄的函數,達到脫殼 的目的。

ndk hook主要用到的庫

github.com/rrrfff/ndk_d

github.com/ele7enxxh/An

SDK層hook

回到入口的那個章節,還記得嗎,還有一個LowSdkDump.init 這個是低版本的時候的邏輯

public static void init(final XC_LoadPackage.LoadPackageParam lpparam, PackerInfo.Type type) {
//如果sdk是23,24,25,26,27之一,那麼繼續使用native層hook
if (DeviceUtils.supportNativeHook()) {
Native.dump(lpparam.packageName);
}
//額。。。。。。。。可能百度充錢了
if (type == PackerInfo.Type.BAI_DU) {
return;
}
//見下文說明
XposedHelpers.findAndHookMethod("android.app.Instrumentation", lpparam.classLoader, "newApplication", ClassLoader.class, String.class, Context.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
//執行真正的dump方法,然後保存
dump(lpparam.packageName, param.getResult().getClass());
attachBaseContextHook(lpparam, ((Application) param.getResult()));
}
});
}

上面的主要就是先檢測可不可以natvie層hook,可以的話優先native層hook, 然後就是百度可能充錢了,當然開個玩笑,這個可以大家自己嘗試。

接下來就是java層hook了,hook了載入類Instrumentation類,的newApplication方法,然後進行dump.

還有attachBaseContextHook裡面也是主要Hook ClassLoader的loadClass方法,

主要看java層的dump函數

private static void dump(String packageName, Class<?> aClass) {
Object dexCache = XposedHelpers.getObjectField(aClass, "dexCache");
log("decCache=" + dexCache);
Object o = XposedHelpers.callMethod(dexCache, "getDex");
byte[] bytes = (byte[]) XposedHelpers.callMethod(o, "getBytes");
String path = "/data/data/" + packageName + "/dump";
File file = new File(path, "source-" + bytes.length + ".dex");
if (file.exists()) {
log(file.getName() + " exists");
return;
}
FileUtils.writeByteToFile(bytes, file.getAbsolutePath());
}

大功告成

代碼陸陸續續的看了一遍,可以嘗試畫一個流程圖了

後記

其實文中涉及到的具體的hook的函數,需要我們具體的去看,去研讀android源碼。 這樣才能融匯貫通。

了解了系統是如何載入一個dex的,才能真真正正的理解如何攔截,如何從內存dump出來。 dump的時候用的別人的庫,的工具,都還好,主要是思路。如何找到關鍵點,進行dump。

如何使用呢,可以見我的的上一篇文章

點我:Android逆向之路---脫殼360加固、與xposed hook注意事項

寫在最後

偶爾聊聊技術,偶爾聊聊逆向,偶爾聊聊生活

不能總聊技術呀,下次一起聊點輕鬆的。

博主還是一個懶散的博主。

關於我

個人博客:MartinHan的小站

博客網站:hanhan12312的專欄

知乎:MartinHan01

我的公眾號: 程序技術指北

(剛開不久,最近在琢磨新東西,謹慎關注!)

你要是看到了一個變形的海綿寶寶,就是他了

推薦閱讀:

相关文章