ANDROID黑科技:保活机制深度逆向

admin 2026-04-25 04:54:17 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文深度逆向分析了某头部大厂APP在Android10+环境下的Native层保活机制。核心采用三重技术:通过DoubleFork创建孤儿进程脱离原进程组避免连坐查杀;利用flock文件锁监控主进程存活状态;通过纯C++构造Parcel数据实现Binder穿透直接唤醒主进程。该方案具有高隐蔽性和存活率,为Android安全防护提供了重要参考。 综合评分: 85 文章分类: 移动安全,逆向分析,安全工具,恶意软件,技术标准


cover_image

ANDROID 黑科技 : 保活机制深度逆向

易之生生 易之生生

看雪学苑

2026年4月24日 17:59 上海

在小说阅读器读本章

去阅读

在 Android 逆向与安全防护的博弈中,进程保活(Keep-Alive)始终是一个充满争议且技术密集的话题。随着 Android 系统的迭代,从早期的1 像素 ActivityJobScheduler,到后来的各种同步账号机制,系统对后台进程的容忍度越来越低。

本文将以某头部大厂 APP 中的保活模块(libundead_native_ability_q.so)为例,深度剖析其在 Android 10+ (API 29及以上) 环境下,如何利用 C/C++ 层的双重 Fork 逃逸、Flock 文件锁监控以及纯 Native 层的 Binder 穿透技术,实现高隐蔽性、高存活率的“不死”机制。

Java 层切入:寻根溯源

通过对 APK 的初步分析,我们定位到核心的保活入口位于包com.bytedance.platform.ka下。针对高版本 Android 系统,应用采用了分层策略,其中针对 Android 10+ 的核心类为KaAbilityQ

查看其反编译代码:

package com.bytedance.platform.ka.ability.q.KaAbilityQ;

public class KaAbilityQ extends 09xQ implements 09xS {
// 指定加载的 SO 库名称
public void KaAbilityQ(Application p0, 09xO p1){
super(p0, p1);
this.LIZLLL = "undead_native_ability_q";
    }

// 核心 JNI 方法声明
private native intdoKaOnNative(IBinder p0, long[] p1, String p2, long p3,
                                    String p4, String p5, String p6, String p7,
                                    String p8, boolean p9, String p10, String p11,
int p12, int p13);

// JAVA 调用接口
public final intLIZ(IBinder p0,long[] p1,String p2,long p3,String p4,String p5,String p6,String p7,String p8,boolean p9,String p10,String p11,int p12,int p13){
this.LIZIZ();
if (this.LIZJ != null) {
          UnDeadLog.d("KaAbilityQ", "library load success,invoke doKaOnNative");
return this.doKaOnNative(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13);
       }else {
          UnDeadLog.e("KaAbilityQ", "library load failed,not doKA");
return -1;
       }
    }

}

从这里可以看出,Java 层主要负责环境准备和参数收集(如当前进程名、各种开关 Flag),真正的硬核逻辑全部通过doKaOnNative交给了libundead_native_ability_q.so处理。

在 ServiceImpl 的 LIZIZ 函数中进行了参数初始化并调用了 LIZ 函数。

ServiceImplplatform = serviceImpl.platform;
IBinderbinder = serviceImpl.o.getBinder();
ServiceImplg = serviceImpl.g;
ServiceImplmInstrConfig = serviceImpl.mInstrConfig;
mInstrConfig.getClass();
Stringstr4 = StringBuilderCache.release(StringBuilderCache.get() + mInstrConfig.LJI() + "/enable.flag");
mInstrConfig = serviceImpl.mInstrConfig;
mInstrConfig.getClass();
Stringstr5 = StringBuilderCache.release(StringBuilderCache.get() + mInstrConfig.LJI() + "/top.flag");
mInstrConfig = serviceImpl.mInstrConfig;
mInstrConfig.getClass();
StringBuilderCache.release(StringBuilderCache.get() + mInstrConfig.LJI() + "/mcomm");

