不错的文章!
http://weishu.me/2020/01/16/a-keep-alive-method-on-android/
http://www.52im.net/thread-2893-1-1.html
http://weishu.me/2021/01/25/another-keep-alive-method/
还有后台CPU消耗过多也会导致系统杀进程。
总结了以上被杀死的场景分析,我们得出两种技术方案:
Ⅰ. 提升进程优先级(降低被杀死的概率)
Ⅱ.进程杀死后,拉活进程
一 提升进程优先级的方法
1像素悬浮层
监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素的 Activity,在用户解锁时将 Activity 销毁掉。注意该 Activity 需设计成用户无感知。
通过该方案,可以使进程的优先级在屏幕锁屏时间由4提升为最高优先级1,主要解决第三方应用及系统管理工具在检测到锁屏事件后一段时间(一般为5分钟以内)内会杀死后台进程,已达到省电的目的问题
实现方案:
首先定义 Activity,并设置 Activity 的大小为1像素:
其次,从 AndroidManifest 中通过如下属性,排除 Activity 在 RecentTask 中的显示:
最后,控制 Activity 为透明:
2) 将Service设置为前台服务
Android 中 Service 的优先级为4,通过 setForeground 接口可以将后台 Service 设置为前台 Service,使进程的优先级由4提升为2,从而使进程的优先级仅仅低于用户当前正在交互的进程,与可见进程优先级一致,使进程被杀死的概率大大降低。
从 Android2.3 开始调用 setForeground 将后台 Service 设置为前台 Service 时,必须在系统的通知栏发送一条通知,也就是前台 Service 与一条可见的通知时绑定在一起的。
对于不需要常驻通知栏的应用来说,该方案虽好,但却是用户感知的,无法直接使用。
通过实现一个内部 Service,在 KeepLiveService 和其内部 Service 中同时发送具有相同 ID 的 Notification,然后将内部 Service 结束掉。随着内部 Service 的结束,Notification 将会消失,但系统优先级依然保持为2。
该方案虽好,但却是用户感知的,无法直接使用。
通过实现一个内部 Service,在 KeepLiveService 和其内部 Service 中同时发送具有相同 ID 的 Notification,然后将内部 Service 结束掉。随着内部 Service 的结束,Notification 将会消失,但系统优先级依然保持为2。
二 死了被拉起的方法
1 利用android系统支持的能力
1.1 利用Android账户同步机制拉活进程
前在做项目的时候,为了保证APP可以在手机长期存活,我找到了利用Android账户同步机制来拉活APP的进程。在华为6.0、4.4的版本测试可以拉活,在oppo5.0版本测试可以拉活,但是在小米6.0下,杀死进程后就无法通过账户机制拉活,我猜测是小米对账户机制进行了修改。SyncAdapter介绍
Android提供了SyncAdapter类用于需要同步本地数据和在线账户信息的应用,如电子邮件的定时收取、笔记应用的云备份、天气应用的及时同步等。它的优势在于可以根据不同条件自动发起数据传输,比如数据变更,间隔一定时间,或者是每天定时。而且,系统会将暂时不能运行的操作添加到队列里,在可能的情况下重新发起。
漏洞原理:
因为账户同步服务是系统维护的一个服务,所以我们的同步的进程是跟随系统的生命周期走,这就意味着只要不关机,这个服务会一直运行,帮我们同步账号。我们看一下功能流程图:
图是我总结了Android利用SyncAdapter同步账户的逻辑,而开发者可以随意重写onPerformSync()的内容,因此我们可以在onPerformSync()方法中去开启我们APP的Activity、Service等,一旦启动了Activity、Service之后,就等于拉活了我们APP的进程。
‘’由于该机制可定制度太高,而且比较冷门的功能,因此google一直没有去修补这块,不过小米有阉割掉了这里的拉活,而且刚刚用原生Android 8.0系统测了一下,貌似也是拉活不了。8.0以下都可以拉活。
总结
Android账户机制本身意义是google为了方便用户,不需要总是APP登录和验证账户信息,由系统来维护这些账户的验证。换个思路,这就是为攻击者提供了可以利用的提取系统权限的攻击点。我们在之后的研究可以多找找有系统提供的服务或者间接调用了系统服务的功能,这样可以让系统为我们“服务”。
目前大多数手机厂商针对于账户机制这些相对冷门的功能没有太大的关注,所以在各自品牌手机的rom包中,对于这些机制也未再进行认真的检验处理,这样也就导致很多原生系统存在的漏洞,在各大手机厂商上面同样可以进行攻击,所以需要系统开发人员认真的关注这些功能。
1.2 预装应用会默认授予 ACCESS_NOTIFICATION 权限
https://stackoverflow.com/questions/31145981/notification-listener-service-for-system-app-without-user-intervention
Notification Listener Service for System app without user intervention
I have a system app. In this app I want to capture Notifications, so I'm using NotificationListenerService. But for this service to start we need to do startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")) and this will get us to the notification access permissions where the user has to check the app and give permission. As my app is a system app is it possible to give permission by default without having the user check the box. I have already tried uses-permission but that is not working. Any help here would be much appreciated.
1.3 Broadcast 拉活:
1.3.1 监听一堆系统静态广播
监听ACTION_BOOT_COMPLETED
在发生特定系统事件时,系统会发出响应的广播,通过在 AndroidManifest 中“静态”注册对应的广播监听器,即可在发生响应事件时拉活。
但是有如下问题:
a. 广播接收器被管理软件、系统软件通过“自启管理”等功能禁用的场景无法接收到广播,从而无法自启。
b. 系统广播事件不可控,只能保证发生事件时拉活进程,但无法保证进程挂掉后立即拉活。
c 极光sdk 通常的广播拉起
07-07 15:27:39.549 653 728 V ActivityManager: Broadcast: Intent { flg=0x14 cmp=com.sohu.sohuvideo/cn.jpush.android.service.AlarmReceiver (has extras) } ordered=true userid=0
07-07 15:27:39.550 653 728 I ActivityManager: Broadcast intent Intent { flg=0x14 cmp=com.sohu.sohuvideo/cn.jpush.android.service.AlarmReceiver (has extras) } on background queue
07-07 15:27:39.552 653 669 I BroadcastQueue: Need to start app process [background] com.sohu.sohuvideo from com.sohu.sohuvideo for broadcast BroadcastRecord{bd80fa6 u0 null}
07-07 15:27:39.553 653 669 V ActivityManager: startProcess: name=com.sohu.sohuvideo app=null knownToBeDead=true thread=null pid=-1
07-07 15:27:39.555 653 669 I ActivityManager: Posting procStart msg for 0:com.sohu.sohuvideo/u0a79
07-07 15:27:39.618 653 671 I ActivityManager: Start proc 25681:com.sohu.sohuvideo/u0a79 for broadcast com.sohu.sohuvideo/cn.jpush.android.service.AlarmReceiver
1.3.2 监听第三方应用的静态广播
与接收系统广播类似,不同的是该方案为接收第三方应用广播。
通过反编译第三方应用,如:手机QQ、微信、支付宝、UC浏览器等找出它们外发的广播,在应用中进行监听,这样当这些应用发出广播时,就会将我们的应用拉活。
有效程度除与系统广播一样的因素外, 第三方应用的广播属于应用私有,当前版本中有效的广播,在后续版本随时就可能被移除或被改为不外发。
与接收系统广播类似,不同的是该方案为接收第三方应用广播。
通过反编译第三方应用,如:手机QQ、微信、支付宝、UC浏览器等找出它们外发的广播,在应用中进行监听,这样当这些应用发出广播时,就会将我们的应用拉活。
有效程度除与系统广播一样的因素外, 第三方应用的广播属于应用私有,当前版本中有效的广播,在后续版本随时就可能被移除或被改为不外发。
主要是实现也一个监听开机的广播,和一个周期性的闹钟,不过比较致命的是耗电量是很高的。
1.3.3 AlarmManager唤醒
主要是实现也一个监听开机的广播,和一个周期性的闹钟,不过比较致命的是耗电量是很高的。
1.3.4 Native进程拉起
原理就是通过 JNI fork出一个 c 进程,c 进程监控主进程是否存活,主要通过管道和文件监控的方式实现监控,发现主进程死后,通过调起一个 service 将主进程拉活
具体的实现方案:
此方案存在两个问题:
1.如果直接使用 JNI fork 出一个子进程,这样会存在一个内存较大问题,也就是主版占用多大内存,native 进程也占用多大内存,因此需要去写一个独立的 c 进程来降低内存大小
2.上面讲到5.0以上的系统会将根据 uid 杀进程,因此 native 进程也会被杀死,因此在5.0以上此方案失效的。
1.3.5 JobSchedule机制拉活
Android5.0 以后系统对 Native 进程等加强了管理,Native 拉活方式失效。系统在 Android5.0 以上版本提供了 JobScheduler 接口,系统会定时调用该进程以使应用进行一些逻辑操作。
1.3.2 igexin的保活手段
com.igexin.sdk.PushReceiver
1.3.3
后台监听各种系统信息的广播、timetick之类的
1.4 三方SDK
极光/igexin SDK.
1.5 service拉起
cn.jpush.android.service.DaemonService
1.6 添加Manifest文件属性值为android:persistent=“true”
app.persistent = true不仅仅标志着此apk不能轻易的被kill掉,亦或在被kill掉后能够自动restart,并且还涉及到了进程的优先级。将被设置为CORE_SERVER_ADJ,此值为-12,而核心进程init的值为-16。当前正在前台运行的进程的值为0。如果应用能设置这个属性,那就真的可以做到保活,因为他真的可以杀不死,像系统的keyguard进程,media进程,且这些进程的adj都是负数,代表了前台activity黑屏了他们也不会死。但是这个属性需要系统shareuid,然后编译不过,因为需要系统签名!
因此,要弄这个属性需获取两个关键的信息:
(1).在apk的AndroidManifest.xml文件中设置android:persistent=true
(2).此apk需要放入到system/app目录下,成为一个systemapp
最主要的是让程序成为系统程序,这个可以做到吗?如果你技术过硬是可以尝试下的!首先弄个自动root的apk,加壳放到程序中,然后程序运行的时候,自动运行自动root的apk,获取root权限,然后在native层中,使用命令的方式把程序移到system/app目录下,成为系统程序!
1.7 覆写Service的onDestroy方法
在设置里面的正在运行,注意是正在运行里面,点击关闭,会走onDestroy回调方法,你在这里可以把自己启动起来。注意是正常关闭的时候是会自己启动起来,可是使用第三方的清理软件360,root过的360,force close这些来搞,压根不会走到onDestory的方法。
1.8 双Service守护进程保活(这个也很流氓,不过如果不提高优先级(允许被杀),也算稍微良心)
前文我们分析过Android Binder的讣告机制:如果Service Binder实体的进程挂掉,系统会向Client发送讣告,而这个讣告系统就给进程保活一个可钻的空子。可以通过两个进程中启动两个binder服务,并且互为C/S,一旦一个进程挂掉,另一个进程就会收到讣告,在收到讣告的时候,唤起被杀进程。逻辑如下下:
首先编写两个binder实体服务PairServiceA ,PairServiceB,并且在onCreate的时候相互绑定,并在onServiceDisconnected收到讣告的时候再次绑定。
ublicclassPairServiceAextendsService{@Nullable@OverridepublicIBinderonBind(Intentintent){returnnewAliveBinder();}@OverridepublicvoidonCreate(){super.onCreate();bindService(newIntent(PairServiceA.this,PairServiceB.class),mServiceConnection,BIND_AUTO_CREATE);}privateServiceConnectionmServiceConnection=newServiceConnection(){@OverridepublicvoidonServiceConnected(ComponentNamename,IBinderservice){}@OverridepublicvoidonServiceDisconnected(ComponentNamename){bindService(newIntent(PairServiceA.this,PairServiceB.class),mServiceConnection,BIND_AUTO_CREATE);ToastUtil.show("bind A");}};
与之配对的B
publicclassPairServiceBextendsService{@Nullable@OverridepublicIBinderonBind(Intentintent){returnnewAliveBinder();}@OverridepublicvoidonCreate(){super.onCreate();bindService(newIntent(PairServiceB.this,PairServiceA.class),mServiceConnection,BIND_AUTO_CREATE);}privateServiceConnectionmServiceConnection=newServiceConnection(){@OverridepublicvoidonServiceConnected(ComponentNamename,IBinderservice){}@OverridepublicvoidonServiceDisconnected(ComponentNamename){bindService(newIntent(PairServiceB.this,PairServiceA.class),mServiceConnection,BIND_AUTO_CREATE);}};}
之后再Manifest中注册,注意要进程分离
1.9 通过START_STICKY与START_REDELIVER_INTENT实现被杀唤醒
通过startService启动的Service,如果没用呗stopService结束掉,在进程被杀掉之后,是有可能重新启动的,实现方式:
@OverridepublicintonStartCommand(Intentintent,intflags,intstartId){returnSTART_STICKY;//或者START_REDELIVER_INTENT}'
当然,前提是该进程可以被杀掉(无论被AMS还是LMDK),用户主动杀死(最近任务列表或者退出应用),都一定会通过Binder讣告机制回调:
privatefinalvoidhandleAppDiedLocked(ProcessRecordapp,booleanrestarting,booleanallowRestart){intpid=app.pid;booleankept=cleanUpApplicationRecordLocked(app,restarting,allowRestart,-1);...}
进而调用cleanUpApplicationRecordLocked函数进行一系列清理及通知工作,这里先看Service相关的工作:
privatefinalbooleancleanUpApplicationRecordLocked(ProcessRecordapp,booleanrestarting,booleanallowRestart,intindex){...// 这里先出处理servicemServices.killServicesLocked(app,allowRestart);...}
这里传入的allowRestart==true,也就说:允许重新启动Service:
final void killServicesLocked(ProcessRecord app, boolean allowRestart) { ... ServiceMap smap = getServiceMap(app.userId); // Now do remaining service cleanup. for (int i=app.services.size()-1; i>=0; i--) { ServiceRecord sr = app.services.valueAt(i); if (!app.persistent) { app.services.removeAt(i); } ... if (allowRestart && sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags &ApplicationInfo.FLAG_PERSISTENT) == 0) { bringDownServiceLocked(sr); } else if (!allowRestart || !mAm.mUserController.isUserRunningLocked(sr.userId, 0)) { bringDownServiceLocked(sr); } else {<!--关键点1 先进行判定,如果有需要将重启的消息发送到消息队列等待执行-->boolean canceled = scheduleServiceRestartLocked(sr, true); // 受时间跟次数的限制 sr.stopIfKilled<!--关键点2 二次确认,如果不应该启动Service,就将重启Service的消息移除-->if (sr.startRequested && (sr.stopIfKilled || canceled)) { if (sr.pendingStarts.size() == 0) { sr.startRequested = false; if (!sr.hasAutoCreateConnections()) { bringDownServiceLocked(sr); } ... }
先看关键点1:如果允许重新启动,并且APP Crash的次数小于两次,就视图将为结束的Service重新唤起,其实就是调用scheduleServiceRestartLocked,发送消息,等待唤醒,关键点2是二次确认下,是不是需要被唤醒,如果不需要就将上面的消息移除,并进行一定的清理工作,这里的sr.stopIfKilled,其实主要跟onStartCommand返回值有关系:
voidserviceDoneExecutingLocked(ServiceRecordr,inttype,intstartId,intres){booleaninDestroying=mDestroyingServices.contains(r);if(r!=null){if(type==ActivityThread.SERVICE_DONE_EXECUTING_START){r.callStart=true;switch(res){caseService.START_STICKY_COMPATIBILITY:caseService.START_STICKY:{r.findDeliveredStart(startId,true);r.stopIfKilled=false;break;}caseService.START_NOT_STICKY:{r.findDeliveredStart(startId,true);if(r.getLastStartId()==startId){r.stopIfKilled=true;}break;}caseService.START_REDELIVER_INTENT:{ServiceRecord.StartItemsi=r.findDeliveredStart(startId,false);if(si!=null){si.deliveryCount=0;si.doneExecutingCount++;r.stopIfKilled=true;}break;}
所以,如果onStartCommand返回的是Service.START_STICKY,在被杀死后是会重新启动的,有必要的话,还会重启进程:
private final boolean scheduleServiceRestartLocked(ServiceRecord r, boolean allowCancel) { boolean canceled = false; ...<!--关键点1-->mAm.mHandler.removeCallbacks(r.restarter); mAm.mHandler.postAtTime(r.restarter, r.nextRestartTime); r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay; return canceled;}
2 与三方应用合作
2.1 百度输入法开机拉起微信
百度输入法发送 com.tencent.mm.plugin.openapi.Intent.ACTION_HANDLE_APP_REGISTER 广播拉微信。
3 将同一个APP的不同Activity在Recent中显示
如果需要将同一个APP的不同Activity在Recent中显示,需满足如下两点:
LaunchMode设置为singleInstance。
设置android:taskAffinity参数。
4 使用厂商推荐的Push通道
5 注意的保活的过由不及问题
应用如果过度保活以及在后台做过多的事情,反而会导致手机厂商有针对性的处理,比如到后台就会被杀。
结果反而起到了反作用。做好的方式是合作。
综上,常用的方法分为利用系统bug或者机制、全家桶、徬大款、灭屏下播放。
所有流氓手段的进程保活,都是下策,建议不要使用,本文只是分析实验用。当APP退回后台,优先级变低,就应该适时释放内存,以提高系统流畅度,依赖流氓手段提高优先级,还不释放内存,保持不死的,都是作死。
提升应用的进程优先级是保活的王道。LAST_ACTIVITY 级别就可以搞定大多数后台控制。
网友评论