返回

Android应用启动方式鉴别:区分用户直接启动与远程控制

Android

Android 应用启动方式鉴别:用户直接打开 vs. 远程访问(免 Root)

应用开发中,有时我们需要分辨 App 是被用户直接在设备上打开,还是通过 PhoneLink、AnyDesk、TeamViewer 这类远程控制工具启动的。区分这两者非常关键,关系到数据安全、用户隐私。特别是 PhoneLink,能在用户几乎无感知的情况下控制设备。可惜 Android 系统似乎并没直接提供区分的 API (无需 Root 权限)。这篇文章聊聊几种可能的判断方法,并深入分析其原理和局限。

一、为什么需要区分启动方式?

想象一下,有人通过远程桌面控制了用户的手机,悄悄启动了你的应用,获取敏感信息,或者干了点别的,这很危险。对于金融类、支付类或涉及敏感数据的 App,搞清楚应用是在怎样的环境下被启动的,显得非常重要。

如果确定是远程访问启动, 可以立即采取措施:比如,禁用某些功能,要求重新验证身份, 或者直接退出应用,最大程度保障安全。

二、 常规检测手段和其局限性

1. 检查RunningAppProcessInfo

通常的想法是遍历当前运行的进程, 看是否有远程控制类App的特征:

  public boolean isRemoteAppRunning() {
        ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> runningProcesses = manager.getRunningAppProcesses();

      if (runningProcesses != null) {
            for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) {
                // 常见的远程控制应用包名 (需要不断补充完善)
                if (processInfo.processName.contains("com.microsoft.appmanager") || // Phone Link
                    processInfo.processName.contains("anydesk") ||  // AnyDesk
                    processInfo.processName.contains("teamviewer") // TeamViewer
                   ) {
                   return true;
                }
            }
        }
        return false;
    }
  • 原理: 通过ActivityManager.getRunningAppProcesses()获取系统当前正在运行的进程列表,逐个检查进程名。
  • 局限:
    • 易被绕过: 稍微改改远程控制应用的包名, 这招就失效了.
    • 误报: 有些应用的包名可能碰巧包含了特征字符串。
    • 不完全可靠: 即便找到了特定进程, 也不能百分百确定用户正在用它远程控制, 它可能只是后台运行. 尤其是 Phone Link 这种系统级应用,其进程一直存在.

2. 检测 FLAG_DISMISS_KEYGUARD

一些远程控制工具可能会在启动你的应用时禁用 Keyguard(锁屏)。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
      
       getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);

      if ((getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD) != 0) {
             // 锁屏可能被禁用, 怀疑远程启动
      }
       //... 其他操作...
        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
    }

  • 原理:onCreate中, 先尝试添加 FLAG_DISMISS_KEYGUARD, 然后立即检查该 flag 是否实际设置成功。如果成功,说明之前的 Keyguard 被关闭过(很可能是被远程控制软件干的)。注意最后清除 flag.
  • 局限:
    • 兼容性: 一些定制 ROM 或新版本的 Android 可能已修改了锁屏机制,此方法可能失效。
    • 误报: 其他类型的应用(例如闹钟、来电界面)也可能会暂时禁用锁屏。

3. 检测特定广播事件

有些远程控制工具(如早期版本的 Anydesk)会在建立连接时发送一些特定的广播。我们可以通过监听这些广播来间接推断是否正在进行远程控制。

//AndroidManifest.xml里声明广播接收器(或者动态注册):
 <receiver android:name=".MyBroadcastReceiver">
      <intent-filter>
         <!--  假设某个远程控制软件建立连接时会发这个广播(需要通过逆向等手段去了解) -->
           <action android:name="SOME_REMOTE_CONTROL_ACTION" />
       </intent-filter>
</receiver>
// MyBroadcastReceiver.java
public class MyBroadcastReceiver extends BroadcastReceiver {
      @Override
      public void onReceive(Context context, Intent intent) {
        if ("SOME_REMOTE_CONTROL_ACTION".equals(intent.getAction())) {
              // 收到特定广播, 怀疑是远程控制...
          }
      }
  }
  • 原理: 通过静态/动态注册 BroadcastReceiver 来监听特定广播. 当收到这类广播, 大概率可以判断是远程接入。
  • 局限:
    • 强依赖特定软件行为: 如果远程控制 App 不发送广播, 或改变了广播名称,这招就没用了。 挖掘这类广播意图很困难,通常要分析相关App.
    • 安全性: 系统广播可能被拦截/伪造。

三、 进阶检测方法及组合应用

1. 触摸事件分析

核心思想:人手操作触摸屏 和 鼠标/触控板通过远程软件控制触摸屏, 两者产生的MotionEvent会有差异。