if ((iKADepend = serviceImpl.mInstrConfig.LIZ()) != null) {
    daemonProces = iKADepend.getDaemonProcessName();
if (!TextUtils.isEmpty(daemonProces)) {
StringpackageName = serviceImpl.mInstrConfig.LIZIZ.getPackageName();
        b = serviceImpl.mIKADepend.useNativeMode();
if ((config1 = serviceImpl.mInstrConfig.LIZ.getConfig("instr")) != null && !config1.isEmpty()) {
            str = config1;
        }
inti1 = platform.LJ.LIZ(binder, g, str1, l2, str4, str5, p1, daemonProces, packageName, b, str, ToolUtils.LJIIIZ(platform.LIZ), startInstrum, serviceImpl.mIKADepend.transactFlags());
    }
}

#

Native 层核心对抗逻辑剖析

将 SO 拖入 IDA Pro 进行分析,定位到导出函数Java_com_bytedance_platform_ka_ability_q_KaAbilityQ_doKaOnNative,内部调用了真实的实现逻辑do_ka

该 SO 的核心保活流转经历了三个精妙的阶段:

1. 进程树逃逸:Double Fork 机制

为了防止系统在杀死主应用时,顺藤摸瓜将子进程“一锅端”,该组件在 Native 层实现了经典的 Double Fork 逃逸:

signal(17, (__sighandler_t)((char * ) & dword_0 + 1));
v27 = fork();
if (!v27) {
if (a3) {
        v28 = _JNIEnv -> functions -> GetLongArrayElements(_JNIEnv, a3, 0 LL);
        v29 = v28;
if (v28 && * v28 && v28[1]) {
            v30 = ((__int64( * )(void)) * v28)();
            ((void(__fastcall * )(__int64,
constchar * )) v29[1])(v30, v46);
        }
        v31 = inited;
prctl(PR_SET_NAME, v46, 0 LL, 0 LL, 0 LL);
if (fork())
goto LABEL_42;
    } else {
        v31 = inited;
        v29 = 0 LL;
if (fork())
goto LABEL_42;
    }

// ...

    LABEL_42:
        _exit(0);
}

主进程调用fork()创建子进程 1

子进程 1

立即再次调用fork()创建孙子进程(即未来的守护进程)。

子进程 1

随即调用_exit(0)主动退出。

孙子进程

由于生父死亡,瞬间变为孤儿进程,被系统的init进程(PID 为 1)接管,成功脱离原 APP 的进程组(Process Group)。

(注:与部分保活方案使用 Pipe 管道阻塞监控不同,这里脱离进程树是为了避免被 ActivityManagerService (AMS) 的ProcessRecord.kill()连坐查杀。)

2. 状态嗅探:Flock 文件锁无级监听

脱离进程组后,守护进程需要一种极低功耗的方式来感知主进程的生死。轮询/proc/目录显然太耗电且容易被查杀。

