前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >App安全(一) Android防止升级过程被劫持和换包

App安全(一) Android防止升级过程被劫持和换包

作者头像
开发者技术前线
发布2020-11-23 11:17:28
1.3K0
发布2020-11-23 11:17:28
举报
文章被收录于专栏:开发者技术前线

文/ Tamic 地址/ http://www.jianshu.com/users/3bbb1ddf4fd5/latest_articles

可能你很久没看到我的动态了,不是我懈怠了,也不是我没有可以用来新知识产出了,只是Tamic也是普通的开发者,也是一个普通人,最近一直在忙着完成人生中的大事之一,最近项目已经开启了ReactNative,但本人感觉造轮子的Weex还是适合业务开发,毕竟更适合国人的习惯,前一阵子微信小程序也诈尸一样的风靡了朋友圈,最近也没什么动静了,因此针对普通开发者,这些东东说白了没什么Luan用, 看着很多XX公司的架构演化之路,最近正在申请以公司名义发布架构演变之路的文章,其实对于开发者这也没什么卵用,还有一件事,那就是万圣节快乐!

前言


APP 安全一直是开发者头痛的事情,越来越多的安全漏洞,使得开发者

越来越重视app安全,目前app安全主要有由以下几部分

APP组件安全


Android 包括四大组件:Activitie、Service、Content Provider、Broadband Receiver,

它们每一个都可以通过外面隐式的Intent方式打开, android组件对外开放 就会被其他程序劫持,因此必须在manifest里面声明 exported为false,禁止其他程序访问改组件, 对于要和外部交互的组件,应当添加一下访问权限的控制, 在有权限后外部程序才可以开启,或者提供特定的action过滤器达到启动目的。 还需要要对传递的数据进行安全的校验。不符合规则的一律不处理。

Webview 安全漏洞


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反编译


App被反编后,源码暴露,不仅对数据造成隐私泄露,而且对一些接口造成易攻击的潜在风险。 我们必须对apk进行源码混淆,并且进行apk加固

APP二次打包

即反编译后重新加入恶意的代码逻辑,或置入新病毒重新生成一个新APK文件。 二次的目的一般都是是盈利广告和病毒结合,对正版apk进行解包,插入恶意病毒后重新打包并发布,因此伪装性很强。截住app重打包就一定程度上防止了病毒的传播。因此app加固是防止二次打包的重要措施。

APP进程劫持


一般我们称为进程注入,也就动态注入技术,hook技术目前主流的进程注入方式,通过对linux进行so注入,达到挂钩远程函数实现监控远程进程的目的。

APP DNS劫持


DNS劫持俗称抓包。通过对url的二次劫持,修改参数和返回值,进而进行对app访问web数据伪装,实现注入广告和假数据,甚至起到导流用户的作用,严重的可以通过对登录APi的劫持可以获取用户密码,也可以对app升级做劫持,下载病毒apk等目的,解决方法一般用https进行传输数据。

APP APi接口验签


一般服务端的接口也会被攻击,虽然是服务端安全问题,但还是属于App系统维护体系,如果app后端挂了,app也不叫app了,一般变现为被恶意程序频繁请求某一接口,导致订单等重复注入,验证码被盗刷,虚假用户注册,严重的甚至拖垮app.

今天先看下APP升级过程被劫持的问题

我们做app版本升级时一般流程是采用请求升级接口,如果有升级,服务端返回下一个下载地址,下载好Apk后,再点击安装。 其实这个过程中有三个地方会被劫持。 请求升级时,下载文件时,安装时。

  • 升级APi

升级Api建议用https,防止被恶意程序劫持,结果是恶意返回下载地址,这样就把伪装的apk下载到本地,结果你应该懂的!

  • 下载API: 如果升级api你做了加固,下载api没做加固,还是徒劳,恶意程序也可以返回恶意文件或者apk,直到被你错误的安装在手机上。
  • 安装过程; 假设你以上两个过程都做了加固,但是在安装apk的时候,本地文件path被错误修改了,仍然可以安装错误的apk,这不仅 会对用户体验产生不利,甚至会威胁手机安全。

解决方案:


  • 升级api加入https 这个肯定不用再过多介绍,看了我介绍的retrofit 的https就明白了。
  • 下载Api也需加入https,也不用再做介绍,这里着重强调的是需要对服务端返回的文件进行Hash值校验,防止文件被篡改, 通过对文件hash值,还要对服务端返回的自定义key的进行校验验签,防止不是自己服务器返回错误的文件
  • 安装过程也必须对Apk文件进行包名和签名验证,防止Apk被恶意植入木马,或替换。

假设我的升级bean为;

代码语言:javascript
复制
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

代码语言:javascript
复制
 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接受请求头 ,看是否符合自己和服务器约定的key和hash之,下载好apk到本地后,继续判断文件的hash和升级api返回的hashcode, 加之key是否是和下载服务器返回的key,如果不一致,就不安装

代码语言:javascript
复制
      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文件进行包名和签名校验,如果包名和签名不一致,那么就是伪装程序,这个漏洞显而易见!

代码语言:javascript
复制
/** 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安全类如下:

代码语言:javascript
复制
/**
* 安全校验
* 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;
}
}

后续

通过上面的处理和防范,我们对apk升级流程的安全漏洞已经做到很安全细微了,很难被恶意程序轻易劫持,其他深入的安全问题:服务器验签,js注入,hook注入 下期再接着介绍。

Tamic : http://www.jianshu.com/users/3bbb1ddf4fd5/latest_articles gitHub: https://github.com/NeglectedByBoss

Tamic开发社区

To You,For Me

Android & iOS

长按二维码关注

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2016-10-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开发者技术前线 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 可能你很久没看到我的动态了,不是我懈怠了,也不是我没有可以用来新知识产出了,只是Tamic也是普通的开发者,也是一个普通人,最近一直在忙着完成人生中的大事之一,最近项目已经开启了ReactNative,但本人感觉造轮子的Weex还是适合业务开发,毕竟更适合国人的习惯,前一阵子微信小程序也诈尸一样的风靡了朋友圈,最近也没什么动静了,因此针对普通开发者,这些东东说白了没什么Luan用, 看着很多XX公司的架构演化之路,最近正在申请以公司名义发布架构演变之路的文章,其实对于开发者这也没什么卵用,还有一件事,那就是万圣节快乐!
    • 前言
      • APP组件安全
        • Webview 安全漏洞
          • APP反编译
            • APP二次打包
              • APP进程劫持
                • APP DNS劫持
                  • APP APi接口验签
                  • 解决方案:
                  • 后续
                  相关产品与服务
                  移动应用安全
                  移动应用安全(Mobile Application Security,MS)针对移动应用普遍存在的破解、篡改、重打包等各类安全风险,提供Android应用加固、iOS源码混淆、SDK加固等多种加固技术,拥有丰富的行业经验,已服务于金融、互联网、车联网、物联网,运营商等多个行业。稳定、简单、有效,让移动安全建设不再是一种负担。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档