在移动应用生态中,安全威胁如同潜伏的暗流,而Activity劫持则是其中最具迷惑性的攻击手段之一。想象一下,用户正打开银行应用准备转账,却不知此时屏幕已被精心伪造的界面覆盖——这种"偷梁换柱"的把戏,正是Activity劫持的典型作案手法。作为开发者,我们不仅需要理解这种攻击的运行机制,更要在代码层面构建主动防御体系。本文将深入探讨如何利用Android Studio等工具,通过技术手段实现从检测到防御的全流程解决方案。
Activity劫持(Activity Hijacking)本质上是一种界面欺骗攻击。恶意应用通过监听系统广播或利用系统漏洞,在目标Activity启动时快速覆盖一个仿冒界面。这种攻击之所以危险,在于它完美复刻了原应用的UI设计,用户很难察觉异常。
从技术实现看,攻击者主要依赖两种途径:
getRunningTasks API滥用:通过周期性查询当前栈顶Activity,匹配目标包名后立即启动钓鱼界面android.permission.BIND_ACCESSIBILITY_SERVICE权限后,可监控窗口变化事件java复制// 典型攻击代码片段(模拟)
public class MaliciousService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if(event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
String packageName = event.getPackageName().toString();
if(targetPackages.contains(packageName)) {
launchPhishingActivity(packageName);
}
}
}
}
防御的难点在于,这种攻击发生在系统层面,常规应用权限无法直接阻止。但通过组合检测策略,我们可以显著降低风险:
| 检测维度 | 实现方法 | 可靠性 |
|---|---|---|
| 当前窗口归属 | ActivityManager.getRunningTasks() |
中(需处理API限制) |
| 界面覆盖检测 | ViewTreeObserver.OnGlobalLayoutListener |
高 |
| 输入法状态 | InputMethodManager.isActive() |
低 |
在开发阶段构建检测机制,需要兼顾准确性和性能开销。以下是经过实战验证的三层检测体系:
利用ActivityLifecycleCallbacks监听应用生命周期,结合OnGlobalLayoutListener检测视图变化:
kotlin复制class SecurityApplication : Application() {
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityResumed(activity: Activity) {
setupWindowCheck(activity)
}
// 其他生命周期方法省略...
})
}
private fun setupWindowCheck(activity: Activity) {
activity.window.decorView.viewTreeObserver.addOnGlobalLayoutListener {
val rect = Rect()
activity.window.decorView.getWindowVisibleDisplayFrame(rect)
if (rect.top > statusBarHeight) {
triggerWarning(activity, "SUSPICIOUS_WINDOW_OVERLAY")
}
}
}
}
通过ActivityManager获取任务栈信息,需注意不同Android版本的API差异:
java复制public boolean isCurrentActivityTop() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
UsageStatsManager usm = (UsageStatsManager) getSystemService(USAGE_STATS_SERVICE);
long time = System.currentTimeMillis();
UsageEvents events = usm.queryEvents(time - 1000, time);
UsageEvents.Event event = new UsageEvents.Event();
if (events.getNextEvent(event)) {
return getPackageName().equals(event.getPackageName());
}
} else {
List<ActivityManager.RunningTaskInfo> tasks =
activityManager.getRunningTasks(1);
return getPackageName().equals(tasks.get(0).topActivity.getPackageName());
}
return false;
}
注意:从Android 5.0开始,
getRunningTasks()仅返回调用者自身任务信息。需引导用户授予android.permission.PACKAGE_USAGE_STATS权限
减少误报的关键在于建立合理的白名单策略:
ApplicationInfo.FLAG_SYSTEM判断)InputMethodManager.getInputMethodList())KeyguardManager.isKeyguardLocked())xml复制<!-- 白名单配置示例 -->
<string-array name="trusted_overlays">
<item>com.android.systemui</item>
<item>com.google.android.inputmethod.latin</item>
<item>com.sec.android.inputmethod</item>
</string-array>
基础检测只是第一步,成熟的防御方案需要多维度策略组合:
通过动态UI元素增加仿冒难度:
kotlin复制class SecureActivity : AppCompatActivity() {
private val dynamicElements = mutableListOf<View>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_secure)
// 添加动态水印
addDynamicWatermark()
// 随机布局微调
applyLayoutJitter()
}
private fun addDynamicWatermark() {
val watermark = TextView(this).apply {
text = "${System.currentTimeMillis()}"
alpha = 0.1f
rotation = -15f
}
windowManager.addView(watermark, LayoutParams().apply {
gravity = Gravity.CENTER
})
dynamicElements.add(watermark)
}
}
在敏感操作前增加不可伪造的验证步骤:
java复制public void validateWithBiometric(Runnable successAction) {
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle("Security Verification")
.setSubtitle("Confirm current activity is authentic")
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
.build();
new BiometricPrompt(this, executor, new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
successAction.run();
}
}).authenticate(promptInfo);
}
客户端检测结合服务端风控:
python复制# 伪代码示例:服务端异常检测
def detect_hijacking(user_session):
current_activity = user_session.get('current_activity')
prev_activities = user_session.get('activity_history')
if current_activity == 'LoginActivity':
if 'LoginActivity' in prev_activities[-2:]:
return True # 重复登录界面可疑
display_time = time() - user_session['activity_start_time']
if display_time < 0.5: # 短于500ms显示时间
return True
return False
将安全机制融入开发生命周期,需要建立标准化流程:
推荐的分层防护架构:
code复制┌───────────────────────┐
│ UI Layer │
│ - Dynamic Watermark │
│ - Layout Jitter │
├───────────────────────┤
│ Business Layer │
│ - Critical Operation │
│ - Biometric Auth │
├───────────────────────┤
│ Security Layer │
│ - Activity Monitor │
│ - Environment Check │
├───────────────────────┤
│ Network Layer │
│ - Behavior Analysis │
│ - Device Fingerprint │
└───────────────────────┘
在BaseActivity中集成自动化检测:
java复制public abstract class SecureBaseActivity extends AppCompatActivity {
private static final long CHECK_INTERVAL = 500;
private Handler securityHandler;
@Override
protected void onResume() {
super.onResume();
startSecurityCheck();
}
private void startSecurityCheck() {
securityHandler = new Handler(Looper.getMainLooper());
securityHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (!isActivitySecure()) {
handleHijackingEvent();
} else {
securityHandler.postDelayed(this, CHECK_INTERVAL);
}
}
}, CHECK_INTERVAL);
}
protected abstract boolean isActivitySecure();
protected abstract void handleHijackingEvent();
}
构建自动化测试场景验证防御效果:
bash复制adb shell am start -n com.phishing/.FakeLoginActivity
java复制@Test
public void testActivityProtection() throws Exception {
device.wait(Until.hasObject(By.pkg("com.target.app")), 3000);
device.executeShellCommand("am start -n com.phishing/.FakeLoginActivity");
UiObject warning = device.findObject(new UiSelector()
.textContains("security warning"));
assertTrue(warning.exists());
}
bash复制adb shell monkey -p com.target.app --throttle 100 -v 500
在实际项目中,我们发现结合窗口焦点变化监听和任务栈检测的方案,能捕获约85%的劫持尝试。但要注意过度检测可能影响应用性能,建议在debug模式开启完整检测,release版本采用抽样策略。