日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android免Root环境下Hook框架Legend原理分析

發布時間:2025/3/15 Android 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android免Root环境下Hook框架Legend原理分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

0x1 應用場景

現如今,免Root環境下的逆向分析已經成為一種潮流!

在2015年之前的iOS軟件逆向工程領域,要想對iOS平臺上的軟件進行逆向工程分析,越獄iOS設備與安裝Cydia是必須的!幾乎絕大多數的逆向相關的動態調試工具、Hook注入框架都依賴于獲取IOS設備的最高訪問權限,就技術本身上而言,對當前程序進行Hook與動態調試,只需要擁有與當前程序相同的權限即可,理論上無需對設備進行Root越獄,實際上,在2015年就出現了在非越獄設備上進行插件開發的實用案例,2016年的iOS軟件逆向工程界,更是一發不可收拾,各種名越獄環境下的逆向工具與逆向技巧被安全人員所發掘,在沒有越獄的iOS設備上進行軟件的動態調試與逆向工程已經是主流的趨勢胃。這樣的情況下,最直接的影響是安全研究人員不再對iOS設備越獄有著強烈的追求了,越獄需求的下降可能會直接影響到iOS設備越獄工具的發布與技術的更新迭代。

同樣的,在Android設備的免Root環境下,進行軟件動態調試與逆向工程分析的需求更加強烈。免Root環境下動態調試與逆向工程就技術本質而言是可行的,安全研究人員的智慧更是有力的證明了這一點,LBE發布免Root環境下APK雙開工具平行空間就是最好的例子,它是打破逆向工程技術的原始格局的第一個大錘!隨后的,各種APK多開框架、免Root環境下的Hook、免Root環境下的動態調試等技術都被研究人員公開,這是Android軟件逆向工程界的福音,逆向工程人員在以后的逆向分析過程中,可能再也不需要為自己的手機能否越獄而感到苦惱,手上在吃灰淘汰的Android小米機可能就是你的逆向必備工具之一。

好了,說了這么多,無非是告訴大家,開發技術在更新迭代,軟件的逆向工程技術也在不停的更新,各位研究軟件安全的朋友們,你們跟上了時代的腳步嗎?!

0x2 Legend框架簡介

Legend是Lody開源的一個Android免Root環境下的一個APK Hook框架,代碼放在github上:https://github.com/asLody/legend。?該框架代碼設計簡潔,通用性高,適合逆向工程時一些Hook場景。

先來看看如何使用它??蚣芴峁┝藘煞N使用方法:基于Annotation注解與代碼直接調用?;贏nnotation注解的Hook技術不是第一次被發現了,在Java開發的世界里,這種技術被廣泛使用,大名鼎鼎的基于AOP開發的Aspectj就大量使用這種技術。使用Annotation方式編寫的Java代碼有著很強的靈活與擴展性。Legend中Annotation方式的Hook這樣使用:

12345678@Hook("android.app.Activity::startActivity@android.content.Intent")public static void Activity_startActivity(Activity thiz, Intent intent) {if (!ALLOW_LAUNCH_ACTIVITY) {Toast.makeText(thiz, "I am sorry to turn your Activity down :)", Toast.LENGTH_SHORT).show();} else {HookManager.getDefault().callSuper(thiz, intent);}}

@Hook("xxx")部分指明需要Hook的類與方法以及方法的簽名,此處的Activity_startActivity()是自己實現的替換android.app.Activity::startActivity()的方法,HookManager.getDefault().callSuper(thiz, intent);調用是調用原方法。

這種方式Hook的方法,需要執行一次Hook應用操作來激活所有注解Hook,方法是執行下面的方法,傳入的YourClass.class是包含了注解的類:

1HookManager.getDefault().applyHooks(YourClass.class);

另一種代碼方式進行Hook使用起來更簡單,Hook操作只需要一行代碼:

1HookManager.getDefault().hookMethod(originMethod, hookMethod);

