讓你的app無法使用系統截圖的探究

PhilCoulson發表於2017-12-16

#由來: 最近專案要接入各種支付,其中一個是銀聯支付。於是開始搗鼓,之前也沒接入過,只是做過微信和支付寶。 下載銀聯的SDK、Demo、文件等等若干東西開始啃,一開啟文件,發現100多頁。。。於是瞬間就不想看了,隨便翻了翻,全都是方案規範什麼的,頭都大了,於是開始搗鼓SDK和Demo。 老樣子,先把Demo跑起來(此處省略若干字)。

跑起來後的銀聯demo首頁如圖所示:

首頁.png

好了現在開始測試一波,使用銀聯提供的測試卡號和手機號以及驗證碼,開始付款,上截圖:

截圖.png

WTF??(黑人問號),為何AS的截圖一直菊花,進不去截圖介面?估計是ADB歇菜了吧(自從AS2.2正式版+Sieera正式版)釋出後,adb一直有問題。好吧我重啟AS

。。。還是不行,一直菊花! 好吧,我用手機自帶的截圖:

什麼鬼.jpg

無法進行螢幕截圖,原因可能是儲存空間不足,或者該應用或您所屬的單位不允許執行此操作。

黑人問號??這是什麼鬼,還能這樣?


#開始探究 對!前面說了這麼多廢話,就是為了給這個東西做鋪墊。 開始分析一波:這東西在銀聯Demo裡存在,首頁上面已經截圖了,證明沒問題,有問題的是開始支付之後的所有介面,開始猜測:因為支付涉及到的東西比較複雜,尤其是隱私和安全性,所以這些東西一般都是被封裝起來的,對外提供為混淆後的jar包。 去驗證一波,先找到Demo中支付的按鈕所觸發的動作:

public class JARActivity extends BaseActivity {    
    @Override    
    public void doStartUnionPayPlugin(Activity activity, String tn, String mode) {        
         UPPayAssistEx.startPay(activity, null, null, tn, mode);    
    }    
    //省略部分程式碼...
}
複製程式碼

可以看到,發起支付之後,到了UPPayAssistEx.startPay()方法去了,而這個方法正好是在SDK的jar包內。 也就是說導致我們沒法截圖的程式碼在SDK中,有可能是C層面控制的(畢竟有so),也有可能是Android自己控制的,提供了API。 C層面的不好找,所以先看看是否是Android的API吧。 組織我們截圖,也就是說在xxxActivity下,我們沒辦法獲得這個Activity的“資訊”,所以先去看看支付的Activity是怎麼寫的吧。 這裡要祭出神器了:TopActivity.apk 我從事Android開發一年,這個東西也伴隨了我一年,他可以獲取當前執行的apk的Activity名字以及包名,這個app被作者開源在了Github


祭出誅仙劍之後,開始嘗試尋找支付介面的類名和包名(快看左上角快看左上角):

類名和包名.jpg

可以看到,包名是com.unionpay.uppay,Activity名字是PayActivity。 OK,有了這些資訊,開始去SDK中尋找:

public final class PayActivity extends BaseActivity {    
      private b c = null;    
      private f d = null;   
      private n e;    
      public static String a;   
      private k f = null;   

      public PayActivity() {}    
      public final void onCreate(Bundle var1) {        
          super.onCreate(var1);    
     }
      //省略部分程式碼...
}
複製程式碼

找到了這個支付介面的Activity,一般來說要對視窗進行操作,需要在 onCreate() 中,但是這個Activity的onCreate()是呼叫父類的方法。其實也很正常,畢竟是有個基類的Activity,所以我們看看BaseActivity:

public abstract class BaseActivity extends 
        Activity implements com.unionpay.mobile.android.plugin.a, b {    
      //省略部分程式碼...
      public void onCreate(Bundle var1) { 
           //省略部分程式碼...       
           UPAgent.LOG_ON = false;
           this.requestWindowFeature(1);
           super.onCreate(var1);
           this.c = (l)this.a(1, (e)null);
           this.setContentView(this.c);
           this.getWindow().addFlags(8192);
           ++f;
           //省略部分程式碼...
      }    
//省略部分程式碼...
}
複製程式碼

好了,這個就是銀聯支付所有Activity的父類了,畢竟繼承了Activity。 分析下有沒有什麼有用的線索: 發現一個東西:this.getWindow().addFlags(8192);。這個和我們之前的假設差不多,因為Activity和window有極大的關係,很多操作都要依靠getWindow()來進行,比如去掉標題欄之類的。那麼這個8912是什麼鬼? Android中的這種系統常量一般都是16進位制的,所以我們把這個8192轉換成16進位制看看是多少:

0x2000

因為這個常量是給window的,回想一下之前我們設定全屏:

this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

所以我們進入到WindowManager.LayoutParams去看看,搜尋常量先。 搜尋2000

/** Window flag: treat the content of the window as secure, preventing 
  * it from appearing in screenshots or from being viewed on non-secure 
  * displays.
  * 
  * <p>See {@link android.view.Display#FLAG_SECURE} for more details about 
  * secure surfaces and secure displays. 
  */
public static final int FLAG_SECURE             = 0x00002000;
複製程式碼

**NICE!**看這個變數名字就知道了,FLAG_SECURE->安全。當然不能依照名字來斷定,還是看看註釋。這上面說:這個標誌是用來將視窗內容視為安全的,它不會出現在螢幕截圖裡面。


也就是說:我們自己的Activity,只要加上了這個標誌,就會變得“安全”,不會被螢幕截圖捕捉到,即使是adb命令。


驗證之後,果然如此,AS獲取不到螢幕截圖,手機自帶的截圖也拿不到了,豌豆莢等第三方客戶端暫時沒測試,電腦上沒有豌豆莢,感興趣的朋友可以試試看。

#結語 想要像銀聯一樣,在某Activity做到手機無法截圖,甚至是adb也拿不到,那麼可以在Activity中加入: getWindow().addFlags(WindowManager.LayoutParams. FLAG_SECURE);

相關文章