@Override
public boolean onTouchEvent(MotionEvent event) {
     int pointerCount = event.getPointerCount();
     float pressure = event.getPressure();
      float size = event.getSize();
      int toolType = event.getToolType(0); // 触控类型

    // 分析触摸事件的参数
     if (pointerCount == 1 && //单点
          toolType == MotionEvent.TOOL_TYPE_FINGER && //手指触摸, 排除鼠标
          (pressure < 0.1f || size < 0.1f)) {     // 力度和尺寸过小,可能是鼠标模拟
              // 怀疑远程控制...
          }
      return super.onTouchEvent(event);
 }

  • 原理: 分析 MotionEventpointerCount(多点触控)、pressure(压力)、size(触摸面积)、toolType (工具类型, 手指/触控笔/鼠标)等属性.

  • 注意: 远程软件如果也在改进,比如模拟人手按压、多点触摸等等。该方法有效性就打折扣了.
    * 进阶: 分析更详细的事件参数, 比如: getHistoricalX/Y (获取历史轨迹), getEventTime, getDownTime, 计算滑动速度, 加速度等.

2.传感器数据分析
思路: 真机上手操作和远程控制在一些传感器的数据上,存在潜在差异

 SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
 Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); // 加速计
 Sensor gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);      //陀螺仪
 //注册监听器
 sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL);
  sensorManager.registerListener(this, gyroscope, SensorManager.SENSOR_DELAY_NORMAL);

 @Override
 public void onSensorChanged(SensorEvent event) {
      if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
        float x = event.values[0];
          float y = event.values[1];
          float z = event.values[2];
            // 加速度数据分析 (例如, 是否有轻微的、持续的抖动 -- 真人拿手机通常会有,而远程桌面大概率没有)
    } else if(event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
     // ... 陀螺仪数据
     }
}

@Override
  public void onAccuracyChanged(Sensor sensor, int accuracy) {
   //传感器精度变化
 }
  • 原理: 远程操作设备时,手机本身的物理状态通常是静止的(放在桌面上),而真实用户使用时, 手机多多少少会有晃动. 分析加速度传感器、陀螺仪、磁力计等数据,可作为辅助判断.

3. Accessibility Service (辅助功能)

思路:如果本App开启了辅助服务,且系统设置中显示有其他辅助服务也开启了,则可进行一定程度的推断。 虽然不能直接知道对方是不是远程控制软件

  • 原理: 很多远程控制 App 需要开启 Accessibility Service 才能实现全局模拟点击、输入等。 但请注意,这 不代表 开启了辅助功能的都是远程软件.
  • 实现 编写AccessibilityService,并监控其他辅助功能的变化情况.

4. Display 信息的判断

思路: 比较真实物理屏幕信息和当前应用的DisplayMetrics是否有差别. 远程软件, 如果做了屏幕缩放之类的,可能会露出马脚。

  • 通过 WindowManagergetDefaultDisplay() 方法拿到 真实物理屏幕 信息 (包括尺寸, 刷新率等).
    通过 Context.getResources().getDisplayMetrics() 获得应用当前 显示逻辑相关 的 DisplayMetrics.
    对比两者的宽高, 密度(density, densityDpi, scaledDensity), 屏幕方向(rotation)等等。

5. 多种策略组合 + 机器学习(可选)

前面提到的单个方法,都有局限, 很容易被绕过。最佳实践是把这些方法组合起来。例如:

  • 同时检查可疑进程、触摸事件特征、传感器数据。
  • 加权打分。如果超过一定阈值, 就认为是远程控制。

更进一步的, 可以利用机器学习:

  1. 收集数据: 在用户知情同意的前提下,分别在 真机操作多种远程工具控制 两种场景下, 收集:
    • MotionEvent的各种参数
      * 各种传感器数据 (加速度,陀螺仪,光线...)
      * Display信息变化
      • getRunningAppProcesses列表
        ... 等等。
  2. 训练模型: 用这些数据训练一个二分类模型(是否远程)。常用的模型: 决策树、随机森林、SVM, 轻量级神经网络.
    3. 部署和应用: 把训练好的模型集成到 App 里, 实时进行判断。

优势: 更智能, 更难被绕过 (对抗性更强).
难点: 需要一定的机器学习基础; 模型训练需要数据(数据采集是关键!且要注意用户隐私); 模型要足够轻量级, 不能拖慢 App; 要持续更新模型.

四、安全性考虑和最佳实践

  1. 代码混淆 + 防反编译: 防止别人轻易分析你的检测逻辑.

  2. 不要过分依赖检测结果: 任何检测方法都不是百分百准确, 只是增加了攻击成本。安全防护应该多层次.

  3. 明确告知用户: 如果检测到疑似远程访问,要明确告知用户 (UI 弹窗),而不是悄悄做一些事情。用户有知情权.

  4. 合理控制权限: 申请必要的权限就好。

总结: 检测应用启动方式虽然没有“银弹”,但通过结合使用多种手段,构建一个综合的检测机制,能够提高远程控制的检测准确性, 让 App 更有保障.