編輯:關於Android編程
APP 安全一直是開發者頭痛的事情,越來越多的安全漏洞,使得開發者
越來越重視app安全,目前app安全主要有由以下幾部分
Android 包括四大組件:Activitie、Service、Content Provider、Broadband Receiver,
它們每一個都可以通過外面隱式的Intent方式打開,
android組件對外開放 就會被其他程序劫持,因此必須在manifest裡面聲明
exported為false,禁止其他程序訪問改組件,
對於要和外部交互的組件,應當添加一下訪問權限的控制, 在有權限後外部程序才可以開啟,
還需要要對傳遞的數據進行安全的校驗。不符合規則的一律不處理。
Android API 4.4以前,谷歌的webview存在安全漏洞,網站可以通過js注入就可以隨便拿到客戶端的重要信息,
甚至輕而易舉的調用本地代碼進行流氓行為,谷歌後來發現有此漏洞後
,在API 4.4以後增加了防御措施,如果用js調用本地代碼,開發者必須在代碼申明JavascriptInterface,
列如在4.0之前我們要使得webView加載js只需如下代碼:
mWebView.addJavascriptInterface(new JsToJava(), “myjsfunction”);
4.4之後使用需要在調用Java方法加入@JavascriptInterface注解,
如果代碼無此申明,那麼也就無法使得js生效,也就是說這樣就可以避免惡意網頁利用js對客戶端的進行竊取和攻擊。
app被反編後,源碼暴露,不僅對數據造成隱私,而且對一些接口造成攻擊的潛在風險。
我們必須對apk進行源碼混淆,也可以進行apk加固
即反編譯後重新加入惡意的代碼邏輯,或置入新病毒重新生成一個新APK文件。
二次的目的一般都是是盈利廣告和病毒結合,對正版apk進行解包,插入惡意病毒後重新打包並發布,
因此偽裝性很強。截住app重打包就一定程度上防止了病毒的傳播。因此app加固是防止二次打包的重要措施。
一般我們稱為進程注入,也就動態注入技術,hook技術目前主流的進程注入方式,通過對linux進行so注入,達到掛鉤遠程函數實現監控遠程進程的目的。
DNS劫持俗稱抓包。通過對url的二次劫持,修改參數和返回值,進行對app的web數據偽裝,實現注入廣告和假數據,甚至導流用戶的作用,嚴重的可以
通過對登錄APi的劫持可以獲取用戶密碼,也可以對app升級做劫持,下載病毒apk等目的,解決方法一般用https進行傳輸數據。
一般服務端的接口也會被攻擊,雖然是服務端安全問題,但還是屬於App系統維護體系,如果app後端掛了,app也不叫app了,一般會被
惡意程序頻繁請求,導致訂單等重復注入,嚴重的甚至拖垮app.
今天先看下APP升級過程被劫持的問題
我們做app版本升級時一般流程是采用請求升級接口,如果有升級,服務端返回下一個下載地址,下載好Apk後,再點擊安裝。
其實這個過程中有三個地方會被劫持。 請求升級時,下載文件時,安裝時。
升級Api建議用https,防止被惡意程序劫持,結果是惡意返回下載地址,這樣就把偽裝apk下載到本地,結果你應該懂的
下載API:
那如果升級api你做了加固,下載api沒做加過,還是徒勞,惡意程序也可以給你返回惡意文件或者apk,直到被你錯誤的安裝在
手機上。
假設你的以上兩個過程都做了加固,但是在安裝apk的時候,本地文件path被錯誤修改了,仍然可以安裝錯誤的apk,這不僅
會對用戶體驗產生不利,甚至會威脅手機安全。
下載Api也需加入https,也不用介紹,這裡著重強調的是你的服務端需要對文件進行文件Hash值校驗,防止文件被篡改,
通過對文件hash值,還要對服務端返回的key的進行校驗驗簽,防止不是自己服務器返回錯誤的文件
安裝過程也必須對Apk文件進行包名和簽名驗證,防止Apk被惡意植入木馬,或替換。
假設我的升級bean為;
public class UpgradeModel {
private int code;
private String msg;
private DataBean data;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public DataBean getData() {
return data;
}
public void setData(DataBean data) {
this.data = data;
}
public static class DataBean {
private String description;
private String downUrl;
private String version;
private String hashcod;
private String key;
private String isForce;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getDownUrl() {
return downUrl;
}
public void setDownUrl(String downUrl) {
this.downUrl = downUrl;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getHashcod() {
return hashcod;
}
public void setHashcod(String hashcod) {
this.hashcod = hashcod;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getIsForce() {
return isForce;
}
public void setIsForce(String isForce) {
this.isForce = isForce;
}
}
}
通過一次請求到服務端數據後,如果有版本更新 我們應該先驗證
Url和key是不是我們和服務端協商好的Url和key
UpgradeModel aResult = xxxx//解析服務器返回的後數據
if (aResult != null && aResult.getData() != null ) {
String url = aResult.getData().getDownUrl();
if (url == null || !TextUtils.equals(url, "這裡是你知道的下載地址: 也可以只驗證hostUrl")) {
// 如果符合,就不去下載
}
接著我們驗證下載url是你自己app的服務器地址,然後再請求下載Api時用DownLoadModel接受請求頭 ,下載好apk到本地後,繼續判斷文件的hash和升級api返回的hashcode,加之key是否是和下載服務器返回的key,如果不一致,就不安裝
File file = DownUtils.getFile(url);
// 監測是否要重新下載
if (file.exists() && TextUtils.equals(aResult.getData().getHashCode(), EncryptUtils.Md5File(file))) {
&& TextUtils.equals(aResult.getData().getKey(), DownLoadModel.getData()..getKey())
// 如果符合,就去安裝 不符合重新下載 刪除惡意文件
}
等我們驗證下載文件的地址是我們服務器提供的,驗證沒問題就只剩安裝了。接著還要對apk文件進行包名和簽名校驗,
/** installApK
* @param context
* @param path
* @param name
*/
public static void installApK(Context context, final String path, final String name ) {
if (!SafetyUtils.checkFile(path + name, context)) {
return;
}
if (!SafetyUtils.checkPagakgeName(context, path + name)) {
Toast.makeText(context, "升級包被惡意軟件篡改 請重新升級下載安裝", Toast.LENGTH_SHORT ).show();
DLUtils.deleteFile(path + name);
((Activity)context).finish();
return;
}
switch (SafetyUtils.checkPagakgeSign(context, path + name)) {
case SafetyUtils.SUCCESS:
DLUtils.openFile(path + name, context);
break;
case SafetyUtils.SIGNATURES_INVALIDATE:
Toast.makeText(context, "升級包安全校驗失敗 請重新升級", Toast.LENGTH_SHORT ).show();
((Activity)context).finish();
break;
case SafetyUtils.VERIFY_SIGNATURES_FAIL:
Toast.makeText(context, "升級包為盜版應用 請重新升級", Toast.LENGTH_SHORT ).show();
((Activity)context).finish();
break;
default:
break;
}
}
SafetyUtils安全類如下:
/**
* 安全校驗
* Created by LIUYONGKUI on 2016-04-21.
*/
public class SafetyUtils {
/** install sucess */
protected static final int SUCCESS = 0;
/** SIGNATURES_INVALIDATE */
protected static final int SIGNATURES_INVALIDATE = 3;
/** SIGNATURES_NOT_SAME */
protected static final int VERIFY_SIGNATURES_FAIL = 4;
/** is needcheck */
private static final boolean NEED_VERIFY_CERT = true;
/**
* checkPagakgeSigns.
*/
public static int checkPagakgeSign(Context context, String srcPluginFile) {
PackageInfo PackageInfo = context.getPackageManager().getPackageArchiveInfo(srcPluginFile, 0);
//Signature[] pluginSignatures = PackageInfo.signatures;
Signature[] pluginSignatures = PackageVerifyer.collectCertificates(srcPluginFile, false);
boolean isDebugable = (0 != (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE));
if (pluginSignatures == null) {
PaLog.e("簽名驗證失敗", srcPluginFile);
new File(srcPluginFile).delete();
return SIGNATURES_INVALIDATE;
} else if (NEED_VERIFY_CERT && !isDebugable) {
//可選步驟,驗證APK證書是否和現在程序證書相同。
Signature[] mainSignatures = null;
try {
PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(
context.getPackageName(), PackageManager.GET_SIGNATURES);
mainSignatures = pkgInfo.signatures;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (!PackageVerifyer.isSignaturesSame(mainSignatures, pluginSignatures)) {
PaLog.e("升級包證書和舊版本證書不一致", srcPluginFile);
new File(srcPluginFile).delete();
return VERIFY_SIGNATURES_FAIL;
}
}
return SUCCESS;
}
/**
* checkPagakgeName
* @param context
* @param srcNewFile
* @return
*/
public static boolean checkPagakgeName (Context context, String srcNewFile) {
PackageInfo packageInfo = context.getPackageManager().getPackageArchiveInfo(srcNewFile, PackageManager.GET_ACTIVITIES);
if (packageInfo != null) {
return TextUtils.equals(context.getPackageName(), packageInfo.packageName);
}
return false;
}
/**
* checkFile
*
* @param aPath
* 文件路徑
* @param context
* context
*/
public static boolean checkFile(String aPath, Context context) {
File aFile = new File(aPath);
if (aFile == null || !aFile.exists()) {
Toast.makeText(context, "安裝包已被惡意軟件刪除", Toast.LENGTH_SHORT).show();
return false;
}
if (context == null) {
Toast.makeText(context, "安裝包異常", Toast.LENGTH_SHORT).show();
return false;
}
return true;
}
}
後續
這樣我們的對升級流程的安全以及做到很安全細微了,很難被惡意程序輕易劫持,其他js注入,hook注入下期接講解。
效果圖:看網上的都是兩個view拼接,默認右側的不顯示,水平移動的時候把右側的view顯示出來。但是看最新版QQ上的效果不是這樣的,但給人的感覺卻很好,所以獻丑來一發比較
在上一篇文章中介紹了使用非RxJava環境下,使用Handler機制SyncBarrier的特性實現預加載功能的方法。在RxJava的環境下使用BehaviorSubje
動畫在我們實際開發中占有很重要的地位,一個優秀的動畫能為我們的app應用增色很多,同時一個優秀的動畫銜接能夠增加我們app的邏輯展示。在Android系統中,系統給我們
本博文是《第一行代碼 Android》的讀書筆記/摘錄。一、Content Provider簡介內容提供器(Content Provider)主要用於在不同的應用程序之間