這是Legend提供的demo展示的一個完整實例:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758package com.legend.demo;import android.app.Activity;import android.app.Application;import android.content.Context;import android.content.Intent;import android.telephony.TelephonyManager;import android.widget.Toast;import com.lody.legend.Hook;import com.lody.legend.HookManager;/*** @author Lody* @version 1.0*/public class App extends Application {public static boolean ENABLE_TOAST = true;public static boolean ALLOW_LAUNCH_ACTIVITY = true;@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);HookManager.getDefault().applyHooks(App.class);}@Hook("android.app.Application::onCreate")public static void Application_onCreate(Application app) {Toast.makeText(app, "Application => onCreate()", Toast.LENGTH_SHORT).show();HookManager.getDefault().callSuper(app);}@Hook("android.telephony.TelephonyManager::getSimSerialNumber")public static String TelephonyManager_getSimSerialNumber(TelephonyManager thiz) {return "110";}@Hook("android.widget.Toast::show")public static void Toast_show(Toast toast) {if (ENABLE_TOAST) {HookManager.getDefault().callSuper(toast);}}@Hook("android.app.Activity::startActivity@android.content.Intent")public static void Activity_startActivity(Activity activity, Intent intent) {if (!ALLOW_LAUNCH_ACTIVITY) {Toast.makeText(activity, "I am sorry to turn your Activity down :)", Toast.LENGTH_SHORT).show();}else {HookManager.getDefault().callSuper(activity, intent);}}}

0x3 原理分析

先來看看Hook注解的實現:

123456789101112131415161718// Legend/legendCore/src/main/java/com/lody/legend/Hook.javapackage com.lody.legend;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/*** @author Lody* @version 1.0*/@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Hook {String value() default "";}

`@Target(ElementType.METHOD)`指明Hook注解用于修飾類中的Method,與之類似的還有@Target(ElementType.FIELD)用來修飾類中的Field。如果想讓注解同時修飾類的Field與Method,可以這么寫:

123@Target({ElementType.FIELD, ElementType.METHOD})public @interface Hook{}......

@Retention(RetentionPolicy.RUNTIME)指明Hook注解以何種形式進行保留。RetentionPolicy是一個enum類型,聲明如下:

12345public enum RetentionPolicy { SOURCE, CLASS, RUNTIME }

`SOURCE`表明該注解類型的信息只保留在程序源碼里,源碼經過編譯之后,注解的數據就會消失;CLASS表明注解類型的信息除了保留在程序源碼里,同時也保留在編譯好的class文件里面,但在執行的時候,并不會把這些信息加載到內存中去;RUNTIME是最大范圍的保留,表示同時在源碼與編譯好的class文件中保留信息,并且在執行的時候會把這些信息加載到內存中去。

定義好了Hook注解,看它是如何使用的,這就是HookManager.getDefault().applyHooks()方法要做的工作,它的代碼如下:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859// Legend/legendCore/src/main/java/com/lody/legend/HookManager.javapublic void applyHooks(Class<?> holdClass) {for (Method hookMethod : holdClass.getDeclaredMethods()) {Hook hook = hookMethod.getAnnotation(Hook.class);if (hook != null) {String statement = hook.value();String[] splitValues = statement.split("::");if (splitValues.length == 2) {String className = splitValues[0];String[] methodNameWithSignature = splitValues[1].split("@");if (methodNameWithSignature.length <= 2) {String methodName = methodNameWithSignature[0];String signature = methodNameWithSignature.length == 2 ? methodNameWithSignature[1] : "";String[] paramList = signature.split("#");if (paramList[0].equals("")) {paramList = new String[0];}try {Class<?> clazz = Class.forName(className);boolean isResolve = false;for (Method method : clazz.getDeclaredMethods()) {if (method.getName().equals(methodName)) {Class<?>[] types = method.getParameterTypes();if (paramList.length == types.length) {boolean isMatch = true;for (int N = 0; N < types.length; N++) {if (!types[N].getName().equals(paramList[N])) {isMatch = false;break;}}if (isMatch) {hookMethod(method, hookMethod);isResolve = true;Logger.d("[+++] %s have hooked.", method.getName());}}}if (isResolve) {break;}}if (!isResolve) {Logger.e("[---] Cannot resolve Method : %s.", Arrays.toString(methodNameWithSignature));}} catch (Throwable e) {Logger.e("[---] Error to Load Hook Method From : %s." , hookMethod.getName());e.printStackTrace();}}else {Logger.e("[---] Can't split method and signature : %s.", Arrays.toString(methodNameWithSignature));}}else {Logger.e("[---] Can't understand your statement : [%s].", statement);}}}}