while (1) {
    fd_2 = open(file, O_RDWR | O_CREAT, 432 LL);
if (fd_2 >= LOCK_SH) {
        fd = fd_2;
if&nbsp;(flock(fd_2, (ENUM_LOCK)&nbsp;6) <&nbsp;0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v34 =&nbsp;close(fd);
if&nbsp;( * (_DWORD * ) __errno(v34) ==&nbsp;11) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stream_1 = fopen(filename,&nbsp;"rw");
if&nbsp;(stream_1) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stream = stream_1;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; remove(filename);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fclose(stream);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fflush(stream);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fd_1 =&nbsp;open(file, O_RDWR | O_CREAT,&nbsp;432&nbsp;LL);
if&nbsp;(fd_1 >= LOCK_SH)
flock(fd_1, LOCK_EX);
if&nbsp;(a5) {
if&nbsp;(gettimeofday( & tv,&nbsp;0&nbsp;LL))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v38 =&nbsp;1;
else
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v38 =&nbsp;1000&nbsp;* tv.tv_sec + tv.tv_usec /&nbsp;0x3E8&nbsp;uLL < a5;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v38 =&nbsp;0;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v39 = access(name, F_OK);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v40&nbsp;= access(name_1, F_OK);
if&nbsp;(!v38 && !v39 && v40) {
if&nbsp;(!a11)
return&nbsp;0;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; start_instr();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; LABEL_42:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _exit(0);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
flock(fd, LOCK_UN);
close(fd);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
&nbsp; &nbsp; usleep(0x3D0900&nbsp;u);
}

该组件巧妙地利用了 Linux 的flock(File Lock) 机制:

  • 主应用在启动时,对一个特定的本地文件(如.ka_monitor)进行加锁 (LOCK_EX)。
  • 守护进程在其死循环中,尝试对同一个文件进行flock
  • 由于互斥锁的存在,守护进程会被内核挂起阻塞(或者usleep轮询),不占用 CPU 资源。
  • 一旦主进程被系统 Kill (OOM 或用户划掉),Linux 内核会强制回收主进程持有的所有文件句柄,该文件锁被瞬间释放。
  • 守护进程的flock立刻返回成功,从而精准捕获主进程的死亡事件。

3. 破土重生:纯 C++ 构造 Parcel 与 Binder 穿透

这是该方案最为硬核的部分。当守护进程发现主进程死亡后,如果通过常规的am start命令行去拉起,不仅速度慢,而且极易被高版本 Android 的后台拦截机制阻断。

逆向伪代码显示,该 SO 直接引入了 NDK 的AIBinderAPI,徒手拼接底层 IPC 数据包(Parcel):

__int64 __fastcall&nbsp;init_instr_internal(__int64 kaAbility,constchar&nbsp;* s,constchar&nbsp;* s_1,constchar&nbsp;* s_2,int&nbsp;a5,int&nbsp;a6)&nbsp;{
unsignedint&nbsp;v12;&nbsp;// w22
unsignedint&nbsp;DataPosition;&nbsp;// w24
unsignedint&nbsp;DataPosition_1;&nbsp;// w23

if&nbsp;((unsignedint)&nbsp;AIBinder_prepareTransaction(kaAbility, & ::DataPosition)) {
return&nbsp;0;
&nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; v12 =&nbsp;1;
AParcel_writeInt32();
strlen(s);
AParcel_writeString();
strlen(s_1);
AParcel_writeString();
AParcel_writeString();
AParcel_writeInt32();
AParcel_writeInt32();
&nbsp; &nbsp; &nbsp; &nbsp; DataPosition =&nbsp;AParcel_getDataPosition(::DataPosition);
AParcel_writeInt32();
AParcel_writeInt32();
AParcel_getDataPosition(::DataPosition);
AParcel_writeInt32();
&nbsp; &nbsp; &nbsp; &nbsp; __strlen_chk("instrumentation_type",&nbsp;0x15&nbsp;uLL);
AParcel_writeString();
AParcel_writeInt32();
&nbsp; &nbsp; &nbsp; &nbsp; __strlen_chk("instrumentation_type_ka",&nbsp;0x18&nbsp;uLL);
AParcel_writeString();
&nbsp; &nbsp; &nbsp; &nbsp; __strlen_chk("source_process",&nbsp;0xF&nbsp;uLL);
AParcel_writeString();
AParcel_writeInt32();
strlen(s_2);
AParcel_writeString();
&nbsp; &nbsp; &nbsp; &nbsp; __strlen_chk("use_native_mode",&nbsp;0x10&nbsp;uLL);
AParcel_writeString();
AParcel_writeInt32();
AParcel_writeInt32();
&nbsp; &nbsp; &nbsp; &nbsp; DataPosition_1 =&nbsp;AParcel_getDataPosition(::DataPosition);
AParcel_setDataPosition(::DataPosition, DataPosition);
AParcel_writeInt32();
AParcel_setDataPosition(::DataPosition, DataPosition_1);
AParcel_writeStrongBinder(::DataPosition,&nbsp;0&nbsp;LL);
AParcel_writeStrongBinder(::DataPosition,&nbsp;0&nbsp;LL);
AParcel_writeInt32();
AParcel_writeString();
&nbsp; &nbsp; &nbsp; &nbsp; dword_410C0 = a5;
&nbsp; &nbsp; &nbsp; &nbsp; unk_410C4 = a6;::kaAbility = kaAbility;
&nbsp; &nbsp; &nbsp; &nbsp; byte_410D8 =&nbsp;1;
&nbsp; &nbsp; }
return&nbsp;v12;
}
boolstart_instr(void){
return&nbsp;byte_410D8 && (unsignedint)AIBinder_transact(kaAbility, (unsignedint)dword_410C0, &DataPosition) ==&nbsp;0;
}

守护进程提前在内存中组装好了一通向ActivityManagerService发送startInstrumentation事务的 Parcel 包。触发时,直接调用AIBinder_transact将伪造的请求发给 AMS。

系统接收到请求后,会主动分配进程资源,拉起该 APP 注册的自定义Instrumentation。应用随之在callApplicationOnCreate中完成复苏。

调用链分析

守护进程通过AIBinder_transact向 AMS 发起startInstrumentation请求,其底层完整的调用链涉及客户端 Binder 代理与服务端实现两个关键环节。

客户端代理实现android.app.IActivityManager的 Stub 代理):

@Override// android.app.IActivityManager
public&nbsp;booleanstartInstrumentation(ComponentName componentName, String str,&nbsp;int&nbsp;i, Bundle bundle, IInstrumentationWatcher iInstrumentationWatcher, IUiAutomationConnection iUiAutomationConnection,&nbsp;int&nbsp;i2, String str2)&nbsp;throws&nbsp;RemoteException {
ParcelparcelObtain&nbsp;=&nbsp;Parcel.obtain(asBinder());
ParcelparcelObtain2&nbsp;=&nbsp;Parcel.obtain();
try&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; parcelObtain.writeInterfaceToken(Stub.DESCRIPTOR);
&nbsp; &nbsp; &nbsp; &nbsp; parcelObtain.writeTypedObject(componentName,&nbsp;0);
&nbsp; &nbsp; &nbsp; &nbsp; parcelObtain.writeString(str);
&nbsp; &nbsp; &nbsp; &nbsp; parcelObtain.writeInt(i);
&nbsp; &nbsp; &nbsp; &nbsp; parcelObtain.writeTypedObject(bundle,&nbsp;0);
&nbsp; &nbsp; &nbsp; &nbsp; parcelObtain.writeStrongInterface(iInstrumentationWatcher);
&nbsp; &nbsp; &nbsp; &nbsp; parcelObtain.writeStrongInterface(iUiAutomationConnection);
&nbsp; &nbsp; &nbsp; &nbsp; parcelObtain.writeInt(i2);
&nbsp; &nbsp; &nbsp; &nbsp; parcelObtain.writeString(str2);
this.mRemote.transact(51, parcelObtain, parcelObtain2,&nbsp;0);
&nbsp; &nbsp; &nbsp; &nbsp; parcelObtain2.readException();
return&nbsp;parcelObtain2.readBoolean();
&nbsp; &nbsp; }&nbsp;finally&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; parcelObtain2.recycle();
&nbsp; &nbsp; &nbsp; &nbsp; parcelObtain.recycle();
&nbsp; &nbsp; }
}

服务端真实处理ActivityManagerService.java):

