也許每個人出生的時候都以為這世界都是為他一個人而存在的,當他發(fā)現(xiàn)自己錯的時候,他便開始長大
少走了彎路,也就錯過了風景,無論如何,感謝經(jīng)歷
轉(zhuǎn)移發(fā)布平臺通知:將不再在CSDN博客發(fā)布新文章,敬請移步知識星球
感謝大家一直以來對我CSDN博客的關注和支持,但是我決定不再在這里發(fā)布新文章了。為了給大家提供更好的服務和更深入的交流,我開設了一個知識星球,內(nèi)部將會提供更深入、更實用的技術文章,這些文章將更有價值,并且能夠幫助你更好地解決實際問題。期待你加入我的知識星球,讓我們一起成長和進步
Android安全付費專欄長期更新,本篇最新內(nèi)容請前往:
- [車聯(lián)網(wǎng)安全自學篇] Android安全之Android Xposed插件開發(fā),小白都能看得懂的教程
0x01 前言
1.1 安卓操作系統(tǒng)架構
Android是一種基于Linux的自由及開放源代碼的操作系統(tǒng)。而Android系統(tǒng)構架是安卓系統(tǒng)的體系結構,其系統(tǒng)架構和其操作系統(tǒng)一樣,采用了分層的架構,共分為四層五部分,四層指的是從高到低分別是Android應用層,Android應用框架層,Android系統(tǒng)運行層和Linux內(nèi)核層;五部分指的是Linux Kernel、Android Runtime、Libraries、Application Framework、Applications。
1.1.1 Linux Kernel
在所有層的最底下是 Linux,它提供了基本的系統(tǒng)功能,比如進程管理,內(nèi)存管理,設備管理(如攝像頭,鍵盤,顯示器)。
1.1.2 Android Runtime
Android 運行時同時提供一系列核心的庫來為 Android 應用程序開發(fā)者使用標準的 Java 語言來編寫 Android 應用程序。Dalvik 虛擬機使得每一個 Android 應用程序運行在自己獨立的虛擬機進程。Dalvik虛擬機可執(zhí)行文件格式是.dex,dex格式是專為Dalvik設計的一種壓縮格式,適合內(nèi)存和處理器速度有限的系統(tǒng)。
1.1.3 Libraries
Android包含一個C/C++庫的集合,供Android系統(tǒng)的各個組件使用。這些功能通過Android的應用程序框架(application framework)暴露給開發(fā)者。
1.1.4 Application Framework
通過提供開放的開發(fā)平臺,Android使開發(fā)者能夠編制極其豐富和新穎的應用程序。
1.1.5 Applications
應用框架層以 Java 類的形式為應用程序提供許多高級的服務。
1.2 安卓應用程序組件
應用程序組件是一個Android應用程序的基本構建塊。在AndroidManifest.xml中描述了應用程序的每個組件,以及他們?nèi)绾谓换ァ?/p>
1.2.1 Android應用程序中四個主要組件
組件名 | 描述 |
---|---|
Activities | 描述UI,并且處理用戶與機器屏幕的交互 |
Services | 處理與應用程序關聯(lián)的后臺操作 |
Broadcast Receivers | 處理Android操作系統(tǒng)和應用程序之間的通信 |
Content Providers | 處理數(shù)據(jù)和數(shù)據(jù)庫管理方面的問題 |
1.2.2 附加組件
組件名 | 描述 |
---|---|
Fragments | 代表活動中的一個行為或者一部分用戶界面 |
Views | 繪制在屏幕上的UI元素,包括按鈕,列表等 |
Layouts | 控制屏幕格式,展示視圖外觀的View的繼承 |
Intents | 組件間的消息連線 |
Resources | 外部元素,例如字符串資源、常量資源及圖片資源等 |
Manifest | 應用程序的配置文件 |
1.3 什么是 Hook?
Hook 又叫“鉤子”,它可以在事件傳送的過程中截獲并監(jiān)控事件的傳輸,將自身的代碼與系統(tǒng)方法進行融入
這樣當這些方法被調(diào)用時,也就可以執(zhí)行我們自己的代碼,這也是面向切面編程的思想(AOP)
1.4 Hook 分類
1)根據(jù)Android開發(fā)模式,Native模式(C/C++)和Java模式(Java)區(qū)分,在Android平臺上
- Java層級的Hook
- Native層級的Hook
2)根 Hook 對象與 Hook 后處理事件方式不同,Hook還分為:
- 消息Hook
- API Hook
3)針對Hook的不同進程上來說,還可分為:
- 全局Hook
- 單個進程Hook
1.5 Hook原理
Hook技術本質(zhì)是函數(shù)調(diào)用,由于處于Linux用戶狀態(tài),每個進程有自己獨立的進程控件,所以必須先注入所要Hook的進程空間,修改其內(nèi)存中進程代碼,替換過程表的符號地址,通過ptrace函數(shù)附加進程,向遠程進程注入so庫,從而達到監(jiān)控以及遠程進程關鍵函數(shù)掛鉤
Hook工作流程:
-
Android相關內(nèi)核函數(shù):
- ptrace函數(shù):跟蹤一個目標進程,結束跟蹤一個目標進程,獲取內(nèi)存字節(jié),像內(nèi)存寫入地址
- dlopen函數(shù):以指定模式打開指定的動態(tài)鏈接庫文件
- mmap函數(shù):分配一段臨時的內(nèi)存來完成代碼的存放
-
向目標進程注入代碼總結后的步驟分為以下幾步:
- 用ptrace函數(shù)attch上目標進程
- 發(fā)現(xiàn)裝載共享庫so函數(shù)
- 裝載指定的.so
- 讓目標進程的執(zhí)行流程跳轉(zhuǎn)到注入的代碼執(zhí)行
- 使用ptrace函數(shù)的detach釋放目標集成
1.6 常見 Hook 框架
在Android開發(fā)中,有以下常見的一些Hook框架:
1)Xposed
Xposed 是國外大牛開發(fā)的一個工具,Xposed通過攔截安卓程序運行過程來達到修改程序行為的目的。不需要修改安卓源文件,而是通過分析程序運行來攔截并影響運行情況。具體需要把安卓apk逆向后然后分析代碼,定位到具體的類,方法等,然后通過xposed來攔截修改方法等。
使用Xposed模塊的兩個條件:
- 手機必須root(Xposed需要往/system里寫入東西)
- 安裝Xposed Installer
通過替換 /system/bin/app_process 程序控制 Zygote 進程,使得 app_process 在啟動過程中會加載 XposedBridge.jar 這個 Jar 包,從而完成對 Zygote 進程及其創(chuàng)建的 Dalvik 虛擬機的劫持。
Xposed 在開機的時候完成對所有的 Hook Function 的劫持,在原 Function 執(zhí)行的前后加上自定義代碼。
下載地址:
下面這個僅適用于 Android 4.0.3 至 Android 4.4 上的 root 訪問
https://repo.xposed.info/module/de.robv.android.xposed.installer
對于 Android 5.0 或更高版本改用下面這個
https://forum.xda-developers.com/t/official-xposed-for-lollipop-marshmallow-nougat-oreo-v90-beta3-2018-01-29.3034811/
2)Cydia Substrate
Cydia Substrate是一個基于Hook的代碼修改框架,其可以在Android、iOS平臺使用,并實現(xiàn)修改系統(tǒng)默認代碼
Cydia Substrate 框架為蘋果用戶提供了越獄相關的服務框架,當然也推出了 Android 版 。Cydia Substrate 是一個代碼修改平臺,它可以修改任何進程的代碼。
不管是用 Java 還是 C/C++(native代碼)編寫的,而 Xposed 只支持 Hook app_process 中的 Java 函數(shù)。
下載地址:
http://www.cydiasubstrate.com
3)Legend
Legend 是 Android 免 Root 環(huán)境下的一個 Apk Hook 框架,該框架代碼設計簡潔,通用性高,適合逆向工程時一些 Hook 場景。大部分的功能都放到了 Java 層,這樣的兼容性就非常好
原理是這樣的,直接構造出新舊方法對應的虛擬機數(shù)據(jù)結構,然后替換信息寫到內(nèi)存中即可
下載地址:
https://github.com/asLody/legend
4)VirtualXposed
VirtualXposed 是基于VirtualApp 和 epic 在非ROOT環(huán)境下運行Xposed模塊的實現(xiàn)(支持5.0~10.0)。
與 Xposed 相比,目前 VirtualXposed 有兩個限制:
不支持修改系統(tǒng)(可以修改普通APP中對系統(tǒng)API的調(diào)用),因此重力工具箱,應用控制器等無法使用
暫不支持資源HOOK,因此資源鉤子不會起任何作用;使用資源HOOK的模塊,相應的功能不會生效
下載地址:
https://github.com/android-hacker/VirtualXposed
1.7 Hook 必須掌握的知識
-
反射
-
java 的動態(tài)代理
動態(tài)代理是指在運行時動態(tài)生成代理類,不需要我們像靜態(tài)代理那個去手動寫一個個的代理類。在 java 中,可使用 InvocationHandler 實現(xiàn)動態(tài)代理
1.8 Hook 選擇的關鍵點
Hook 的選擇點:盡量靜態(tài)變量和單例,因為一旦創(chuàng)建對象,它們不容易變化,非常容易定位
Hook 過程:
- 尋找 Hook 點,原則是盡量靜態(tài)變量或者單例對象,盡量 Hook public 的對象和方法
- 選擇合適的代理方式,如果是接口可以用動態(tài)代理
- 偷梁換柱 —— 用代理對象替換原始對象
Android 的 API 版本比較多,方法和類可能不一樣,所以要做好 API 的兼容工作
1.9 Xposed的一些類的介紹
- IXposedHookLoadPackage.java
加載回調(diào)接口,在xposed入口類繼承,實現(xiàn)handleLoadPackage方法
IXposedHookLoadPackage接口。該接口提供了一個名為handleLoadPackage的方法,這個方法在Android系統(tǒng)每次加載一個包的時候都會被調(diào)用
handleLoadPackage | 用于在加載應用程序的包的時候執(zhí)行用戶的操作 |
---|---|
LoadPackageParam loadPackageParam | 包含了加載的應用程序的一些基本信息 |
- IXposedHookInitPackageResources.java
加載回調(diào)接口,用于修改app的資源文件,在xposed入口類繼承,實現(xiàn)handleInitPackageResources(InitPackageResourcesParam resparam)方法
handleInitPackageResources | 用于在加載應用程序的包的時候執(zhí)行用戶的操作 |
---|---|
InitPackageResourcesParam resparam | 包含了加載的應用程序的一些資源基本信息 |
- XposedHelpers.java
一些輔助方法,簡化連接和調(diào)用方法/構造函數(shù),獲取和設置字段
HOOK無參數(shù)的方法,XposedHelpers.findAndHookMethod()方法一般四個參數(shù),分別為完整類名、ClassLoader對象、方法名以及一個回調(diào)接口
XposedHelpers.findAndHookMethod("com.example.demo.MortgageActivity", loadPackageParam.classLoader,
"showLoan", new XC_MethodHook() {
//……
});
HOOK有參數(shù)的方法,比如要傳入兩個int參數(shù),則:
XposedHelpers.findAndHookMethod("com.example.demo.MortgageActivity", loadPackageParam.classLoader,
"showLoan", int.class, int.class, new XC_MethodHook() {
//……
});
PS:new XC_MethodHook()有兩個重要的內(nèi)部函數(shù)beforeHookedMethod()和afterHookedMethod(),通過重寫它們可以實現(xiàn)對任意方法的掛鉤,它們的區(qū)別在于Hook前調(diào)用還是后調(diào)用
beforeHookedMethod 該方法在hook目標方法執(zhí)行前調(diào)用,
其中,參數(shù)param指的是目標方法的相關參數(shù)、回調(diào)、方法等信息
afterHookedMethod 該方法在hook目標方法執(zhí)行后調(diào)用,
其中,參數(shù)param指的是目標方法的相關參數(shù)、回調(diào)、方法等信息。
Xposed運行多個模塊對同一個方法進行hook時,
框架就會根據(jù)Xposed模塊的優(yōu)先級來排序
XposedBridge類中hookAllMethods和log方法主要用于一次hook每個類的所有方法或夠造函數(shù)
hookAllMethods(
Class<?> hookClass,//需要進行hook的類
String methodName,//需要進行hook的方法名
XC_MethodHook callback//回調(diào)函數(shù)
)
XposedHookLoadPackage中的handleLoadPackage方法主要用于加載應用程序包時執(zhí)行用戶的操作。
findAndHookMethod | hook一個類中的方法 |
---|---|
className | 要hook的方法的所在類 |
classloader | 要hook的包的classLoader,一般都寫loadPackageParam.classLoader |
methodName | 要hook的方法 |
parameterTypesAndCallback | 方法的參數(shù)和監(jiān)聽器 |
callMethod | 在目標app中調(diào)用方法 |
---|---|
Object | 要調(diào)用方法的所在類 |
methodName | 要調(diào)用的方法名稱 |
args | 方法的參數(shù) |
findClass | 獲取class類實例 |
---|---|
className | 類名 |
classLoader | 類加載器 |
- XposedBridge.java
log | 在Xposed的app的日志功能里輸出日志 |
---|---|
text | 要輸出的內(nèi)容 |
- XC_MethodHook中定義了回調(diào)方法
1.10 Android應用各部分說明
1.10.1 MainActivity.java文件
主要活動代碼,實際的應用程序文件,將被轉(zhuǎn)化為Dalvik可執(zhí)行文件并運行。R.layout.activity_main
引用res/layout/activity_main.xml
文件。
onCreate() 活動被加載之后眾多被調(diào)用的方法之一。
1.10.2 AndroidManifest.xml文件
AndroidManifest.xml文件是整個應用程序的信息描述文件,定義了應用程序中包含的Activity、Service、Content provider和BroadcastReceiver組件信息。每個應用程序在根目錄下必須包含一個AndroidManifest.xml文件,且文件名不能修改。在AndroidManifest.xml文件中,首先看到是的<manifest>
節(jié)點,它是整個應用程序的基本屬性,涵蓋了默認進程名字,應用程序標識,安裝位置,對系統(tǒng)的要求以及應用程序的版本等。
- android:icon是普通圖標
- android:roundIcon是圓形圖標
- android:label屬性指定應用的名稱
- android:name屬性指定一個Activity類子類的全名
意圖過濾器的action被命名為android.intent.action.MAIN
,表明這個活動被用做應用程序的入口。
意圖過濾器的category被命名為android.intent.category.LAUNCHER
,表明應用程序可以通過設備啟動器的圖標來啟動。
@string
指的是strings.xml,因此@string/app_name
指的是定義在strings.xml中的app_name,這里實際為"HO22K"
1.10.3 activity_main.xml文件
activity_main.xml
可能將頻繁修改這個文件來改變應用程序的布局。
TextView是一個用于構建用戶圖形界面的Android控件。它包含有許多不同的屬性,諸如android:layout_width
,android:layout_height
等用來設置它的寬度和高度等。這里我們給它顯示一句話“橙留香的Hook”,引用自strings.xml文件
1.11 Android layout屬性大全
activity_main.xml 編寫時需要用到
1.11.1 第一類:屬性值 true或者 false
屬性 | 描述 |
---|---|
android:layout_centerHrizontal | 水平居中 |
android:layout_centerVertical | 垂直居中 |
android:layout_centerInparent | 相對于父元素完全居中 |
android:layout_alignParentBottom | 貼緊父元素的下邊緣 |
android:layout_alignParentLeft | 貼緊父元素的左邊緣 |
android:layout_alignParentRight | 貼緊父元素的右邊緣 |
android:layout_alignParentTop | 貼緊父元素的上邊緣 |
android:layout_alignWithParentIfMissing | 如果對應的兄弟元素找不到的話就以父元素做參照物 |
android:layout_alignParentStart | 緊貼父元素結束位置開始 |
android:layout_alignParentEnd | 緊貼父元素結束位置結束 |
android:animateLayoutChanges | 布局改變時是否有動畫效果 |
android:clipChildren | 定義子布局是否一定要在限定的區(qū)域內(nèi) |
android:clipToPadding | 定義布局間是否有間距 |
android:animationCache | 定義子布局也有動畫效果 |
android:alwaysDrawnWithCache | 定義子布局是否應用繪圖的高速緩存 |
android:addStatesFromChildren | 定義布局是否應用子布局的背景 |
android:splitMotionEvents | 定義布局是否傳遞touch事件到子布局 |
android:focusableInTouchMode | 定義是否可以通過touch獲取到焦點 |
android:isScrollContainer | 定義布局是否作為一個滾動容器 可以調(diào)整整個窗體 |
android:fadeScrollbars | 滾動條自動隱藏 |
android:fitsSystemWindows | 設置布局調(diào)整時是否考慮系統(tǒng)窗口(如狀態(tài)欄) |
android:visibility | 定義布局是否可見 |
android:requiresFadingEdge | 定義滾動時邊緣是否褪色 |
android:clickable | 定義是否可點擊 |
android:longClickable | 定義是否可長點擊 |
android:saveEnabled | 設置是否在窗口凍結時(如旋轉(zhuǎn)屏幕)保存View的數(shù)據(jù) |
android:filterTouchesWhenObscured | 所在窗口被其它可見窗口遮住時,是否過濾觸摸事件 |
android:keepScreenOn | 設置屏幕常亮 |
android:duplicateParentState | 是否從父容器中獲取繪圖狀態(tài)(光標,按下等) |
android:soundEffectsEnabled | 點擊或觸摸是否有聲音效果 |
android:hapticFeedbackEnabled | 設置觸感反饋 |
1.11.2 第二類:屬性值必須為id的引用名“@id/id-name”
屬性 | 描述 |
---|---|
android:layout_alignBaseline | 本元素的文本與父元素文本對齊 |
android:layout_below | 在某元素的下方 |
android:layout_above | 在某元素的的上方 |
android:layout_toLeftOf | 在某元素的左邊 |
android:layout_toRightOf | 在某元素的右邊 |
android:layout_toStartOf | 本元素從某個元素開始 |
android:layout_toEndOf | 本元素在某個元素結束 |
android:layout_alignTop | 本元素的上邊緣和某元素的的上邊緣對齊 |
android:layout_alignLeft | 本元素的左邊緣和某元素的的左邊緣對齊 |
android:layout_alignBottom | 本元素的下邊緣和某元素的的下邊緣對齊 |
android:layout_alignRight | 本元素的右邊緣和某元素的的右邊緣對齊 |
android:layout_alignStart | 本元素與開始的父元素對齊 |
android:layout_alignEnd | 本元素與結束的父元素對齊 |
android:ignoreGravity | 指定元素不受重力的影響 |
android:layoutAnimation | 定義布局顯示時候的動畫 |
android:id | 為布局添加ID方便查找 |
android:tag | 為布局添加tag方便查找與類似 |
android:scrollbarThumbHorizontal | 設置水平滾動條的drawable |
android:scrollbarThumbVertical | 設置垂直滾動條的drawable |
android:scrollbarTrackHorizontal | 設置水平滾動條背景(軌跡)的色drawable |
android:scrollbarTrackVertical | 設置垂直滾動條背景(軌跡)的色drawable |
android:scrollbarAlwaysDrawHorizontalTrack | 設置水平滾動條是否含有軌道 |
android:scrollbarAlwaysDrawVerticalTrack | 設置垂直滾動條是否含有軌道 |
android:nextFocusLeft | 設置左邊指定視圖獲得下一個焦點 |
android:nextFocusRight | 設置右邊指定視圖獲得下一個焦點 |
android:nextFocusUp | 設置上邊指定視圖獲得下一個焦點 |
android:nextFocusDown | 設置下邊指定視圖獲得下一個焦點 |
android:nextFocusForward | 設置指定視圖獲得下一個焦點 |
android:contentDescription | 說明 |
android:OnClick | 點擊時從上下文中調(diào)用指定的方法 |
1.11.3 第三類:屬性值為具體的像素值,如30dip,40px,50dp
屬性 | 描述 |
---|---|
android:layout_width | 定義本元素的寬度 |
android:layout_height | 定義本元素的高度 |
android:layout_margin | 本元素離上下左右間的距離 |
android:layout_marginBottom | 離某元素底邊緣的距離 |
android:layout_marginLeft | 離某元素左邊緣的距離 |
android:layout_marginRight | 離某元素右邊緣的距離 |
android:layout_marginTop | 離某元素上邊緣的距離 |
android:layout_marginStart | 本元素里開始的位置的距離 |
android:layout_marginEnd | 本元素里結束位置的距離 |
android:scrollX | 水平初始滾動偏移 |
android:scrollY | 垂直初始滾動偏移 |
android:background | 本元素的背景 |
android:padding | 指定布局與子布局的間距 |
android:paddingLeft | 指定布局左邊與子布局的間距 |
android:paddingTop | 指定布局上邊與子布局的間距 |
android:paddingRight | 指定布局右邊與子布局的間距 |
android:paddingBottom | 指定布局下邊與子布局的間距 |
android:paddingStart | 指定布局左邊與子布局的間距與android:paddingLeft相同 |
android:paddingEnd | 指定布局右邊與子布局的間距與android:paddingRight相同 |
android:fadingEdgeLength | 設置邊框漸變的長度 |
android:minHeight | 最小高度 |
android:minWidth | 最小寬度 |
android:translation | X水平方向的移動距離 |
android:translation | Y垂直方向的移動距離 |
android:transformPivot | X相對于一點的水平方向偏轉(zhuǎn)量 |
android:transformPivot | Y相對于一點的垂直方向偏轉(zhuǎn)量 |
1.11.4 第四類:屬性值為Android內(nèi)置值
屬性 | 描述 |
---|---|
android:gravity | 控件布局方式 |
android:layout_gravity | 布局方式 |
android:persistentDrawingCachehua | 定義繪圖的高速緩存的持久性 |
android:descendantFocusability | 控制子布局焦點獲取方式 常用于listView的item中包含多個控件點擊無效 |
android:scrollbars | 設置滾動條的狀態(tài) |
android:scrollbarStyle | 設置滾動條的樣式 |
android:fitsSystemWindows | 設置布局調(diào)整時是否考慮系統(tǒng)窗口(如狀態(tài)欄) |
android:scrollbarFadeDuration | 設置滾動條淡入淡出時間 |
android:scrollbarDefaultDelayBeforeFade | 設置滾動條N毫秒后開始淡化,以毫秒為單位 |
android:scrollbarSize | 設置滾動調(diào)大小 |
android:fadingEdge | 設置拉滾動條時,邊框漸變的放向 |
android:drawingCacheQuality | 設置繪圖時半透明質(zhì)量 |
android:OverScrollMode | 滑動到邊界時樣式 |
android:alpha | 設置透明度 |
android:rotation | 旋轉(zhuǎn)度數(shù) |
android:rotation | X水平旋轉(zhuǎn)度數(shù) |
android:rotation | Y垂直旋轉(zhuǎn)度數(shù) |
android:scale | X設置X軸縮放 |
android:scale | Y設置Y軸縮放 |
android:verticalScrollbarPosition | 攝者垂直滾動條的位置 |
android:layerType | 設定支持 |
android:layoutDirection | 定義布局圖紙的方向 |
android:textDirection | 定義文字方向 |
android:textAlignment | 文字對齊方式 |
android:importantForAccessibility | 設置可達性的重要行 |
android:labelFor | 添加標簽 |
0x02 Xposed環(huán)境搭建
下載Xposed APK:
https://dl-xda.xposed.info/modules/de.robv.android.xposed.installer_v33_36570c.apk
打開剛剛安裝好的Xposed
這里為了方便選擇永久記住
安裝Xposed可能會出現(xiàn)如下情況:
安裝完畢后可能會出現(xiàn)未激活, 拉入JustTrustMe.apk, 然后重啟模擬器就可以了
然后修改wifi的ip和端口
- 下載
xposed-x86_64.zip
下載xposed作者模擬器是x86_64的
https://github.com/youling257/XposedTools/files/1931996/xposed-x86_64.zip
如果模擬器是x86下載下面這個
https://dl-xda.xposed.info/framework/
- 下載
script.sh
找到安卓對應版本,作何雷電模擬器是7.1,搜索對應為安卓7.1 sdk
x86_64的下載這個:
https://forum.xda-developers.com/attachment.php?attachmentid=4489568&d=1525092710
x86的下載這個,script.txt:
https://forum.xda-developers.com/attachments/script-txt.4489568/,改名為script.sh
創(chuàng)建文件夾xposed,然后解壓一下xposed壓縮包,最后把xposed壓縮包中的system文件夾和script.sh放入剛剛創(chuàng)建的xposed文件夾,最后的最后執(zhí)行如下命令:
adb.exe remount
adb.exe push D:\test\xposed /system
adb.exe shell
cd /system
mount -o remount -w /system
chmod 777 script.sh
sh script.sh
0x03 Xposed原理分析
Xposed框架的原理是修改系統(tǒng)文件,替換了/system/bin/app_process
可執(zhí)行文件,在啟動Zygote時加載額外的jar文件(/data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar
),并執(zhí)行一些初始化操作(執(zhí)行XposedBridge的main方法)。然后開發(fā)人員就可以在這個Zygote上下文中進行某些Hook操作。
在Android中,zygote是整個系統(tǒng)創(chuàng)建新進程的核心進程。zygote進程在內(nèi)部會先啟動Dalvik虛擬機,繼而加載一些必要的系統(tǒng)資源和系統(tǒng)類,最后進入一種監(jiān)聽狀態(tài)。
在之后的運作中,當其他系統(tǒng)模塊(比如AMS)希望創(chuàng)建新進程時,只需向zygote進程發(fā)出請求,zygote進程監(jiān)聽到該請求后,會相應地fork出新的進程,于是這個新進程在初生之時,就先天具有了自己的Dalvik虛擬機以及系統(tǒng)資源。
zygote進程是由init進程啟動起來,由init.rc 腳本中關于zygote的描述可知:zygote對應的可執(zhí)行文件就是/system/bin/app_process
,也就是說系統(tǒng)啟動時會執(zhí)行到這個可執(zhí)行文件的main()函數(shù)里
Xposed 提供了幾個接口類供xposed模塊繼承,不同的接口類對應不同的hook時機 IXposedHookZygoteInit zygote 初始化前就執(zhí)行掛鉤,即loadModule執(zhí)行時就掛鉤了 IXposedHookLoadPackage apk包加載的時候執(zhí)行掛鉤,先將掛鉤函數(shù)保存起來,等加載apk函數(shù)執(zhí)行后觸發(fā)callback (這里的callback是xposed框架自己掛鉤的函數(shù)),再執(zhí)行模塊注冊的掛鉤函數(shù) IXposedHookInitPackageResources apk資源實例化時執(zhí)行掛鉤,同上
0x03 編寫Xposed 模塊
從本質(zhì)上來講,Xposed 模塊也是一個 Android 程序。但與普通程序不同的是,想要讓寫出的Android程序成為一個Xposed 模塊,要額外多完成以下四個硬性任務:
- 讓手機上的xposed框架知道我們安裝的這個程序是個xposed模塊
- 模塊里要包含有xposed的API的jar包,以實現(xiàn)下一步的hook操作
- 這個模塊里面要有對目標程序進行hook操作的方法
- 要讓手機上的xposed框架知道,我們編寫的xposed模塊中,哪一個方法是實現(xiàn)hook操作的
這就引出如下的四大件(與前四步一一對照):
- AndroidManifest.xml
- XposedBridgeApi-xx.jar 與 build.gradle
- 實現(xiàn)hook操作的具體代碼
- xposed_Init
PS:牢記以上四大件,按照順序一個一個實現(xiàn),就能完成Xposed模塊編寫
3.1 創(chuàng)建一個Android項目
官網(wǎng)下載地址:https://developer.android.com/studio?hl=zh-cn
傻瓜式一鍵點點安裝,安裝過程忽略不寫,不懂的可百度自行搜索
首先打開AndroidStudio(以版本3.1為例),建立一個工程,提示我們選擇“Activity”,那就選一個Empty Activity吧。(這個是模塊的界面,隨意選擇即可)
新建完成后下載依賴時,可能會出現(xiàn)如下報錯(是沒有科學上網(wǎng)導致的,要么你科學上網(wǎng),要么你把gradle版本下載到本地來安裝
改為如下地址:
distributionUrl=file:///C:/Users/xxxxx/.gradle/wrapper/dists/gradle-7.2-bin.zip
然后又有可能發(fā)現(xiàn)gradle插件下載又出現(xiàn)問題,解決辦法如下:
如上圖,注釋掉google(),新增如下maven:
//google()
maven { url 'https://maven.aliyun.com/repository/google'}
maven { url 'https://maven.aliyun.com/repository/jcenter'}
maven { url 'https://maven.aliyun.com/repository/public'}
以及如下位置:
最后關閉Android Studio,再重新打開項目會自動加載,此時如果沒有再有紅色警示就代表OK了,然后直接編譯一個Demo的APK(app/build/outputs/apk/debug目錄下,可以看到app-debug.apk),看看是否能正常運行,如下圖:
放到模擬器中,發(fā)現(xiàn)能正常安裝并運行,如下圖:
3.2 配置AndroidManifest.xml
為了讓Xposed 識別出這是個Xposed模塊,需要添加如下內(nèi)容:
<!--告訴xposed框架這是一個xposed模塊 -->
<meta-data
android:name="xposedmodule"
android:value="true"/>
<!--關于xposed模塊的描述 -->
<meta-data
android:name="xposeddescription"
android:value="XposeHook例程"/>
<!--xposed模塊支持的最低版本(以為54為例) -->
<meta-data
android:name="xposedminversion"
android:value="54"/>
修改文本內(nèi)容為:橙留香的HooK
此時把編譯好的APK丟進去,打開后,如下
PS:從上圖已看出,Xposed框架已經(jīng)認出了剛剛寫的程序,但是現(xiàn)在這個模塊什么都沒有做,因為我們還沒有做出修改
3.3 配置XposedBridgeApi-xx.jar與build.gradle
Xposed模塊主要功能是用來Hook其他程序的各種函數(shù)。
- 接下來,如何讓剛剛創(chuàng)建的那個Xposed“一窮二白”的模塊增加一些其它的功能呢?
引入 XposedBridgeApi.jar包,可理解該包是一把兵器,Xposed模塊有了這把絕世神器才能施展出Hook武功。
3.x以前,都需要手動下載諸如XposedBridgeApi的jar包,然后手工導入到libs目錄里
- XposedBridgeApi-54下載
https://forum.xda-developers.com/attachment.php?s=5903ce1b3edb1032faba7292b21e1801&attachmentid=2748878&d=1400342298
PS:除了XposedBridgeApi-54,還有XposedBridgeApi-82.jar、XposedBridgeApi-87.jar、XposedBridgeApi-89.jar等版本
官網(wǎng)下載:https://bintray.com/rovo89/de.robv.android.xposed/api
在AndroidStudio 3.1里面,只需要一行代碼,AndroidStuido就會自動配置XposedBridgeApi.jar
- 方法1:
Android Studio的依賴:
Xposed框架需要用到第三方庫,在 app --> build-gradle 添加依賴(最后一行)
repositories {
// 告訴AndroidStuido使用jcenter作為代碼倉庫,
// 從這個倉庫里遠程尋找
// de.robv.android.xposed:api:82 這個API
// 但最新版的3.x版本已不推薦使用
jcenter();
}
dependencies {
provided 'de.robv.android.xposed:api:82'
}
注:
此處要用compileOnly這個修飾符,
網(wǎng)上有些寫的是provide,
已經(jīng)停用了
compileOnly 'de.robv.android.xposed:api:82'
compileOnly 'de.robv.android.xposed:api:82:sources'
修改完成后,build.gradle會提示文件已經(jīng)修改,是否同步。點擊 “sync now”,同步即可,如下:
3.4 實現(xiàn)hook操作修改
在MainActivity的同級路徑下新建一個類“HO22K.java”,操作如下圖:
代碼如下:
package com.example.ho22k;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class HO22K implements IXposedHookLoadPackage
{
/**
* xpose插件入口點
* @param lpparam
* @throws Throwable
*/
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
// 獲取加載的apk程序包名
XposedBridge.log("當前啟動的應用程序是: " + loadPackageParam.packageName);
XposedBridge.log("Hook成功咯,寶們~_~!");
}
}
3.5 添加入口點xposed_Init
右鍵點擊 “main” 文件夾 , 選擇new --> Folder -->Assets Folder,新建assets 文件夾:
然后右鍵點擊 assets文件夾, new–> file,文件名為xposed_init(文件類型選text),并在其中寫上入口類的完整路徑(就是自己編寫的那一個Hook類),這樣, Xposed框架就能夠從這個 xposed_init 讀取信息來找到模塊的入口,然后進行Hook操作了
xposed_init里寫當前類的路徑 如果存在多個類,那么每行寫一個,多個寫多行
編譯APK,在如下路徑:
PS:編譯的時候需要關閉Android Studio的instant Run功能(不太清楚為什么要關閉,我沒有關閉一樣可以正常使用),注意注意Android Studio 3.5往后的版本,Instant Run被HotSwap代替,如下圖
安裝apk,在Xposed模塊中勾選插件,重啟,此時插件已經(jīng)可正常使用
觀察日志,可通過Xposed框架內(nèi)部日志模塊或LogCat 查看
PS:Windows CMD查看日志可能是會有亂碼,解決方法如下:
執(zhí)行如下命令后,代碼頁就被變成UTF-8
chcp 65001
然后修改窗口屬性,改變字體在命令行標題欄上點擊右鍵,選擇"屬性"->“字體”,將字體修改為True Type字體"Lucida Console",然后點擊確定將屬性應用到當前窗口
adb.exe shell
logcat
- 方法2:下載jar文件,存放至libs目錄,其它細節(jié)自行百度了解
不是網(wǎng)上說的單獨建立lib文件夾,那是很久以前的方法,然后右鍵“Add As Library” 自行添加這個jar包。而compileOnly 'de.robv.android.xposed:api:82'
和compileOnly 'de.robv.android.xposed:api:82:sources'
仍然照常添加
0x04 其它一些案例
4.1 按鈕劫持Hook
實現(xiàn)一個APK,基礎功能就是點擊界面的按鈕,就會彈出消息你未被劫持的消息,具體完整代碼如下(test APK代碼):
- MainActivity:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private Button button;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Toast.makeText(MainActivity.this, toastMessage(), Toast.LENGTH_SHORT).show();
}
});
}
public String toastMessage() {
return "想啥呢?同學,我未被劫持";
}
}
- activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按一下按鈕,確認是否被劫持"
tools:layout_editor_absoluteX="78dp"
tools:layout_editor_absoluteY="364dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
然后在HO22K 項目里面創(chuàng)建Xposed插件,這里博主為了偷懶,直接在一個項目里面寫了,所以不加載Xposed,只要APK執(zhí)行就會調(diào)用HO22K類,達到類似HO22K效果(不建議跟博主一樣,自己重新創(chuàng)建一個APK和插件APK,不要兩個都寫在一起),代碼和效果如下:
package com.example.ho22k;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class HO22K implements IXposedHookLoadPackage
{
//Module繼承了IXposedHookLoadPackage接口,當系統(tǒng)加載應用包的時候回回調(diào) handleLoadPackage;
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
//過濾包名,定位要Hook的包名
if (loadPackageParam.packageName.equals("com.example.ho22k")) {
//定位要Hook的具體的類名
Class clazz = loadPackageParam.classLoader.loadClass("com.example.ho22k.MainActivity");
//Hook的方法為toastMessage,XposedHelpers的靜態(tài)方法 findAndHookMethod就是hook函數(shù)的的方法,其參數(shù)對應為 類名+loadPackageParam.classLoader(照寫)+方法名+參數(shù)類型(根據(jù)所hook方法的參數(shù)的類型,即有多少個寫多少個,加上.class)+XC_MethodHook回調(diào)接口;
XposedHelpers.findAndHookMethod(clazz, "toastMessage", new XC_MethodHook() {
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
}
protected void afterHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {
//param.setResult("你已被劫持")將返回的結果設置成了你已被劫持
param.setResult("哦吼,同學你已被劫持");
}
});
}
}
}
4.2 登陸劫持
登陸劫持密碼這樣的操作,首先寫一個簡單的登陸程序,完整代碼如下:
- MainActivity:
package com.example.ho22k;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
EditText Name; //定義Plain Test控件第一個輸入框的名字
EditText Pass; //定義Plain Test控件第二個輸入框的名字
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Name = (EditText) findViewById(R.id.TEXT_NAME); //通過findViewById找到輸入框控件對應的id并給它起一個名字
Pass = (EditText) findViewById(R.id.TEXT_PASS);//通過findViewById找到輸入框控件對應的id并給它起一個名字
Button Login = (Button) findViewById(R.id.BTN_Login);//通過findViewById找到按鈕控件對應的id并給它起一個名字
Login.setOnClickListener(new View.OnClickListener() { //監(jiān)聽有沒有點擊按鈕控件 如果點擊了就會執(zhí)行onClick函數(shù)
@Override
public void onClick(View view) {
check(Name.getText().toString().trim(),Pass.getText().toString().trim()); //調(diào)用check函數(shù)
}
});
}
public void check(String name,String pass) //自定義函數(shù)check 這里用來檢查用戶名和密碼是否是cck和1234
{
if(name.equals("Orangey")&&pass.equals("123456"))
{
Toast.makeText(MainActivity.this,"登錄成功", Toast.LENGTH_SHORT).show();//彈框
}
else
Toast.makeText(MainActivity.this,"登錄失敗", Toast.LENGTH_SHORT).show();//彈框
}
}
- activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="23dp"
android:text="賬號:"
app:layout_constraintBaseline_toBaselineOf="@+id/TEXT_NAME"
app:layout_constraintEnd_toStartOf="@+id/TEXT_NAME"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
tools:ignore="MissingConstraints" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="23dp"
android:text="密碼:"
app:layout_constraintBaseline_toBaselineOf="@+id/TEXT_PASS"
app:layout_constraintEnd_toStartOf="@+id/TEXT_PASS"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
tools:ignore="MissingConstraints" />
<EditText
android:id="@+id/TEXT_NAME"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/textView"
android:layout_marginTop="180dp"
android:layout_marginEnd="1dp"
android:layout_toEndOf="@+id/textView"
android:layout_toRightOf="@+id/textView"
android:ems="10"
android:inputType="textPersonName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/textView"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="MissingConstraints" />
<EditText
android:id="@+id/TEXT_PASS"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/textView2"
android:layout_marginTop="35dp"
android:layout_marginEnd="1dp"
android:layout_toEndOf="@+id/textView2"
android:layout_toRightOf="@+id/textView2"
android:ems="10"
android:inputType="textPassword"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/textView2"
app:layout_constraintTop_toBottomOf="@+id/TEXT_NAME"
tools:ignore="MissingConstraints" />
<Button
android:id="@+id/BTN_Login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="42dp"
android:text="登錄"
app:layout_constraintEnd_toEndOf="@+id/TEXT_PASS"
app:layout_constraintStart_toStartOf="@+id/TEXT_PASS"
app:layout_constraintTop_toBottomOf="@+id/TEXT_PASS" />
</androidx.constraintlayout.widget.ConstraintLayout>
賬號:Orangey 密碼:123456
正確的賬號密碼登錄如下圖:
賬號:a 密碼:123456
錯誤的賬號或密碼登錄如下圖:
PS:如果出現(xiàn)界面布局混亂,只需要設置一下約束即可,如下圖:
目的:不管輸入什么都會顯示登陸成功,Hook對應的方法,并對相應的參數(shù)進行修改,還是使用上面的回調(diào)方法來實現(xiàn)
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
public class HO22K implements IXposedHookLoadPackage {
/**
* 包加載時候的回調(diào)
*/
public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
// 將包名不是 com.example.ho22k 的應用剔除掉,可以減少管理的類
if (!lpparam.packageName.equals("com.example.ho22k"))
return;
XposedBridge.log("當前APP應用程序是: " + lpparam.packageName);
//第一個參數(shù)是className,表示被注入的方法所在的類
//第二個參數(shù)是類加載器,照抄就行
//第三個參數(shù)是被注入的方法名
//第四五個參數(shù)是第三個參數(shù)的兩個形參的類型
//最后一個參數(shù)是匿名內(nèi)部類
findAndHookMethod("com.example.ho22k.MainActivity", lpparam.classLoader, "check", String.class,
String.class, new XC_MethodHook() {
/**
* 該方法在check方法調(diào)用之前被調(diào)用,輸出一些日志,并且捕獲參數(shù)的值。
* 最后兩行的目的是改變參數(shù)的值。也就是說無論參數(shù)是什么值,都會被替換為name為Orangey,pass為123456
* @param param
* @throws Throwable
*/
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("同學,你正在被人開始劫持");
XposedBridge.log("參數(shù)1 = " + param.args[0]);
XposedBridge.log("參數(shù)2 = " + param.args[1]);
param.args[0] = "Orangey";
param.args[1] = "123456";
}
/**
* 該方法在check方法調(diào)用之后被調(diào)用
* @param param
* @throws Throwable
*/
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("哦吼,同學劫持已結束");
XposedBridge.log("參數(shù)1 = " + param.args[0]);
XposedBridge.log("參數(shù)2 = " + param.args[1]);
}
});
}
}
通過對方法的參數(shù)進行了重賦值,效果如下圖:
Github上的一些Xposed案例APK地址:
- 原始程序:https://github.com/Gordon0918/XposedHookTarget
- hook修改源程序地址:https://github.com/Gordon0918/XposedHook
參考鏈接:
https://blog.csdn.net/gdutxiaoxu/article/details/81459830
https://eastmoon.blog.csdn.net/article/details/103810710
https://blog.csdn.net/SouthWind0/article/details/100669530
https://blog.csdn.net/JBlock/article/details/84202240
https://www.cnblogs.com/mukekeheart/p/5662842.html
https://blog.csdn.net/song_lee/article/details/103299353文章來源:http://www.zghlxwxcb.cn/news/detail-491374.html
你以為你有很多路可以選擇,其實你只有一條路可以走文章來源地址http://www.zghlxwxcb.cn/news/detail-491374.html
到了這里,關于[免費專欄] Android安全之Android Xposed插件開發(fā),小白都能看得懂的教程的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!