該方法遍歷類的所有方法,查找匹配注解信息中指定的方法,方法是:對于需要Hook的Class類holdClass,調用它的getDeclaredMethods()獲取所有聲明的方法,依次調用每個類的方法的getAnnotation()獲取注解信息,取到的注解信息保存在String類型的statement變量中,類與完整的方法簽名以“::”進行分隔,方法簽名中的方法名與參數簽名使用“@”進行分隔,參數簽名中每個參數之間使用“#”進行分隔,取完一個方法所有的信息后,與類中的方法進行比較,如果完全匹配說明找到了需要Hook的方法,這個時候,調用hookMethod()方法進行Hook操作,注意這里的hookMethod()方法,即Legend框架支持的第二種Hook方式。

hookMethod()調用Runtime.isArt()判斷當前代碼執行在Art還是Dalvik模式,如果是Art模式,執行hookMethodArt()來完成Hook操作,如果是Dalvik模式,執行hookMethodDalvik()完成Hook。

Runtime.isArt()的代碼只有一行,即判斷虛擬機版本字符串是否以字符2開頭,如下:

1234567public static boolean isArt() {return getVmVersion().startsWith("2");}public static String getVmVersion() {return System.getProperty("java.vm.version");}

執行完Hook后會返回一個backupMethod,這是一個原始方法的備份,最后將backupMethod放入以methodName命令的backupList,在methodNameToBackupMethodsMap備份就完事了。

接下來看看hookMethodArt()都干了啥:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465private static Method hookMethodArt(Method origin, Method hook) {ArtMethod artOrigin = ArtMethod.of(origin);ArtMethod artHook = ArtMethod.of(hook);Method backup = artOrigin.backup().getMethod();backup.setAccessible(true);long originPointFromQuickCompiledCode = artOrigin.getEntryPointFromQuickCompiledCode();long originEntryPointFromJni = artOrigin.getEntryPointFromJni();long originEntryPointFromInterpreter = artOrigin.getEntryPointFromInterpreter();long originDeclaringClass = artOrigin.getDeclaringClass();long originAccessFlags = artOrigin.getAccessFlags();long originDexCacheResolvedMethods = artOrigin.getDexCacheResolvedMethods();long originDexCacheResolvedTypes = artOrigin.getDexCacheResolvedTypes();long originDexCodeItemOffset = artOrigin.getDexCodeItemOffset();long originDexMethodIndex = artOrigin.getDexMethodIndex();long hookPointFromQuickCompiledCode = artHook.getEntryPointFromQuickCompiledCode();long hookEntryPointFromJni = artHook.getEntryPointFromJni();long hookEntryPointFromInterpreter = artHook.getEntryPointFromInterpreter();long hookDeclaringClass = artHook.getDeclaringClass();long hookAccessFlags = artHook.getAccessFlags();long hookDexCacheResolvedMethods = artHook.getDexCacheResolvedMethods();long hookDexCacheResolvedTypes = artHook.getDexCacheResolvedTypes();long hookDexCodeItemOffset = artHook.getDexCodeItemOffset();long hookDexMethodIndex = artHook.getDexMethodIndex();ByteBuffer hookInfo = ByteBuffer.allocate(ART_HOOK_INFO_SIZE);hookInfo.putLong(originPointFromQuickCompiledCode);hookInfo.putLong(originEntryPointFromJni);hookInfo.putLong(originEntryPointFromInterpreter);hookInfo.putLong(originDeclaringClass);hookInfo.putLong(originAccessFlags);hookInfo.putLong(originDexCacheResolvedMethods);hookInfo.putLong(originDexCacheResolvedTypes);hookInfo.putLong(originDexCodeItemOffset);hookInfo.putLong(originDexMethodIndex);hookInfo.putLong(hookPointFromQuickCompiledCode);hookInfo.putLong(hookEntryPointFromJni);hookInfo.putLong(hookEntryPointFromInterpreter);hookInfo.putLong(hookDeclaringClass);hookInfo.putLong(hookAccessFlags);hookInfo.putLong(hookDexCacheResolvedMethods);hookInfo.putLong(hookDexCacheResolvedTypes);hookInfo.putLong(hookDexCodeItemOffset);hookInfo.putLong(hookDexMethodIndex);artOrigin.setEntryPointFromQuickCompiledCode(hookPointFromQuickCompiledCode);artOrigin.setEntryPointFromInterpreter(hookEntryPointFromInterpreter);artOrigin.setDeclaringClass(hookDeclaringClass);artOrigin.setDexCacheResolvedMethods(hookDexCacheResolvedMethods);artOrigin.setDexCacheResolvedTypes(hookDexCacheResolvedTypes);artOrigin.setDexCodeItemOffset((int) hookDexCodeItemOffset);artOrigin.setDexMethodIndex((int) hookDexMethodIndex);int accessFlags = origin.getModifiers();if (Modifier.isNative(accessFlags)) {accessFlags &= ~ Modifier.NATIVE;artOrigin.setAccessFlags(accessFlags);}long memoryAddress = Memory.alloc(ART_HOOK_INFO_SIZE);Memory.write(memoryAddress,hookInfo.array());artOrigin.setEntryPointFromJni(memoryAddress);return backup;}