public&nbsp;booleanstartInstrumentation(ComponentName className,
&nbsp; &nbsp; &nbsp; &nbsp; String profileFile,&nbsp;int&nbsp;flags, Bundle arguments,
&nbsp; &nbsp; &nbsp; &nbsp; IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection,
int&nbsp;userId, String abiOverride)&nbsp;{
&nbsp; &nbsp; enforceNotIsolatedCaller("startInstrumentation");
final&nbsp;intcallingUid&nbsp;=&nbsp;Binder.getCallingUid();
final&nbsp;intcallingPid&nbsp;=&nbsp;Binder.getCallingPid();
&nbsp; &nbsp; userId = mUserController.handleIncomingUser(callingPid, callingUid,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; userId,&nbsp;false, ALLOW_FULL_ONLY,&nbsp;"startInstrumentation",&nbsp;null);
// Refuse possible leaked file descriptors
if&nbsp;(arguments !=&nbsp;null&nbsp;&& arguments.hasFileDescriptors()) {
throw&nbsp;new&nbsp;IllegalArgumentException("File descriptors passed in Bundle");
&nbsp; &nbsp; }
final&nbsp;IPackageManagerpm&nbsp;=&nbsp;AppGlobals.getPackageManager();
synchronized(this) {
InstrumentationInfoii&nbsp;=null;
ApplicationInfoai&nbsp;=null;
booleannoRestart&nbsp;=&nbsp;(flags & INSTR_FLAG_NO_RESTART) !=&nbsp;0;
try&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ii = pm.getInstrumentationInfoAsUser(className, STOCK_PM_FLAGS, userId);
if&nbsp;(ii ==&nbsp;null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reportStartInstrumentationFailureLocked(watcher, className,
"Unable to find instrumentation info for: "&nbsp;+ className);
return&nbsp;false;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ai = pm.getApplicationInfo(ii.targetPackage, STOCK_PM_FLAGS, userId);
if&nbsp;(ai ==&nbsp;null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reportStartInstrumentationFailureLocked(watcher, className,
"Unable to find instrumentation target package: "&nbsp;+ ii.targetPackage);
return&nbsp;false;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;catch&nbsp;(RemoteException e) {
&nbsp; &nbsp; &nbsp; &nbsp; }
if&nbsp;(ii.targetPackage.equals("android")) {
if&nbsp;(!noRestart) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reportStartInstrumentationFailureLocked(watcher, className,
"Cannot instrument system server without 'no-restart'");
return&nbsp;false;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;if&nbsp;(!ai.hasCode()) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reportStartInstrumentationFailureLocked(watcher, className,
"Instrumentation target has no code: "&nbsp;+ ii.targetPackage);
return&nbsp;false;
&nbsp; &nbsp; &nbsp; &nbsp; }
intmatch&nbsp;=&nbsp;SIGNATURE_NO_MATCH;
try&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; match = pm.checkSignatures(ii.targetPackage, ii.packageName, userId);
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;catch&nbsp;(RemoteException e) {
&nbsp; &nbsp; &nbsp; &nbsp; }
if&nbsp;(match <&nbsp;0&nbsp;&& match != PackageManager.SIGNATURE_FIRST_NOT_SIGNED) {
if&nbsp;(Build.IS_DEBUGGABLE && (callingUid == Process.ROOT_UID)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; && (flags & INSTR_FLAG_ALWAYS_CHECK_SIGNATURE) ==&nbsp;0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Slog.w(TAG,&nbsp;"Instrumentation test "&nbsp;+ ii.packageName
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; +&nbsp;" doesn't have a signature matching the target "&nbsp;+ ii.targetPackage
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; +&nbsp;", which would not be allowed on the production Android builds");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
Stringmsg&nbsp;="Permission Denial: starting instrumentation "
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; + className +&nbsp;" from pid="
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; + Binder.getCallingPid()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; +&nbsp;", uid="&nbsp;+ Binder.getCallingUid()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; +&nbsp;" not allowed because package "&nbsp;+ ii.packageName
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; +&nbsp;" does not have a signature matching the target "
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; + ii.targetPackage;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reportStartInstrumentationFailureLocked(watcher, className, msg);
throw&nbsp;new&nbsp;SecurityException(msg);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
if&nbsp;(!Build.IS_DEBUGGABLE && callingUid != ROOT_UID && callingUid != SHELL_UID
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; && callingUid != SYSTEM_UID && !hasActiveInstrumentationLocked(callingPid)) {
// If it's not debug build and not called from root/shell/system uid, reject it.
final&nbsp;Stringmsg&nbsp;="Permission Denial: instrumentation test "
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; + className +&nbsp;" from pid="&nbsp;+ callingPid +&nbsp;", uid="&nbsp;+ callingUid
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; +&nbsp;", pkgName="&nbsp;+ mInternal.getPackageNameByPid(callingPid)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; +&nbsp;" not allowed because it's not started from SHELL";
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Slog.wtfQuiet(TAG, msg);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reportStartInstrumentationFailureLocked(watcher, className, msg);
throw&nbsp;new&nbsp;SecurityException(msg);
&nbsp; &nbsp; &nbsp; &nbsp; }
booleandisableHiddenApiChecks&nbsp;=&nbsp;ai.usesNonSdkApi()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; || (flags & INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS) !=&nbsp;0;
booleandisableTestApiChecks&nbsp;=&nbsp;disableHiddenApiChecks
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; || (flags & INSTR_FLAG_DISABLE_TEST_API_CHECKS) !=&nbsp;0;
if&nbsp;(disableHiddenApiChecks || disableTestApiChecks) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; enforceCallingPermission(android.Manifest.permission.DISABLE_HIDDEN_API_CHECKS,
"disable hidden API checks");
&nbsp; &nbsp; &nbsp; &nbsp; }
if&nbsp;((flags & ActivityManager.INSTR_FLAG_INSTRUMENT_SDK_SANDBOX) !=&nbsp;0) {
return&nbsp;startInstrumentationOfSdkSandbox(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; className,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; profileFile,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; arguments,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; watcher,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; uiAutomationConnection,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; userId,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; abiOverride,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ii,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ai,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; noRestart,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; disableHiddenApiChecks,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; disableTestApiChecks,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (flags & ActivityManager.INSTR_FLAG_INSTRUMENT_SDK_IN_SANDBOX) !=&nbsp;0);
&nbsp; &nbsp; &nbsp; &nbsp; }
ActiveInstrumentationactiveInstr&nbsp;=new&nbsp;ActiveInstrumentation(this);
&nbsp; &nbsp; &nbsp; &nbsp; activeInstr.mClass = className;
StringdefProcess&nbsp;=&nbsp;ai.processName;;
if&nbsp;(ii.targetProcesses ==&nbsp;null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; activeInstr.mTargetProcesses =&nbsp;new&nbsp;String[]{ai.processName};
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;if&nbsp;(ii.targetProcesses.equals("*")) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; activeInstr.mTargetProcesses =&nbsp;new&nbsp;String[0];
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; activeInstr.mTargetProcesses = ii.targetProcesses.split(",");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; defProcess = activeInstr.mTargetProcesses[0];
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; activeInstr.mTargetInfo = ai;
&nbsp; &nbsp; &nbsp; &nbsp; activeInstr.mProfileFile = profileFile;
&nbsp; &nbsp; &nbsp; &nbsp; activeInstr.mArguments = arguments;
&nbsp; &nbsp; &nbsp; &nbsp; activeInstr.mWatcher = watcher;
&nbsp; &nbsp; &nbsp; &nbsp; activeInstr.mUiAutomationConnection = uiAutomationConnection;
&nbsp; &nbsp; &nbsp; &nbsp; activeInstr.mResultClass = className;
&nbsp; &nbsp; &nbsp; &nbsp; activeInstr.mHasBackgroundActivityStartsPermission = checkPermission(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; == PackageManager.PERMISSION_GRANTED;
&nbsp; &nbsp; &nbsp; &nbsp; activeInstr.mHasBackgroundForegroundServiceStartsPermission = checkPermission(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; START_FOREGROUND_SERVICES_FROM_BACKGROUND, callingPid, callingUid)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; == PackageManager.PERMISSION_GRANTED;
&nbsp; &nbsp; &nbsp; &nbsp; activeInstr.mNoRestart = noRestart;
final&nbsp;longorigId&nbsp;=&nbsp;Binder.clearCallingIdentity();
&nbsp; &nbsp; &nbsp; &nbsp; ProcessRecord app;
synchronized&nbsp;(mProcLock) {
if&nbsp;(noRestart) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; app = getProcessRecordLocked(ai.processName, ai.uid);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
// Instrumentation can kill and relaunch even persistent processes
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; forceStopPackageLocked(ii.targetPackage, -1,&nbsp;true,&nbsp;false,&nbsp;true,&nbsp;true,&nbsp;false,
false, userId,&nbsp;"start instr");
// Inform usage stats to make the target package active
if&nbsp;(mUsageStatsService !=&nbsp;null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mUsageStatsService.reportEvent(ii.targetPackage, userId,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; UsageEvents.Event.SYSTEM_INTERACTION);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; app = addAppLocked(ai, defProcess,&nbsp;false, disableHiddenApiChecks,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; disableTestApiChecks, abiOverride, ZYGOTE_POLICY_FLAG_EMPTY);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_INSTRUMENTATION);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; app.setActiveInstrumentation(activeInstr);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; activeInstr.mFinished =&nbsp;false;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; activeInstr.mSourceUid = callingUid;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; activeInstr.mRunningProcesses.add(app);
if&nbsp;(!mActiveInstrumentation.contains(activeInstr)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mActiveInstrumentation.add(activeInstr);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
if&nbsp;((flags & INSTR_FLAG_DISABLE_ISOLATED_STORAGE) !=&nbsp;0) {
// Allow OP_NO_ISOLATED_STORAGE app op for the package running instrumentation with
// --no-isolated-storage flag.
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mAppOpsService.setMode(AppOpsManager.OP_NO_ISOLATED_STORAGE, ai.uid,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ii.packageName, AppOpsManager.MODE_ALLOWED);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; Binder.restoreCallingIdentity(origId);
if&nbsp;(noRestart) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; instrumentWithoutRestart(activeInstr, ai);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
return&nbsp;true;
}

以上两段代码完整展示了从客户端通过Binder事务码51发起startInstrumentation请求,到服务端ActivityManagerService进行权限校验、签名匹配、进程强制停止(或复用)以及ActiveInstrumentation注册的完整流程。

守护进程正是利用这一底层通道,在主进程死亡后伪造合法请求,触发 AMS 重新分配进程并拉起自定义Instrumentation,从而实现隐蔽唤醒。

总结

该厂的保活方案代表了目前 Android 端企服/核心业务模块在进程驻留方面的一流水平。“Double Fork 逃避进程树监控 + Flock 零功耗挂起 + Native Binder 直接穿透 AMS”的组合拳,巧妙绕过了 Java 层层层加码的系统限制。

面对未来 API 36 (Android 16) 更加严苛的Foreground Service限制和广播冻结机制,此类纯底层触发Instrumentation的方式是否还能如鱼得水?系统是否会在内核层面切断非信任进程的 Binder 节点访问权?这些都是非常值得持续跟进和逆向分析的研究方向。

#

看雪ID:易之生生

https://bbs.kanxue.com/user-home-920134.htm

*本文为看雪论坛精华文章,由 易之生生 原创,转载请注明来自看雪社区

往期推荐

安卓逆向基础知识之frida Hook

2025 强网杯和强网拟态部分题解

在逆向分析方面-unidbg真的适合 MCP 吗?

AI静态分析,内核模块隐藏 Frida 特征,绕过linker私有结构遍历崩溃链

某安全so库深度解析

球分享

球点赞

球在看

点击阅读原文查看更多


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:看雪学苑 易之生生 易之生生《ANDROID 黑科技 : 保活机制深度逆向》

评论:0   参与:  0