原方法與替換的方法分別為artOrigin與artHook,執行artOrigin的backup()完成方法的備份操作,backup()內部通過反射獲取AbstractMethod類的artMethod字段,然后使用當前類的method進行填充,實際的操作就是復制一份當前類的method,此處不展開它的代碼。

接下來的代碼是獲取artOrigin與artHook的重要字段,然后構造ByteBuffer類型的hookInfo,最后調用以下三行代碼來完成Hook:

123long memoryAddress = Memory.alloc(ART_HOOK_INFO_SIZE);Memory.write(memoryAddress,hookInfo.array());artOrigin.setEntryPointFromJni(memoryAddress);

ArtMethod在底層的內存結構定義僅次于Android源碼的“art/runtime/art_method.h”文件,不同系統版本的Android這個結構體都可能會發現變化,為了保持兼容性,Legend在Java層手動定義保存了它們的字段偏移信息,與“Legend/legendCore/src/main/java/com/lody/legend/art/ArtMethod.java”文件保存在同一目錄,在調用ArtMethod::of()方法構造ArtMethod時,會根據不同的系統版本來構造不同的對象。

Memory.write()方法底層調用的LegendNative.memput(),它是一個native方法,對應的實現是android_memput(),代碼如下:

12345678910// Legend/Native/jni/legend_native.cppvoid android_memput(JNIEnv * env, jclass clazz, jlong dest, jbyteArray src) {jbyte *srcPnt = env->GetByteArrayElements(src, 0);jsize length = env->GetArrayLength(src);unsigned char * destPnt = (unsigned char *)dest;for(int i = 0; i < length; ++i) {destPnt[i] = srcPnt[i];}env->ReleaseByteArrayElements(src, srcPnt, 0);}

可以看出,饈的內存寫操作是直接使用底層指定長度的字節流覆蓋的,簡單與暴力,而能夠這樣操作的原因,是當前操作的內存是自己的內存,想怎么干就怎么干!

setEntryPointFromJni()直接將原方法起始地址的指針內容,通過構造的memoryAddress覆蓋寫入!如此這般,Art模式下的Hook就完成了,當然,這其中很多小細節沒有講到,讀者可以看行閱讀它的代碼。

接下來看看Dalvik下的Hook方法hookMethodDalvik()都干了啥:

1234567891011121314151617181920212223242526272829303132333435363738// Legend/legendCore/src/main/java/com/lody/legend/HookManager.javaprivate static Method hookMethodDalvik(Method origin, Method hook) {DalvikMethodStruct dvmOriginMethod = DalvikMethodStruct.of(origin);DalvikMethodStruct dvmHookMethod = DalvikMethodStruct.of(hook);byte[] originClassData = dvmOriginMethod.clazz.read();byte[] originInsnsData = dvmOriginMethod.insns.read();byte[] originInsSizeData = dvmOriginMethod.insSize.read();byte[] originRegisterSizeData = dvmOriginMethod.registersSize.read();byte[] originAccessFlags = dvmOriginMethod.accessFlags.read();byte[] originNativeFunc = dvmOriginMethod.nativeFunc.read();byte[] hookClassData = dvmHookMethod.clazz.read();byte[] hookInsnsData = dvmHookMethod.insns.read();byte[] hookInsSizeData = dvmHookMethod.insSize.read();byte[] hookRegisterSizeData = dvmHookMethod.registersSize.read();byte[] hookAccessFlags = dvmHookMethod.accessFlags.read();byte[] hookNativeFunc = dvmHookMethod.nativeFunc.read();dvmOriginMethod.clazz.write(hookClassData);dvmOriginMethod.insns.write(hookInsnsData);dvmOriginMethod.insSize.write(hookInsSizeData);dvmOriginMethod.registersSize.write(hookRegisterSizeData);dvmOriginMethod.accessFlags.write(hookAccessFlags);ByteBuffer byteBuffer = ByteBuffer.allocate(DVM_HOOK_INFO_SIZE);byteBuffer.put(originClassData);byteBuffer.put(originInsnsData);byteBuffer.put(originInsSizeData);byteBuffer.put(originRegisterSizeData);byteBuffer.put(originAccessFlags);byteBuffer.put(originNativeFunc);//May leaklong memoryAddress = Memory.alloc(DVM_HOOK_INFO_SIZE);Memory.write(memoryAddress, byteBuffer.array());dvmOriginMethod.nativeFunc.write(memoryAddress);return origin;}

分析完Art模式,Dalvik下的就不難看懂的,DalvikMethodStruct.of()會返回DalvikMethodStruct類型結構體,它是Dalvik虛擬機內部DalvikMethod結構體的內線性布局表示。

dvmOriginMethod與dvmHookMethod分別代表原方法與Hook替換的方法,同樣的,使用底層內存的寫操作,對所有需要替換的字段進行替換。

最后就是Hook后的方法調用原方法了,它的代碼如下:

1234567891011121314151617181920212223242526272829// Legend/legendCore/src/main/java/com/lody/legend/HookManager.javapublic <T> T callSuper(Object who, Object... args) {StackTraceElement[] traceElements = Thread.currentThread().getStackTrace();StackTraceElement currentInvoking = traceElements[3];String invokingClassName = currentInvoking.getClassName();String invokingMethodName = currentInvoking.getMethodName();Map<String,List<Method>> methodNameToBackupMethodsMap = classToBackupMethodsMapping.get(invokingClassName);if (methodNameToBackupMethodsMap != null) {List<Method> methodList = methodNameToBackupMethodsMap.get(invokingMethodName);if (methodList != null) {Method method = matchSimilarMethod(methodList, args);if (method != null) {try {if (Runtime.isArt()) {return callSuperArt(method, who, args);}else {return callSuperDalvik(method, who, args);}} catch (Throwable e) {Logger.e("[---] Call super method with error : %s, detail message please see the [Logcat :system.err].", e.getMessage());e.printStackTrace();}}else {Logger.e("[---] Super method cannot found in backup map.");}}}return null;}

這段代碼是在之前保存的methodNameToBackupMethodsMap中查找備份的方法,找到后對Art與Dalvik模式分別調用callSuperArt()與callSuperDalvik(),前者比較簡單,只是調用方法的invoke()就完事,而Dalvik模式由于沒有像Art那樣做備份,所以多出了一個字段回替換的操作,完事也是調用的invoke()來執行原方法。

0x4 一些感想

分析完上面的代碼,可以出來Legend盡管實現了Art與Dalvik雙模式下的Hook,但在實際逆向Hook中,還是有一些不足:

  • 不能Hook字段。在很多應用場景中可能會用到,這里有一個迂回的替代的方案是:在字段較敏感的方法中對方法做Hook,然后在Hook代碼中反射操作字段。
  • Hook自定義的類加載器加載的類方法。由于反射查找的類的方法列表依賴于類的查找,對于部分自定義ClassLoader的情況,獲取Class本身就存在著難度,更別說Hook它的方法了。
  • 兼容性。只支持4.2到6.0,當然,根據技術原理,從2.3到7.1應該都是可以做到的。
  • 穩定性。與該框架技術原理類似的還有很多,比較alibaba的AndFix,在系統自定義修改較多的情況下,框加要的穩定性存疑,當然,逆向工程時使用的穩定性遠沒有做產品要求的高,一些全新思路的Hook修改方案如Tinker可能也是一個不錯的選擇,留待以后測試了!
  • 最后,講完了它的原理,并沒有講如何在逆向工程中使用,這個交給聰明的安全研究人員作為思維發散。


    https://feicong.github.io/2017/02/12/legend/

    總結

    以上是生活随笔為你收集整理的Android免Root环境下Hook框架Legend原理分析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。